mirror of
https://github.com/ImranR98/Obtainium.git
synced 2025-08-01 21:30:16 +02:00
Added search bar on Add App page
This commit is contained in:
@@ -5,6 +5,7 @@ import 'package:obtainium/components/generated_form.dart';
|
||||
import 'package:obtainium/components/generated_form_modal.dart';
|
||||
import 'package:obtainium/custom_errors.dart';
|
||||
import 'package:obtainium/pages/app.dart';
|
||||
import 'package:obtainium/pages/import_export.dart';
|
||||
import 'package:obtainium/providers/apps_provider.dart';
|
||||
import 'package:obtainium/providers/settings_provider.dart';
|
||||
import 'package:obtainium/providers/source_provider.dart';
|
||||
@@ -22,6 +23,7 @@ class _AddAppPageState extends State<AddAppPage> {
|
||||
bool gettingAppInfo = false;
|
||||
|
||||
String userInput = '';
|
||||
String searchQuery = '';
|
||||
AppSource? pickedSource;
|
||||
List<String> sourceSpecificAdditionalData = [];
|
||||
bool sourceSpecificDataIsValid = true;
|
||||
@@ -31,6 +33,107 @@ class _AddAppPageState extends State<AddAppPage> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
SourceProvider sourceProvider = SourceProvider();
|
||||
AppsProvider appsProvider = context.read<AppsProvider>();
|
||||
|
||||
changeUserInput(String input, bool valid, bool isBuilding) {
|
||||
userInput = input;
|
||||
fn() {
|
||||
var source = valid ? sourceProvider.getSource(userInput) : null;
|
||||
if (pickedSource != source) {
|
||||
pickedSource = source;
|
||||
sourceSpecificAdditionalData =
|
||||
source != null ? source.additionalSourceAppSpecificDefaults : [];
|
||||
sourceSpecificDataIsValid = source != null
|
||||
? sourceProvider.ifSourceAppsRequireAdditionalData(source)
|
||||
: true;
|
||||
}
|
||||
}
|
||||
|
||||
if (isBuilding) {
|
||||
fn();
|
||||
} else {
|
||||
setState(() {
|
||||
fn();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
addApp({bool resetUserInputAfter = false}) async {
|
||||
setState(() {
|
||||
gettingAppInfo = true;
|
||||
});
|
||||
var settingsProvider = context.read<SettingsProvider>();
|
||||
() async {
|
||||
var userPickedTrackOnly = findGeneratedFormValueByKey(
|
||||
pickedSource!.additionalAppSpecificSourceAgnosticFormItems,
|
||||
otherAdditionalData,
|
||||
'trackOnlyFormItemKey') ==
|
||||
'true';
|
||||
var cont = true;
|
||||
if ((userPickedTrackOnly || pickedSource!.enforceTrackOnly) &&
|
||||
await showDialog(
|
||||
context: context,
|
||||
builder: (BuildContext ctx) {
|
||||
return GeneratedFormModal(
|
||||
title:
|
||||
'${pickedSource!.enforceTrackOnly ? 'Source' : 'App'} is Track-Only',
|
||||
items: const [],
|
||||
defaultValues: const [],
|
||||
message:
|
||||
'${pickedSource!.enforceTrackOnly ? 'Apps from this source are \'Track-Only\'.' : 'You have selected the \'Track-Only\' option.'}\n\nThe App will be tracked for updates, but Obtainium will not be able to download or install it.',
|
||||
);
|
||||
}) ==
|
||||
null) {
|
||||
cont = false;
|
||||
}
|
||||
if (cont) {
|
||||
HapticFeedback.selectionClick();
|
||||
var trackOnly = pickedSource!.enforceTrackOnly || userPickedTrackOnly;
|
||||
App app = await sourceProvider.getApp(
|
||||
pickedSource!, userInput, sourceSpecificAdditionalData,
|
||||
trackOnly: trackOnly);
|
||||
if (!trackOnly) {
|
||||
await settingsProvider.getInstallPermission();
|
||||
}
|
||||
// Only download the APK here if you need to for the package ID
|
||||
if (sourceProvider.isTempId(app.id) && !app.trackOnly) {
|
||||
// ignore: use_build_context_synchronously
|
||||
var apkUrl = await appsProvider.confirmApkUrl(app, context);
|
||||
if (apkUrl == null) {
|
||||
throw ObtainiumError('Cancelled');
|
||||
}
|
||||
app.preferredApkIndex = app.apkUrls.indexOf(apkUrl);
|
||||
var downloadedApk = await appsProvider.downloadApp(app);
|
||||
app.id = downloadedApk.appId;
|
||||
}
|
||||
if (appsProvider.apps.containsKey(app.id)) {
|
||||
throw ObtainiumError('App already added');
|
||||
}
|
||||
if (app.trackOnly) {
|
||||
app.installedVersion = app.latestVersion;
|
||||
}
|
||||
await appsProvider.saveApps([app]);
|
||||
|
||||
return app;
|
||||
}
|
||||
}()
|
||||
.then((app) {
|
||||
if (app != null) {
|
||||
Navigator.push(context,
|
||||
MaterialPageRoute(builder: (context) => AppPage(appId: app.id)));
|
||||
}
|
||||
}).catchError((e) {
|
||||
showError(e, context);
|
||||
}).whenComplete(() {
|
||||
setState(() {
|
||||
gettingAppInfo = false;
|
||||
if (resetUserInputAfter) {
|
||||
changeUserInput('', false, true);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
return Scaffold(
|
||||
backgroundColor: Theme.of(context).colorScheme.surface,
|
||||
body: CustomScrollView(slivers: <Widget>[
|
||||
@@ -70,34 +173,8 @@ class _AddAppPageState extends State<AddAppPage> {
|
||||
]
|
||||
],
|
||||
onValueChanges: (values, valid, isBuilding) {
|
||||
fn() {
|
||||
userInput = values[0];
|
||||
var source = valid
|
||||
? sourceProvider.getSource(userInput)
|
||||
: null;
|
||||
if (pickedSource != source) {
|
||||
pickedSource = source;
|
||||
sourceSpecificAdditionalData = source !=
|
||||
null
|
||||
? source
|
||||
.additionalSourceAppSpecificDefaults
|
||||
: [];
|
||||
sourceSpecificDataIsValid = source !=
|
||||
null
|
||||
? sourceProvider
|
||||
.ifSourceAppsRequireAdditionalData(
|
||||
source)
|
||||
: true;
|
||||
}
|
||||
}
|
||||
|
||||
if (isBuilding) {
|
||||
fn();
|
||||
} else {
|
||||
setState(() {
|
||||
fn();
|
||||
});
|
||||
}
|
||||
changeUserInput(
|
||||
values[0], valid, isBuilding);
|
||||
},
|
||||
defaultValues: const [])),
|
||||
const SizedBox(
|
||||
@@ -117,111 +194,91 @@ class _AddAppPageState extends State<AddAppPage> {
|
||||
.isNotEmpty &&
|
||||
!otherAdditionalDataIsValid)
|
||||
? null
|
||||
: () async {
|
||||
setState(() {
|
||||
gettingAppInfo = true;
|
||||
});
|
||||
var appsProvider =
|
||||
context.read<AppsProvider>();
|
||||
var settingsProvider =
|
||||
context.read<SettingsProvider>();
|
||||
() async {
|
||||
var userPickedTrackOnly =
|
||||
findGeneratedFormValueByKey(
|
||||
pickedSource!
|
||||
.additionalAppSpecificSourceAgnosticFormItems,
|
||||
otherAdditionalData,
|
||||
'trackOnlyFormItemKey') ==
|
||||
'true';
|
||||
var cont = true;
|
||||
if ((userPickedTrackOnly ||
|
||||
pickedSource!
|
||||
.enforceTrackOnly) &&
|
||||
await showDialog(
|
||||
context: context,
|
||||
builder:
|
||||
(BuildContext ctx) {
|
||||
return GeneratedFormModal(
|
||||
title:
|
||||
'${pickedSource!.enforceTrackOnly ? 'Source' : 'App'} is Track-Only',
|
||||
items: const [],
|
||||
defaultValues: const [],
|
||||
message:
|
||||
'${pickedSource!.enforceTrackOnly ? 'Apps from this source are \'Track-Only\'.' : 'You have selected the \'Track-Only\' option.'}\n\nThe App will be tracked for updates, but Obtainium will not be able to download or install it.',
|
||||
);
|
||||
}) ==
|
||||
null) {
|
||||
cont = false;
|
||||
}
|
||||
if (cont) {
|
||||
HapticFeedback.selectionClick();
|
||||
var trackOnly = pickedSource!
|
||||
.enforceTrackOnly ||
|
||||
userPickedTrackOnly;
|
||||
App app =
|
||||
await sourceProvider.getApp(
|
||||
pickedSource!,
|
||||
userInput,
|
||||
sourceSpecificAdditionalData,
|
||||
trackOnly: trackOnly);
|
||||
if (!trackOnly) {
|
||||
await settingsProvider
|
||||
.getInstallPermission();
|
||||
}
|
||||
// Only download the APK here if you need to for the package ID
|
||||
if (sourceProvider
|
||||
.isTempId(app.id) &&
|
||||
!app.trackOnly) {
|
||||
// ignore: use_build_context_synchronously
|
||||
var apkUrl = await appsProvider
|
||||
.confirmApkUrl(
|
||||
app, context);
|
||||
if (apkUrl == null) {
|
||||
throw ObtainiumError(
|
||||
'Cancelled');
|
||||
}
|
||||
app.preferredApkIndex =
|
||||
app.apkUrls.indexOf(apkUrl);
|
||||
var downloadedApk =
|
||||
await appsProvider
|
||||
.downloadApp(app);
|
||||
app.id = downloadedApk.appId;
|
||||
}
|
||||
if (appsProvider.apps
|
||||
.containsKey(app.id)) {
|
||||
throw ObtainiumError(
|
||||
'App already added');
|
||||
}
|
||||
if (app.trackOnly) {
|
||||
app.installedVersion =
|
||||
app.latestVersion;
|
||||
}
|
||||
await appsProvider
|
||||
.saveApps([app]);
|
||||
|
||||
return app;
|
||||
}
|
||||
}()
|
||||
.then((app) {
|
||||
if (app != null) {
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (context) =>
|
||||
AppPage(
|
||||
appId: app.id)));
|
||||
}
|
||||
}).catchError((e) {
|
||||
showError(e, context);
|
||||
}).whenComplete(() {
|
||||
setState(() {
|
||||
gettingAppInfo = false;
|
||||
});
|
||||
});
|
||||
},
|
||||
: addApp,
|
||||
child: const Text('Add'))
|
||||
],
|
||||
),
|
||||
const SizedBox(
|
||||
height: 16,
|
||||
),
|
||||
if (sourceProvider.sources
|
||||
.where((e) => e.canSearch)
|
||||
.isNotEmpty &&
|
||||
pickedSource == null)
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: GeneratedForm(
|
||||
items: [
|
||||
[
|
||||
GeneratedFormItem(
|
||||
label: 'Search (Some Sources Only)',
|
||||
required: false),
|
||||
]
|
||||
],
|
||||
onValueChanges: (values, valid, isBuilding) {
|
||||
if (values.isNotEmpty && valid) {
|
||||
setState(() {
|
||||
searchQuery = values[0].trim();
|
||||
});
|
||||
}
|
||||
},
|
||||
defaultValues: const ['']),
|
||||
),
|
||||
const SizedBox(
|
||||
width: 16,
|
||||
),
|
||||
ElevatedButton(
|
||||
onPressed: searchQuery.isEmpty || gettingAppInfo
|
||||
? null
|
||||
: () {
|
||||
Future.wait(sourceProvider.sources
|
||||
.where((e) => e.canSearch)
|
||||
.map((e) =>
|
||||
e.search(searchQuery)))
|
||||
.then((results) async {
|
||||
var res = // TODO: Interleave results
|
||||
results.reduce((value, element) {
|
||||
value.addAll(element);
|
||||
return value;
|
||||
});
|
||||
// Map<String, String> res = {};
|
||||
// var si = 0;
|
||||
// var done = false;
|
||||
// for (var r in results) {
|
||||
// if (r.length > si) {
|
||||
// res.addEntries(r.entries.toList()[si]);
|
||||
// }
|
||||
// }
|
||||
// for (var rs in results) {
|
||||
// for (var r in rs.entries) {}
|
||||
// }
|
||||
List<String>? selectedUrls = res
|
||||
.isEmpty
|
||||
? []
|
||||
: await showDialog<List<String>?>(
|
||||
context: context,
|
||||
builder: (BuildContext ctx) {
|
||||
return UrlSelectionModal(
|
||||
urlsWithDescriptions: res,
|
||||
selectedByDefault: false,
|
||||
onlyOneSelectionAllowed:
|
||||
true,
|
||||
);
|
||||
});
|
||||
if (selectedUrls != null &&
|
||||
selectedUrls.isNotEmpty) {
|
||||
changeUserInput(
|
||||
selectedUrls[0], true, true);
|
||||
addApp(resetUserInputAfter: true);
|
||||
}
|
||||
}).catchError((e) {
|
||||
showError(e, context);
|
||||
});
|
||||
},
|
||||
child: const Text('Search'))
|
||||
],
|
||||
),
|
||||
if (pickedSource != null &&
|
||||
(pickedSource!.additionalSourceAppSpecificDefaults
|
||||
.isNotEmpty ||
|
||||
@@ -314,7 +371,7 @@ class _AddAppPageState extends State<AddAppPage> {
|
||||
LaunchMode.externalApplication);
|
||||
},
|
||||
child: Text(
|
||||
'${e.runtimeType.toString()}${e.enforceTrackOnly ? ' (Track-Only)' : ''}',
|
||||
'${e.runtimeType.toString()}${e.enforceTrackOnly ? ' (Track-Only)' : ''}${e.canSearch ? ' (Searchable)' : ''}',
|
||||
style: const TextStyle(
|
||||
decoration:
|
||||
TextDecoration.underline,
|
||||
@@ -322,6 +379,9 @@ class _AddAppPageState extends State<AddAppPage> {
|
||||
)))
|
||||
.toList()
|
||||
])),
|
||||
const SizedBox(
|
||||
height: 8,
|
||||
),
|
||||
])),
|
||||
)
|
||||
]));
|
||||
|
Reference in New Issue
Block a user