Compare commits

...

2 Commits

Author SHA1 Message Date
Imran Remtulla
77e1768f3b Bugfix 2022-09-25 11:46:25 -04:00
Imran Remtulla
da9e5aed5e Apps page UI improvements 2022-09-25 11:32:57 -04:00
3 changed files with 161 additions and 145 deletions

View File

@@ -13,7 +13,7 @@ import 'package:dynamic_color/dynamic_color.dart';
import 'package:device_info_plus/device_info_plus.dart'; import 'package:device_info_plus/device_info_plus.dart';
const String currentReleaseTag = 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') @pragma('vm:entry-point')
void bgTaskCallback() { void bgTaskCallback() {

View File

@@ -18,6 +18,8 @@ class AppsPage extends StatefulWidget {
class AppsPageState extends State<AppsPage> { class AppsPageState extends State<AppsPage> {
AppsFilter? filter; AppsFilter? filter;
var updatesOnlyFilter =
AppsFilter(includeUptodate: false, includeNonInstalled: false);
Set<String> selectedIds = {}; Set<String> selectedIds = {};
clearSelected() { clearSelected() {
@@ -45,6 +47,8 @@ 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 sortedApps = appsProvider.apps.values.toList(); var sortedApps = appsProvider.apps.values.toList();
var currentFilterIsUpdatesOnly =
filter?.isIdenticalTo(updatesOnlyFilter) ?? false;
selectedIds = selectedIds selectedIds = selectedIds
.where((element) => sortedApps.map((e) => e.app.id).contains(element)) .where((element) => sortedApps.map((e) => e.app.id).contains(element))
@@ -112,6 +116,19 @@ class AppsPageState extends State<AppsPage> {
sortedApps = sortedApps.reversed.toList(); sortedApps = sortedApps.reversed.toList();
} }
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();
return Scaffold( return Scaffold(
backgroundColor: Theme.of(context).colorScheme.surface, backgroundColor: Theme.of(context).colorScheme.surface,
body: RefreshIndicator( body: RefreshIndicator(
@@ -133,8 +150,9 @@ class AppsPageState extends State<AppsPage> {
: Text( : Text(
appsProvider.apps.isEmpty appsProvider.apps.isEmpty
? 'No Apps' ? 'No Apps'
: 'No Search Results', : 'No Apps for Filter',
style: Theme.of(context).textTheme.headlineMedium, style: Theme.of(context).textTheme.headlineMedium,
textAlign: TextAlign.center,
))), ))),
SliverList( SliverList(
delegate: SliverChildBuilderDelegate( delegate: SliverChildBuilderDelegate(
@@ -175,154 +193,156 @@ class AppsPageState extends State<AppsPage> {
persistentFooterButtons: [ persistentFooterButtons: [
Row( Row(
children: [ children: [
TextButton.icon( IconButton(
onPressed: () { onPressed: () {
selectedIds.isEmpty selectedIds.isEmpty
? selectThese(sortedApps.map((e) => e.app.id).toList()) ? selectThese(sortedApps.map((e) => e.app.id).toList())
: clearSelected(); : clearSelected();
}, },
icon: Icon(selectedIds.isEmpty icon: Icon(
? Icons.select_all_outlined selectedIds.isEmpty
: Icons.deselect_outlined), ? Icons.select_all_outlined
label: Text(selectedIds.isEmpty : Icons.deselect_outlined,
color: Theme.of(context).colorScheme.primary,
),
tooltip: selectedIds.isEmpty
? 'Select All' ? 'Select All'
: 'Deselect ${selectedIds.length.toString()}')), : 'Deselect ${selectedIds.length.toString()}'),
const VerticalDivider(), const VerticalDivider(),
Expanded( Expanded(
child: selectedIds.isEmpty child: Row(
? Container() mainAxisAlignment: MainAxisAlignment.spaceEvenly,
: Row( children: [
mainAxisAlignment: MainAxisAlignment.spaceEvenly, selectedIds.isEmpty
children: [ ? const SizedBox()
IconButton( : IconButton(
visualDensity: VisualDensity.compact, visualDensity: VisualDensity.compact,
onPressed: () { onPressed: () {
showDialog<List<String>?>( showDialog<List<String>?>(
context: context, context: context,
builder: (BuildContext ctx) { builder: (BuildContext ctx) {
return GeneratedFormModal( return GeneratedFormModal(
title: 'Remove Selected Apps?', title: 'Remove Selected Apps?',
items: const [], items: const [],
defaultValues: const [], defaultValues: const [],
initValid: true, initValid: true,
message: 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.', '${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) { }).then((values) {
if (values != null) { if (values != null) {
appsProvider.removeApps(selectedIds.toList()); appsProvider.removeApps(selectedIds.toList());
} }
}); });
}, },
tooltip: 'Remove Selected Apps', tooltip: 'Remove Selected Apps',
icon: const Icon(Icons.delete_outline_outlined), icon: const Icon(Icons.delete_outline_outlined),
), ),
IconButton( IconButton(
visualDensity: VisualDensity.compact, visualDensity: VisualDensity.compact,
onPressed: appsProvider.areDownloadsRunning() || onPressed: appsProvider.areDownloadsRunning() ||
selectedIds (existingUpdateIdsAllOrSelected.isEmpty &&
.where((id) => newInstallIdsAllOrSelected.isEmpty)
appsProvider.apps[id]!.app ? null
.installedVersion != : () {
appsProvider HapticFeedback.heavyImpact();
.apps[id]!.app.latestVersion) List<List<GeneratedFormItem>> formInputs = [];
.isEmpty if (existingUpdateIdsAllOrSelected.isNotEmpty &&
? null newInstallIdsAllOrSelected.isNotEmpty) {
: () { formInputs.add([
HapticFeedback.heavyImpact(); GeneratedFormItem(
var existingUpdateIdsSelected = label:
appsProvider 'Update ${existingUpdateIdsAllOrSelected.length} App${existingUpdateIdsAllOrSelected.length == 1 ? '' : 's'}',
.getExistingUpdates( type: FormItemType.bool)
installedOnly: true) ]);
.where((element) => formInputs.add([
selectedIds.contains(element)) GeneratedFormItem(
.toList(); label:
var newInstallIdsSelected = appsProvider 'Install ${newInstallIdsAllOrSelected.length} new App${newInstallIdsAllOrSelected.length == 1 ? '' : 's'}',
.getExistingUpdates( type: FormItemType.bool)
nonInstalledOnly: true) ]);
.where((element) => }
selectedIds.contains(element)) showDialog<List<String>?>(
.toList(); context: context,
List<List<GeneratedFormItem>> formInputs = builder: (BuildContext ctx) {
[]; return GeneratedFormModal(
if (existingUpdateIdsSelected title:
.isNotEmpty && 'Install${selectedIds.isEmpty ? ' ' : ' Selected '}Apps?',
newInstallIdsSelected.isNotEmpty) { message:
formInputs.add([ '${existingUpdateIdsAllOrSelected.length} update${existingUpdateIdsAllOrSelected.length == 1 ? '' : 's'} and ${newInstallIdsAllOrSelected.length} new install${newInstallIdsAllOrSelected.length == 1 ? '' : 's'}.',
GeneratedFormItem( items: formInputs,
label: defaultValues: const ['true', 'true'],
'Update ${existingUpdateIdsSelected.length} Apps?', initValid: true,
type: FormItemType.bool) );
]); }).then((values) {
formInputs.add([ if (values != null) {
GeneratedFormItem( bool shouldInstallUpdates =
label: values.length < 2 || values[0] == 'true';
'Install ${newInstallIdsSelected.length} new Apps?', bool shouldInstallNew =
type: FormItemType.bool) values.length < 2 || values[1] == 'true';
]); settingsProvider
} .getInstallPermission()
showDialog<List<String>?>( .then((_) {
context: context, List<String> toInstall = [];
builder: (BuildContext ctx) { if (shouldInstallUpdates) {
return GeneratedFormModal( toInstall
title: 'Install Selected Apps?', .addAll(existingUpdateIdsAllOrSelected);
message: }
'${existingUpdateIdsSelected.length} update${existingUpdateIdsSelected.length == 1 ? '' : 's'} and ${newInstallIdsSelected.length} new install${newInstallIdsSelected.length == 1 ? '' : 's'}.', if (shouldInstallNew) {
items: formInputs, toInstall
defaultValues: const [ .addAll(newInstallIdsAllOrSelected);
'true', }
'true' appsProvider.downloadAndInstallLatestApp(
], toInstall, context);
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<String> 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';
} }
urls = urls.substring(0, urls.length - 1); });
Share.share(urls, },
subject: 'Selected App URLs from Obtainium'); tooltip:
}, 'Install/Update${selectedIds.isEmpty ? ' ' : ' Selected '}Apps',
tooltip: 'Share Selected App URLs', icon: const Icon(
icon: const Icon(Icons.share), 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(), 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 appsProvider.apps.isEmpty
? const SizedBox() ? const SizedBox()
: TextButton.icon( : TextButton.icon(
@@ -368,10 +388,6 @@ class AppsPageState extends State<AppsPage> {
filter = null; filter = null;
} }
}); });
} else {
setState(() {
filter = null;
});
} }
}); });
}, },

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.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: environment:
sdk: '>=2.19.0-79.0.dev <3.0.0' sdk: '>=2.19.0-79.0.dev <3.0.0'