Started switching additionaldata to map

This commit is contained in:
Imran Remtulla
2022-12-19 19:34:43 -05:00
parent dbd6dec0a6
commit 1fe9e4f91e
19 changed files with 155 additions and 188 deletions

View File

@ -25,7 +25,7 @@ class APKMirror extends AppSource {
@override @override
Future<APKDetails> getLatestAPKDetails( Future<APKDetails> getLatestAPKDetails(
String standardUrl, List<String> additionalData, String standardUrl, Map<String, String> additionalData,
{bool trackOnly = false}) async { {bool trackOnly = false}) async {
Response res = await get(Uri.parse('$standardUrl/feed')); Response res = await get(Uri.parse('$standardUrl/feed'));
if (res.statusCode == 200) { if (res.statusCode == 200) {

View File

@ -32,7 +32,7 @@ class FDroid extends AppSource {
@override @override
String? tryInferringAppId(String standardUrl, String? tryInferringAppId(String standardUrl,
{List<String> additionalData = const []}) { {Map<String, String> additionalData = const {}}) {
return Uri.parse(standardUrl).pathSegments.last; return Uri.parse(standardUrl).pathSegments.last;
} }
@ -60,7 +60,7 @@ class FDroid extends AppSource {
@override @override
Future<APKDetails> getLatestAPKDetails( Future<APKDetails> getLatestAPKDetails(
String standardUrl, List<String> additionalData, String standardUrl, Map<String, String> additionalData,
{bool trackOnly = false}) async { {bool trackOnly = false}) async {
String? appId = tryInferringAppId(standardUrl); String? appId = tryInferringAppId(standardUrl);
return getAPKUrlsFromFDroidPackagesAPIResponse( return getAPKUrlsFromFDroidPackagesAPIResponse(

View File

@ -11,11 +11,10 @@ class FDroidRepo extends AppSource {
additionalSourceAppSpecificFormItems = [ additionalSourceAppSpecificFormItems = [
[ [
GeneratedFormItem( GeneratedFormItem('appIdOrName',
label: tr('appIdOrName'), label: tr('appIdOrName'),
hint: tr('reposHaveMultipleApps'), hint: tr('reposHaveMultipleApps'),
required: true, required: true)
key: 'appIdOrName')
] ]
]; ];
} }
@ -33,13 +32,9 @@ class FDroidRepo extends AppSource {
@override @override
Future<APKDetails> getLatestAPKDetails( Future<APKDetails> getLatestAPKDetails(
String standardUrl, List<String> additionalData, String standardUrl, Map<String, String> additionalData,
{bool trackOnly = false}) async { {bool trackOnly = false}) async {
String? appIdOrName = findGeneratedFormValueByKey( String? appIdOrName = additionalData['appIdOrName'];
additionalSourceAppSpecificFormItems
.reduce((value, element) => [...value, ...element]),
additionalData,
'appIdOrName');
if (appIdOrName == null) { if (appIdOrName == null) {
throw NoReleasesError(); throw NoReleasesError();
} }

View File

@ -12,12 +12,9 @@ class GitHub extends AppSource {
GitHub() { GitHub() {
host = 'github.com'; host = 'github.com';
additionalSourceAppSpecificDefaults = ['true', 'true', ''];
additionalSourceSpecificSettingFormItems = [ additionalSourceSpecificSettingFormItems = [
GeneratedFormItem( GeneratedFormItem('github-creds',
label: tr('githubPATLabel'), label: tr('githubPATLabel'),
id: 'github-creds',
required: false, required: false,
additionalValidators: [ additionalValidators: [
(value) { (value) {
@ -52,17 +49,23 @@ class GitHub extends AppSource {
]) ])
]; ];
additionalSourceAppSpecificDefaults = {
'includePrereleases': 'true',
'fallbackToOlderReleases': 'true',
'filterReleaseTitlesByRegEx': ''
};
additionalSourceAppSpecificFormItems = [ additionalSourceAppSpecificFormItems = [
[ [
GeneratedFormItem( GeneratedFormItem('includePrereleases',
label: tr('includePrereleases'), type: FormItemType.bool) label: tr('includePrereleases'), type: FormItemType.bool)
], ],
[ [
GeneratedFormItem( GeneratedFormItem('fallbackToOlderReleases',
label: tr('fallbackToOlderReleases'), type: FormItemType.bool) label: tr('fallbackToOlderReleases'), type: FormItemType.bool)
], ],
[ [
GeneratedFormItem( GeneratedFormItem('filterReleaseTitlesByRegEx',
label: tr('filterReleaseTitlesByRegEx'), label: tr('filterReleaseTitlesByRegEx'),
type: FormItemType.string, type: FormItemType.string,
required: false, required: false,
@ -99,7 +102,7 @@ class GitHub extends AppSource {
SettingsProvider settingsProvider = SettingsProvider(); SettingsProvider settingsProvider = SettingsProvider();
await settingsProvider.initializeSettings(); await settingsProvider.initializeSettings();
String? creds = settingsProvider String? creds = settingsProvider
.getSettingString(additionalSourceSpecificSettingFormItems[0].id); .getSettingString(additionalSourceSpecificSettingFormItems[0].key);
return creds != null && creds.isNotEmpty ? '$creds@' : ''; return creds != null && creds.isNotEmpty ? '$creds@' : '';
} }
@ -109,15 +112,15 @@ class GitHub extends AppSource {
@override @override
Future<APKDetails> getLatestAPKDetails( Future<APKDetails> getLatestAPKDetails(
String standardUrl, List<String> additionalData, String standardUrl, Map<String, String> additionalData,
{bool trackOnly = false}) async { {bool trackOnly = false}) async {
var includePrereleases = var includePrereleases = additionalData['includePrereleases'] == 'true';
additionalData.isNotEmpty && additionalData[0] == 'true';
var fallbackToOlderReleases = var fallbackToOlderReleases =
additionalData.length >= 2 && additionalData[1] == 'true'; additionalData['fallbackToOlderReleases'] == 'true';
var regexFilter = additionalData.length >= 3 && additionalData[2].isNotEmpty var regexFilter =
? additionalData[2] additionalData['filterReleaseTitlesByRegEx']?.isNotEmpty == true
: null; ? additionalData['filterReleaseTitlesByRegEx']
: null;
Response res = await get(Uri.parse( Response res = await get(Uri.parse(
'https://${await getCredentialPrefixIfAny()}api.$host/repos${standardUrl.substring('https://$host'.length)}/releases')); 'https://${await getCredentialPrefixIfAny()}api.$host/repos${standardUrl.substring('https://$host'.length)}/releases'));
if (res.statusCode == 200) { if (res.statusCode == 200) {

View File

@ -25,7 +25,7 @@ class GitLab extends AppSource {
@override @override
Future<APKDetails> getLatestAPKDetails( Future<APKDetails> getLatestAPKDetails(
String standardUrl, List<String> additionalData, String standardUrl, Map<String, String> additionalData,
{bool trackOnly = false}) async { {bool trackOnly = false}) async {
Response res = await get(Uri.parse('$standardUrl/-/tags?format=atom')); Response res = await get(Uri.parse('$standardUrl/-/tags?format=atom'));
if (res.statusCode == 200) { if (res.statusCode == 200) {

View File

@ -23,13 +23,13 @@ class IzzyOnDroid extends AppSource {
@override @override
String? tryInferringAppId(String standardUrl, String? tryInferringAppId(String standardUrl,
{List<String> additionalData = const []}) { {Map<String, String> additionalData = const {}}) {
return FDroid().tryInferringAppId(standardUrl); return FDroid().tryInferringAppId(standardUrl);
} }
@override @override
Future<APKDetails> getLatestAPKDetails( Future<APKDetails> getLatestAPKDetails(
String standardUrl, List<String> additionalData, String standardUrl, Map<String, String> additionalData,
{bool trackOnly = false}) async { {bool trackOnly = false}) async {
String? appId = tryInferringAppId(standardUrl); String? appId = tryInferringAppId(standardUrl);
return FDroid().getAPKUrlsFromFDroidPackagesAPIResponse( return FDroid().getAPKUrlsFromFDroidPackagesAPIResponse(

View File

@ -24,7 +24,7 @@ class Mullvad extends AppSource {
@override @override
Future<APKDetails> getLatestAPKDetails( Future<APKDetails> getLatestAPKDetails(
String standardUrl, List<String> additionalData, String standardUrl, Map<String, String> additionalData,
{bool trackOnly = false}) async { {bool trackOnly = false}) async {
Response res = await get(Uri.parse('$standardUrl/en/download/android')); Response res = await get(Uri.parse('$standardUrl/en/download/android'));
if (res.statusCode == 200) { if (res.statusCode == 200) {

View File

@ -18,7 +18,7 @@ class Signal extends AppSource {
@override @override
Future<APKDetails> getLatestAPKDetails( Future<APKDetails> getLatestAPKDetails(
String standardUrl, List<String> additionalData, String standardUrl, Map<String, String> additionalData,
{bool trackOnly = false}) async { {bool trackOnly = false}) async {
Response res = Response res =
await get(Uri.parse('https://updates.$host/android/latest.json')); await get(Uri.parse('https://updates.$host/android/latest.json'));

View File

@ -23,7 +23,7 @@ class SourceForge extends AppSource {
@override @override
Future<APKDetails> getLatestAPKDetails( Future<APKDetails> getLatestAPKDetails(
String standardUrl, List<String> additionalData, String standardUrl, Map<String, String> additionalData,
{bool trackOnly = false}) async { {bool trackOnly = false}) async {
Response res = await get(Uri.parse('$standardUrl/rss?path=/')); Response res = await get(Uri.parse('$standardUrl/rss?path=/'));
if (res.statusCode == 200) { if (res.statusCode == 200) {

View File

@ -11,11 +11,8 @@ class SteamMobile extends AppSource {
name = tr('steam'); name = tr('steam');
additionalSourceAppSpecificFormItems = [ additionalSourceAppSpecificFormItems = [
[ [
GeneratedFormItem( GeneratedFormItem('app',
label: tr('app'), label: tr('app'), required: true, opts: apks.entries.toList())
key: 'app',
required: true,
opts: apks.entries.toList())
] ]
]; ];
} }
@ -32,15 +29,11 @@ class SteamMobile extends AppSource {
@override @override
Future<APKDetails> getLatestAPKDetails( Future<APKDetails> getLatestAPKDetails(
String standardUrl, List<String> additionalData, String standardUrl, Map<String, String> additionalData,
{bool trackOnly = false}) async { {bool trackOnly = false}) async {
Response res = await get(Uri.parse('https://$host/mobile')); Response res = await get(Uri.parse('https://$host/mobile'));
if (res.statusCode == 200) { if (res.statusCode == 200) {
var apkNamePrefix = findGeneratedFormValueByKey( var apkNamePrefix = additionalData['app'];
additionalSourceAppSpecificFormItems
.reduce((value, element) => [...value, ...element]),
additionalData,
'app');
if (apkNamePrefix == null) { if (apkNamePrefix == null) {
throw NoReleasesError(); throw NoReleasesError();
} }

View File

@ -4,7 +4,7 @@ import 'package:flutter/material.dart';
enum FormItemType { string, bool } enum FormItemType { string, bool }
typedef OnValueChanges = void Function( typedef OnValueChanges = void Function(
List<String> values, bool valid, bool isBuilding); Map<String, String> values, bool valid, bool isBuilding);
class GeneratedFormItem { class GeneratedFormItem {
late String key; late String key;
@ -13,22 +13,19 @@ class GeneratedFormItem {
late bool required; late bool required;
late int max; late int max;
late List<String? Function(String? value)> additionalValidators; late List<String? Function(String? value)> additionalValidators;
late String id;
late List<Widget> belowWidgets; late List<Widget> belowWidgets;
late String? hint; late String? hint;
late List<MapEntry<String, String>>? opts; late List<MapEntry<String, String>>? opts;
GeneratedFormItem( GeneratedFormItem(this.key,
{this.label = 'Input', {this.label = 'Input',
this.type = FormItemType.string, this.type = FormItemType.string,
this.required = true, this.required = true,
this.max = 1, this.max = 1,
this.additionalValidators = const [], this.additionalValidators = const [],
this.id = 'input',
this.belowWidgets = const [], this.belowWidgets = const [],
this.hint, this.hint,
this.opts, this.opts}) {
this.key = 'default'}) {
if (type != FormItemType.string) { if (type != FormItemType.string) {
required = false; required = false;
} }
@ -44,7 +41,7 @@ class GeneratedForm extends StatefulWidget {
final List<List<GeneratedFormItem>> items; final List<List<GeneratedFormItem>> items;
final OnValueChanges onValueChanges; final OnValueChanges onValueChanges;
final List<String> defaultValues; final Map<String, String> defaultValues;
@override @override
State<GeneratedForm> createState() => _GeneratedFormState(); State<GeneratedForm> createState() => _GeneratedFormState();
@ -52,17 +49,18 @@ class GeneratedForm extends StatefulWidget {
class _GeneratedFormState extends State<GeneratedForm> { class _GeneratedFormState extends State<GeneratedForm> {
final _formKey = GlobalKey<FormState>(); final _formKey = GlobalKey<FormState>();
late List<List<String>> values; Map<String, String> values = {};
late List<List<Widget>> formInputs; late List<List<Widget>> formInputs;
List<List<Widget>> rows = []; List<List<Widget>> rows = [];
// If any value changes, call this to update the parent with value and validity // If any value changes, call this to update the parent with value and validity
void someValueChanged({bool isBuilding = false}) { void someValueChanged({bool isBuilding = false}) {
List<String> returnValues = []; Map<String, String> returnValues = {};
var valid = true; var valid = true;
for (int r = 0; r < values.length; r++) { for (int r = 0; r < widget.items.length; r++) {
for (int i = 0; i < values[r].length; i++) { for (int i = 0; i < widget.items[r].length; i++) {
returnValues.add(values[r][i]); returnValues[widget.items[r][i].key] =
values[widget.items[r][i].key] ?? '';
if (formInputs[r][i] is TextFormField) { if (formInputs[r][i] is TextFormField) {
valid = valid && valid = valid &&
((formInputs[r][i].key as GlobalKey<FormFieldState>) ((formInputs[r][i].key as GlobalKey<FormFieldState>)
@ -80,16 +78,13 @@ class _GeneratedFormState extends State<GeneratedForm> {
super.initState(); super.initState();
// Initialize form values as all empty // Initialize form values as all empty
values.clear();
int j = 0; int j = 0;
values = widget.items for (var row in widget.items) {
.map((row) => row.map((e) { for (var e in row) {
return j < widget.defaultValues.length values[e.key] = widget.defaultValues[e.key] ?? e.opts?.first.key ?? '';
? widget.defaultValues[j++] }
: e.opts != null }
? e.opts!.first.key
: '';
}).toList())
.toList();
// Dynamically create form inputs // Dynamically create form inputs
formInputs = widget.items.asMap().entries.map((row) { formInputs = widget.items.asMap().entries.map((row) {
@ -98,11 +93,11 @@ class _GeneratedFormState extends State<GeneratedForm> {
final formFieldKey = GlobalKey<FormFieldState>(); final formFieldKey = GlobalKey<FormFieldState>();
return TextFormField( return TextFormField(
key: formFieldKey, key: formFieldKey,
initialValue: values[row.key][e.key], initialValue: values[e.value.key],
autovalidateMode: AutovalidateMode.onUserInteraction, autovalidateMode: AutovalidateMode.onUserInteraction,
onChanged: (value) { onChanged: (value) {
setState(() { setState(() {
values[row.key][e.key] = value; values[e.value.key] = value;
someValueChanged(); someValueChanged();
}); });
}, },
@ -131,14 +126,14 @@ class _GeneratedFormState extends State<GeneratedForm> {
} }
return DropdownButtonFormField( return DropdownButtonFormField(
decoration: InputDecoration(labelText: e.value.label), decoration: InputDecoration(labelText: e.value.label),
value: values[row.key][e.key], value: values[e.value.key],
items: e.value.opts! items: e.value.opts!
.map((e) => .map((e) =>
DropdownMenuItem(value: e.key, child: Text(e.value))) DropdownMenuItem(value: e.key, child: Text(e.value)))
.toList(), .toList(),
onChanged: (value) { onChanged: (value) {
setState(() { setState(() {
values[row.key][e.key] = value ?? e.value.opts!.first.key; values[e.value.key] = value ?? e.value.opts!.first.key;
someValueChanged(); someValueChanged();
}); });
}); });
@ -160,10 +155,10 @@ class _GeneratedFormState extends State<GeneratedForm> {
children: [ children: [
Text(widget.items[r][e].label), Text(widget.items[r][e].label),
Switch( Switch(
value: values[r][e] == 'true', value: values[widget.items[r][e].key] == 'true',
onChanged: (value) { onChanged: (value) {
setState(() { setState(() {
values[r][e] = value ? 'true' : ''; values[widget.items[r][e].key] = value ? 'true' : '';
someValueChanged(); 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;
}

View File

@ -15,7 +15,7 @@ class GeneratedFormModal extends StatefulWidget {
final String title; final String title;
final String message; final String message;
final List<List<GeneratedFormItem>> items; final List<List<GeneratedFormItem>> items;
final List<String> defaultValues; final Map<String, String> defaultValues;
final bool initValid; final bool initValid;
@override @override
@ -23,13 +23,15 @@ class GeneratedFormModal extends StatefulWidget {
} }
class _GeneratedFormModalState extends State<GeneratedFormModal> { class _GeneratedFormModalState extends State<GeneratedFormModal> {
List<String> values = []; Map<String, String> values = {};
bool valid = false; bool valid = false;
@override @override
void initState() { void initState() {
super.initState(); super.initState();
values = widget.defaultValues; widget.defaultValues.forEach((key, value) {
values[key] = value;
});
valid = widget.initValid || widget.items.isEmpty; valid = widget.initValid || widget.items.isEmpty;
} }

View File

@ -200,7 +200,7 @@ class _ObtainiumState extends State<Obtainium> {
currentReleaseTag, currentReleaseTag,
[], [],
0, 0,
['true'], {},
null, null,
false, false,
false) false)

View File

@ -27,9 +27,9 @@ class _AddAppPageState extends State<AddAppPage> {
String userInput = ''; String userInput = '';
String searchQuery = ''; String searchQuery = '';
AppSource? pickedSource; AppSource? pickedSource;
List<String> sourceSpecificAdditionalData = []; Map<String, String> sourceSpecificAdditionalData = {};
bool sourceSpecificDataIsValid = true; bool sourceSpecificDataIsValid = true;
List<String> otherAdditionalData = []; Map<String, String> otherAdditionalData = {};
bool otherAdditionalDataIsValid = true; bool otherAdditionalDataIsValid = true;
@override @override
@ -44,7 +44,7 @@ class _AddAppPageState extends State<AddAppPage> {
if (pickedSource.runtimeType != source.runtimeType) { if (pickedSource.runtimeType != source.runtimeType) {
pickedSource = source; pickedSource = source;
sourceSpecificAdditionalData = sourceSpecificAdditionalData =
source != null ? source.additionalSourceAppSpecificDefaults : []; source != null ? source.additionalSourceAppSpecificDefaults : {};
sourceSpecificDataIsValid = source != null sourceSpecificDataIsValid = source != null
? !sourceProvider.ifSourceAppsRequireAdditionalData(source) ? !sourceProvider.ifSourceAppsRequireAdditionalData(source)
: true; : true;
@ -66,16 +66,10 @@ class _AddAppPageState extends State<AddAppPage> {
}); });
var settingsProvider = context.read<SettingsProvider>(); var settingsProvider = context.read<SettingsProvider>();
() async { () async {
var userPickedTrackOnly = findGeneratedFormValueByKey( var userPickedTrackOnly =
pickedSource!.additionalAppSpecificSourceAgnosticFormItems, otherAdditionalData['trackOnlyFormItemKey'] == 'true';
otherAdditionalData, var userPickedNoVersionDetection =
'trackOnlyFormItemKey') == otherAdditionalData['noVersionDetectionKey'] == 'true';
'true';
var userPickedNoVersionDetection = findGeneratedFormValueByKey(
pickedSource!.additionalAppSpecificSourceAgnosticFormItems,
otherAdditionalData,
'noVersionDetectionKey') ==
'true';
var cont = true; var cont = true;
if ((userPickedTrackOnly || pickedSource!.enforceTrackOnly) && if ((userPickedTrackOnly || pickedSource!.enforceTrackOnly) &&
await showDialog( await showDialog(
@ -88,7 +82,7 @@ class _AddAppPageState extends State<AddAppPage> {
: tr('app') : tr('app')
]), ]),
items: const [], items: const [],
defaultValues: const [], defaultValues: const {},
message: message:
'${pickedSource!.enforceTrackOnly ? tr('appsFromSourceAreTrackOnly') : tr('youPickedTrackOnly')}\n\n${tr('trackOnlyAppDescription')}', '${pickedSource!.enforceTrackOnly ? tr('appsFromSourceAreTrackOnly') : tr('youPickedTrackOnly')}\n\n${tr('trackOnlyAppDescription')}',
); );
@ -100,10 +94,10 @@ class _AddAppPageState extends State<AddAppPage> {
await showDialog( await showDialog(
context: context, context: context,
builder: (BuildContext ctx) { builder: (BuildContext ctx) {
return GeneratedFormModal( return const GeneratedFormModal(
title: 'Disable Version Detection', // TODO title: 'Disable Version Detection', // TODO
items: const [], items: [],
defaultValues: const [], defaultValues: {},
message: 'TODO', message: 'TODO',
); );
}) == }) ==
@ -177,7 +171,7 @@ class _AddAppPageState extends State<AddAppPage> {
child: GeneratedForm( child: GeneratedForm(
items: [ items: [
[ [
GeneratedFormItem( GeneratedFormItem('appSourceURL',
label: tr('appSourceURL'), label: tr('appSourceURL'),
additionalValidators: [ additionalValidators: [
(value) { (value) {
@ -200,10 +194,10 @@ class _AddAppPageState extends State<AddAppPage> {
] ]
], ],
onValueChanges: (values, valid, isBuilding) { onValueChanges: (values, valid, isBuilding) {
changeUserInput( changeUserInput(values['appSourceURL']!,
values[0], valid, isBuilding); valid, isBuilding);
}, },
defaultValues: const [])), defaultValues: const {'appSourceURL': ''})),
const SizedBox( const SizedBox(
width: 16, width: 16,
), ),
@ -244,7 +238,7 @@ class _AddAppPageState extends State<AddAppPage> {
child: GeneratedForm( child: GeneratedForm(
items: [ items: [
[ [
GeneratedFormItem( GeneratedFormItem('searchSomeSources',
label: tr('searchSomeSourcesLabel'), label: tr('searchSomeSourcesLabel'),
required: false), required: false),
] ]
@ -252,11 +246,14 @@ class _AddAppPageState extends State<AddAppPage> {
onValueChanges: (values, valid, isBuilding) { onValueChanges: (values, valid, isBuilding) {
if (values.isNotEmpty && valid) { if (values.isNotEmpty && valid) {
setState(() { setState(() {
searchQuery = values[0].trim(); searchQuery =
values['searchSomeSources']!.trim();
}); });
} }
}, },
defaultValues: const ['']), defaultValues: const {
'searchSomeSources': ''
}),
), ),
const SizedBox( const SizedBox(
width: 16, width: 16,

View File

@ -208,7 +208,7 @@ class _AppPageState extends State<AppPage> {
onPressed: app?.downloadProgress != null onPressed: app?.downloadProgress != null
? null ? null
: () { : () {
showDialog<List<String>>( showDialog<Map<String, String>>(
context: context, context: context,
builder: (BuildContext ctx) { builder: (BuildContext ctx) {
return GeneratedFormModal( return GeneratedFormModal(

View File

@ -349,7 +349,7 @@ class AppsPageState extends State<AppsPage> {
return GeneratedFormModal( return GeneratedFormModal(
title: tr('removeSelectedAppsQuestion'), title: tr('removeSelectedAppsQuestion'),
items: const [], items: const [],
defaultValues: const [], defaultValues: const {},
initValid: true, initValid: true,
message: tr( message: tr(
'xWillBeRemovedButRemainInstalled', 'xWillBeRemovedButRemainInstalled',
@ -377,40 +377,37 @@ class AppsPageState extends State<AppsPage> {
: () { : () {
HapticFeedback.heavyImpact(); HapticFeedback.heavyImpact();
List<GeneratedFormItem> formInputs = []; List<GeneratedFormItem> formInputs = [];
List<String> defaultValues = []; Map<String, String> defaultValues = {};
if (existingUpdateIdsAllOrSelected.isNotEmpty) { if (existingUpdateIdsAllOrSelected.isNotEmpty) {
formInputs.add(GeneratedFormItem( formInputs.add(GeneratedFormItem('updates',
label: tr('updateX', args: [ label: tr('updateX', args: [
plural('apps', plural('apps',
existingUpdateIdsAllOrSelected.length) existingUpdateIdsAllOrSelected.length)
]), ]),
type: FormItemType.bool, type: FormItemType.bool));
key: 'updates')); defaultValues['updates'] = 'true';
defaultValues.add('true');
} }
if (newInstallIdsAllOrSelected.isNotEmpty) { if (newInstallIdsAllOrSelected.isNotEmpty) {
formInputs.add(GeneratedFormItem( formInputs.add(GeneratedFormItem('installs',
label: tr('installX', args: [ label: tr('installX', args: [
plural('apps', plural('apps',
newInstallIdsAllOrSelected.length) newInstallIdsAllOrSelected.length)
]), ]),
type: FormItemType.bool, type: FormItemType.bool));
key: 'installs')); defaultValues['installs'] =
defaultValues defaultValues.isEmpty ? 'true' : '';
.add(defaultValues.isEmpty ? 'true' : '');
} }
if (trackOnlyUpdateIdsAllOrSelected.isNotEmpty) { if (trackOnlyUpdateIdsAllOrSelected.isNotEmpty) {
formInputs.add(GeneratedFormItem( formInputs.add(GeneratedFormItem('trackonlies',
label: tr('markXTrackOnlyAsUpdated', args: [ label: tr('markXTrackOnlyAsUpdated', args: [
plural('apps', plural('apps',
trackOnlyUpdateIdsAllOrSelected.length) trackOnlyUpdateIdsAllOrSelected.length)
]), ]),
type: FormItemType.bool, type: FormItemType.bool));
key: 'trackonlies')); defaultValues['trackonlies'] =
defaultValues defaultValues.isEmpty ? 'true' : '';
.add(defaultValues.isEmpty ? 'true' : '');
} }
showDialog<List<String>?>( showDialog<Map<String, String>?>(
context: context, context: context,
builder: (BuildContext ctx) { builder: (BuildContext ctx) {
var totalApps = existingUpdateIdsAllOrSelected var totalApps = existingUpdateIdsAllOrSelected
@ -430,17 +427,11 @@ class AppsPageState extends State<AppsPage> {
values = defaultValues; values = defaultValues;
} }
bool shouldInstallUpdates = bool shouldInstallUpdates =
findGeneratedFormValueByKey( values['updates'] == 'true';
formInputs, values, 'updates') ==
'true';
bool shouldInstallNew = bool shouldInstallNew =
findGeneratedFormValueByKey( values['installs'] == 'true';
formInputs, values, 'installs') ==
'true';
bool shouldMarkTrackOnlies = bool shouldMarkTrackOnlies =
findGeneratedFormValueByKey(formInputs, values['trackonlies'] == 'true';
values, 'trackonlies') ==
'true';
(() async { (() async {
if (shouldInstallNew || if (shouldInstallNew ||
shouldInstallUpdates) { shouldInstallUpdates) {
@ -613,7 +604,7 @@ class AppsPageState extends State<AppsPage> {
title: tr( title: tr(
'resetInstallStatusForSelectedAppsQuestion'), 'resetInstallStatusForSelectedAppsQuestion'),
items: const [], items: const [],
defaultValues: const [], defaultValues: const {},
initValid: true, initValid: true,
message: tr( message: tr(
'installStatusOfXWillBeResetExplanation', 'installStatusOfXWillBeResetExplanation',
@ -683,36 +674,36 @@ class AppsPageState extends State<AppsPage> {
: FontWeight.bold), : FontWeight.bold),
), ),
onPressed: () { onPressed: () {
showDialog<List<String>?>( showDialog<Map<String, String>?>(
context: context, context: context,
builder: (BuildContext ctx) { builder: (BuildContext ctx) {
return GeneratedFormModal( return GeneratedFormModal(
title: tr('filterApps'), title: tr('filterApps'),
items: [ items: [
[ [
GeneratedFormItem( GeneratedFormItem('appName',
label: tr('appName'), required: false), label: tr('appName'), required: false),
GeneratedFormItem( GeneratedFormItem('author',
label: tr('author'), required: false) label: tr('author'), required: false)
], ],
[ [
GeneratedFormItem( GeneratedFormItem('upToDateApps',
label: tr('upToDateApps'), label: tr('upToDateApps'),
type: FormItemType.bool) type: FormItemType.bool)
], ],
[ [
GeneratedFormItem( GeneratedFormItem('nonInstalledApps',
label: tr('nonInstalledApps'), label: tr('nonInstalledApps'),
type: FormItemType.bool) type: FormItemType.bool)
] ]
], ],
defaultValues: filter == null defaultValues: filter == null
? AppsFilter().toValuesArray() ? AppsFilter().toValuesMap()
: filter!.toValuesArray()); : filter!.toValuesMap());
}).then((values) { }).then((values) {
if (values != null) { if (values != null) {
setState(() { setState(() {
filter = AppsFilter.fromValuesArray(values); filter = AppsFilter.fromValuesMap(values);
if (AppsFilter().isIdenticalTo(filter!)) { if (AppsFilter().isIdenticalTo(filter!)) {
filter = null; filter = null;
} }
@ -740,20 +731,20 @@ class AppsFilter {
this.includeUptodate = true, this.includeUptodate = true,
this.includeNonInstalled = true}); this.includeNonInstalled = true});
List<String> toValuesArray() { Map<String, String> toValuesMap() {
return [ return {
nameFilter, 'appName': nameFilter,
authorFilter, 'author': authorFilter,
includeUptodate ? 'true' : '', 'upToDateApps': includeUptodate ? 'true' : '',
includeNonInstalled ? 'true' : '' 'nonInstalledApps': includeNonInstalled ? 'true' : ''
]; };
} }
AppsFilter.fromValuesArray(List<String> values) { AppsFilter.fromValuesMap(Map<String, String> values) {
nameFilter = values[0]; nameFilter = values['appName']!;
authorFilter = values[1]; authorFilter = values['author']!;
includeUptodate = values[2] == 'true'; includeUptodate = values['upToDateApps'] == 'true';
includeNonInstalled = values[3] == 'true'; includeNonInstalled = values['nonInstalledApps'] == 'true';
} }
bool isIdenticalTo(AppsFilter other) => bool isIdenticalTo(AppsFilter other) =>

View File

@ -145,7 +145,7 @@ class _ImportExportPageState extends State<ImportExportPage> {
title: tr('importFromURLList'), title: tr('importFromURLList'),
items: [ items: [
[ [
GeneratedFormItem( GeneratedFormItem('appURLList',
label: tr('appURLList'), label: tr('appURLList'),
max: 7, max: 7,
additionalValidators: [ additionalValidators: [
@ -172,7 +172,7 @@ class _ImportExportPageState extends State<ImportExportPage> {
]) ])
] ]
], ],
defaultValues: const [], defaultValues: const {},
); );
}).then((values) { }).then((values) {
if (values != null) { if (values != null) {
@ -237,11 +237,12 @@ class _ImportExportPageState extends State<ImportExportPage> {
items: [ items: [
[ [
GeneratedFormItem( GeneratedFormItem(
'searchQuery',
label: tr( label: tr(
'searchQuery')) 'searchQuery'))
] ]
], ],
defaultValues: const [], defaultValues: const {},
); );
}); });
if (values != null && if (values != null &&
@ -346,10 +347,11 @@ class _ImportExportPageState extends State<ImportExportPage> {
.requiredArgs .requiredArgs
.map( .map(
(e) => [ (e) => [
GeneratedFormItem(label: e) GeneratedFormItem(e,
label: e)
]) ])
.toList(), .toList(),
defaultValues: const [], defaultValues: const {},
); );
}); });
if (values != null) { if (values != null) {

View File

@ -143,16 +143,16 @@ class _SettingsPageState extends State<SettingsPage> {
.toList(), .toList(),
onValueChanges: (values, valid, isBuilding) { onValueChanges: (values, valid, isBuilding) {
if (valid) { if (valid) {
for (var i = 0; i < values.length; i++) { values.forEach((key, value) {
settingsProvider.setSettingString( settingsProvider.setSettingString(key, value);
e.additionalSourceSpecificSettingFormItems[i].id, });
values[i]);
}
} }
}, },
defaultValues: e.additionalSourceSpecificSettingFormItems.map((e) { defaultValues: Map.fromEntries(
return settingsProvider.getSettingString(e.id) ?? ''; e.additionalSourceSpecificSettingFormItems.map((e) {
}).toList()); return MapEntry(
e.key, settingsProvider.getSettingString(e.key) ?? '');
})));
} else { } else {
return Container(); return Container();
} }

View File

@ -44,7 +44,7 @@ class App {
late String latestVersion; late String latestVersion;
List<String> apkUrls = []; List<String> apkUrls = [];
late int preferredApkIndex; late int preferredApkIndex;
late List<String> additionalData; late Map<String, String> additionalData;
late DateTime? lastUpdateCheck; late DateTime? lastUpdateCheck;
bool pinned = false; bool pinned = false;
bool trackOnly = false; bool trackOnly = false;
@ -86,7 +86,7 @@ class App {
? SourceProvider() ? SourceProvider()
.getSource(json['url']) .getSource(json['url'])
.additionalSourceAppSpecificDefaults .additionalSourceAppSpecificDefaults
: List<String>.from(jsonDecode(json['additionalData'])), : Map<String, String>.from(jsonDecode(json['additionalData'])),
json['lastUpdateCheck'] == null json['lastUpdateCheck'] == null
? null ? null
: DateTime.fromMicrosecondsSinceEpoch(json['lastUpdateCheck']), : DateTime.fromMicrosecondsSinceEpoch(json['lastUpdateCheck']),
@ -155,27 +155,30 @@ class AppSource {
} }
Future<APKDetails> getLatestAPKDetails( Future<APKDetails> getLatestAPKDetails(
String standardUrl, List<String> additionalData, String standardUrl, Map<String, String> additionalData,
{bool trackOnly = false}) { {bool trackOnly = false}) {
throw NotImplementedError(); throw NotImplementedError();
} }
// Different Sources may need different kinds of additional data for Apps // Different Sources may need different kinds of additional data for Apps
List<List<GeneratedFormItem>> additionalSourceAppSpecificFormItems = []; List<List<GeneratedFormItem>> additionalSourceAppSpecificFormItems = [];
List<String> additionalSourceAppSpecificDefaults = []; Map<String, String> additionalSourceAppSpecificDefaults = {};
// Some additional data may be needed for Apps regardless of Source // Some additional data may be needed for Apps regardless of Source
final List<GeneratedFormItem> additionalAppSpecificSourceAgnosticFormItems = [ final List<GeneratedFormItem> additionalAppSpecificSourceAgnosticFormItems = [
GeneratedFormItem( GeneratedFormItem(
label: tr('trackOnly'), 'trackOnlyFormItemKey',
type: FormItemType.bool, label: tr('trackOnly'),
key: 'trackOnlyFormItemKey'), type: FormItemType.bool,
GeneratedFormItem( ),
GeneratedFormItem('noVersionDetectionKey',
label: 'Do not attempt version detection', // TODO label: 'Do not attempt version detection', // TODO
type: FormItemType.bool, type: FormItemType.bool)
key: 'noVersionDetectionKey')
]; ];
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 // Some Sources may have additional settings at the Source level (not specific to Apps) - these use SettingsProvider
List<GeneratedFormItem> additionalSourceSpecificSettingFormItems = []; List<GeneratedFormItem> additionalSourceSpecificSettingFormItems = [];
@ -194,7 +197,7 @@ class AppSource {
} }
String? tryInferringAppId(String standardUrl, String? tryInferringAppId(String standardUrl,
{List<String> additionalData = const []}) { {Map<String, String> additionalData = const {}}) {
return null; return null;
} }
} }
@ -282,7 +285,8 @@ class SourceProvider {
return true; 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, {App? currentApp,
bool trackOnlyOverride = false, bool trackOnlyOverride = false,
noVersionDetectionOverride = false}) async { noVersionDetectionOverride = false}) async {