From da9e5aed5e77196f2f5f5099f1775329c67c94e0 Mon Sep 17 00:00:00 2001 From: Imran Remtulla Date: Sun, 25 Sep 2022 11:32:57 -0400 Subject: [PATCH] Apps page UI improvements --- lib/main.dart | 2 +- lib/pages/apps.dart | 302 +++++++++++++++++++++++--------------------- pubspec.yaml | 2 +- 3 files changed, 161 insertions(+), 145 deletions(-) diff --git a/lib/main.dart b/lib/main.dart index d34806d..c1cfefd 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -13,7 +13,7 @@ import 'package:dynamic_color/dynamic_color.dart'; import 'package:device_info_plus/device_info_plus.dart'; const String currentReleaseTag = - 'v0.4.0-beta'; // KEEP THIS IN SYNC WITH GITHUB RELEASES + 'v0.4.1-beta'; // KEEP THIS IN SYNC WITH GITHUB RELEASES @pragma('vm:entry-point') void bgTaskCallback() { diff --git a/lib/pages/apps.dart b/lib/pages/apps.dart index 3a539a9..c08ebda 100644 --- a/lib/pages/apps.dart +++ b/lib/pages/apps.dart @@ -18,6 +18,8 @@ class AppsPage extends StatefulWidget { class AppsPageState extends State { AppsFilter? filter; + var updatesOnlyFilter = + AppsFilter(includeUptodate: false, includeNonInstalled: false); Set selectedIds = {}; clearSelected() { @@ -45,11 +47,26 @@ class AppsPageState extends State { var appsProvider = context.watch(); var settingsProvider = context.watch(); var sortedApps = appsProvider.apps.values.toList(); + var currentFilterIsUpdatesOnly = + filter?.isIdenticalTo(updatesOnlyFilter) ?? false; selectedIds = selectedIds .where((element) => sortedApps.map((e) => e.app.id).contains(element)) .toSet(); + var existingUpdateIdsAllOrSelected = appsProvider + .getExistingUpdates(installedOnly: true) + .where((element) => selectedIds.isEmpty + ? sortedApps.where((a) => a.app.id == element).isNotEmpty + : selectedIds.contains(element)) + .toList(); + var newInstallIdsAllOrSelected = appsProvider + .getExistingUpdates(nonInstalledOnly: true) + .where((element) => selectedIds.isEmpty + ? sortedApps.where((a) => a.app.id == element).isNotEmpty + : selectedIds.contains(element)) + .toList(); + toggleAppSelected(String appId) { setState(() { if (selectedIds.contains(appId)) { @@ -133,8 +150,9 @@ class AppsPageState extends State { : Text( appsProvider.apps.isEmpty ? 'No Apps' - : 'No Search Results', + : 'No Apps for Filter', style: Theme.of(context).textTheme.headlineMedium, + textAlign: TextAlign.center, ))), SliverList( delegate: SliverChildBuilderDelegate( @@ -175,154 +193,156 @@ class AppsPageState extends State { persistentFooterButtons: [ Row( children: [ - TextButton.icon( + IconButton( onPressed: () { selectedIds.isEmpty ? selectThese(sortedApps.map((e) => e.app.id).toList()) : clearSelected(); }, - icon: Icon(selectedIds.isEmpty - ? Icons.select_all_outlined - : Icons.deselect_outlined), - label: Text(selectedIds.isEmpty + icon: Icon( + selectedIds.isEmpty + ? Icons.select_all_outlined + : Icons.deselect_outlined, + color: Theme.of(context).colorScheme.primary, + ), + tooltip: selectedIds.isEmpty ? 'Select All' - : 'Deselect ${selectedIds.length.toString()}')), + : 'Deselect ${selectedIds.length.toString()}'), const VerticalDivider(), Expanded( - child: selectedIds.isEmpty - ? Container() - : Row( - mainAxisAlignment: MainAxisAlignment.spaceEvenly, - children: [ - IconButton( - visualDensity: VisualDensity.compact, - onPressed: () { - showDialog?>( - context: context, - builder: (BuildContext ctx) { - return GeneratedFormModal( - title: 'Remove Selected Apps?', - items: const [], - defaultValues: const [], - initValid: true, - message: - '${selectedIds.length} App${selectedIds.length == 1 ? '' : 's'} will be removed from Obtainium but remain installed. You still need to uninstall ${selectedIds.length == 1 ? 'it' : 'them'} manually.', - ); - }).then((values) { - if (values != null) { - appsProvider.removeApps(selectedIds.toList()); - } - }); - }, - tooltip: 'Remove Selected Apps', - icon: const Icon(Icons.delete_outline_outlined), - ), - IconButton( - visualDensity: VisualDensity.compact, - onPressed: appsProvider.areDownloadsRunning() || - selectedIds - .where((id) => - appsProvider.apps[id]!.app - .installedVersion != - appsProvider - .apps[id]!.app.latestVersion) - .isEmpty - ? null - : () { - HapticFeedback.heavyImpact(); - var existingUpdateIdsSelected = - appsProvider - .getExistingUpdates( - installedOnly: true) - .where((element) => - selectedIds.contains(element)) - .toList(); - var newInstallIdsSelected = appsProvider - .getExistingUpdates( - nonInstalledOnly: true) - .where((element) => - selectedIds.contains(element)) - .toList(); - List> formInputs = - []; - if (existingUpdateIdsSelected - .isNotEmpty && - newInstallIdsSelected.isNotEmpty) { - formInputs.add([ - GeneratedFormItem( - label: - 'Update ${existingUpdateIdsSelected.length} Apps?', - type: FormItemType.bool) - ]); - formInputs.add([ - GeneratedFormItem( - label: - 'Install ${newInstallIdsSelected.length} new Apps?', - type: FormItemType.bool) - ]); - } - showDialog?>( - context: context, - builder: (BuildContext ctx) { - return GeneratedFormModal( - title: 'Install Selected Apps?', - message: - '${existingUpdateIdsSelected.length} update${existingUpdateIdsSelected.length == 1 ? '' : 's'} and ${newInstallIdsSelected.length} new install${newInstallIdsSelected.length == 1 ? '' : 's'}.', - items: formInputs, - defaultValues: const [ - 'true', - 'true' - ], - initValid: true, - ); - }).then((values) { - if (values != null) { - bool shouldInstallUpdates = - values.length < 2 || - values[0] == 'true'; - bool shouldInstallNew = - values.length < 2 || - values[1] == 'true'; - settingsProvider - .getInstallPermission() - .then((_) { - List toInstall = []; - if (shouldInstallUpdates) { - toInstall.addAll( - existingUpdateIdsSelected); - } - if (shouldInstallNew) { - toInstall.addAll( - newInstallIdsSelected); - } - appsProvider - .downloadAndInstallLatestApp( - toInstall, context); - }); - } - }); - }, - tooltip: 'Install/Update Selected Apps', - icon: const Icon( - Icons.file_download_outlined, - )), - IconButton( - visualDensity: VisualDensity.compact, - onPressed: () { - String urls = ''; - for (var id in selectedIds) { - urls += '${appsProvider.apps[id]!.app.url}\n'; + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + selectedIds.isEmpty + ? const SizedBox() + : IconButton( + visualDensity: VisualDensity.compact, + onPressed: () { + showDialog?>( + context: context, + builder: (BuildContext ctx) { + return GeneratedFormModal( + title: 'Remove Selected Apps?', + items: const [], + defaultValues: const [], + initValid: true, + message: + '${selectedIds.length} App${selectedIds.length == 1 ? '' : 's'} will be removed from Obtainium but remain installed. You still need to uninstall ${selectedIds.length == 1 ? 'it' : 'them'} manually.', + ); + }).then((values) { + if (values != null) { + appsProvider.removeApps(selectedIds.toList()); + } + }); + }, + tooltip: 'Remove Selected Apps', + icon: const Icon(Icons.delete_outline_outlined), + ), + IconButton( + visualDensity: VisualDensity.compact, + onPressed: appsProvider.areDownloadsRunning() || + (existingUpdateIdsAllOrSelected.isEmpty && + newInstallIdsAllOrSelected.isEmpty) + ? null + : () { + HapticFeedback.heavyImpact(); + List> formInputs = []; + if (existingUpdateIdsAllOrSelected.isNotEmpty && + newInstallIdsAllOrSelected.isNotEmpty) { + formInputs.add([ + GeneratedFormItem( + label: + 'Update ${existingUpdateIdsAllOrSelected.length} Apps?', + type: FormItemType.bool) + ]); + formInputs.add([ + GeneratedFormItem( + label: + 'Install ${newInstallIdsAllOrSelected.length} new Apps?', + type: FormItemType.bool) + ]); + } + showDialog?>( + context: context, + builder: (BuildContext ctx) { + return GeneratedFormModal( + title: + 'Install${selectedIds.isEmpty ? ' ' : ' Selected '}Apps?', + message: + '${existingUpdateIdsAllOrSelected.length} update${existingUpdateIdsAllOrSelected.length == 1 ? '' : 's'} and ${newInstallIdsAllOrSelected.length} new install${newInstallIdsAllOrSelected.length == 1 ? '' : 's'}.', + items: formInputs, + defaultValues: const ['true', 'true'], + initValid: true, + ); + }).then((values) { + if (values != null) { + bool shouldInstallUpdates = + values.length < 2 || values[0] == 'true'; + bool shouldInstallNew = + values.length < 2 || values[1] == 'true'; + settingsProvider + .getInstallPermission() + .then((_) { + List toInstall = []; + if (shouldInstallUpdates) { + toInstall + .addAll(existingUpdateIdsAllOrSelected); + } + if (shouldInstallNew) { + toInstall + .addAll(newInstallIdsAllOrSelected); + } + appsProvider.downloadAndInstallLatestApp( + toInstall, context); + }); } - urls = urls.substring(0, urls.length - 1); - Share.share(urls, - subject: 'Selected App URLs from Obtainium'); - }, - tooltip: 'Share Selected App URLs', - icon: const Icon(Icons.share), - ), - ], - )), + }); + }, + tooltip: + 'Install/Update${selectedIds.isEmpty ? ' ' : ' Selected '}Apps', + icon: const Icon( + Icons.file_download_outlined, + )), + selectedIds.isEmpty + ? const SizedBox() + : IconButton( + visualDensity: VisualDensity.compact, + onPressed: () { + String urls = ''; + for (var id in selectedIds) { + urls += '${appsProvider.apps[id]!.app.url}\n'; + } + urls = urls.substring(0, urls.length - 1); + Share.share(urls, + subject: 'Selected App URLs from Obtainium'); + }, + tooltip: 'Share Selected App URLs', + icon: const Icon(Icons.share), + ), + ], + )), const VerticalDivider(), + IconButton( + visualDensity: VisualDensity.compact, + onPressed: () { + setState(() { + if (currentFilterIsUpdatesOnly) { + filter = null; + } else { + filter = updatesOnlyFilter; + } + }); + }, + tooltip: currentFilterIsUpdatesOnly + ? 'Remove Out-of-Date App Filter' + : 'Show Out-of-Date Apps Only', + icon: Icon( + currentFilterIsUpdatesOnly + ? Icons.update_disabled_rounded + : Icons.update_rounded, + color: Theme.of(context).colorScheme.primary, + ), + ), appsProvider.apps.isEmpty ? const SizedBox() : TextButton.icon( @@ -368,10 +388,6 @@ class AppsPageState extends State { filter = null; } }); - } else { - setState(() { - filter = null; - }); } }); }, diff --git a/pubspec.yaml b/pubspec.yaml index afa428e..a6282f0 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.4.0+19 # When changing this, update the tag in main() accordingly +version: 0.4.1+20 # When changing this, update the tag in main() accordingly environment: sdk: '>=2.19.0-79.0.dev <3.0.0'