mirror of
https://github.com/ImranR98/Obtainium.git
synced 2025-10-28 20:13:28 +01:00
Done w/ filter and multi select stuff
This commit is contained in:
@@ -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: () {
|
||||||
|
|||||||
@@ -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((_) =>
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
Reference in New Issue
Block a user