mirror of
https://github.com/wanghongenpin/proxypin.git
synced 2026-06-03 17:25:48 +08:00
Add autocomplete suggestions for HTTP headers in request editor
This commit is contained in:
@@ -208,7 +208,7 @@ class RequestEditorState extends State<RequestEditor> {
|
||||
]))
|
||||
]),
|
||||
message: response,
|
||||
readOnly: widget.source == RequestEditorSource.breakpointRequest))
|
||||
readOnly: widget.source != RequestEditorSource.breakpointResponse))
|
||||
],
|
||||
);
|
||||
}),
|
||||
@@ -377,7 +377,7 @@ class _HttpState extends State<_HttpWidget> {
|
||||
}
|
||||
}
|
||||
|
||||
change(HttpMessage? message) {
|
||||
void change(HttpMessage? message) {
|
||||
this.message = message;
|
||||
body?.text = message?.bodyAsString ?? '';
|
||||
headerKey.currentState?.refreshParam(message?.headers.getHeaders());
|
||||
@@ -416,7 +416,8 @@ class _HttpState extends State<_HttpWidget> {
|
||||
KeyValWidget(
|
||||
key: headerKey,
|
||||
params: message?.headers.getHeaders() ?? initHeader,
|
||||
readOnly: widget.readOnly),
|
||||
readOnly: widget.readOnly,
|
||||
suggestions: HttpHeaders.commonHeaderKeys),
|
||||
_body()
|
||||
],
|
||||
)),
|
||||
@@ -521,6 +522,8 @@ class KeyVal {
|
||||
bool enabled = true;
|
||||
TextEditingController key;
|
||||
TextEditingController value;
|
||||
FocusNode? keyFocusNode;
|
||||
FocusNode? valueFocusNode;
|
||||
|
||||
KeyVal(this.key, this.value);
|
||||
}
|
||||
@@ -530,8 +533,9 @@ class KeyValWidget extends StatefulWidget {
|
||||
final Map<String, List<String>>? params;
|
||||
final bool readOnly; //只读
|
||||
final UrlQueryNotifier? paramNotifier;
|
||||
final List<String>? suggestions;
|
||||
|
||||
const KeyValWidget({super.key, this.params, this.readOnly = false, this.paramNotifier});
|
||||
const KeyValWidget({super.key, this.params, this.readOnly = false, this.paramNotifier, this.suggestions});
|
||||
|
||||
@override
|
||||
State<StatefulWidget> createState() => KeyValState();
|
||||
@@ -570,7 +574,7 @@ class KeyValState extends State<KeyValWidget> with AutomaticKeepAliveClientMixin
|
||||
}
|
||||
|
||||
//监听url发生变化 更改表单
|
||||
onChange(String value) {
|
||||
void onChange(String value) {
|
||||
var query = value.split("&");
|
||||
int index = 0;
|
||||
while (index < query.length) {
|
||||
@@ -591,7 +595,7 @@ class KeyValState extends State<KeyValWidget> with AutomaticKeepAliveClientMixin
|
||||
setState(() {});
|
||||
}
|
||||
|
||||
notifierChange() {
|
||||
void notifierChange() {
|
||||
if (widget.paramNotifier == null) return;
|
||||
String query = _params
|
||||
.where((e) => e.enabled && e.key.text.isNotEmpty)
|
||||
@@ -600,7 +604,7 @@ class KeyValState extends State<KeyValWidget> with AutomaticKeepAliveClientMixin
|
||||
widget.paramNotifier?.onParamChange(query);
|
||||
}
|
||||
|
||||
clear() {
|
||||
void clear() {
|
||||
for (var element in _params) {
|
||||
element.key.dispose();
|
||||
element.value.dispose();
|
||||
@@ -609,7 +613,7 @@ class KeyValState extends State<KeyValWidget> with AutomaticKeepAliveClientMixin
|
||||
}
|
||||
|
||||
//刷新param
|
||||
refreshParam(Map<String, List<String>>? headers) {
|
||||
void refreshParam(Map<String, List<String>>? headers) {
|
||||
clear();
|
||||
setState(() {
|
||||
headers?.forEach((name, values) {
|
||||
@@ -692,13 +696,103 @@ class KeyValState extends State<KeyValWidget> with AutomaticKeepAliveClientMixin
|
||||
return list;
|
||||
}
|
||||
|
||||
Widget _cell(TextEditingController val, {bool isKey = false}) {
|
||||
Widget _cell(KeyVal keyVal,
|
||||
{bool isKey = false,
|
||||
FocusNode? focusNode,
|
||||
List<String>? suggestions,
|
||||
Map<String, List<String>>? valueSuggestions}) {
|
||||
TextEditingController textController = isKey ? keyVal.key : keyVal.value;
|
||||
|
||||
if (!widget.readOnly && (suggestions != null || valueSuggestions != null)) {
|
||||
return Container(
|
||||
padding: const EdgeInsets.only(right: 5),
|
||||
child: RawAutocomplete<String>(
|
||||
textEditingController: textController,
|
||||
focusNode: focusNode,
|
||||
optionsBuilder: (TextEditingValue textEditingValue) {
|
||||
if (textEditingValue.text.isEmpty) {
|
||||
return const Iterable<String>.empty();
|
||||
}
|
||||
|
||||
var currentSuggestions = suggestions;
|
||||
if (!isKey && valueSuggestions?.containsKey(keyVal.key.text) == true) {
|
||||
currentSuggestions = valueSuggestions![keyVal.key.text];
|
||||
}
|
||||
|
||||
if (currentSuggestions == null) {
|
||||
return const Iterable<String>.empty();
|
||||
}
|
||||
|
||||
return currentSuggestions.where((String option) {
|
||||
return option.toLowerCase().contains(textEditingValue.text.toLowerCase());
|
||||
});
|
||||
},
|
||||
onSelected: (String selection) {
|
||||
textController.text = selection;
|
||||
notifierChange();
|
||||
},
|
||||
fieldViewBuilder: (BuildContext context, TextEditingController textEditingController,
|
||||
FocusNode fieldFocusNode, VoidCallback onFieldSubmitted) {
|
||||
return TextFormField(
|
||||
controller: textEditingController,
|
||||
focusNode: fieldFocusNode,
|
||||
onFieldSubmitted: (String value) {
|
||||
onFieldSubmitted();
|
||||
},
|
||||
onChanged: (val) {
|
||||
if (isKey) setState(() {});
|
||||
notifierChange();
|
||||
},
|
||||
style: const TextStyle(fontSize: 13, fontWeight: FontWeight.w500),
|
||||
minLines: 1,
|
||||
maxLines: 3,
|
||||
decoration: InputDecoration(
|
||||
isDense: true,
|
||||
hintStyle: const TextStyle(color: Colors.grey),
|
||||
contentPadding: const EdgeInsets.fromLTRB(5, 13, 5, 13),
|
||||
focusedBorder: OutlineInputBorder(
|
||||
borderSide: BorderSide(color: Theme.of(context).colorScheme.primary, width: 1.5)),
|
||||
border: InputBorder.none,
|
||||
hintText: isKey ? "Key" : "Value"));
|
||||
},
|
||||
optionsViewBuilder:
|
||||
(BuildContext context, AutocompleteOnSelected<String> onSelected, Iterable<String> options) {
|
||||
return Align(
|
||||
alignment: Alignment.topLeft,
|
||||
child: Material(
|
||||
elevation: 4.0,
|
||||
child: ConstrainedBox(
|
||||
constraints: const BoxConstraints(maxHeight: 200, maxWidth: 300),
|
||||
child: ListView.builder(
|
||||
padding: EdgeInsets.zero,
|
||||
shrinkWrap: true,
|
||||
itemCount: options.length,
|
||||
itemBuilder: (BuildContext context, int index) {
|
||||
final String option = options.elementAt(index);
|
||||
return InkWell(
|
||||
onTap: () {
|
||||
onSelected(option);
|
||||
},
|
||||
child: Container(
|
||||
padding: const EdgeInsets.all(10.0),
|
||||
child: _buildHighlightText(option, textController.text),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
));
|
||||
}
|
||||
|
||||
return Container(
|
||||
padding: const EdgeInsets.only(right: 5),
|
||||
child: TextFormField(
|
||||
readOnly: widget.readOnly,
|
||||
style: TextStyle(fontSize: 13, fontWeight: isKey ? FontWeight.w500 : null),
|
||||
controller: val,
|
||||
controller: textController,
|
||||
onChanged: (val) => notifierChange(),
|
||||
minLines: 1,
|
||||
maxLines: 3,
|
||||
@@ -715,6 +809,16 @@ class KeyValState extends State<KeyValWidget> with AutomaticKeepAliveClientMixin
|
||||
}
|
||||
|
||||
Widget _row(KeyVal keyVal, Widget? op) {
|
||||
if (widget.suggestions != null) {
|
||||
keyVal.keyFocusNode ??= FocusNode();
|
||||
}
|
||||
|
||||
Map<String, List<String>>? valueSuggestions;
|
||||
if (widget.suggestions != null) {
|
||||
keyVal.valueFocusNode ??= FocusNode();
|
||||
valueSuggestions = HttpHeaders.commonHeaderValues;
|
||||
}
|
||||
|
||||
return Row(crossAxisAlignment: CrossAxisAlignment.center, children: [
|
||||
if (op != null)
|
||||
Checkbox(
|
||||
@@ -726,11 +830,31 @@ class KeyValState extends State<KeyValWidget> with AutomaticKeepAliveClientMixin
|
||||
notifierChange();
|
||||
}),
|
||||
Container(width: 5),
|
||||
Expanded(flex: 4, child: _cell(keyVal.key, isKey: true)),
|
||||
Expanded(
|
||||
flex: 4, child: _cell(keyVal, isKey: true, suggestions: widget.suggestions, focusNode: keyVal.keyFocusNode)),
|
||||
const Text(":", style: TextStyle(color: Colors.deepOrangeAccent)),
|
||||
const SizedBox(width: 8),
|
||||
Expanded(flex: 6, child: _cell(keyVal.value)),
|
||||
Expanded(flex: 6, child: _cell(keyVal, focusNode: keyVal.valueFocusNode, valueSuggestions: valueSuggestions)),
|
||||
op ?? const SizedBox()
|
||||
]);
|
||||
}
|
||||
|
||||
Widget _buildHighlightText(String text, String query) {
|
||||
if (query.isEmpty) {
|
||||
return Text(text);
|
||||
}
|
||||
|
||||
int index = text.toLowerCase().indexOf(query.toLowerCase());
|
||||
if (index < 0) {
|
||||
return Text(text);
|
||||
}
|
||||
|
||||
return Text.rich(TextSpan(children: [
|
||||
TextSpan(text: text.substring(0, index)),
|
||||
TextSpan(
|
||||
text: text.substring(index, index + query.length),
|
||||
style: TextStyle(color: Theme.of(context).colorScheme.primary, fontWeight: FontWeight.bold)),
|
||||
TextSpan(text: text.substring(index + query.length))
|
||||
]));
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user