Done w/ filter and multi select stuff

This commit is contained in:
Imran Remtulla
2022-09-25 00:12:02 -04:00
parent 45e5544c5b
commit f58d26524c
4 changed files with 224 additions and 76 deletions

View File

@@ -7,11 +7,15 @@ class GeneratedFormModal extends StatefulWidget {
{super.key, {super.key,
required this.title, required this.title,
required this.items, required this.items,
required this.defaultValues}); required this.defaultValues,
this.initValid = false,
this.message = ""});
final String title; final String title;
final String message;
final List<List<GeneratedFormItem>> items; final List<List<GeneratedFormItem>> items;
final List<String> defaultValues; final List<String> defaultValues;
final bool initValid;
@override @override
State<GeneratedFormModal> createState() => _GeneratedFormModalState(); State<GeneratedFormModal> createState() => _GeneratedFormModalState();
@@ -21,20 +25,34 @@ class _GeneratedFormModalState extends State<GeneratedFormModal> {
List<String> values = []; List<String> values = [];
bool valid = false; bool valid = false;
@override
void initState() {
super.initState();
valid = widget.initValid;
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return AlertDialog( return AlertDialog(
scrollable: true, scrollable: true,
title: Text(widget.title), title: Text(widget.title),
content: GeneratedForm( content:
items: widget.items, Column(crossAxisAlignment: CrossAxisAlignment.stretch, children: [
onValueChanges: (values, valid) { if (widget.message.isNotEmpty) Text(widget.message),
setState(() { if (widget.message.isNotEmpty)
this.values = values; SizedBox(
this.valid = valid; height: 16,
}); ),
}, GeneratedForm(
defaultValues: widget.defaultValues), items: widget.items,
onValueChanges: (values, valid) {
setState(() {
this.values = values;
this.valid = valid;
});
},
defaultValues: widget.defaultValues)
]),
actions: [ actions: [
TextButton( TextButton(
onPressed: () { onPressed: () {

View File

@@ -247,9 +247,8 @@ class _AppPageState extends State<AppPage> {
onPressed: () { onPressed: () {
HapticFeedback HapticFeedback
.selectionClick(); .selectionClick();
appsProvider appsProvider.removeApps(
.removeApp(app!.app.id) [app!.app.id]).then((_) {
.then((_) {
int count = 0; int count = 0;
Navigator.of(context) Navigator.of(context)
.popUntil((_) => .popUntil((_) =>

View File

@@ -43,7 +43,6 @@ class AppsPageState extends State<AppsPage> {
Widget build(BuildContext context) { Widget build(BuildContext context) {
var appsProvider = context.watch<AppsProvider>(); var appsProvider = context.watch<AppsProvider>();
var settingsProvider = context.watch<SettingsProvider>(); var settingsProvider = context.watch<SettingsProvider>();
var existingUpdateAppIds = appsProvider.getExistingUpdates();
var sortedApps = appsProvider.apps.values.toList(); var sortedApps = appsProvider.apps.values.toList();
selectedIds = selectedIds selectedIds = selectedIds
@@ -63,7 +62,11 @@ class AppsPageState extends State<AppsPage> {
if (filter != null) { if (filter != null) {
sortedApps = sortedApps.where((app) { sortedApps = sortedApps.where((app) {
if (app.app.installedVersion == app.app.latestVersion && if (app.app.installedVersion == app.app.latestVersion &&
filter!.onlyNonLatest) { !(filter!.includeUptodate)) {
return false;
}
if (app.app.installedVersion == null &&
!(filter!.includeNonInstalled)) {
return false; return false;
} }
if (filter!.nameFilter.isEmpty && filter!.authorFilter.isEmpty) { if (filter!.nameFilter.isEmpty && filter!.authorFilter.isEmpty) {
@@ -184,38 +187,133 @@ class AppsPageState extends State<AppsPage> {
? 'Select All' ? 'Select All'
: 'Deselect ${selectedIds.length.toString()}')), : 'Deselect ${selectedIds.length.toString()}')),
const VerticalDivider(), const VerticalDivider(),
const Spacer(), Expanded(
selectedIds.isEmpty child: selectedIds.isEmpty
? const SizedBox() ? Container()
: IconButton( : Row(
onPressed: () { mainAxisAlignment: MainAxisAlignment.spaceEvenly,
// TODO: Delete selected Apps after confirming children: [
}, IconButton(
icon: const Icon(Icons.install_mobile_outlined)), onPressed: () {
selectedIds.isEmpty showDialog<List<String>?>(
? const SizedBox() context: context,
: IconButton( builder: (BuildContext ctx) {
onPressed: () { return GeneratedFormModal(
// TODO: Install selected Apps if they are not up to date after confirming (replace existing button) title: 'Remove Selected Apps?',
}, items: const [],
icon: const Icon(Icons.delete_outline_rounded)), defaultValues: const [],
existingUpdateAppIds.isEmpty || filter != null initValid: true,
? const SizedBox() message:
: IconButton( '${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.',
onPressed: appsProvider.areDownloadsRunning() );
? null }).then((values) {
: () { if (values != null) {
HapticFeedback.heavyImpact(); appsProvider.removeApps(selectedIds.toList());
settingsProvider.getInstallPermission().then((_) { }
appsProvider.downloadAndInstallLatestApp( });
existingUpdateAppIds, context); },
}); icon: const Icon(Icons.delete_outline_outlined),
}, ),
icon: const Icon(Icons.install_mobile_outlined), IconButton(
), 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<List<GeneratedFormItem>> 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<List<String>?>(
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<String> toInstall = [];
if (shouldInstallUpdates) {
toInstall.addAll(
existingUpdateIdsSelected);
}
if (shouldInstallNew) {
toInstall.addAll(
newInstallIdsSelected);
}
appsProvider
.downloadAndInstallLatestApp(
toInstall, context);
});
}
});
},
icon: const Icon(
Icons.file_download_outlined,
)),
],
)),
const VerticalDivider(),
appsProvider.apps.isEmpty appsProvider.apps.isEmpty
? const SizedBox() ? const SizedBox()
: IconButton( : TextButton.icon(
label: Text(
filter == null ? 'Filter' : 'Filter *',
style: TextStyle(
fontWeight: filter == null
? FontWeight.normal
: FontWeight.bold),
),
onPressed: () { onPressed: () {
showDialog<List<String>?>( showDialog<List<String>?>(
context: context, context: context,
@@ -231,27 +329,25 @@ class AppsPageState extends State<AppsPage> {
], ],
[ [
GeneratedFormItem( GeneratedFormItem(
label: "Ignore Up-to-Date Apps", label: "Up to Date Apps",
type: FormItemType.bool)
],
[
GeneratedFormItem(
label: "Non-Installed Apps",
type: FormItemType.bool) type: FormItemType.bool)
] ]
], ],
defaultValues: filter == null defaultValues: filter == null
? [] ? AppsFilter().toValuesArray()
: [ : filter!.toValuesArray());
filter!.nameFilter,
filter!.authorFilter,
filter!.onlyNonLatest ? 'true' : ''
]);
}).then((values) { }).then((values) {
if (values != null && if (values != null) {
values
.where((element) => element.isNotEmpty)
.isNotEmpty) {
setState(() { setState(() {
filter = AppsFilter( filter = AppsFilter.fromValuesArray(values);
nameFilter: values[0], if (AppsFilter().isIdenticalTo(filter!)) {
authorFilter: values[1], filter = null;
onlyNonLatest: values[2] == "true"); }
}); });
} else { } else {
setState(() { setState(() {
@@ -260,8 +356,7 @@ class AppsPageState extends State<AppsPage> {
} }
}); });
}, },
icon: Icon( icon: const Icon(Icons.filter_list_rounded))
filter == null ? Icons.search : Icons.manage_search))
], ],
), ),
], ],
@@ -272,10 +367,34 @@ class AppsPageState extends State<AppsPage> {
class AppsFilter { class AppsFilter {
late String nameFilter; late String nameFilter;
late String authorFilter; late String authorFilter;
late bool onlyNonLatest; late bool includeUptodate;
late bool includeNonInstalled;
AppsFilter( AppsFilter(
{this.nameFilter = "", {this.nameFilter = "",
this.authorFilter = "", this.authorFilter = "",
this.onlyNonLatest = false}); this.includeUptodate = true,
this.includeNonInstalled = true});
List<String> toValuesArray() {
return [
nameFilter,
authorFilter,
includeUptodate ? "true" : "",
includeNonInstalled ? "true" : ""
];
}
AppsFilter.fromValuesArray(List<String> values) {
nameFilter = values[0];
authorFilter = values[1];
includeUptodate = values[2] == "true";
includeNonInstalled = values[3] == "true";
}
bool isIdenticalTo(AppsFilter other) =>
authorFilter.trim() == other.authorFilter.trim() &&
nameFilter.trim() == other.nameFilter.trim() &&
includeUptodate == other.includeUptodate &&
includeNonInstalled == other.includeNonInstalled;
} }

View File

@@ -98,7 +98,7 @@ class AppsProvider with ChangeNotifier {
.isNotEmpty; .isNotEmpty;
Future<bool> canInstallSilently(App app) async { Future<bool> canInstallSilently(App app) async {
// TODO: This is unreliable - try to get from OS // TODO: This is unreliable - try to get from OS in the future
var osInfo = await DeviceInfoPlugin().androidInfo; var osInfo = await DeviceInfoPlugin().androidInfo;
return app.installedVersion != null && return app.installedVersion != null &&
osInfo.version.sdkInt! >= 30 && osInfo.version.sdkInt! >= 30 &&
@@ -203,9 +203,11 @@ class AppsProvider with ChangeNotifier {
} }
if (context != null) { if (context != null) {
for (var i in regularInstalls) { if (regularInstalls.isNotEmpty) {
// ignore: use_build_context_synchronously // ignore: use_build_context_synchronously
await askUserToReturnToForeground(context); await askUserToReturnToForeground(context);
}
for (var i in regularInstalls) {
await installApk(i); await installApk(i);
} }
} }
@@ -256,15 +258,19 @@ class AppsProvider with ChangeNotifier {
notifyListeners(); notifyListeners();
} }
Future<void> removeApp(String appId) async { Future<void> removeApps(List<String> appIds) async {
File file = File('${(await getAppsDir()).path}/$appId.json'); for (var appId in appIds) {
if (file.existsSync()) { File file = File('${(await getAppsDir()).path}/$appId.json');
file.deleteSync(); if (file.existsSync()) {
file.deleteSync();
}
if (apps.containsKey(appId)) {
apps.remove(appId);
}
} }
if (apps.containsKey(appId)) { if (appIds.isNotEmpty) {
apps.remove(appId); notifyListeners();
} }
notifyListeners();
} }
bool checkAppObjectForUpdate(App app) { bool checkAppObjectForUpdate(App app) {
@@ -309,14 +315,20 @@ class AppsProvider with ChangeNotifier {
return updates; return updates;
} }
List<String> getExistingUpdates({bool installedOnly = false}) { List<String> getExistingUpdates(
{bool installedOnly = false, bool nonInstalledOnly = false}) {
List<String> updateAppIds = []; List<String> updateAppIds = [];
List<String> appIds = apps.keys.toList(); List<String> appIds = apps.keys.toList();
for (int i = 0; i < appIds.length; i++) { for (int i = 0; i < appIds.length; i++) {
App? app = apps[appIds[i]]!.app; App? app = apps[appIds[i]]!.app;
if (app.installedVersion != app.latestVersion && if (app.installedVersion != app.latestVersion &&
(app.installedVersion != null || !installedOnly)) { (!installedOnly || !nonInstalledOnly)) {
updateAppIds.add(app.id); if ((app.installedVersion == null &&
(nonInstalledOnly || !installedOnly) ||
(app.installedVersion != null &&
(installedOnly || !nonInstalledOnly)))) {
updateAppIds.add(app.id);
}
} }
} }
return updateAppIds; return updateAppIds;