Use app deep copy in places to avoid bugs

This commit is contained in:
Imran Remtulla
2023-04-07 01:54:14 -04:00
parent 9193788356
commit 334ac8d3d6
7 changed files with 62 additions and 33 deletions

View File

@@ -21,7 +21,7 @@ import 'package:easy_localization/src/easy_localization_controller.dart';
// ignore: implementation_imports // ignore: implementation_imports
import 'package:easy_localization/src/localization.dart'; import 'package:easy_localization/src/localization.dart';
const String currentVersion = '0.11.27'; const String currentVersion = '0.11.28';
const String currentReleaseTag = const String currentReleaseTag =
'v$currentVersion-beta'; // KEEP THIS IN SYNC WITH GITHUB RELEASES 'v$currentVersion-beta'; // KEEP THIS IN SYNC WITH GITHUB RELEASES

View File

@@ -38,7 +38,7 @@ class _AppPageState extends State<AppPage> {
bool areDownloadsRunning = appsProvider.areDownloadsRunning(); bool areDownloadsRunning = appsProvider.areDownloadsRunning();
var sourceProvider = SourceProvider(); 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; var source = app != null ? sourceProvider.getSource(app.app.url) : null;
if (!areDownloadsRunning && prevApp == null && app != null) { if (!areDownloadsRunning && prevApp == null && app != null) {
prevApp = app; prevApp = app;

View File

@@ -29,13 +29,13 @@ class AppsPageState extends State<AppsPage> {
final AppsFilter neutralFilter = AppsFilter(); final AppsFilter neutralFilter = AppsFilter();
var updatesOnlyFilter = var updatesOnlyFilter =
AppsFilter(includeUptodate: false, includeNonInstalled: false); AppsFilter(includeUptodate: false, includeNonInstalled: false);
Set<App> selectedApps = {}; Set<String> selectedAppIds = {};
DateTime? refreshingSince; DateTime? refreshingSince;
clearSelected() { clearSelected() {
if (selectedApps.isNotEmpty) { if (selectedAppIds.isNotEmpty) {
setState(() { setState(() {
selectedApps.clear(); selectedAppIds.clear();
}); });
return true; return true;
} }
@@ -43,10 +43,10 @@ class AppsPageState extends State<AppsPage> {
} }
selectThese(List<App> apps) { selectThese(List<App> apps) {
if (selectedApps.isEmpty) { if (selectedAppIds.isEmpty) {
setState(() { setState(() {
for (var a in apps) { for (var a in apps) {
selectedApps.add(a); selectedAppIds.add(a.id);
} }
}); });
} }
@@ -57,20 +57,20 @@ class AppsPageState extends State<AppsPage> {
var appsProvider = context.watch<AppsProvider>(); var appsProvider = context.watch<AppsProvider>();
var settingsProvider = context.watch<SettingsProvider>(); var settingsProvider = context.watch<SettingsProvider>();
var sourceProvider = SourceProvider(); var sourceProvider = SourceProvider();
var listedApps = appsProvider.apps.values.toList(); var listedApps = appsProvider.getAppValues().toList();
var currentFilterIsUpdatesOnly = var currentFilterIsUpdatesOnly =
filter.isIdenticalTo(updatesOnlyFilter, settingsProvider); filter.isIdenticalTo(updatesOnlyFilter, settingsProvider);
selectedApps = selectedApps selectedAppIds = selectedAppIds
.where((element) => listedApps.map((e) => e.app).contains(element)) .where((element) => listedApps.map((e) => e.app.id).contains(element))
.toSet(); .toSet();
toggleAppSelected(App app) { toggleAppSelected(App app) {
setState(() { setState(() {
if (selectedApps.contains(app)) { if (selectedAppIds.map((e) => e).contains(app.id)) {
selectedApps.remove(app); selectedAppIds.removeWhere((a) => a == app.id);
} else { } else {
selectedApps.add(app); selectedAppIds.add(app.id);
} }
}); });
} }
@@ -143,15 +143,15 @@ class AppsPageState extends State<AppsPage> {
var existingUpdates = appsProvider.findExistingUpdates(installedOnly: true); var existingUpdates = appsProvider.findExistingUpdates(installedOnly: true);
var existingUpdateIdsAllOrSelected = existingUpdates var existingUpdateIdsAllOrSelected = existingUpdates
.where((element) => selectedApps.isEmpty .where((element) => selectedAppIds.isEmpty
? listedApps.where((a) => a.app.id == element).isNotEmpty ? listedApps.where((a) => a.app.id == element).isNotEmpty
: selectedApps.map((e) => e.id).contains(element)) : selectedAppIds.map((e) => e).contains(element))
.toList(); .toList();
var newInstallIdsAllOrSelected = appsProvider var newInstallIdsAllOrSelected = appsProvider
.findExistingUpdates(nonInstalledOnly: true) .findExistingUpdates(nonInstalledOnly: true)
.where((element) => selectedApps.isEmpty .where((element) => selectedAppIds.isEmpty
? listedApps.where((a) => a.app.id == element).isNotEmpty ? listedApps.where((a) => a.app.id == element).isNotEmpty
: selectedApps.map((e) => e.id).contains(element)) : selectedAppIds.map((e) => e).contains(element))
.toList(); .toList();
List<String> trackOnlyUpdateIdsAllOrSelected = []; List<String> trackOnlyUpdateIdsAllOrSelected = [];
@@ -212,6 +212,11 @@ class AppsPageState extends State<AppsPage> {
: -1; : -1;
}); });
Set<App> selectedApps = listedApps
.map((e) => e.app)
.where((a) => selectedAppIds.contains(a.id))
.toSet();
showChangeLogDialog( showChangeLogDialog(
String? changesUrl, AppSource appSource, String changeLog, int index) { String? changesUrl, AppSource appSource, String changeLog, int index) {
showDialog( showDialog(
@@ -288,7 +293,8 @@ class AppsPageState extends State<AppsPage> {
if (refreshingSince != null) if (refreshingSince != null)
SliverToBoxAdapter( SliverToBoxAdapter(
child: LinearProgressIndicator( child: LinearProgressIndicator(
value: appsProvider.apps.values value: appsProvider
.getAppValues()
.where((element) => !(element.app.lastUpdateCheck .where((element) => !(element.app.lastUpdateCheck
?.isBefore(refreshingSince!) ?? ?.isBefore(refreshingSince!) ??
true)) true))
@@ -467,7 +473,8 @@ class AppsPageState extends State<AppsPage> {
.colorScheme .colorScheme
.primary .primary
.withOpacity(listedApps[index].app.pinned ? 0.2 : 0.1), .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: () { onLongPress: () {
toggleAppSelected(listedApps[index].app); toggleAppSelected(listedApps[index].app);
}, },
@@ -497,7 +504,7 @@ class AppsPageState extends State<AppsPage> {
])) ]))
: trailingRow, : trailingRow,
onTap: () { onTap: () {
if (selectedApps.isNotEmpty) { if (selectedAppIds.isNotEmpty) {
toggleAppSelected(listedApps[index].app); toggleAppSelected(listedApps[index].app);
} else { } else {
Navigator.push( Navigator.push(
@@ -534,7 +541,7 @@ class AppsPageState extends State<AppsPage> {
} }
getSelectAllButton() { getSelectAllButton() {
return selectedApps.isEmpty return selectedAppIds.isEmpty
? TextButton.icon( ? TextButton.icon(
style: const ButtonStyle(visualDensity: VisualDensity.compact), style: const ButtonStyle(visualDensity: VisualDensity.compact),
onPressed: () { onPressed: () {
@@ -548,17 +555,17 @@ class AppsPageState extends State<AppsPage> {
: TextButton.icon( : TextButton.icon(
style: const ButtonStyle(visualDensity: VisualDensity.compact), style: const ButtonStyle(visualDensity: VisualDensity.compact),
onPressed: () { onPressed: () {
selectedApps.isEmpty selectedAppIds.isEmpty
? selectThese(listedApps.map((e) => e.app).toList()) ? selectThese(listedApps.map((e) => e.app).toList())
: clearSelected(); : clearSelected();
}, },
icon: Icon( icon: Icon(
selectedApps.isEmpty selectedAppIds.isEmpty
? Icons.select_all_outlined ? Icons.select_all_outlined
: Icons.deselect_outlined, : Icons.deselect_outlined,
color: Theme.of(context).colorScheme.primary, color: Theme.of(context).colorScheme.primary,
), ),
label: Text(selectedApps.length.toString())); label: Text(selectedAppIds.length.toString()));
} }
getMassObtainFunction() { getMassObtainFunction() {
@@ -706,7 +713,7 @@ class AppsPageState extends State<AppsPage> {
builder: (BuildContext ctx) { builder: (BuildContext ctx) {
return AlertDialog( return AlertDialog(
title: Text(tr('markXSelectedAppsAsUpdated', title: Text(tr('markXSelectedAppsAsUpdated',
args: [selectedApps.length.toString()])), args: [selectedAppIds.length.toString()])),
content: Text( content: Text(
tr('onlyWorksWithNonVersionDetectApps'), tr('onlyWorksWithNonVersionDetectApps'),
style: const TextStyle( style: const TextStyle(
@@ -760,7 +767,7 @@ class AppsPageState extends State<AppsPage> {
items: const [], items: const [],
initValid: true, initValid: true,
message: tr('installStatusOfXWillBeResetExplanation', message: tr('installStatusOfXWillBeResetExplanation',
args: [plural('app', selectedApps.length)]), args: [plural('app', selectedAppIds.length)]),
); );
}); });
if (values != null) { if (values != null) {
@@ -836,7 +843,7 @@ class AppsPageState extends State<AppsPage> {
children: [ children: [
IconButton( IconButton(
visualDensity: VisualDensity.compact, visualDensity: VisualDensity.compact,
onPressed: selectedApps.isEmpty onPressed: selectedAppIds.isEmpty
? null ? null
: () { : () {
appsProvider.removeAppsWithModal( appsProvider.removeAppsWithModal(
@@ -848,7 +855,7 @@ class AppsPageState extends State<AppsPage> {
IconButton( IconButton(
visualDensity: VisualDensity.compact, visualDensity: VisualDensity.compact,
onPressed: getMassObtainFunction(), onPressed: getMassObtainFunction(),
tooltip: selectedApps.isEmpty tooltip: selectedAppIds.isEmpty
? tr('installUpdateApps') ? tr('installUpdateApps')
: tr('installUpdateSelectedApps'), : tr('installUpdateSelectedApps'),
icon: const Icon( icon: const Icon(
@@ -856,13 +863,13 @@ class AppsPageState extends State<AppsPage> {
)), )),
IconButton( IconButton(
visualDensity: VisualDensity.compact, visualDensity: VisualDensity.compact,
onPressed: selectedApps.isEmpty ? null : launchCategorizeDialog(), onPressed: selectedAppIds.isEmpty ? null : launchCategorizeDialog(),
tooltip: tr('categorize'), tooltip: tr('categorize'),
icon: const Icon(Icons.category_outlined), icon: const Icon(Icons.category_outlined),
), ),
IconButton( IconButton(
visualDensity: VisualDensity.compact, visualDensity: VisualDensity.compact,
onPressed: selectedApps.isEmpty ? null : showMoreOptionsDialog, onPressed: selectedAppIds.isEmpty ? null : showMoreOptionsDialog,
tooltip: tr('more'), tooltip: tr('more'),
icon: const Icon(Icons.more_horiz), icon: const Icon(Icons.more_horiz),
), ),

View File

@@ -34,6 +34,8 @@ class AppInMemory {
AppInfo? installedInfo; AppInfo? installedInfo;
AppInMemory(this.app, this.downloadProgress, this.installedInfo); AppInMemory(this.app, this.downloadProgress, this.installedInfo);
AppInMemory deepCopy() =>
AppInMemory(app.deepCopy(), downloadProgress, installedInfo);
} }
class DownloadedApk { class DownloadedApk {
@@ -97,6 +99,8 @@ class AppsProvider with ChangeNotifier {
late Stream<FGBGType>? foregroundStream; late Stream<FGBGType>? foregroundStream;
late StreamSubscription<FGBGType>? foregroundSubscription; late StreamSubscription<FGBGType>? foregroundSubscription;
Iterable<AppInMemory> getAppValues() => apps.values.map((a) => a.deepCopy());
AppsProvider() { AppsProvider() {
// Subscribe to changes in the app foreground status // Subscribe to changes in the app foreground status
foregroundStream = FGBGEvents.stream.asBroadcastStream(); foregroundStream = FGBGEvents.stream.asBroadcastStream();
@@ -667,7 +671,8 @@ class AppsProvider with ChangeNotifier {
bool onlyIfExists = true}) async { bool onlyIfExists = true}) async {
attemptToCorrectInstallStatus = attemptToCorrectInstallStatus =
attemptToCorrectInstallStatus && (await doesInstalledAppsPluginWork()); attemptToCorrectInstallStatus && (await doesInstalledAppsPluginWork());
for (var app in apps) { for (var a in apps) {
var app = a.deepCopy();
AppInfo? info = await getInstalledInfo(app.id); AppInfo? info = await getInstalledInfo(app.id);
app.name = info?.name ?? app.name; app.name = info?.name ?? app.name;
if (app.additionalSettings['appName']?.toString().isNotEmpty == true) { if (app.additionalSettings['appName']?.toString().isNotEmpty == true) {

View File

@@ -164,7 +164,8 @@ class SettingsProvider with ChangeNotifier {
void setCategories(Map<String, int> cats, {AppsProvider? appsProvider}) { void setCategories(Map<String, int> cats, {AppsProvider? appsProvider}) {
if (appsProvider != null) { if (appsProvider != null) {
List<App> changedApps = appsProvider.apps.values List<App> changedApps = appsProvider
.getAppValues()
.map((a) { .map((a) {
var n1 = a.app.categories.length; var n1 = a.app.categories.length;
a.app.categories.removeWhere((c) => !cats.keys.contains(c)); a.app.categories.removeWhere((c) => !cats.keys.contains(c));

View File

@@ -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'; 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<String, dynamic> json) { factory App.fromJson(Map<String, dynamic> json) {
var source = SourceProvider().getSource(json['url']); var source = SourceProvider().getSource(json['url']);
var formItems = source.combinedAppSpecificSettingFormItems var formItems = source.combinedAppSpecificSettingFormItems

View File

@@ -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 # 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 # 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. # 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: environment:
sdk: '>=2.18.2 <3.0.0' sdk: '>=2.18.2 <3.0.0'