From 907286286245692f9debf550f64518c10d0b7eb6 Mon Sep 17 00:00:00 2001 From: Imran Remtulla Date: Wed, 21 Dec 2022 18:23:25 -0500 Subject: [PATCH 1/2] Broke GeneratedFormItem into sub-types Prep for "chips" input type --- lib/app_sources/apkmirror.dart | 2 +- lib/app_sources/fdroid.dart | 4 +- lib/app_sources/fdroidrepo.dart | 4 +- lib/app_sources/github.dart | 33 +++-- lib/app_sources/gitlab.dart | 2 +- lib/app_sources/izzyondroid.dart | 4 +- lib/app_sources/mullvad.dart | 2 +- lib/app_sources/signal.dart | 2 +- lib/app_sources/sourceforge.dart | 2 +- lib/app_sources/steammobile.dart | 9 +- lib/components/generated_form.dart | 153 +++++++++++++++-------- lib/components/generated_form_modal.dart | 2 +- lib/main.dart | 2 +- lib/pages/add_app.dart | 15 +-- lib/pages/app.dart | 10 +- lib/pages/apps.dart | 67 +++++----- lib/pages/import_export.dart | 27 ++-- lib/pages/settings.dart | 14 ++- lib/providers/apps_provider.dart | 6 +- lib/providers/settings_provider.dart | 16 +-- lib/providers/source_provider.dart | 50 ++++---- 21 files changed, 238 insertions(+), 188 deletions(-) diff --git a/lib/app_sources/apkmirror.dart b/lib/app_sources/apkmirror.dart index b562b89..ac0b527 100644 --- a/lib/app_sources/apkmirror.dart +++ b/lib/app_sources/apkmirror.dart @@ -26,7 +26,7 @@ class APKMirror extends AppSource { @override Future getLatestAPKDetails( String standardUrl, - Map additionalSettings, + Map additionalSettings, ) async { Response res = await get(Uri.parse('$standardUrl/feed')); if (res.statusCode == 200) { diff --git a/lib/app_sources/fdroid.dart b/lib/app_sources/fdroid.dart index f5ef19f..f5493a8 100644 --- a/lib/app_sources/fdroid.dart +++ b/lib/app_sources/fdroid.dart @@ -32,7 +32,7 @@ class FDroid extends AppSource { @override String? tryInferringAppId(String standardUrl, - {Map additionalSettings = const {}}) { + {Map additionalSettings = const {}}) { return Uri.parse(standardUrl).pathSegments.last; } @@ -61,7 +61,7 @@ class FDroid extends AppSource { @override Future getLatestAPKDetails( String standardUrl, - Map additionalSettings, + Map additionalSettings, ) async { String? appId = tryInferringAppId(standardUrl); return getAPKUrlsFromFDroidPackagesAPIResponse( diff --git a/lib/app_sources/fdroidrepo.dart b/lib/app_sources/fdroidrepo.dart index dd4c85f..0f63de5 100644 --- a/lib/app_sources/fdroidrepo.dart +++ b/lib/app_sources/fdroidrepo.dart @@ -11,7 +11,7 @@ class FDroidRepo extends AppSource { additionalSourceAppSpecificSettingFormItems = [ [ - GeneratedFormItem('appIdOrName', + GeneratedFormTextField('appIdOrName', label: tr('appIdOrName'), hint: tr('reposHaveMultipleApps'), required: true) @@ -33,7 +33,7 @@ class FDroidRepo extends AppSource { @override Future getLatestAPKDetails( String standardUrl, - Map additionalSettings, + Map additionalSettings, ) async { String? appIdOrName = additionalSettings['appIdOrName']; if (appIdOrName == null) { diff --git a/lib/app_sources/github.dart b/lib/app_sources/github.dart index e7aecc1..82af377 100644 --- a/lib/app_sources/github.dart +++ b/lib/app_sources/github.dart @@ -13,7 +13,7 @@ class GitHub extends AppSource { host = 'github.com'; additionalSourceSpecificSettingFormItems = [ - GeneratedFormItem('github-creds', + GeneratedFormTextField('github-creds', label: tr('githubPATLabel'), required: false, additionalValidators: [ @@ -51,21 +51,16 @@ class GitHub extends AppSource { additionalSourceAppSpecificSettingFormItems = [ [ - GeneratedFormItem('includePrereleases', - label: tr('includePrereleases'), - type: FormItemType.bool, - defaultValue: '') + GeneratedFormSwitch('includePrereleases', + label: tr('includePrereleases'), defaultValue: false) ], [ - GeneratedFormItem('fallbackToOlderReleases', - label: tr('fallbackToOlderReleases'), - type: FormItemType.bool, - defaultValue: 'true') + GeneratedFormSwitch('fallbackToOlderReleases', + label: tr('fallbackToOlderReleases'), defaultValue: true) ], [ - GeneratedFormItem('filterReleaseTitlesByRegEx', + GeneratedFormTextField('filterReleaseTitlesByRegEx', label: tr('filterReleaseTitlesByRegEx'), - type: FormItemType.string, required: false, additionalValidators: [ (value) { @@ -111,13 +106,15 @@ class GitHub extends AppSource { @override Future getLatestAPKDetails( String standardUrl, - Map additionalSettings, + Map additionalSettings, ) async { - var includePrereleases = additionalSettings['includePrereleases'] == 'true'; - var fallbackToOlderReleases = - additionalSettings['fallbackToOlderReleases'] == 'true'; - var regexFilter = - additionalSettings['filterReleaseTitlesByRegEx']?.isNotEmpty == true + bool includePrereleases = additionalSettings['includePrereleases']; + bool fallbackToOlderReleases = + additionalSettings['fallbackToOlderReleases']; + String? regexFilter = + (additionalSettings['filterReleaseTitlesByRegEx'] as String?) + ?.isNotEmpty == + true ? additionalSettings['filterReleaseTitlesByRegEx'] : null; Response res = await get(Uri.parse( @@ -150,7 +147,7 @@ class GitHub extends AppSource { continue; } var apkUrls = getReleaseAPKUrls(releases[i]); - if (apkUrls.isEmpty && additionalSettings['trackOnly'] != 'true') { + if (apkUrls.isEmpty && additionalSettings['trackOnly'] != true) { continue; } targetRelease = releases[i]; diff --git a/lib/app_sources/gitlab.dart b/lib/app_sources/gitlab.dart index 225c610..dedd7d8 100644 --- a/lib/app_sources/gitlab.dart +++ b/lib/app_sources/gitlab.dart @@ -26,7 +26,7 @@ class GitLab extends AppSource { @override Future getLatestAPKDetails( String standardUrl, - Map additionalSettings, + Map additionalSettings, ) async { Response res = await get(Uri.parse('$standardUrl/-/tags?format=atom')); if (res.statusCode == 200) { diff --git a/lib/app_sources/izzyondroid.dart b/lib/app_sources/izzyondroid.dart index eb174f8..8d61693 100644 --- a/lib/app_sources/izzyondroid.dart +++ b/lib/app_sources/izzyondroid.dart @@ -23,14 +23,14 @@ class IzzyOnDroid extends AppSource { @override String? tryInferringAppId(String standardUrl, - {Map additionalSettings = const {}}) { + {Map additionalSettings = const {}}) { return FDroid().tryInferringAppId(standardUrl); } @override Future getLatestAPKDetails( String standardUrl, - Map additionalSettings, + Map additionalSettings, ) async { String? appId = tryInferringAppId(standardUrl); return FDroid().getAPKUrlsFromFDroidPackagesAPIResponse( diff --git a/lib/app_sources/mullvad.dart b/lib/app_sources/mullvad.dart index 283cf37..6700ec2 100644 --- a/lib/app_sources/mullvad.dart +++ b/lib/app_sources/mullvad.dart @@ -25,7 +25,7 @@ class Mullvad extends AppSource { @override Future getLatestAPKDetails( String standardUrl, - Map additionalSettings, + Map additionalSettings, ) async { Response res = await get(Uri.parse('$standardUrl/en/download/android')); if (res.statusCode == 200) { diff --git a/lib/app_sources/signal.dart b/lib/app_sources/signal.dart index 90004a0..e0aa618 100644 --- a/lib/app_sources/signal.dart +++ b/lib/app_sources/signal.dart @@ -19,7 +19,7 @@ class Signal extends AppSource { @override Future getLatestAPKDetails( String standardUrl, - Map additionalSettings, + Map additionalSettings, ) async { Response res = await get(Uri.parse('https://updates.$host/android/latest.json')); diff --git a/lib/app_sources/sourceforge.dart b/lib/app_sources/sourceforge.dart index d847dbd..e258a3a 100644 --- a/lib/app_sources/sourceforge.dart +++ b/lib/app_sources/sourceforge.dart @@ -24,7 +24,7 @@ class SourceForge extends AppSource { @override Future getLatestAPKDetails( String standardUrl, - Map additionalSettings, + Map additionalSettings, ) async { Response res = await get(Uri.parse('$standardUrl/rss?path=/')); if (res.statusCode == 200) { diff --git a/lib/app_sources/steammobile.dart b/lib/app_sources/steammobile.dart index 8fe8ece..3eed148 100644 --- a/lib/app_sources/steammobile.dart +++ b/lib/app_sources/steammobile.dart @@ -10,10 +10,7 @@ class SteamMobile extends AppSource { host = 'store.steampowered.com'; name = tr('steam'); additionalSourceAppSpecificSettingFormItems = [ - [ - GeneratedFormItem('app', - label: tr('app'), required: true, opts: apks.entries.toList()) - ] + [GeneratedFormDropdown('app', apks.entries.toList(), label: tr('app'))] ]; } @@ -30,11 +27,11 @@ class SteamMobile extends AppSource { @override Future getLatestAPKDetails( String standardUrl, - Map additionalSettings, + Map additionalSettings, ) async { Response res = await get(Uri.parse('https://$host/mobile')); if (res.statusCode == 200) { - var apkNamePrefix = additionalSettings['app']; + var apkNamePrefix = additionalSettings['app'] as String?; if (apkNamePrefix == null) { throw NoReleasesError(); } diff --git a/lib/components/generated_form.dart b/lib/components/generated_form.dart index d927371..f1a845a 100644 --- a/lib/components/generated_form.dart +++ b/lib/components/generated_form.dart @@ -1,39 +1,90 @@ import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; -enum FormItemType { string, bool } - -typedef OnValueChanges = void Function( - Map values, bool valid, bool isBuilding); - -class GeneratedFormItem { +abstract class GeneratedFormItem { late String key; late String label; - late FormItemType type; - late bool required; - late int max; - late List additionalValidators; late List belowWidgets; - late String? hint; - late List>? opts; - late String? defaultValue; + late dynamic defaultValue; + List additionalValidators; + dynamic ensureType(dynamic val); GeneratedFormItem(this.key, {this.label = 'Input', - this.type = FormItemType.string, + this.belowWidgets = const [], + this.defaultValue, + this.additionalValidators = const []}); +} + +class GeneratedFormTextField extends GeneratedFormItem { + late bool required; + late int max; + late String? hint; + + GeneratedFormTextField(String key, + {String label = 'Input', + List belowWidgets = const [], + String defaultValue = '', + List additionalValidators = const [], this.required = true, this.max = 1, - this.additionalValidators = const [], - this.belowWidgets = const [], - this.hint, - this.opts, - this.defaultValue}) { - if (type != FormItemType.string) { - required = false; - } + this.hint}) + : super(key, + label: label, + belowWidgets: belowWidgets, + defaultValue: defaultValue, + additionalValidators: additionalValidators); + + @override + String ensureType(val) { + return val.toString(); } } +class GeneratedFormDropdown extends GeneratedFormItem { + late List>? opts; + + GeneratedFormDropdown( + String key, + this.opts, { + String label = 'Input', + List belowWidgets = const [], + String defaultValue = '', + List additionalValidators = const [], + }) : super(key, + label: label, + belowWidgets: belowWidgets, + defaultValue: defaultValue, + additionalValidators: additionalValidators); + + @override + String ensureType(val) { + return val.toString(); + } +} + +class GeneratedFormSwitch extends GeneratedFormItem { + GeneratedFormSwitch( + String key, { + String label = 'Input', + List belowWidgets = const [], + bool defaultValue = false, + List additionalValidators = const [], + }) : super(key, + label: label, + belowWidgets: belowWidgets, + defaultValue: defaultValue, + additionalValidators: additionalValidators); + + @override + bool ensureType(val) { + return val == true || val == 'true'; + } +} + +typedef OnValueChanges = void Function( + Map values, bool valid, bool isBuilding); + class GeneratedForm extends StatefulWidget { const GeneratedForm( {super.key, required this.items, required this.onValueChanges}); @@ -47,18 +98,16 @@ class GeneratedForm extends StatefulWidget { class _GeneratedFormState extends State { final _formKey = GlobalKey(); - Map values = {}; + Map values = {}; late List> formInputs; List> rows = []; // If any value changes, call this to update the parent with value and validity void someValueChanged({bool isBuilding = false}) { - Map returnValues = {}; + Map returnValues = values; var valid = true; 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) @@ -80,35 +129,37 @@ class _GeneratedFormState extends State { int j = 0; for (var row in widget.items) { for (var e in row) { - values[e.key] = e.defaultValue ?? e.opts?.first.key ?? ''; + values[e.key] = e.defaultValue; } } // Dynamically create form inputs formInputs = widget.items.asMap().entries.map((row) { return row.value.asMap().entries.map((e) { - if (e.value.type == FormItemType.string && e.value.opts == null) { + var formItem = e.value; + if (formItem is GeneratedFormTextField) { final formFieldKey = GlobalKey(); return TextFormField( key: formFieldKey, - initialValue: values[e.value.key], + initialValue: values[formItem.key], autovalidateMode: AutovalidateMode.onUserInteraction, onChanged: (value) { setState(() { - values[e.value.key] = value; + values[formItem.key] = value; someValueChanged(); }); }, decoration: InputDecoration( - helperText: e.value.label + (e.value.required ? ' *' : ''), - hintText: e.value.hint), - minLines: e.value.max <= 1 ? null : e.value.max, - maxLines: e.value.max <= 1 ? 1 : e.value.max, + helperText: formItem.label + (formItem.required ? ' *' : ''), + hintText: formItem.hint), + minLines: formItem.max <= 1 ? null : formItem.max, + maxLines: formItem.max <= 1 ? 1 : formItem.max, validator: (value) { - if (e.value.required && (value == null || value.trim().isEmpty)) { - return '${e.value.label} ${tr('requiredInBrackets')}'; + if (formItem.required && + (value == null || value.trim().isEmpty)) { + return '${formItem.label} ${tr('requiredInBrackets')}'; } - for (var validator in e.value.additionalValidators) { + for (var validator in formItem.additionalValidators) { String? result = validator(value); if (result != null) { return result; @@ -117,21 +168,20 @@ class _GeneratedFormState extends State { return null; }, ); - } else if (e.value.type == FormItemType.string && - e.value.opts != null) { - if (e.value.opts!.isEmpty) { + } else if (formItem is GeneratedFormDropdown) { + if (formItem.opts!.isEmpty) { return Text(tr('dropdownNoOptsError')); } return DropdownButtonFormField( - decoration: InputDecoration(labelText: e.value.label), - value: values[e.value.key], - items: e.value.opts! - .map((e) => - DropdownMenuItem(value: e.key, child: Text(e.value))) + decoration: InputDecoration(labelText: formItem.label), + value: values[formItem.key], + items: formItem.opts! + .map((e2) => + DropdownMenuItem(value: e2.key, child: Text(e2.value))) .toList(), onChanged: (value) { setState(() { - values[e.value.key] = value ?? e.value.opts!.first.key; + values[formItem.key] = value ?? formItem.opts!.first.key; someValueChanged(); }); }); @@ -147,16 +197,16 @@ class _GeneratedFormState extends State { Widget build(BuildContext context) { for (var r = 0; r < formInputs.length; r++) { for (var e = 0; e < formInputs[r].length; e++) { - if (widget.items[r][e].type == FormItemType.bool) { + if (widget.items[r][e] is GeneratedFormSwitch) { formInputs[r][e] = Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text(widget.items[r][e].label), Switch( - value: values[widget.items[r][e].key] == 'true', + value: values[widget.items[r][e].key], onChanged: (value) { setState(() { - values[widget.items[r][e].key] = value ? 'true' : ''; + values[widget.items[r][e].key] = value; someValueChanged(); }); }) @@ -171,9 +221,8 @@ class _GeneratedFormState extends State { if (rowInputs.key > 0) { rows.add([ SizedBox( - height: widget.items[rowInputs.key][0].type == FormItemType.bool && - widget.items[rowInputs.key - 1][0].type == - FormItemType.string + height: widget.items[rowInputs.key][0] is GeneratedFormSwitch && + widget.items[rowInputs.key - 1][0] is! GeneratedFormSwitch ? 25 : 8, ) diff --git a/lib/components/generated_form_modal.dart b/lib/components/generated_form_modal.dart index 2a24ae1..498b20c 100644 --- a/lib/components/generated_form_modal.dart +++ b/lib/components/generated_form_modal.dart @@ -21,7 +21,7 @@ class GeneratedFormModal extends StatefulWidget { } class _GeneratedFormModalState extends State { - Map values = {}; + Map values = {}; bool valid = false; @override diff --git a/lib/main.dart b/lib/main.dart index dd819be..806f54f 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -200,7 +200,7 @@ class _ObtainiumState extends State { currentReleaseTag, [], 0, - {'includePrereleases': 'true'}, + {'includePrereleases': true}, null, false) ]); diff --git a/lib/pages/add_app.dart b/lib/pages/add_app.dart index 4408a3c..687667b 100644 --- a/lib/pages/add_app.dart +++ b/lib/pages/add_app.dart @@ -27,7 +27,7 @@ class _AddAppPageState extends State { String userInput = ''; String searchQuery = ''; AppSource? pickedSource; - Map additionalSettings = {}; + Map additionalSettings = {}; bool additionalSettingsValid = true; @override @@ -66,9 +66,9 @@ class _AddAppPageState extends State { }); var settingsProvider = context.read(); () async { - var userPickedTrackOnly = additionalSettings['trackOnly'] == 'true'; + var userPickedTrackOnly = additionalSettings['trackOnly'] == true; var userPickedNoVersionDetection = - additionalSettings['noVersionDetection'] == 'true'; + additionalSettings['noVersionDetection'] == true; var cont = true; if ((userPickedTrackOnly || pickedSource!.enforceTrackOnly) && await showDialog( @@ -113,7 +113,7 @@ class _AddAppPageState extends State { } // Only download the APK here if you need to for the package ID if (sourceProvider.isTempId(app.id) && - app.additionalSettings['trackOnly'] != 'true') { + app.additionalSettings['trackOnly'] != true) { // ignore: use_build_context_synchronously var apkUrl = await appsProvider.confirmApkUrl(app, context); if (apkUrl == null) { @@ -128,7 +128,7 @@ class _AddAppPageState extends State { if (appsProvider.apps.containsKey(app.id)) { throw ObtainiumError(tr('appAlreadyAdded')); } - if (app.additionalSettings['trackOnly'] == 'true') { + if (app.additionalSettings['trackOnly'] == true) { app.installedVersion = app.latestVersion; } await appsProvider.saveApps([app]); @@ -169,7 +169,7 @@ class _AddAppPageState extends State { child: GeneratedForm( items: [ [ - GeneratedFormItem('appSourceURL', + GeneratedFormTextField('appSourceURL', label: tr('appSourceURL'), additionalValidators: [ (value) { @@ -231,7 +231,8 @@ class _AddAppPageState extends State { child: GeneratedForm( items: [ [ - GeneratedFormItem('searchSomeSources', + GeneratedFormTextField( + 'searchSomeSources', label: tr('searchSomeSourcesLabel'), required: false), ] diff --git a/lib/pages/app.dart b/lib/pages/app.dart index 28a6277..ae6c070 100644 --- a/lib/pages/app.dart +++ b/lib/pages/app.dart @@ -42,7 +42,7 @@ class _AppPageState extends State { prevApp = app; getUpdate(app.app.id); } - var trackOnly = app?.app.additionalSettings['trackOnly'] == 'true'; + var trackOnly = app?.app.additionalSettings['trackOnly'] == true; return Scaffold( appBar: settingsProvider.showAppWebpage ? AppBar() : null, backgroundColor: Theme.of(context).colorScheme.surface, @@ -170,7 +170,7 @@ class _AppPageState extends State { children: [ TextButton( onPressed: () { - showDialog?>( + showDialog?>( context: context, builder: (BuildContext ctx) { return GeneratedFormModal( @@ -273,7 +273,7 @@ class _AppPageState extends State { onPressed: app?.downloadProgress != null ? null : () { - showDialog>( + showDialog?>( context: context, builder: (BuildContext ctx) { var items = source @@ -301,7 +301,7 @@ class _AppPageState extends State { values; if (source.enforceTrackOnly) { changedApp.additionalSettings[ - 'trackOnly'] = 'true'; + 'trackOnly'] = true; showError( tr('appsFromSourceAreTrackOnly'), context); @@ -327,7 +327,7 @@ class _AppPageState extends State { () async { if (app?.app.additionalSettings[ 'trackOnly'] != - 'true') { + true) { await settingsProvider .getInstallPermission(); } diff --git a/lib/pages/apps.dart b/lib/pages/apps.dart index d479b61..3113265 100644 --- a/lib/pages/apps.dart +++ b/lib/pages/apps.dart @@ -142,16 +142,14 @@ class AppsPageState extends State { List trackOnlyUpdateIdsAllOrSelected = []; existingUpdateIdsAllOrSelected = existingUpdateIdsAllOrSelected.where((id) { - if (appsProvider.apps[id]!.app.additionalSettings['trackOnly'] == - 'true') { + if (appsProvider.apps[id]!.app.additionalSettings['trackOnly'] == true) { trackOnlyUpdateIdsAllOrSelected.add(id); return false; } return true; }).toList(); newInstallIdsAllOrSelected = newInstallIdsAllOrSelected.where((id) { - if (appsProvider.apps[id]!.app.additionalSettings['trackOnly'] == - 'true') { + if (appsProvider.apps[id]!.app.additionalSettings['trackOnly'] == true) { trackOnlyUpdateIdsAllOrSelected.add(id); return false; } @@ -286,7 +284,7 @@ class AppsPageState extends State { SizedBox( width: 100, child: Text( - '${sortedApps[index].app.installedVersion ?? tr('notInstalled')}${sortedApps[index].app.additionalSettings['trackOnly'] == 'true' ? ' ${tr('estimateInBrackets')}' : ''}', + '${sortedApps[index].app.installedVersion ?? tr('notInstalled')}${sortedApps[index].app.additionalSettings['trackOnly'] == true ? ' ${tr('estimateInBrackets')}' : ''}', overflow: TextOverflow.fade, textAlign: TextAlign.end, )), @@ -310,7 +308,7 @@ class AppsPageState extends State { .areDownloadsRunning() ? Text(tr('pleaseWait')) : Text( - '${tr('updateAvailable')}${sortedApps[index].app.additionalSettings['trackOnly'] == 'true' ? ' ${tr('estimateInBracketsShort')}' : ''}', + '${tr('updateAvailable')}${sortedApps[index].app.additionalSettings['trackOnly'] == true ? ' ${tr('estimateInBracketsShort')}' : ''}', style: TextStyle( fontStyle: FontStyle.italic, @@ -366,7 +364,7 @@ class AppsPageState extends State { : IconButton( visualDensity: VisualDensity.compact, onPressed: () { - showDialog?>( + showDialog?>( context: context, builder: (BuildContext ctx) { return GeneratedFormModal( @@ -400,40 +398,33 @@ class AppsPageState extends State { HapticFeedback.heavyImpact(); List formItems = []; if (existingUpdateIdsAllOrSelected.isNotEmpty) { - formItems.add(GeneratedFormItem('updates', + formItems.add(GeneratedFormSwitch('updates', label: tr('updateX', args: [ plural('apps', existingUpdateIdsAllOrSelected.length) ]), - type: FormItemType.bool, - defaultValue: 'true')); + defaultValue: true)); } if (newInstallIdsAllOrSelected.isNotEmpty) { - formItems.add(GeneratedFormItem('installs', + formItems.add(GeneratedFormSwitch('installs', label: tr('installX', args: [ plural('apps', newInstallIdsAllOrSelected.length) ]), - type: FormItemType.bool, - defaultValue: - existingUpdateIdsAllOrSelected.isNotEmpty - ? 'true' - : '')); + defaultValue: existingUpdateIdsAllOrSelected + .isNotEmpty)); } if (trackOnlyUpdateIdsAllOrSelected.isNotEmpty) { - formItems.add(GeneratedFormItem('trackonlies', + formItems.add(GeneratedFormSwitch('trackonlies', label: tr('markXTrackOnlyAsUpdated', args: [ plural('apps', trackOnlyUpdateIdsAllOrSelected.length) ]), - type: FormItemType.bool, defaultValue: existingUpdateIdsAllOrSelected - .isNotEmpty || - newInstallIdsAllOrSelected.isNotEmpty - ? 'true' - : '')); + .isNotEmpty || + newInstallIdsAllOrSelected.isNotEmpty)); } - showDialog?>( + showDialog?>( context: context, builder: (BuildContext ctx) { var totalApps = existingUpdateIdsAllOrSelected @@ -453,11 +444,11 @@ class AppsPageState extends State { [formItems]); } bool shouldInstallUpdates = - values['updates'] == 'true'; + values['updates'] == true; bool shouldInstallNew = - values['installs'] == 'true'; + values['installs'] == true; bool shouldMarkTrackOnlies = - values['trackonlies'] == 'true'; + values['trackonlies'] == true; (() async { if (shouldInstallNew || shouldInstallUpdates) { @@ -699,7 +690,7 @@ class AppsPageState extends State { : FontWeight.bold), ), onPressed: () { - showDialog?>( + showDialog?>( context: context, builder: (BuildContext ctx) { var vals = filter == null @@ -709,25 +700,23 @@ class AppsPageState extends State { title: tr('filterApps'), items: [ [ - GeneratedFormItem('appName', + GeneratedFormTextField('appName', label: tr('appName'), required: false, defaultValue: vals['appName']), - GeneratedFormItem('author', + GeneratedFormTextField('author', label: tr('author'), required: false, defaultValue: vals['author']) ], [ - GeneratedFormItem('upToDateApps', + GeneratedFormSwitch('upToDateApps', label: tr('upToDateApps'), - type: FormItemType.bool, defaultValue: vals['upToDateApps']) ], [ - GeneratedFormItem('nonInstalledApps', + GeneratedFormSwitch('nonInstalledApps', label: tr('nonInstalledApps'), - type: FormItemType.bool, defaultValue: vals['nonInstalledApps']) ], [ @@ -768,21 +757,21 @@ class AppsFilter { this.includeNonInstalled = true, this.categoryFilter = ''}); - Map toValuesMap() { + Map toValuesMap() { return { 'appName': nameFilter, 'author': authorFilter, - 'upToDateApps': includeUptodate ? 'true' : '', - 'nonInstalledApps': includeNonInstalled ? 'true' : '', + 'upToDateApps': includeUptodate, + 'nonInstalledApps': includeNonInstalled, 'category': categoryFilter }; } - AppsFilter.fromValuesMap(Map values) { + AppsFilter.fromValuesMap(Map values) { nameFilter = values['appName']!; authorFilter = values['author']!; - includeUptodate = values['upToDateApps'] == 'true'; - includeNonInstalled = values['nonInstalledApps'] == 'true'; + includeUptodate = values['upToDateApps']; + includeNonInstalled = values['nonInstalledApps']; categoryFilter = values['category']!; } diff --git a/lib/pages/import_export.dart b/lib/pages/import_export.dart index db69b7f..ec4abda 100644 --- a/lib/pages/import_export.dart +++ b/lib/pages/import_export.dart @@ -138,18 +138,19 @@ class _ImportExportPageState extends State { onPressed: importInProgress ? null : () { - showDialog( + showDialog?>( context: context, builder: (BuildContext ctx) { return GeneratedFormModal( title: tr('importFromURLList'), items: [ [ - GeneratedFormItem('appURLList', + GeneratedFormTextField( + 'appURLList', label: tr('appURLList'), max: 7, additionalValidators: [ - (String? value) { + (dynamic value) { if (value != null && value.isNotEmpty) { var lines = value @@ -176,7 +177,8 @@ class _ImportExportPageState extends State { }).then((values) { if (values != null) { var urls = - (values[0] as String).split('\n'); + (values['appURLList'] as String) + .split('\n'); setState(() { importInProgress = true; }); @@ -224,7 +226,8 @@ class _ImportExportPageState extends State { : () { () async { var values = await showDialog< - List>( + Map?>( context: context, builder: (BuildContext ctx) { @@ -235,7 +238,7 @@ class _ImportExportPageState extends State { ]), items: [ [ - GeneratedFormItem( + GeneratedFormTextField( 'searchQuery', label: tr( 'searchQuery')) @@ -244,13 +247,17 @@ class _ImportExportPageState extends State { ); }); if (values != null && - values[0].isNotEmpty) { + (values['searchQuery'] + as String?) + ?.isNotEmpty == + true) { setState(() { importInProgress = true; }); var urlsWithDescriptions = - await source - .search(values[0]); + await source.search( + values['searchQuery'] + as String); if (urlsWithDescriptions .isNotEmpty) { var selectedUrls = @@ -345,7 +352,7 @@ class _ImportExportPageState extends State { .requiredArgs .map( (e) => [ - GeneratedFormItem(e, + GeneratedFormTextField(e, label: e) ]) .toList(), diff --git a/lib/pages/settings.dart b/lib/pages/settings.dart index 0c20d9a..bc0e70e 100644 --- a/lib/pages/settings.dart +++ b/lib/pages/settings.dart @@ -158,9 +158,10 @@ class _SettingsPageState extends State { var sourceSpecificFields = sourceProvider.sources.map((e) { if (e.additionalSourceSpecificSettingFormItems.isNotEmpty) { return GeneratedForm( - items: e.additionalSourceSpecificSettingFormItems - .map((e) => [e]) - .toList(), + items: e.additionalSourceSpecificSettingFormItems.map((e) { + e.defaultValue = settingsProvider.getSettingString(e.key); + return [e]; + }).toList(), onValueChanges: (values, valid, isBuilding) { if (valid) { values.forEach((key, value) { @@ -274,7 +275,7 @@ class _SettingsPageState extends State { backgroundColor: Color(e.value), visualDensity: VisualDensity.compact, onDeleted: () { - showDialog?>( + showDialog?>( context: context, builder: (BuildContext ctx) { return GeneratedFormModal( @@ -311,14 +312,15 @@ class _SettingsPageState extends State { horizontal: 4), child: IconButton( onPressed: () { - showDialog?>( + showDialog?>( context: context, builder: (BuildContext ctx) { return GeneratedFormModal( title: tr('addCategory'), items: [ [ - GeneratedFormItem('label', + GeneratedFormTextField( + 'label', label: tr('label')) ] ]); diff --git a/lib/providers/apps_provider.dart b/lib/providers/apps_provider.dart index 570a3dd..67ff3f3 100644 --- a/lib/providers/apps_provider.dart +++ b/lib/providers/apps_provider.dart @@ -313,7 +313,7 @@ class AppsProvider with ChangeNotifier { throw ObtainiumError(tr('appNotFound')); } String? apkUrl; - var trackOnly = apps[id]!.app.additionalSettings['trackOnly'] == 'true'; + var trackOnly = apps[id]!.app.additionalSettings['trackOnly'] == true; if (!trackOnly) { apkUrl = await confirmApkUrl(apps[id]!.app, context); } @@ -452,9 +452,9 @@ class AppsProvider with ChangeNotifier { // Don't save changes, just return the object if changes were made (else null) App? getCorrectedInstallStatusAppIfPossible(App app, AppInfo? installedInfo) { var modded = false; - var trackOnly = app.additionalSettings['trackOnly'] == 'true'; + var trackOnly = app.additionalSettings['trackOnly'] == true; var noVersionDetection = - app.additionalSettings['noVersionDetection'] == 'true'; + app.additionalSettings['noVersionDetection'] == true; if (installedInfo == null && app.installedVersion != null && !trackOnly) { app.installedVersion = null; modded = true; diff --git a/lib/providers/settings_provider.dart b/lib/providers/settings_provider.dart index c9781ef..9e887b0 100644 --- a/lib/providers/settings_provider.dart +++ b/lib/providers/settings_provider.dart @@ -155,12 +155,12 @@ class SettingsProvider with ChangeNotifier { prefs?.setString('categories', jsonEncode(cats)); } - getCategoryFormItem({String initCategory = ''}) => - GeneratedFormItem('category', - label: tr('category'), - opts: [ - MapEntry('', tr('noCategory')), - ...categories.entries.map((e) => MapEntry(e.key, e.key)).toList() - ], - defaultValue: initCategory); + getCategoryFormItem({String initCategory = ''}) => GeneratedFormDropdown( + 'category', + label: tr('category'), + [ + MapEntry('', tr('noCategory')), + ...categories.entries.map((e) => MapEntry(e.key, e.key)).toList() + ], + defaultValue: initCategory); } diff --git a/lib/providers/source_provider.dart b/lib/providers/source_provider.dart index 7d2fc4a..83fd05b 100644 --- a/lib/providers/source_provider.dart +++ b/lib/providers/source_provider.dart @@ -45,7 +45,7 @@ class App { late String latestVersion; List apkUrls = []; late int preferredApkIndex; - late Map additionalSettings; + late Map additionalSettings; late DateTime? lastUpdateCheck; bool pinned = false; String? category; @@ -72,24 +72,36 @@ class App { var source = SourceProvider().getSource(json['url']); var formItems = source.combinedAppSpecificSettingFormItems .reduce((value, element) => [...value, ...element]); - Map additionalSettings = + Map additionalSettings = getDefaultValuesFromFormItems([formItems]); if (json['additionalSettings'] != null) { additionalSettings.addEntries( - Map.from(jsonDecode(json['additionalSettings'])) + Map.from(jsonDecode(json['additionalSettings'])) .entries); } - // If needed, migrate old-style additionalData to new-style additionalSettings + // If needed, migrate old-style additionalData to newer-style additionalSettings (V1) if (json['additionalData'] != null) { List temp = List.from(jsonDecode(json['additionalData'])); temp.asMap().forEach((i, value) { if (i < formItems.length) { - additionalSettings[formItems[i].key] = value; + if (formItems[i] is GeneratedFormSwitch) { + additionalSettings[formItems[i].key] = value == 'true'; + } else { + additionalSettings[formItems[i].key] = value; + } } }); - additionalSettings['trackOnly'] = (json['trackOnly'] ?? false).toString(); + additionalSettings['trackOnly'] = + json['trackOnly'] == 'true' || json['trackOnly'] == true; additionalSettings['noVersionDetection'] = - (json['noVersionDetection'] ?? false).toString(); + json['noVersionDetection'] == 'true' || json['trackOnly'] == true; + } + // Ensure additionalSettings are correctly typed + for (var item in formItems) { + if (additionalSettings[item.key] != null) { + additionalSettings[item.key] = + item.ensureType(additionalSettings[item.key]); + } } return App( json['id'] as String, @@ -160,7 +172,7 @@ List getLinksFromParsedHTML( .map((e) => '$prependToLinks${e.attributes['href']!}') .toList(); -Map getDefaultValuesFromFormItems( +Map getDefaultValuesFromFormItems( List> items) { return Map.fromEntries(items .map((row) => row.map((el) => MapEntry(el.key, el.defaultValue ?? ''))) @@ -181,7 +193,7 @@ class AppSource { } Future getLatestAPKDetails( - String standardUrl, Map additionalSettings) { + String standardUrl, Map additionalSettings) { throw NotImplementedError(); } @@ -193,16 +205,12 @@ class AppSource { final List> additionalAppSpecificSourceAgnosticSettingFormItems = [ [ - GeneratedFormItem( + GeneratedFormSwitch( 'trackOnly', label: tr('trackOnly'), - type: FormItemType.bool, ) ], - [ - GeneratedFormItem('noVersionDetection', - label: tr('noVersionDetection'), type: FormItemType.bool) - ] + [GeneratedFormSwitch('noVersionDetection', label: tr('noVersionDetection'))] ]; // Previous 2 variables combined into one at runtime for convenient usage @@ -230,7 +238,7 @@ class AppSource { } String? tryInferringAppId(String standardUrl, - {Map additionalSettings = const {}}) { + {Map additionalSettings = const {}}) { return null; } } @@ -293,7 +301,7 @@ class SourceProvider { bool ifRequiredAppSpecificSettingsExist(AppSource source) { for (var row in source.combinedAppSpecificSettingFormItems) { for (var element in row) { - if (element.required && element.opts == null) { + if (element is GeneratedFormTextField && element.required) { return true; } } @@ -319,17 +327,17 @@ class SourceProvider { } Future getApp( - AppSource source, String url, Map additionalSettings, + AppSource source, String url, Map additionalSettings, {App? currentApp, bool trackOnlyOverride = false, noVersionDetectionOverride = false}) async { if (trackOnlyOverride) { - additionalSettings['trackOnly'] = 'true'; + additionalSettings['trackOnly'] = true; } if (noVersionDetectionOverride) { - additionalSettings['noVersionDetection'] = 'true'; + additionalSettings['noVersionDetection'] = true; } - var trackOnly = currentApp?.additionalSettings['trackOnly'] == 'true'; + var trackOnly = currentApp?.additionalSettings['trackOnly'] == true; String standardUrl = source.standardizeURL(preStandardizeUrl(url)); APKDetails apk = await source.getLatestAPKDetails(standardUrl, additionalSettings); From 0c2d6ce84d2148cd4606f9c684f6a428405fafb9 Mon Sep 17 00:00:00 2001 From: Imran Remtulla Date: Wed, 21 Dec 2022 18:23:55 -0500 Subject: [PATCH 2/2] Increment version --- lib/main.dart | 2 +- pubspec.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/main.dart b/lib/main.dart index 806f54f..6614be9 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -21,7 +21,7 @@ import 'package:easy_localization/src/easy_localization_controller.dart'; // ignore: implementation_imports import 'package:easy_localization/src/localization.dart'; -const String currentVersion = '0.9.1'; +const String currentVersion = '0.9.2'; const String currentReleaseTag = 'v$currentVersion-beta'; // KEEP THIS IN SYNC WITH GITHUB RELEASES diff --git a/pubspec.yaml b/pubspec.yaml index 70b5df1..4e85d02 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -17,7 +17,7 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html # In Windows, build-name is used as the major, minor, and patch parts # of the product and file versions while build-number is used as the build suffix. -version: 0.9.1+89 # When changing this, update the tag in main() accordingly +version: 0.9.2+90 # When changing this, update the tag in main() accordingly environment: sdk: '>=2.18.2 <3.0.0'