mirror of
https://github.com/ImranR98/Obtainium.git
synced 2025-07-16 06:36:44 +02:00
Started switching additionaldata to map
This commit is contained in:
@ -25,7 +25,7 @@ class APKMirror extends AppSource {
|
||||
|
||||
@override
|
||||
Future<APKDetails> getLatestAPKDetails(
|
||||
String standardUrl, List<String> additionalData,
|
||||
String standardUrl, Map<String, String> additionalData,
|
||||
{bool trackOnly = false}) async {
|
||||
Response res = await get(Uri.parse('$standardUrl/feed'));
|
||||
if (res.statusCode == 200) {
|
||||
|
@ -32,7 +32,7 @@ class FDroid extends AppSource {
|
||||
|
||||
@override
|
||||
String? tryInferringAppId(String standardUrl,
|
||||
{List<String> additionalData = const []}) {
|
||||
{Map<String, String> additionalData = const {}}) {
|
||||
return Uri.parse(standardUrl).pathSegments.last;
|
||||
}
|
||||
|
||||
@ -60,7 +60,7 @@ class FDroid extends AppSource {
|
||||
|
||||
@override
|
||||
Future<APKDetails> getLatestAPKDetails(
|
||||
String standardUrl, List<String> additionalData,
|
||||
String standardUrl, Map<String, String> additionalData,
|
||||
{bool trackOnly = false}) async {
|
||||
String? appId = tryInferringAppId(standardUrl);
|
||||
return getAPKUrlsFromFDroidPackagesAPIResponse(
|
||||
|
@ -11,11 +11,10 @@ class FDroidRepo extends AppSource {
|
||||
|
||||
additionalSourceAppSpecificFormItems = [
|
||||
[
|
||||
GeneratedFormItem(
|
||||
GeneratedFormItem('appIdOrName',
|
||||
label: tr('appIdOrName'),
|
||||
hint: tr('reposHaveMultipleApps'),
|
||||
required: true,
|
||||
key: 'appIdOrName')
|
||||
required: true)
|
||||
]
|
||||
];
|
||||
}
|
||||
@ -33,13 +32,9 @@ class FDroidRepo extends AppSource {
|
||||
|
||||
@override
|
||||
Future<APKDetails> getLatestAPKDetails(
|
||||
String standardUrl, List<String> additionalData,
|
||||
String standardUrl, Map<String, String> additionalData,
|
||||
{bool trackOnly = false}) async {
|
||||
String? appIdOrName = findGeneratedFormValueByKey(
|
||||
additionalSourceAppSpecificFormItems
|
||||
.reduce((value, element) => [...value, ...element]),
|
||||
additionalData,
|
||||
'appIdOrName');
|
||||
String? appIdOrName = additionalData['appIdOrName'];
|
||||
if (appIdOrName == null) {
|
||||
throw NoReleasesError();
|
||||
}
|
||||
|
@ -12,12 +12,9 @@ class GitHub extends AppSource {
|
||||
GitHub() {
|
||||
host = 'github.com';
|
||||
|
||||
additionalSourceAppSpecificDefaults = ['true', 'true', ''];
|
||||
|
||||
additionalSourceSpecificSettingFormItems = [
|
||||
GeneratedFormItem(
|
||||
GeneratedFormItem('github-creds',
|
||||
label: tr('githubPATLabel'),
|
||||
id: 'github-creds',
|
||||
required: false,
|
||||
additionalValidators: [
|
||||
(value) {
|
||||
@ -52,17 +49,23 @@ class GitHub extends AppSource {
|
||||
])
|
||||
];
|
||||
|
||||
additionalSourceAppSpecificDefaults = {
|
||||
'includePrereleases': 'true',
|
||||
'fallbackToOlderReleases': 'true',
|
||||
'filterReleaseTitlesByRegEx': ''
|
||||
};
|
||||
|
||||
additionalSourceAppSpecificFormItems = [
|
||||
[
|
||||
GeneratedFormItem(
|
||||
GeneratedFormItem('includePrereleases',
|
||||
label: tr('includePrereleases'), type: FormItemType.bool)
|
||||
],
|
||||
[
|
||||
GeneratedFormItem(
|
||||
GeneratedFormItem('fallbackToOlderReleases',
|
||||
label: tr('fallbackToOlderReleases'), type: FormItemType.bool)
|
||||
],
|
||||
[
|
||||
GeneratedFormItem(
|
||||
GeneratedFormItem('filterReleaseTitlesByRegEx',
|
||||
label: tr('filterReleaseTitlesByRegEx'),
|
||||
type: FormItemType.string,
|
||||
required: false,
|
||||
@ -99,7 +102,7 @@ class GitHub extends AppSource {
|
||||
SettingsProvider settingsProvider = SettingsProvider();
|
||||
await settingsProvider.initializeSettings();
|
||||
String? creds = settingsProvider
|
||||
.getSettingString(additionalSourceSpecificSettingFormItems[0].id);
|
||||
.getSettingString(additionalSourceSpecificSettingFormItems[0].key);
|
||||
return creds != null && creds.isNotEmpty ? '$creds@' : '';
|
||||
}
|
||||
|
||||
@ -109,14 +112,14 @@ class GitHub extends AppSource {
|
||||
|
||||
@override
|
||||
Future<APKDetails> getLatestAPKDetails(
|
||||
String standardUrl, List<String> additionalData,
|
||||
String standardUrl, Map<String, String> additionalData,
|
||||
{bool trackOnly = false}) async {
|
||||
var includePrereleases =
|
||||
additionalData.isNotEmpty && additionalData[0] == 'true';
|
||||
var includePrereleases = additionalData['includePrereleases'] == 'true';
|
||||
var fallbackToOlderReleases =
|
||||
additionalData.length >= 2 && additionalData[1] == 'true';
|
||||
var regexFilter = additionalData.length >= 3 && additionalData[2].isNotEmpty
|
||||
? additionalData[2]
|
||||
additionalData['fallbackToOlderReleases'] == 'true';
|
||||
var regexFilter =
|
||||
additionalData['filterReleaseTitlesByRegEx']?.isNotEmpty == true
|
||||
? additionalData['filterReleaseTitlesByRegEx']
|
||||
: null;
|
||||
Response res = await get(Uri.parse(
|
||||
'https://${await getCredentialPrefixIfAny()}api.$host/repos${standardUrl.substring('https://$host'.length)}/releases'));
|
||||
|
@ -25,7 +25,7 @@ class GitLab extends AppSource {
|
||||
|
||||
@override
|
||||
Future<APKDetails> getLatestAPKDetails(
|
||||
String standardUrl, List<String> additionalData,
|
||||
String standardUrl, Map<String, String> additionalData,
|
||||
{bool trackOnly = false}) async {
|
||||
Response res = await get(Uri.parse('$standardUrl/-/tags?format=atom'));
|
||||
if (res.statusCode == 200) {
|
||||
|
@ -23,13 +23,13 @@ class IzzyOnDroid extends AppSource {
|
||||
|
||||
@override
|
||||
String? tryInferringAppId(String standardUrl,
|
||||
{List<String> additionalData = const []}) {
|
||||
{Map<String, String> additionalData = const {}}) {
|
||||
return FDroid().tryInferringAppId(standardUrl);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<APKDetails> getLatestAPKDetails(
|
||||
String standardUrl, List<String> additionalData,
|
||||
String standardUrl, Map<String, String> additionalData,
|
||||
{bool trackOnly = false}) async {
|
||||
String? appId = tryInferringAppId(standardUrl);
|
||||
return FDroid().getAPKUrlsFromFDroidPackagesAPIResponse(
|
||||
|
@ -24,7 +24,7 @@ class Mullvad extends AppSource {
|
||||
|
||||
@override
|
||||
Future<APKDetails> getLatestAPKDetails(
|
||||
String standardUrl, List<String> additionalData,
|
||||
String standardUrl, Map<String, String> additionalData,
|
||||
{bool trackOnly = false}) async {
|
||||
Response res = await get(Uri.parse('$standardUrl/en/download/android'));
|
||||
if (res.statusCode == 200) {
|
||||
|
@ -18,7 +18,7 @@ class Signal extends AppSource {
|
||||
|
||||
@override
|
||||
Future<APKDetails> getLatestAPKDetails(
|
||||
String standardUrl, List<String> additionalData,
|
||||
String standardUrl, Map<String, String> additionalData,
|
||||
{bool trackOnly = false}) async {
|
||||
Response res =
|
||||
await get(Uri.parse('https://updates.$host/android/latest.json'));
|
||||
|
@ -23,7 +23,7 @@ class SourceForge extends AppSource {
|
||||
|
||||
@override
|
||||
Future<APKDetails> getLatestAPKDetails(
|
||||
String standardUrl, List<String> additionalData,
|
||||
String standardUrl, Map<String, String> additionalData,
|
||||
{bool trackOnly = false}) async {
|
||||
Response res = await get(Uri.parse('$standardUrl/rss?path=/'));
|
||||
if (res.statusCode == 200) {
|
||||
|
@ -11,11 +11,8 @@ class SteamMobile extends AppSource {
|
||||
name = tr('steam');
|
||||
additionalSourceAppSpecificFormItems = [
|
||||
[
|
||||
GeneratedFormItem(
|
||||
label: tr('app'),
|
||||
key: 'app',
|
||||
required: true,
|
||||
opts: apks.entries.toList())
|
||||
GeneratedFormItem('app',
|
||||
label: tr('app'), required: true, opts: apks.entries.toList())
|
||||
]
|
||||
];
|
||||
}
|
||||
@ -32,15 +29,11 @@ class SteamMobile extends AppSource {
|
||||
|
||||
@override
|
||||
Future<APKDetails> getLatestAPKDetails(
|
||||
String standardUrl, List<String> additionalData,
|
||||
String standardUrl, Map<String, String> additionalData,
|
||||
{bool trackOnly = false}) async {
|
||||
Response res = await get(Uri.parse('https://$host/mobile'));
|
||||
if (res.statusCode == 200) {
|
||||
var apkNamePrefix = findGeneratedFormValueByKey(
|
||||
additionalSourceAppSpecificFormItems
|
||||
.reduce((value, element) => [...value, ...element]),
|
||||
additionalData,
|
||||
'app');
|
||||
var apkNamePrefix = additionalData['app'];
|
||||
if (apkNamePrefix == null) {
|
||||
throw NoReleasesError();
|
||||
}
|
||||
|
@ -4,7 +4,7 @@ import 'package:flutter/material.dart';
|
||||
enum FormItemType { string, bool }
|
||||
|
||||
typedef OnValueChanges = void Function(
|
||||
List<String> values, bool valid, bool isBuilding);
|
||||
Map<String, String> values, bool valid, bool isBuilding);
|
||||
|
||||
class GeneratedFormItem {
|
||||
late String key;
|
||||
@ -13,22 +13,19 @@ class GeneratedFormItem {
|
||||
late bool required;
|
||||
late int max;
|
||||
late List<String? Function(String? value)> additionalValidators;
|
||||
late String id;
|
||||
late List<Widget> belowWidgets;
|
||||
late String? hint;
|
||||
late List<MapEntry<String, String>>? opts;
|
||||
|
||||
GeneratedFormItem(
|
||||
GeneratedFormItem(this.key,
|
||||
{this.label = 'Input',
|
||||
this.type = FormItemType.string,
|
||||
this.required = true,
|
||||
this.max = 1,
|
||||
this.additionalValidators = const [],
|
||||
this.id = 'input',
|
||||
this.belowWidgets = const [],
|
||||
this.hint,
|
||||
this.opts,
|
||||
this.key = 'default'}) {
|
||||
this.opts}) {
|
||||
if (type != FormItemType.string) {
|
||||
required = false;
|
||||
}
|
||||
@ -44,7 +41,7 @@ class GeneratedForm extends StatefulWidget {
|
||||
|
||||
final List<List<GeneratedFormItem>> items;
|
||||
final OnValueChanges onValueChanges;
|
||||
final List<String> defaultValues;
|
||||
final Map<String, String> defaultValues;
|
||||
|
||||
@override
|
||||
State<GeneratedForm> createState() => _GeneratedFormState();
|
||||
@ -52,17 +49,18 @@ class GeneratedForm extends StatefulWidget {
|
||||
|
||||
class _GeneratedFormState extends State<GeneratedForm> {
|
||||
final _formKey = GlobalKey<FormState>();
|
||||
late List<List<String>> values;
|
||||
Map<String, String> values = {};
|
||||
late List<List<Widget>> formInputs;
|
||||
List<List<Widget>> rows = [];
|
||||
|
||||
// If any value changes, call this to update the parent with value and validity
|
||||
void someValueChanged({bool isBuilding = false}) {
|
||||
List<String> returnValues = [];
|
||||
Map<String, String> returnValues = {};
|
||||
var valid = true;
|
||||
for (int r = 0; r < values.length; r++) {
|
||||
for (int i = 0; i < values[r].length; i++) {
|
||||
returnValues.add(values[r][i]);
|
||||
for (int r = 0; r < widget.items.length; r++) {
|
||||
for (int i = 0; i < widget.items[r].length; i++) {
|
||||
returnValues[widget.items[r][i].key] =
|
||||
values[widget.items[r][i].key] ?? '';
|
||||
if (formInputs[r][i] is TextFormField) {
|
||||
valid = valid &&
|
||||
((formInputs[r][i].key as GlobalKey<FormFieldState>)
|
||||
@ -80,16 +78,13 @@ class _GeneratedFormState extends State<GeneratedForm> {
|
||||
super.initState();
|
||||
|
||||
// Initialize form values as all empty
|
||||
values.clear();
|
||||
int j = 0;
|
||||
values = widget.items
|
||||
.map((row) => row.map((e) {
|
||||
return j < widget.defaultValues.length
|
||||
? widget.defaultValues[j++]
|
||||
: e.opts != null
|
||||
? e.opts!.first.key
|
||||
: '';
|
||||
}).toList())
|
||||
.toList();
|
||||
for (var row in widget.items) {
|
||||
for (var e in row) {
|
||||
values[e.key] = widget.defaultValues[e.key] ?? e.opts?.first.key ?? '';
|
||||
}
|
||||
}
|
||||
|
||||
// Dynamically create form inputs
|
||||
formInputs = widget.items.asMap().entries.map((row) {
|
||||
@ -98,11 +93,11 @@ class _GeneratedFormState extends State<GeneratedForm> {
|
||||
final formFieldKey = GlobalKey<FormFieldState>();
|
||||
return TextFormField(
|
||||
key: formFieldKey,
|
||||
initialValue: values[row.key][e.key],
|
||||
initialValue: values[e.value.key],
|
||||
autovalidateMode: AutovalidateMode.onUserInteraction,
|
||||
onChanged: (value) {
|
||||
setState(() {
|
||||
values[row.key][e.key] = value;
|
||||
values[e.value.key] = value;
|
||||
someValueChanged();
|
||||
});
|
||||
},
|
||||
@ -131,14 +126,14 @@ class _GeneratedFormState extends State<GeneratedForm> {
|
||||
}
|
||||
return DropdownButtonFormField(
|
||||
decoration: InputDecoration(labelText: e.value.label),
|
||||
value: values[row.key][e.key],
|
||||
value: values[e.value.key],
|
||||
items: e.value.opts!
|
||||
.map((e) =>
|
||||
DropdownMenuItem(value: e.key, child: Text(e.value)))
|
||||
.toList(),
|
||||
onChanged: (value) {
|
||||
setState(() {
|
||||
values[row.key][e.key] = value ?? e.value.opts!.first.key;
|
||||
values[e.value.key] = value ?? e.value.opts!.first.key;
|
||||
someValueChanged();
|
||||
});
|
||||
});
|
||||
@ -160,10 +155,10 @@ class _GeneratedFormState extends State<GeneratedForm> {
|
||||
children: [
|
||||
Text(widget.items[r][e].label),
|
||||
Switch(
|
||||
value: values[r][e] == 'true',
|
||||
value: values[widget.items[r][e].key] == 'true',
|
||||
onChanged: (value) {
|
||||
setState(() {
|
||||
values[r][e] = value ? 'true' : '';
|
||||
values[widget.items[r][e].key] = value ? 'true' : '';
|
||||
someValueChanged();
|
||||
});
|
||||
})
|
||||
@ -217,18 +212,3 @@ class _GeneratedFormState extends State<GeneratedForm> {
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
String? findGeneratedFormValueByKey(
|
||||
List<GeneratedFormItem> items, List<String> values, String key) {
|
||||
var foundIndex = -1;
|
||||
for (var i = 0; i < items.length; i++) {
|
||||
if (items[i].key == key) {
|
||||
foundIndex = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (foundIndex >= 0 && foundIndex < values.length) {
|
||||
return values[foundIndex];
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
@ -15,7 +15,7 @@ class GeneratedFormModal extends StatefulWidget {
|
||||
final String title;
|
||||
final String message;
|
||||
final List<List<GeneratedFormItem>> items;
|
||||
final List<String> defaultValues;
|
||||
final Map<String, String> defaultValues;
|
||||
final bool initValid;
|
||||
|
||||
@override
|
||||
@ -23,13 +23,15 @@ class GeneratedFormModal extends StatefulWidget {
|
||||
}
|
||||
|
||||
class _GeneratedFormModalState extends State<GeneratedFormModal> {
|
||||
List<String> values = [];
|
||||
Map<String, String> values = {};
|
||||
bool valid = false;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
values = widget.defaultValues;
|
||||
widget.defaultValues.forEach((key, value) {
|
||||
values[key] = value;
|
||||
});
|
||||
valid = widget.initValid || widget.items.isEmpty;
|
||||
}
|
||||
|
||||
|
@ -200,7 +200,7 @@ class _ObtainiumState extends State<Obtainium> {
|
||||
currentReleaseTag,
|
||||
[],
|
||||
0,
|
||||
['true'],
|
||||
{},
|
||||
null,
|
||||
false,
|
||||
false)
|
||||
|
@ -27,9 +27,9 @@ class _AddAppPageState extends State<AddAppPage> {
|
||||
String userInput = '';
|
||||
String searchQuery = '';
|
||||
AppSource? pickedSource;
|
||||
List<String> sourceSpecificAdditionalData = [];
|
||||
Map<String, String> sourceSpecificAdditionalData = {};
|
||||
bool sourceSpecificDataIsValid = true;
|
||||
List<String> otherAdditionalData = [];
|
||||
Map<String, String> otherAdditionalData = {};
|
||||
bool otherAdditionalDataIsValid = true;
|
||||
|
||||
@override
|
||||
@ -44,7 +44,7 @@ class _AddAppPageState extends State<AddAppPage> {
|
||||
if (pickedSource.runtimeType != source.runtimeType) {
|
||||
pickedSource = source;
|
||||
sourceSpecificAdditionalData =
|
||||
source != null ? source.additionalSourceAppSpecificDefaults : [];
|
||||
source != null ? source.additionalSourceAppSpecificDefaults : {};
|
||||
sourceSpecificDataIsValid = source != null
|
||||
? !sourceProvider.ifSourceAppsRequireAdditionalData(source)
|
||||
: true;
|
||||
@ -66,16 +66,10 @@ class _AddAppPageState extends State<AddAppPage> {
|
||||
});
|
||||
var settingsProvider = context.read<SettingsProvider>();
|
||||
() async {
|
||||
var userPickedTrackOnly = findGeneratedFormValueByKey(
|
||||
pickedSource!.additionalAppSpecificSourceAgnosticFormItems,
|
||||
otherAdditionalData,
|
||||
'trackOnlyFormItemKey') ==
|
||||
'true';
|
||||
var userPickedNoVersionDetection = findGeneratedFormValueByKey(
|
||||
pickedSource!.additionalAppSpecificSourceAgnosticFormItems,
|
||||
otherAdditionalData,
|
||||
'noVersionDetectionKey') ==
|
||||
'true';
|
||||
var userPickedTrackOnly =
|
||||
otherAdditionalData['trackOnlyFormItemKey'] == 'true';
|
||||
var userPickedNoVersionDetection =
|
||||
otherAdditionalData['noVersionDetectionKey'] == 'true';
|
||||
var cont = true;
|
||||
if ((userPickedTrackOnly || pickedSource!.enforceTrackOnly) &&
|
||||
await showDialog(
|
||||
@ -88,7 +82,7 @@ class _AddAppPageState extends State<AddAppPage> {
|
||||
: tr('app')
|
||||
]),
|
||||
items: const [],
|
||||
defaultValues: const [],
|
||||
defaultValues: const {},
|
||||
message:
|
||||
'${pickedSource!.enforceTrackOnly ? tr('appsFromSourceAreTrackOnly') : tr('youPickedTrackOnly')}\n\n${tr('trackOnlyAppDescription')}',
|
||||
);
|
||||
@ -100,10 +94,10 @@ class _AddAppPageState extends State<AddAppPage> {
|
||||
await showDialog(
|
||||
context: context,
|
||||
builder: (BuildContext ctx) {
|
||||
return GeneratedFormModal(
|
||||
return const GeneratedFormModal(
|
||||
title: 'Disable Version Detection', // TODO
|
||||
items: const [],
|
||||
defaultValues: const [],
|
||||
items: [],
|
||||
defaultValues: {},
|
||||
message: 'TODO',
|
||||
);
|
||||
}) ==
|
||||
@ -177,7 +171,7 @@ class _AddAppPageState extends State<AddAppPage> {
|
||||
child: GeneratedForm(
|
||||
items: [
|
||||
[
|
||||
GeneratedFormItem(
|
||||
GeneratedFormItem('appSourceURL',
|
||||
label: tr('appSourceURL'),
|
||||
additionalValidators: [
|
||||
(value) {
|
||||
@ -200,10 +194,10 @@ class _AddAppPageState extends State<AddAppPage> {
|
||||
]
|
||||
],
|
||||
onValueChanges: (values, valid, isBuilding) {
|
||||
changeUserInput(
|
||||
values[0], valid, isBuilding);
|
||||
changeUserInput(values['appSourceURL']!,
|
||||
valid, isBuilding);
|
||||
},
|
||||
defaultValues: const [])),
|
||||
defaultValues: const {'appSourceURL': ''})),
|
||||
const SizedBox(
|
||||
width: 16,
|
||||
),
|
||||
@ -244,7 +238,7 @@ class _AddAppPageState extends State<AddAppPage> {
|
||||
child: GeneratedForm(
|
||||
items: [
|
||||
[
|
||||
GeneratedFormItem(
|
||||
GeneratedFormItem('searchSomeSources',
|
||||
label: tr('searchSomeSourcesLabel'),
|
||||
required: false),
|
||||
]
|
||||
@ -252,11 +246,14 @@ class _AddAppPageState extends State<AddAppPage> {
|
||||
onValueChanges: (values, valid, isBuilding) {
|
||||
if (values.isNotEmpty && valid) {
|
||||
setState(() {
|
||||
searchQuery = values[0].trim();
|
||||
searchQuery =
|
||||
values['searchSomeSources']!.trim();
|
||||
});
|
||||
}
|
||||
},
|
||||
defaultValues: const ['']),
|
||||
defaultValues: const {
|
||||
'searchSomeSources': ''
|
||||
}),
|
||||
),
|
||||
const SizedBox(
|
||||
width: 16,
|
||||
|
@ -208,7 +208,7 @@ class _AppPageState extends State<AppPage> {
|
||||
onPressed: app?.downloadProgress != null
|
||||
? null
|
||||
: () {
|
||||
showDialog<List<String>>(
|
||||
showDialog<Map<String, String>>(
|
||||
context: context,
|
||||
builder: (BuildContext ctx) {
|
||||
return GeneratedFormModal(
|
||||
|
@ -349,7 +349,7 @@ class AppsPageState extends State<AppsPage> {
|
||||
return GeneratedFormModal(
|
||||
title: tr('removeSelectedAppsQuestion'),
|
||||
items: const [],
|
||||
defaultValues: const [],
|
||||
defaultValues: const {},
|
||||
initValid: true,
|
||||
message: tr(
|
||||
'xWillBeRemovedButRemainInstalled',
|
||||
@ -377,40 +377,37 @@ class AppsPageState extends State<AppsPage> {
|
||||
: () {
|
||||
HapticFeedback.heavyImpact();
|
||||
List<GeneratedFormItem> formInputs = [];
|
||||
List<String> defaultValues = [];
|
||||
Map<String, String> defaultValues = {};
|
||||
if (existingUpdateIdsAllOrSelected.isNotEmpty) {
|
||||
formInputs.add(GeneratedFormItem(
|
||||
formInputs.add(GeneratedFormItem('updates',
|
||||
label: tr('updateX', args: [
|
||||
plural('apps',
|
||||
existingUpdateIdsAllOrSelected.length)
|
||||
]),
|
||||
type: FormItemType.bool,
|
||||
key: 'updates'));
|
||||
defaultValues.add('true');
|
||||
type: FormItemType.bool));
|
||||
defaultValues['updates'] = 'true';
|
||||
}
|
||||
if (newInstallIdsAllOrSelected.isNotEmpty) {
|
||||
formInputs.add(GeneratedFormItem(
|
||||
formInputs.add(GeneratedFormItem('installs',
|
||||
label: tr('installX', args: [
|
||||
plural('apps',
|
||||
newInstallIdsAllOrSelected.length)
|
||||
]),
|
||||
type: FormItemType.bool,
|
||||
key: 'installs'));
|
||||
defaultValues
|
||||
.add(defaultValues.isEmpty ? 'true' : '');
|
||||
type: FormItemType.bool));
|
||||
defaultValues['installs'] =
|
||||
defaultValues.isEmpty ? 'true' : '';
|
||||
}
|
||||
if (trackOnlyUpdateIdsAllOrSelected.isNotEmpty) {
|
||||
formInputs.add(GeneratedFormItem(
|
||||
formInputs.add(GeneratedFormItem('trackonlies',
|
||||
label: tr('markXTrackOnlyAsUpdated', args: [
|
||||
plural('apps',
|
||||
trackOnlyUpdateIdsAllOrSelected.length)
|
||||
]),
|
||||
type: FormItemType.bool,
|
||||
key: 'trackonlies'));
|
||||
defaultValues
|
||||
.add(defaultValues.isEmpty ? 'true' : '');
|
||||
type: FormItemType.bool));
|
||||
defaultValues['trackonlies'] =
|
||||
defaultValues.isEmpty ? 'true' : '';
|
||||
}
|
||||
showDialog<List<String>?>(
|
||||
showDialog<Map<String, String>?>(
|
||||
context: context,
|
||||
builder: (BuildContext ctx) {
|
||||
var totalApps = existingUpdateIdsAllOrSelected
|
||||
@ -430,17 +427,11 @@ class AppsPageState extends State<AppsPage> {
|
||||
values = defaultValues;
|
||||
}
|
||||
bool shouldInstallUpdates =
|
||||
findGeneratedFormValueByKey(
|
||||
formInputs, values, 'updates') ==
|
||||
'true';
|
||||
values['updates'] == 'true';
|
||||
bool shouldInstallNew =
|
||||
findGeneratedFormValueByKey(
|
||||
formInputs, values, 'installs') ==
|
||||
'true';
|
||||
values['installs'] == 'true';
|
||||
bool shouldMarkTrackOnlies =
|
||||
findGeneratedFormValueByKey(formInputs,
|
||||
values, 'trackonlies') ==
|
||||
'true';
|
||||
values['trackonlies'] == 'true';
|
||||
(() async {
|
||||
if (shouldInstallNew ||
|
||||
shouldInstallUpdates) {
|
||||
@ -613,7 +604,7 @@ class AppsPageState extends State<AppsPage> {
|
||||
title: tr(
|
||||
'resetInstallStatusForSelectedAppsQuestion'),
|
||||
items: const [],
|
||||
defaultValues: const [],
|
||||
defaultValues: const {},
|
||||
initValid: true,
|
||||
message: tr(
|
||||
'installStatusOfXWillBeResetExplanation',
|
||||
@ -683,36 +674,36 @@ class AppsPageState extends State<AppsPage> {
|
||||
: FontWeight.bold),
|
||||
),
|
||||
onPressed: () {
|
||||
showDialog<List<String>?>(
|
||||
showDialog<Map<String, String>?>(
|
||||
context: context,
|
||||
builder: (BuildContext ctx) {
|
||||
return GeneratedFormModal(
|
||||
title: tr('filterApps'),
|
||||
items: [
|
||||
[
|
||||
GeneratedFormItem(
|
||||
GeneratedFormItem('appName',
|
||||
label: tr('appName'), required: false),
|
||||
GeneratedFormItem(
|
||||
GeneratedFormItem('author',
|
||||
label: tr('author'), required: false)
|
||||
],
|
||||
[
|
||||
GeneratedFormItem(
|
||||
GeneratedFormItem('upToDateApps',
|
||||
label: tr('upToDateApps'),
|
||||
type: FormItemType.bool)
|
||||
],
|
||||
[
|
||||
GeneratedFormItem(
|
||||
GeneratedFormItem('nonInstalledApps',
|
||||
label: tr('nonInstalledApps'),
|
||||
type: FormItemType.bool)
|
||||
]
|
||||
],
|
||||
defaultValues: filter == null
|
||||
? AppsFilter().toValuesArray()
|
||||
: filter!.toValuesArray());
|
||||
? AppsFilter().toValuesMap()
|
||||
: filter!.toValuesMap());
|
||||
}).then((values) {
|
||||
if (values != null) {
|
||||
setState(() {
|
||||
filter = AppsFilter.fromValuesArray(values);
|
||||
filter = AppsFilter.fromValuesMap(values);
|
||||
if (AppsFilter().isIdenticalTo(filter!)) {
|
||||
filter = null;
|
||||
}
|
||||
@ -740,20 +731,20 @@ class AppsFilter {
|
||||
this.includeUptodate = true,
|
||||
this.includeNonInstalled = true});
|
||||
|
||||
List<String> toValuesArray() {
|
||||
return [
|
||||
nameFilter,
|
||||
authorFilter,
|
||||
includeUptodate ? 'true' : '',
|
||||
includeNonInstalled ? 'true' : ''
|
||||
];
|
||||
Map<String, String> toValuesMap() {
|
||||
return {
|
||||
'appName': nameFilter,
|
||||
'author': authorFilter,
|
||||
'upToDateApps': includeUptodate ? 'true' : '',
|
||||
'nonInstalledApps': includeNonInstalled ? 'true' : ''
|
||||
};
|
||||
}
|
||||
|
||||
AppsFilter.fromValuesArray(List<String> values) {
|
||||
nameFilter = values[0];
|
||||
authorFilter = values[1];
|
||||
includeUptodate = values[2] == 'true';
|
||||
includeNonInstalled = values[3] == 'true';
|
||||
AppsFilter.fromValuesMap(Map<String, String> values) {
|
||||
nameFilter = values['appName']!;
|
||||
authorFilter = values['author']!;
|
||||
includeUptodate = values['upToDateApps'] == 'true';
|
||||
includeNonInstalled = values['nonInstalledApps'] == 'true';
|
||||
}
|
||||
|
||||
bool isIdenticalTo(AppsFilter other) =>
|
||||
|
@ -145,7 +145,7 @@ class _ImportExportPageState extends State<ImportExportPage> {
|
||||
title: tr('importFromURLList'),
|
||||
items: [
|
||||
[
|
||||
GeneratedFormItem(
|
||||
GeneratedFormItem('appURLList',
|
||||
label: tr('appURLList'),
|
||||
max: 7,
|
||||
additionalValidators: [
|
||||
@ -172,7 +172,7 @@ class _ImportExportPageState extends State<ImportExportPage> {
|
||||
])
|
||||
]
|
||||
],
|
||||
defaultValues: const [],
|
||||
defaultValues: const {},
|
||||
);
|
||||
}).then((values) {
|
||||
if (values != null) {
|
||||
@ -237,11 +237,12 @@ class _ImportExportPageState extends State<ImportExportPage> {
|
||||
items: [
|
||||
[
|
||||
GeneratedFormItem(
|
||||
'searchQuery',
|
||||
label: tr(
|
||||
'searchQuery'))
|
||||
]
|
||||
],
|
||||
defaultValues: const [],
|
||||
defaultValues: const {},
|
||||
);
|
||||
});
|
||||
if (values != null &&
|
||||
@ -346,10 +347,11 @@ class _ImportExportPageState extends State<ImportExportPage> {
|
||||
.requiredArgs
|
||||
.map(
|
||||
(e) => [
|
||||
GeneratedFormItem(label: e)
|
||||
GeneratedFormItem(e,
|
||||
label: e)
|
||||
])
|
||||
.toList(),
|
||||
defaultValues: const [],
|
||||
defaultValues: const {},
|
||||
);
|
||||
});
|
||||
if (values != null) {
|
||||
|
@ -143,16 +143,16 @@ class _SettingsPageState extends State<SettingsPage> {
|
||||
.toList(),
|
||||
onValueChanges: (values, valid, isBuilding) {
|
||||
if (valid) {
|
||||
for (var i = 0; i < values.length; i++) {
|
||||
settingsProvider.setSettingString(
|
||||
e.additionalSourceSpecificSettingFormItems[i].id,
|
||||
values[i]);
|
||||
}
|
||||
values.forEach((key, value) {
|
||||
settingsProvider.setSettingString(key, value);
|
||||
});
|
||||
}
|
||||
},
|
||||
defaultValues: e.additionalSourceSpecificSettingFormItems.map((e) {
|
||||
return settingsProvider.getSettingString(e.id) ?? '';
|
||||
}).toList());
|
||||
defaultValues: Map.fromEntries(
|
||||
e.additionalSourceSpecificSettingFormItems.map((e) {
|
||||
return MapEntry(
|
||||
e.key, settingsProvider.getSettingString(e.key) ?? '');
|
||||
})));
|
||||
} else {
|
||||
return Container();
|
||||
}
|
||||
|
@ -44,7 +44,7 @@ class App {
|
||||
late String latestVersion;
|
||||
List<String> apkUrls = [];
|
||||
late int preferredApkIndex;
|
||||
late List<String> additionalData;
|
||||
late Map<String, String> additionalData;
|
||||
late DateTime? lastUpdateCheck;
|
||||
bool pinned = false;
|
||||
bool trackOnly = false;
|
||||
@ -86,7 +86,7 @@ class App {
|
||||
? SourceProvider()
|
||||
.getSource(json['url'])
|
||||
.additionalSourceAppSpecificDefaults
|
||||
: List<String>.from(jsonDecode(json['additionalData'])),
|
||||
: Map<String, String>.from(jsonDecode(json['additionalData'])),
|
||||
json['lastUpdateCheck'] == null
|
||||
? null
|
||||
: DateTime.fromMicrosecondsSinceEpoch(json['lastUpdateCheck']),
|
||||
@ -155,27 +155,30 @@ class AppSource {
|
||||
}
|
||||
|
||||
Future<APKDetails> getLatestAPKDetails(
|
||||
String standardUrl, List<String> additionalData,
|
||||
String standardUrl, Map<String, String> additionalData,
|
||||
{bool trackOnly = false}) {
|
||||
throw NotImplementedError();
|
||||
}
|
||||
|
||||
// Different Sources may need different kinds of additional data for Apps
|
||||
List<List<GeneratedFormItem>> additionalSourceAppSpecificFormItems = [];
|
||||
List<String> additionalSourceAppSpecificDefaults = [];
|
||||
Map<String, String> additionalSourceAppSpecificDefaults = {};
|
||||
|
||||
// Some additional data may be needed for Apps regardless of Source
|
||||
final List<GeneratedFormItem> additionalAppSpecificSourceAgnosticFormItems = [
|
||||
GeneratedFormItem(
|
||||
'trackOnlyFormItemKey',
|
||||
label: tr('trackOnly'),
|
||||
type: FormItemType.bool,
|
||||
key: 'trackOnlyFormItemKey'),
|
||||
GeneratedFormItem(
|
||||
),
|
||||
GeneratedFormItem('noVersionDetectionKey',
|
||||
label: 'Do not attempt version detection', // TODO
|
||||
type: FormItemType.bool,
|
||||
key: 'noVersionDetectionKey')
|
||||
type: FormItemType.bool)
|
||||
];
|
||||
final List<String> additionalAppSpecificSourceAgnosticDefaults = ['', ''];
|
||||
final Map<String, String> additionalAppSpecificSourceAgnosticDefaults = {
|
||||
'trackOnlyFormItemKey': '',
|
||||
'noVersionDetectionKey': ''
|
||||
};
|
||||
|
||||
// Some Sources may have additional settings at the Source level (not specific to Apps) - these use SettingsProvider
|
||||
List<GeneratedFormItem> additionalSourceSpecificSettingFormItems = [];
|
||||
@ -194,7 +197,7 @@ class AppSource {
|
||||
}
|
||||
|
||||
String? tryInferringAppId(String standardUrl,
|
||||
{List<String> additionalData = const []}) {
|
||||
{Map<String, String> additionalData = const {}}) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@ -282,7 +285,8 @@ class SourceProvider {
|
||||
return true;
|
||||
}
|
||||
|
||||
Future<App> getApp(AppSource source, String url, List<String> additionalData,
|
||||
Future<App> getApp(
|
||||
AppSource source, String url, Map<String, String> additionalData,
|
||||
{App? currentApp,
|
||||
bool trackOnlyOverride = false,
|
||||
noVersionDetectionOverride = false}) async {
|
||||
|
Reference in New Issue
Block a user