diff --git a/lib/api.dart b/lib/api.dart index 71f6a5da..3949d61d 100644 --- a/lib/api.dart +++ b/lib/api.dart @@ -128,7 +128,13 @@ class InvenTreeAPI { String get imageUrl => _makeUrl("/image/"); - String makeApiUrl(String endpoint) => _makeUrl("/api/" + endpoint); + String makeApiUrl(String endpoint) { + if (endpoint.startsWith("/api/") || endpoint.startsWith("api/")) { + return _makeUrl(endpoint); + } else { + return _makeUrl("/api/" + endpoint); + } + } String makeUrl(String endpoint) => _makeUrl(endpoint); diff --git a/lib/api_form.dart b/lib/api_form.dart index 786c5e02..3c49726e 100644 --- a/lib/api_form.dart +++ b/lib/api_form.dart @@ -1,3 +1,4 @@ +import 'package:flutter_typeahead/flutter_typeahead.dart'; import 'package:font_awesome_flutter/font_awesome_flutter.dart'; import 'package:inventree/api.dart'; import 'package:inventree/app_colors.dart'; @@ -26,6 +27,9 @@ class APIFormField { // JSON data which defines the field final dynamic data; + // Get the "api_url" associated with a related field + String get api_url => data["api_url"] ?? ""; + // Is this field hidden? bool get hidden => (data['hidden'] ?? false) as bool; @@ -53,6 +57,9 @@ class APIFormField { return messages; } + // TODO + dynamic get filters => null; + // Is this field required? bool get required => (data['required'] ?? false) as bool; @@ -72,13 +79,32 @@ class APIFormField { return _constructString(); case "boolean": return _constructBoolean(); + case "related field": + return _constructRelatedField(); default: return ListTile( - title: Text("Unsupported field type: '${type}'") + title: Text( + "Unsupported field type: '${type}'", + style: TextStyle( + color: COLOR_DANGER, + fontStyle: FontStyle.italic), + ) ); } } + // Construct an input for a related field + Widget _constructRelatedField() { + + return AutocompleteFormField( + required ? label + "*" : label, + api_url, + filters: filters, + hint: helpText, + renderer: null, + ); + } + // Consturct a string input element Widget _constructString() { diff --git a/lib/widget/fields.dart b/lib/widget/fields.dart index eb1db803..3db99218 100644 --- a/lib/widget/fields.dart +++ b/lib/widget/fields.dart @@ -1,6 +1,8 @@ import 'package:flutter/material.dart'; +import 'package:flutter_typeahead/flutter_typeahead.dart'; import 'package:image_picker/image_picker.dart'; +import 'package:inventree/api.dart'; import 'package:inventree/l10.dart'; @@ -115,6 +117,81 @@ class CheckBoxField extends FormField { } +class AutocompleteFormField extends TypeAheadFormField { + + final String label; + + final _controller = TextEditingController(); + + final String url; + + dynamic filters = {}; + + AutocompleteFormField( + this.label, + this.url, + { + this.filters, + Widget Function(dynamic)? renderer, + String? hint, + }) : + super( + textFieldConfiguration: TextFieldConfiguration( + autofocus: true, + decoration: InputDecoration( + hintText: hint, + border: OutlineInputBorder(), + ), + ), + suggestionsCallback: (String pattern) async { + + Map _filters = {}; + + if (filters != null) { + for (String key in filters) { + _filters[key] = filters[key].toString(); + } + } + + _filters["search"] = pattern; + _filters["offset"] = "0"; + _filters["limit"] = "25"; + + final APIResponse response = await InvenTreeAPI().get( + url, + params: _filters + ); + + if (response.isValid()) { + + List results = []; + + for (var result in response.data['results'] ?? []) { + results.add(result); + } + + return results; + } else { + return []; + } + + }, + itemBuilder: (context, suggestion) { + print("item builder: " + suggestion.toString()); + return ListTile( + title: Text(suggestion['name']), + ); + }, + onSuggestionSelected: (suggestion) { + // TODO + }, + onSaved: (value) { + // TODO + } + ); +} + + class StringField extends TextFormField { StringField({String label = "", String? hint, String? initial, Function(String?)? onSaved, Function? validator, bool allowEmpty = false, bool isEnabled = true}) : diff --git a/lib/widget/part_detail.dart b/lib/widget/part_detail.dart index 45ad8935..818dd2bb 100644 --- a/lib/widget/part_detail.dart +++ b/lib/widget/part_detail.dart @@ -187,6 +187,8 @@ class _PartDisplayState extends RefreshableState { "keywords": {}, "link": {}, + "category": {}, + // Checkbox fields "active": {}, "assembly": {},