feat: Add ability to set null in patch options (#1947)

This commit is contained in:
aAbed 2024-06-24 22:22:16 +05:45 committed by GitHub
parent fc52560244
commit 5c68d513a3
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 155 additions and 278 deletions

View file

@ -118,16 +118,18 @@
}, },
"patchOptionsView": { "patchOptionsView": {
"customValue": "Custom value", "customValue": "Custom value",
"setToNull": "Set to null",
"nullValue": "This option value is currently null",
"resetOptionsTooltip": "Reset patch options", "resetOptionsTooltip": "Reset patch options",
"viewTitle": "Patch options", "viewTitle": "Patch options",
"saveOptions": "Save", "saveOptions": "Save",
"addOptions": "Add options", "addOptions": "Add options",
"deselectPatch": "Deselect patch", "unselectPatch": "Unselect patch",
"tooltip": "More input options", "tooltip": "More input options",
"selectFilePath": "Select file path", "selectFilePath": "Select file path",
"selectFolder": "Select folder", "selectFolder": "Select folder",
"selectOption": "Select option", "selectOption": "Select option",
"requiredOption": "This option is required", "requiredOption": "Setting this option is required",
"unsupportedOption": "This option is not supported", "unsupportedOption": "This option is not supported",
"requiredOptionNull": "The following options have to be set:\n\n${options}" "requiredOptionNull": "The following options have to be set:\n\n${options}"
}, },

View file

@ -31,7 +31,6 @@ class ManagerAPI {
final String cliRepo = 'revanced-cli'; final String cliRepo = 'revanced-cli';
late SharedPreferences _prefs; late SharedPreferences _prefs;
List<Patch> patches = []; List<Patch> patches = [];
List<Option> modifiedOptions = [];
List<Option> options = []; List<Option> options = [];
Patch? selectedPatch; Patch? selectedPatch;
BuildContext? ctx; BuildContext? ctx;

View file

@ -37,79 +37,35 @@ class PatchOptionsView extends StatelessWidget {
color: Theme.of(context).textTheme.titleLarge!.color, color: Theme.of(context).textTheme.titleLarge!.color,
), ),
), ),
actions: [
IconButton(
onPressed: () {
model.resetOptions();
},
icon: const Icon(
Icons.history,
),
tooltip: t.patchOptionsView.resetOptionsTooltip,
),
],
), ),
SliverToBoxAdapter( SliverToBoxAdapter(
child: Padding( child: Padding(
padding: const EdgeInsets.all(8.0), padding: const EdgeInsets.all(8.0),
child: Column( child: Column(
children: [ children: [
for (final Option option in model.visibleOptions) for (final Option option in model.modifiedOptions)
if (option.valueType == 'String' || if (option.valueType == 'String' ||
option.valueType == 'Int') option.valueType == 'Int')
IntAndStringPatchOption( IntAndStringPatchOption(
patchOption: option, patchOption: option,
removeOption: (option) { model: model,
model.removeOption(option);
},
onChanged: (value, option) {
model.modifyOptions(value, option);
},
) )
else if (option.valueType == 'Boolean') else if (option.valueType == 'Boolean')
BooleanPatchOption( BooleanPatchOption(
patchOption: option, patchOption: option,
removeOption: (option) { model: model,
model.removeOption(option);
},
onChanged: (value, option) {
model.modifyOptions(value, option);
},
) )
else if (option.valueType == 'StringArray' || else if (option.valueType == 'StringArray' ||
option.valueType == 'IntArray' || option.valueType == 'IntArray' ||
option.valueType == 'LongArray') option.valueType == 'LongArray')
IntStringLongListPatchOption( IntStringLongListPatchOption(
patchOption: option, patchOption: option,
removeOption: (option) { model: model,
model.removeOption(option);
},
onChanged: (value, option) {
model.modifyOptions(value, option);
},
) )
else else
UnsupportedPatchOption( UnsupportedPatchOption(
patchOption: option, patchOption: option,
), ),
if (model.visibleOptions.length !=
model.options.length) ...[
const SizedBox(
height: 8,
),
FilledButton(
onPressed: () {
model.showAddOptionDialog(context);
},
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
const Icon(Icons.add),
Text(t.patchOptionsView.addOptions),
],
),
),
],
const SizedBox( const SizedBox(
height: 80, height: 80,
), ),

View file

@ -5,7 +5,6 @@ import 'package:revanced_manager/models/patch.dart';
import 'package:revanced_manager/services/manager_api.dart'; import 'package:revanced_manager/services/manager_api.dart';
import 'package:revanced_manager/ui/views/patcher/patcher_viewmodel.dart'; import 'package:revanced_manager/ui/views/patcher/patcher_viewmodel.dart';
import 'package:revanced_manager/ui/views/patches_selector/patches_selector_viewmodel.dart'; import 'package:revanced_manager/ui/views/patches_selector/patches_selector_viewmodel.dart';
import 'package:revanced_manager/ui/widgets/shared/custom_card.dart';
import 'package:stacked/stacked.dart'; import 'package:stacked/stacked.dart';
class PatchOptionsViewModel extends BaseViewModel { class PatchOptionsViewModel extends BaseViewModel {
@ -14,7 +13,7 @@ class PatchOptionsViewModel extends BaseViewModel {
locator<PatcherViewModel>().selectedApp!.packageName; locator<PatcherViewModel>().selectedApp!.packageName;
List<Option> options = []; List<Option> options = [];
List<Option> savedOptions = []; List<Option> savedOptions = [];
List<Option> visibleOptions = []; List<Option> modifiedOptions = [];
Future<void> initialize() async { Future<void> initialize() async {
options = getDefaultOptions(); options = getDefaultOptions();
@ -28,36 +27,18 @@ class PatchOptionsViewModel extends BaseViewModel {
savedOptions.add(savedOption); savedOptions.add(savedOption);
} }
} }
if (savedOptions.isNotEmpty) { modifiedOptions = [
visibleOptions = [ ...savedOptions,
...savedOptions, ...options.where(
...options.where( (option) => !savedOptions.any((sOption) => sOption.key == option.key),
(option) => ),
option.required && ];
!savedOptions.any((sOption) => sOption.key == option.key),
),
];
} else {
visibleOptions = [
...options.where((option) => option.required),
];
}
}
void addOption(Option option) {
visibleOptions.add(option);
notifyListeners();
}
void removeOption(Option option) {
visibleOptions.removeWhere((vOption) => vOption.key == option.key);
notifyListeners();
} }
bool saveOptions(BuildContext context) { bool saveOptions(BuildContext context) {
final List<Option> requiredNullOptions = []; final List<Option> requiredNullOptions = [];
for (final Option option in options) { for (final Option option in options) {
if (!visibleOptions.any((vOption) => vOption.key == option.key)) { if (modifiedOptions.any((mOption) => mOption.key == option.key)) {
_managerAPI.clearPatchOption( _managerAPI.clearPatchOption(
selectedApp, selectedApp,
_managerAPI.selectedPatch!.name, _managerAPI.selectedPatch!.name,
@ -65,7 +46,7 @@ class PatchOptionsViewModel extends BaseViewModel {
); );
} }
} }
for (final Option option in visibleOptions) { for (final Option option in modifiedOptions) {
if (option.required && option.value == null) { if (option.required && option.value == null) {
requiredNullOptions.add(option); requiredNullOptions.add(option);
} else { } else {
@ -98,11 +79,8 @@ class PatchOptionsViewModel extends BaseViewModel {
required: option.required, required: option.required,
key: option.key, key: option.key,
); );
visibleOptions[visibleOptions modifiedOptions.removeWhere((mOption) => mOption.key == option.key);
.indexWhere((vOption) => vOption.key == option.key)] = modifiedOption; modifiedOptions.add(modifiedOption);
_managerAPI.modifiedOptions
.removeWhere((mOption) => mOption.key == option.key);
_managerAPI.modifiedOptions.add(modifiedOption);
} }
List<Option> getDefaultOptions() { List<Option> getDefaultOptions() {
@ -122,93 +100,11 @@ class PatchOptionsViewModel extends BaseViewModel {
return defaultOptions; return defaultOptions;
} }
void resetOptions() { dynamic getDefaultValue(Option patchOption) => _managerAPI.options
_managerAPI.modifiedOptions.clear(); .firstWhere(
visibleOptions = (option) => option.key == patchOption.key,
getDefaultOptions().where((option) => option.required).toList(); )
notifyListeners(); .value;
}
Future<void> showAddOptionDialog(BuildContext context) async {
await showDialog(
context: context,
builder: (context) => AlertDialog(
title: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: [
Text(
t.patchOptionsView.addOptions,
),
Text(
'',
style: TextStyle(
fontSize: 16,
color: Theme.of(context).colorScheme.onSecondaryContainer,
),
),
],
),
actions: [
FilledButton(
onPressed: () {
Navigator.of(context).pop();
},
child: Text(t.cancelButton),
),
],
contentPadding: const EdgeInsets.all(8),
content: Wrap(
spacing: 14,
runSpacing: 14,
children: options
.where(
(option) =>
!visibleOptions.any((vOption) => vOption.key == option.key),
)
.map((e) {
return CustomCard(
padding: const EdgeInsets.all(4),
backgroundColor: Theme.of(context).colorScheme.surface,
onTap: () {
addOption(e);
Navigator.pop(context);
},
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Row(
children: [
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
e.title,
style: TextStyle(
fontSize: 16,
color: Theme.of(context).colorScheme.onSurface,
),
),
const SizedBox(height: 4),
Text(
e.description,
style: TextStyle(
fontSize: 14,
color: Theme.of(context).colorScheme.onSurface,
),
),
],
),
),
],
),
),
);
}).toList(),
),
),
);
}
} }
Future<void> showRequiredOptionNullDialog( Future<void> showRequiredOptionNullDialog(
@ -248,7 +144,7 @@ Future<void> showRequiredOptionNullDialog(
PatchesSelectorViewModel().showPatchesChangeDialog(context); PatchesSelectorViewModel().showPatchesChangeDialog(context);
} }
}, },
child: Text(t.patchOptionsView.deselectPatch), child: Text(t.patchOptionsView.unselectPatch),
), ),
FilledButton( FilledButton(
onPressed: () { onPressed: () {

View file

@ -61,7 +61,6 @@ class PatchesSelectorViewModel extends BaseViewModel {
void navigateToPatchOptions(List<Option> setOptions, Patch patch) { void navigateToPatchOptions(List<Option> setOptions, Patch patch) {
_managerAPI.options = setOptions; _managerAPI.options = setOptions;
_managerAPI.selectedPatch = patch; _managerAPI.selectedPatch = patch;
_managerAPI.modifiedOptions.clear();
_navigationService.navigateToPatchOptionsView(); _navigationService.navigateToPatchOptionsView();
} }

View file

@ -3,19 +3,18 @@ import 'package:flutter/services.dart';
import 'package:flutter_file_dialog/flutter_file_dialog.dart'; import 'package:flutter_file_dialog/flutter_file_dialog.dart';
import 'package:revanced_manager/gen/strings.g.dart'; import 'package:revanced_manager/gen/strings.g.dart';
import 'package:revanced_manager/models/patch.dart'; import 'package:revanced_manager/models/patch.dart';
import 'package:revanced_manager/ui/views/patch_options/patch_options_viewmodel.dart';
import 'package:revanced_manager/ui/widgets/shared/custom_card.dart'; import 'package:revanced_manager/ui/widgets/shared/custom_card.dart';
class BooleanPatchOption extends StatelessWidget { class BooleanPatchOption extends StatelessWidget {
const BooleanPatchOption({ const BooleanPatchOption({
super.key, super.key,
required this.patchOption, required this.patchOption,
required this.removeOption, required this.model,
required this.onChanged,
}); });
final Option patchOption; final Option patchOption;
final void Function(Option option) removeOption; final PatchOptionsViewModel model;
final void Function(dynamic value, Option option) onChanged;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
@ -30,88 +29,94 @@ class BooleanPatchOption extends StatelessWidget {
value: value ?? false, value: value ?? false,
onChanged: (bool value) { onChanged: (bool value) {
patchOptionValue.value = value; patchOptionValue.value = value;
onChanged(value, patchOption); model.modifyOptions(value, patchOption);
}, },
); );
}, },
), ),
), ),
patchOption: patchOption, patchOption: patchOption,
removeOption: (Option option) { patchOptionValue: patchOptionValue,
removeOption(option); model: model,
},
); );
} }
} }
class IntAndStringPatchOption extends StatelessWidget { class IntAndStringPatchOption extends StatefulWidget {
const IntAndStringPatchOption({ const IntAndStringPatchOption({
super.key, super.key,
required this.patchOption, required this.patchOption,
required this.removeOption, required this.model,
required this.onChanged,
}); });
final Option patchOption; final Option patchOption;
final void Function(Option option) removeOption; final PatchOptionsViewModel model;
final void Function(dynamic value, Option option) onChanged;
@override
State<IntAndStringPatchOption> createState() =>
_IntAndStringPatchOptionState();
}
class _IntAndStringPatchOptionState extends State<IntAndStringPatchOption> {
ValueNotifier? patchOptionValue;
String getKey() {
if (patchOptionValue!.value != null && widget.patchOption.values != null) {
final List values = widget.patchOption.values!.entries
.where((e) => e.value == patchOptionValue!.value)
.toList();
if (values.isNotEmpty) {
return values.first.key;
}
}
return '';
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final ValueNotifier patchOptionValue = ValueNotifier(patchOption.value); patchOptionValue ??= ValueNotifier(widget.patchOption.value);
String getKey() {
if (patchOption.value != null && patchOption.values != null) {
final List values = patchOption.values!.entries
.where((e) => e.value == patchOption.value)
.toList();
if (values.isNotEmpty) {
return values.first.key;
}
}
return '';
}
return PatchOption( return PatchOption(
widget: Column( widget: ValueListenableBuilder(
crossAxisAlignment: CrossAxisAlignment.start, valueListenable: patchOptionValue!,
children: [ builder: (context, value, child) {
TextFieldForPatchOption( return Column(
value: patchOption.value, crossAxisAlignment: CrossAxisAlignment.start,
values: patchOption.values, children: [
optionType: patchOption.valueType, TextFieldForPatchOption(
selectedKey: getKey(), value: value,
onChanged: (value) { patchOption: widget.patchOption,
patchOptionValue.value = value; selectedKey: getKey(),
onChanged(value, patchOption); onChanged: (value) {
}, patchOptionValue!.value = value;
), widget.model.modifyOptions(value, widget.patchOption);
ValueListenableBuilder( },
valueListenable: patchOptionValue, ),
builder: (context, value, child) { if (value == null)
if (patchOption.required && value == null) { Column(
return Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
const SizedBox(height: 8), const SizedBox(height: 8),
Text( Text(
t.patchOptionsView.requiredOption, widget.patchOption.required
? t.patchOptionsView.requiredOption
: t.patchOptionsView.nullValue,
style: TextStyle( style: TextStyle(
color: Theme.of(context).colorScheme.error, color: widget.patchOption.required
? Theme.of(context).colorScheme.error
: Theme.of(context)
.colorScheme
.onSecondaryContainer
.withOpacity(0.6),
), ),
), ),
], ],
); ),
} else { ],
return const SizedBox(); );
} },
},
),
],
), ),
patchOption: patchOption, patchOption: widget.patchOption,
removeOption: (Option option) { patchOptionValue: patchOptionValue!,
removeOption(option); model: widget.model,
},
); );
} }
} }
@ -120,13 +125,11 @@ class IntStringLongListPatchOption extends StatelessWidget {
const IntStringLongListPatchOption({ const IntStringLongListPatchOption({
super.key, super.key,
required this.patchOption, required this.patchOption,
required this.removeOption, required this.model,
required this.onChanged,
}); });
final Option patchOption; final Option patchOption;
final void Function(Option option) removeOption; final PatchOptionsViewModel model;
final void Function(dynamic value, Option option) onChanged;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
@ -172,8 +175,7 @@ class IntStringLongListPatchOption extends StatelessWidget {
final e = values[index]; final e = values[index];
return TextFieldForPatchOption( return TextFieldForPatchOption(
value: e.toString(), value: e.toString(),
values: patchOption.values, patchOption: patchOption,
optionType: type,
selectedKey: value.length > 1 ? '' : getKey(e), selectedKey: value.length > 1 ? '' : getKey(e),
showDropdown: index == 0, showDropdown: index == 0,
onChanged: (newValue) { onChanged: (newValue) {
@ -205,13 +207,13 @@ class IntStringLongListPatchOption extends StatelessWidget {
} }
} }
patchOptionValue.value = List.from(values); patchOptionValue.value = List.from(values);
onChanged(values, patchOption); model.modifyOptions(values, patchOption);
}, },
removeValue: () { removeValue: () {
patchOptionValue.value = List.from(patchOptionValue.value) patchOptionValue.value = List.from(patchOptionValue.value)
..removeAt(index); ..removeAt(index);
values.removeAt(index); values.removeAt(index);
onChanged(values, patchOption); model.modifyOptions(values, patchOption);
}, },
); );
}, },
@ -231,7 +233,7 @@ class IntStringLongListPatchOption extends StatelessWidget {
List.from(patchOptionValue.value)..add(0); List.from(patchOptionValue.value)..add(0);
values.add(0); values.add(0);
} }
onChanged(values, patchOption); model.modifyOptions(values, patchOption);
}, },
child: Row( child: Row(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
@ -254,9 +256,8 @@ class IntStringLongListPatchOption extends StatelessWidget {
}, },
), ),
patchOption: patchOption, patchOption: patchOption,
removeOption: (Option option) { patchOptionValue: patchOptionValue,
removeOption(option); model: model,
},
); );
} }
} }
@ -282,7 +283,8 @@ class UnsupportedPatchOption extends StatelessWidget {
), ),
), ),
patchOption: patchOption, patchOption: patchOption,
removeOption: (_) {}, patchOptionValue: ValueNotifier(null),
model: PatchOptionsViewModel(),
); );
} }
} }
@ -292,15 +294,18 @@ class PatchOption extends StatelessWidget {
super.key, super.key,
required this.widget, required this.widget,
required this.patchOption, required this.patchOption,
required this.removeOption, required this.patchOptionValue,
required this.model,
}); });
final Widget widget; final Widget widget;
final Option patchOption; final Option patchOption;
final void Function(Option option) removeOption; final ValueNotifier patchOptionValue;
final PatchOptionsViewModel model;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final defaultValue = model.getDefaultValue(patchOption);
return Padding( return Padding(
padding: const EdgeInsets.all(8.0), padding: const EdgeInsets.all(8.0),
child: CustomCard( child: CustomCard(
@ -337,11 +342,24 @@ class PatchOption extends StatelessWidget {
], ],
), ),
), ),
if (!patchOption.required) ValueListenableBuilder(
IconButton( valueListenable: patchOptionValue,
onPressed: () => removeOption(patchOption), builder: (context, value, child) {
icon: const Icon(Icons.delete), if (defaultValue != patchOptionValue.value) {
), return IconButton(
onPressed: () {
patchOptionValue.value = defaultValue;
model.modifyOptions(
defaultValue,
patchOption,
);
},
icon: const Icon(Icons.history),
);
}
return const SizedBox();
},
),
], ],
), ),
const SizedBox(height: 4), const SizedBox(height: 4),
@ -360,17 +378,15 @@ class TextFieldForPatchOption extends StatefulWidget {
const TextFieldForPatchOption({ const TextFieldForPatchOption({
super.key, super.key,
required this.value, required this.value,
required this.values, required this.patchOption,
this.removeValue, this.removeValue,
required this.onChanged, required this.onChanged,
required this.optionType,
required this.selectedKey, required this.selectedKey,
this.showDropdown = true, this.showDropdown = true,
}); });
final String? value; final String? value;
final Map<String, dynamic>? values; final Option patchOption;
final String optionType;
final String selectedKey; final String selectedKey;
final bool showDropdown; final bool showDropdown;
final void Function()? removeValue; final void Function()? removeValue;
@ -388,20 +404,19 @@ class _TextFieldForPatchOptionState extends State<TextFieldForPatchOption> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final bool isStringOption = widget.optionType.contains('String'); final bool isStringOption = widget.patchOption.valueType.contains('String');
final bool isArrayOption = widget.optionType.contains('Array'); final bool isArrayOption = widget.patchOption.valueType.contains('Array');
selectedKey ??= widget.selectedKey; selectedKey = selectedKey == '' ? selectedKey : widget.selectedKey;
controller.text = !isStringOption && final bool isValueArray = widget.value?.startsWith('[') ?? false;
isArrayOption && final bool shouldResetValue =
selectedKey == '' && !isStringOption && isArrayOption && selectedKey == '' && isValueArray;
(widget.value != null && widget.value.toString().startsWith('[')) controller.text = shouldResetValue ? '' : widget.value ?? '';
? ''
: widget.value ?? '';
defaultValue ??= controller.text; defaultValue ??= controller.text;
return Column( return Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
if (widget.showDropdown && (widget.values?.isNotEmpty ?? false)) if (widget.showDropdown &&
(widget.patchOption.values?.isNotEmpty ?? false))
DropdownButton<String>( DropdownButton<String>(
style: const TextStyle( style: const TextStyle(
fontSize: 16, fontSize: 16,
@ -410,11 +425,12 @@ class _TextFieldForPatchOptionState extends State<TextFieldForPatchOption> {
dropdownColor: Theme.of(context).colorScheme.secondaryContainer, dropdownColor: Theme.of(context).colorScheme.secondaryContainer,
isExpanded: true, isExpanded: true,
value: selectedKey, value: selectedKey,
items: widget.values!.entries items: widget.patchOption.values!.entries
.map( .map(
(e) => DropdownMenuItem( (e) => DropdownMenuItem(
value: e.key, value: e.key,
child: RichText( child: RichText(
overflow: TextOverflow.ellipsis,
text: TextSpan( text: TextSpan(
text: e.key, text: e.key,
style: TextStyle( style: TextStyle(
@ -427,7 +443,7 @@ class _TextFieldForPatchOptionState extends State<TextFieldForPatchOption> {
TextSpan( TextSpan(
text: ' ${e.value}', text: ' ${e.value}',
style: TextStyle( style: TextStyle(
fontSize: 14, fontSize: 16,
color: Theme.of(context) color: Theme.of(context)
.colorScheme .colorScheme
.onSecondaryContainer .onSecondaryContainer
@ -447,9 +463,7 @@ class _TextFieldForPatchOptionState extends State<TextFieldForPatchOption> {
t.patchOptionsView.customValue, t.patchOptionsView.customValue,
style: TextStyle( style: TextStyle(
fontSize: 16, fontSize: 16,
color: Theme.of(context) color: Theme.of(context).colorScheme.onSecondaryContainer,
.colorScheme
.onSecondaryContainer,
), ),
), ),
), ),
@ -459,9 +473,11 @@ class _TextFieldForPatchOptionState extends State<TextFieldForPatchOption> {
controller.text = defaultValue!; controller.text = defaultValue!;
widget.onChanged(controller.text); widget.onChanged(controller.text);
} else { } else {
controller.text = widget.values![value].toString(); controller.text = widget.patchOption.values![value].toString();
widget.onChanged( widget.onChanged(
isArrayOption ? widget.values![value] : controller.text, isArrayOption
? widget.patchOption.values![value]
: controller.text,
); );
} }
setState(() { setState(() {
@ -472,9 +488,9 @@ class _TextFieldForPatchOptionState extends State<TextFieldForPatchOption> {
if (selectedKey == '') if (selectedKey == '')
TextFormField( TextFormField(
inputFormatters: [ inputFormatters: [
if (widget.optionType.contains('Int')) if (widget.patchOption.valueType.contains('Int'))
FilteringTextInputFormatter.allow(RegExp(r'[0-9]')), FilteringTextInputFormatter.allow(RegExp(r'[0-9]')),
if (widget.optionType.contains('Long')) if (widget.patchOption.valueType.contains('Long'))
FilteringTextInputFormatter.allow(RegExp(r'^[0-9]*\.?[0-9]*')), FilteringTextInputFormatter.allow(RegExp(r'^[0-9]*\.?[0-9]*')),
], ],
controller: controller, controller: controller,
@ -487,31 +503,36 @@ class _TextFieldForPatchOptionState extends State<TextFieldForPatchOption> {
return [ return [
if (isArrayOption) if (isArrayOption)
PopupMenuItem( PopupMenuItem(
value: t.remove, value: 'remove',
child: Text(t.remove), child: Text(t.remove),
), ),
if (isStringOption) ...[ if (isStringOption) ...[
PopupMenuItem( PopupMenuItem(
value: t.patchOptionsView.selectFilePath, value: 'file',
child: Text(t.patchOptionsView.selectFilePath), child: Text(t.patchOptionsView.selectFilePath),
), ),
PopupMenuItem( PopupMenuItem(
value: t.patchOptionsView.selectFolder, value: 'folder',
child: Text(t.patchOptionsView.selectFolder), child: Text(t.patchOptionsView.selectFolder),
), ),
], ],
if (!widget.patchOption.required)
PopupMenuItem(
value: 'null',
child: Text(t.patchOptionsView.setToNull),
),
]; ];
}, },
onSelected: (String selection) async { onSelected: (String selection) async {
switch (selection) { switch (selection) {
case 'patchOptionsView.selectFilePath': case 'file':
final String? result = await FlutterFileDialog.pickFile(); final String? result = await FlutterFileDialog.pickFile();
if (result != null) { if (result != null) {
controller.text = result; controller.text = result;
widget.onChanged(controller.text); widget.onChanged(controller.text);
} }
break; break;
case 'patchOptionsView.selectFolder': case 'folder':
final DirectoryLocation? result = final DirectoryLocation? result =
await FlutterFileDialog.pickDirectory(); await FlutterFileDialog.pickDirectory();
if (result != null) { if (result != null) {
@ -522,6 +543,10 @@ class _TextFieldForPatchOptionState extends State<TextFieldForPatchOption> {
case 'remove': case 'remove':
widget.removeValue!(); widget.removeValue!();
break; break;
case 'null':
controller.text = '';
widget.onChanged(null);
break;
} }
}, },
), ),