diff --git a/lib/main.dart b/lib/main.dart index bef39a8..31fa5bf 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -79,10 +79,10 @@ class FluentApp extends StatelessWidget { themeNotifier = ValueNotifier(uiConfiguration.theme); var light = lightTheme(); - var darkTheme = ThemeData.dark(useMaterial3: false); + var darkTheme = config(ThemeData.dark(useMaterial3: false)); - var material3Light = ThemeData.light(useMaterial3: true); - var material3Dark = ThemeData.dark(useMaterial3: true); + var material3Light = config(ThemeData.light(useMaterial3: true)); + var material3Dark = config(ThemeData.dark(useMaterial3: true)); if (Platform.isWindows) { material3Light = material3Light.useSystemChineseFont(); @@ -108,9 +108,20 @@ class FluentApp extends StatelessWidget { }); } + ThemeData config(ThemeData themeData) { + return themeData.copyWith( + dialogTheme: themeData.dialogTheme.copyWith( + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(10)), + )); + } + + ///浅色主题 ThemeData lightTheme() { var theme = ThemeData.light(useMaterial3: false); theme = theme.copyWith( + dialogTheme: theme.dialogTheme.copyWith( + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(10)), + ), expansionTileTheme: theme.expansionTileTheme.copyWith( textColor: theme.textTheme.titleMedium?.color, ), diff --git a/lib/network/handler.dart b/lib/network/handler.dart index 04d5684..7415286 100644 --- a/lib/network/handler.dart +++ b/lib/network/handler.dart @@ -166,7 +166,7 @@ class HttpProxyChannelHandler extends ChannelHandler { //替换请求体 rewriteBody(HttpRequest httpRequest) { - var rewrite = requestRewrites?.findRequestRewrite(httpRequest.requestUrl, RuleType.body); + var rewrite = requestRewrites?.findRequestRewrite(httpRequest.requestUrl, RuleType.responseReplace); if (rewrite?.requestBody?.isNotEmpty == true) { httpRequest.body = utf8.encode(rewrite!.requestBody!); diff --git a/lib/network/util/request_rewrite.dart b/lib/network/util/request_rewrite.dart index 91c4293..c612672 100644 --- a/lib/network/util/request_rewrite.dart +++ b/lib/network/util/request_rewrite.dart @@ -37,7 +37,20 @@ class RequestRewrites { List? list = map['rules']; rules.clear(); list?.forEach((element) { - rules.add(RequestRewriteRule.formJson(element)); + try { + // body("重写消息体"), 兼容旧版本 + if (element['type'] == '重写消息体' && element['requestBody']?.toString().isNotEmpty == true) { + element['type'] = RuleType.requestReplace.name; + rules.add(RequestRewriteRule.formJson(element)); + } else if (element['type'] == '重写消息体' && element['responseBody']?.toString().isNotEmpty == true) { + element['type'] = RuleType.responseReplace.name; + rules.add(RequestRewriteRule.formJson(element)); + } else { + rules.add(RequestRewriteRule.formJson(element)); + } + } catch (e) { + logger.e('加载请求重写配置失败 $element', error: e); + } }); } @@ -82,7 +95,7 @@ class RequestRewrites { } for (var rule in rules) { - if (rule.enabled && rule.urlReg.hasMatch(url) && rule.type == RuleType.body) { + if (rule.enabled && rule.urlReg.hasMatch(url) && rule.type == RuleType.responseReplace) { return rule.responseBody; } } @@ -123,17 +136,20 @@ class RequestRewrites { } enum RuleType { - body("重写消息体"), + // body("重写消息体"), //OLD VERSION + + requestReplace("替换请求"), + responseReplace("替换响应"), // header("重写Header"), redirect("重定向"); //名称 - final String name; + final String label; - const RuleType(this.name); + const RuleType(this.label); static RuleType fromName(String name) { - return values.firstWhere((element) => element.name == name); + return values.firstWhere((element) => element.name == name || element.label == name); } } @@ -157,7 +173,7 @@ class RequestRewriteRule { RequestRewriteRule(this.enabled, {this.name, required this.url, - this.type = RuleType.body, + required this.type, this.queryParam, this.requestBody, this.responseBody, @@ -169,7 +185,7 @@ class RequestRewriteRule { return RequestRewriteRule(map['enabled'] == true, name: map['name'], url: map['url'] ?? map['domain'] + map['path'], - type: map['type'] == null ? RuleType.body : RuleType.fromName(map['type']), + type: RuleType.fromName(map['type']), queryParam: map['queryParam'], requestBody: map['requestBody'], responseBody: map['responseBody'], diff --git a/lib/ui/component/widgets.dart b/lib/ui/component/widgets.dart index a6fbbb4..2d5c871 100644 --- a/lib/ui/component/widgets.dart +++ b/lib/ui/component/widgets.dart @@ -8,11 +8,11 @@ class CustomPopupMenuItem extends PopupMenuItem { super.key, super.onTap, super.height, - T? value, - bool enabled = true, - required Widget child, + super.value, + super.enabled, + required Widget super.child, this.color, - }) : super(value: value, enabled: enabled, child: child); + }); @override PopupMenuItemState> createState() => _CustomPopupMenuItemState(); @@ -35,8 +35,9 @@ class SwitchWidget extends StatefulWidget { final String? subtitle; final ValueWrap value; final ValueChanged onChanged; + final double scale; - SwitchWidget({super.key, this.title, this.subtitle, required bool value, required this.onChanged}) + SwitchWidget({super.key, this.title, this.subtitle, required bool value, required this.onChanged, this.scale = 1}) : value = ValueWrap.of(value); @override @@ -47,27 +48,46 @@ class _SwitchState extends State { @override Widget build(BuildContext context) { if (widget.title == null) { - return Switch( - value: widget.value.get() == true, - onChanged: (value) { - setState(() { - widget.value.set(value); - }); - widget.onChanged(value); - }, - ); + return Transform.scale( + scale: widget.scale, + child: Switch( + value: widget.value.get() == true, + onChanged: (value) { + setState(() { + widget.value.set(value); + }); + widget.onChanged(value); + }, + )); } - return SwitchListTile( - title: widget.title == null ? null : Text(widget.title!), - subtitle: widget.subtitle == null ? null : Text(widget.subtitle!), - value: widget.value.get() == true, - dense: true, - onChanged: (value) { - setState(() { - widget.value.set(value); - }); - widget.onChanged(value); - }, - ); + return Transform.scale( + scale: widget.scale, + child: SwitchListTile( + title: widget.title == null ? null : Text(widget.title!), + subtitle: widget.subtitle == null ? null : Text(widget.subtitle!), + value: widget.value.get() == true, + dense: true, + onChanged: (value) { + setState(() { + widget.value.set(value); + }); + widget.onChanged(value); + }, + )); } } + +class Dot extends StatelessWidget { + final Color? color; + final double size; + const Dot({super.key, this.color = const Color(0xFF00FF00), this.size = 5}); + + @override + Widget build(BuildContext context) { + return Container( + width: size, + height: size, + decoration: BoxDecoration(color: color, shape: BoxShape.circle), + ); + } +} \ No newline at end of file diff --git a/lib/ui/content/body.dart b/lib/ui/content/body.dart index 609ef27..a140681 100644 --- a/lib/ui/content/body.dart +++ b/lib/ui/content/body.dart @@ -146,6 +146,7 @@ class HttpBodyState extends State { var body = bodyKey.currentState?.body; var rule = RequestRewriteRule(true, + type: type == "Request" ? RuleType.requestReplace : RuleType.responseReplace, url: '${request?.remoteDomain()}${request?.path()}', requestBody: widget.httpMessage is HttpRequest ? body : null, responseBody: widget.httpMessage is HttpResponse ? body : null); diff --git a/lib/ui/desktop/toolbar/setting/filter.dart b/lib/ui/desktop/toolbar/setting/filter.dart index fa44c6c..9ed1ccf 100644 --- a/lib/ui/desktop/toolbar/setting/filter.dart +++ b/lib/ui/desktop/toolbar/setting/filter.dart @@ -23,6 +23,7 @@ class _FilterDialogState extends State { @override Widget build(BuildContext context) { return AlertDialog( + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(10.0)), titlePadding: const EdgeInsets.only(left: 20, top: 10, right: 15), contentPadding: const EdgeInsets.only(left: 20, right: 20), scrollable: true, diff --git a/lib/ui/desktop/toolbar/setting/request_rewrite.dart b/lib/ui/desktop/toolbar/setting/request_rewrite.dart index 936d8de..0c1e8f4 100644 --- a/lib/ui/desktop/toolbar/setting/request_rewrite.dart +++ b/lib/ui/desktop/toolbar/setting/request_rewrite.dart @@ -6,6 +6,7 @@ import 'package:network_proxy/network/util/request_rewrite.dart'; import 'package:network_proxy/ui/component/multi_window.dart'; import 'package:network_proxy/ui/component/utils.dart'; import 'package:network_proxy/ui/component/widgets.dart'; +import 'package:network_proxy/ui/desktop/toolbar/setting/rewite/rewrite_replace.dart'; class RequestRewriteWidget extends StatefulWidget { final int windowId; @@ -146,7 +147,7 @@ class _RuleAddDialogState extends State { @override void initState() { super.initState(); - rule = widget.rule ?? RequestRewriteRule(true, url: ''); + rule = widget.rule ?? RequestRewriteRule(true, url: '', type: RuleType.responseReplace); enableNotifier = ValueNotifier(rule.enabled == true); } @@ -161,10 +162,11 @@ class _RuleAddDialogState extends State { GlobalKey formKey = GlobalKey(); return AlertDialog( - title: const Text("添加请求重写规则", style: TextStyle(fontSize: 14, fontWeight: FontWeight.w500)), scrollable: true, + title: const Text("添加请求重写规则", style: TextStyle(fontSize: 14, fontWeight: FontWeight.w500)), + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(10.0)), content: Container( - constraints: const BoxConstraints(minWidth: 350, minHeight: 460), + constraints: const BoxConstraints(minWidth: 350), child: Form( key: formKey, child: Column( @@ -175,39 +177,39 @@ class _RuleAddDialogState extends State { valueListenable: enableNotifier, builder: (_, bool enable, __) { return SwitchListTile( - dense: true, contentPadding: const EdgeInsets.only(left: 0), - title: const Text('是否启用', textAlign: TextAlign.start), + title: const Text('是否启用', style: TextStyle(fontSize: 16, fontWeight: FontWeight.w500)), value: enable, onChanged: (value) => enableNotifier.value = value); }), - TextFormField( - decoration: decoration('名称'), - initialValue: rule.name, - onSaved: (val) => rule.name = val, - ), const SizedBox(height: 5), - TextFormField( - decoration: decoration('URL', hintText: 'http://www.example.com/api/*'), - initialValue: rule.url, - validator: (val) => val?.isNotEmpty == true ? null : "URL不能为空", - onSaved: (val) => rule.url = val!.trim()), - const SizedBox(height: 5), - DropdownButtonFormField( - value: rule.type, - isDense: true, - decoration: decoration('行为'), - items: RuleType.values - .map((e) => - DropdownMenuItem(value: e, child: Text(e.name, style: const TextStyle(fontSize: 14)))) - .toList(), - onChanged: (val) { - setState(() { - rule.type = val!; - }); - }), - const SizedBox(height: 5), - ...rewriteWidgets() + textField('名称:', rule.name, '请输入名称'), + const SizedBox(height: 10), + textField('URL:', rule.url, 'http://www.example.com/api/*'), + const SizedBox(height: 10), + Row(children: [ + const SizedBox(width: 60, child: Text('行为:')), + SizedBox( + width: 100, + height: 33, + child: DropdownButtonFormField( + value: rule.type, + decoration: InputDecoration( + contentPadding: const EdgeInsets.only(left: 7, right: 7), + focusedBorder: focusedBorder(), + border: const OutlineInputBorder()), + items: RuleType.values + .map((e) => DropdownMenuItem( + value: e, child: Text(e.label, style: const TextStyle(fontSize: 13)))) + .toList(), + onChanged: (val) { + setState(() { + rule.type = val!; + }); + })), + const SizedBox(width: 10), + TextButton(onPressed: () => showEdit(rule), child: const Text("点击编辑")) + ]) ]))), actions: [ FilledButton( @@ -238,71 +240,28 @@ class _RuleAddDialogState extends State { ]); } - InputDecoration decoration(String label, {String? hintText}) { - Color color = Theme.of(context).colorScheme.primary; - // Color color = Colors.blueAccent; - - return InputDecoration( - labelText: label, - hintText: hintText, - labelStyle: const TextStyle(fontSize: 14), - isDense: true, - border: UnderlineInputBorder(borderSide: BorderSide(width: 0.3, color: color)), - enabledBorder: UnderlineInputBorder(borderSide: BorderSide(width: 0.3, color: color)), - focusedBorder: UnderlineInputBorder(borderSide: BorderSide(width: 1.5, color: color))); + void showEdit(RequestRewriteRule rule) { + showDialog( + context: context, + barrierDismissible: false, + builder: (BuildContext context) => RewriteReplaceDialog(rule: rule), + ).then((value) { + if (value != null) setState(() {}); + }); } - List rewriteWidgets() { - if (rule.type == RuleType.redirect) { - return [ - TextFormField( - decoration: decoration('重定向到:', hintText: 'http://www.example.com/api'), - maxLines: 3, - initialValue: rule.redirectUrl, - onSaved: (val) => rule.redirectUrl = val, - validator: (val) { - if (val == null || val.trim().isEmpty) { - return '重定向URL不能为空'; - } - return null; - }), - ]; - } - - return [ - TextFormField( - initialValue: rule.queryParam, - decoration: decoration('URL参数替换为:'), - maxLines: 1, - onSaved: (val) => rule.queryParam = val), - const SizedBox(height: 5), - TextFormField( - initialValue: rule.requestBody, - decoration: decoration('请求体替换为:'), - minLines: 1, - maxLines: 5, - onSaved: (val) => rule.requestBody = val), - const SizedBox(height: 5), - TextFormField( - initialValue: rule.responseBody, - minLines: 3, - maxLines: 10, - decoration: decoration('响应体替换为:', hintText: '{"code":"200","data":{}}'), - onSaved: (val) => rule.responseBody = val) - ]; - } - - Widget textField(String label, TextEditingController controller, String hint, {TextInputType? keyboardType}) { + Widget textField(String label, dynamic value, String hint) { return Row(children: [ - SizedBox(width: 50, child: Text(label)), + SizedBox(width: 60, child: Text(label)), Expanded( child: TextFormField( - controller: controller, + initialValue: value, + style: const TextStyle(fontSize: 14), validator: (val) => val?.isNotEmpty == true ? null : "", - keyboardType: keyboardType, + onChanged: (val) => value = val, decoration: InputDecoration( hintText: hint, - hintStyle: TextStyle(color: Colors.grey.withOpacity(0.5)), + hintStyle: TextStyle(color: Colors.grey.shade500, fontSize: 14), contentPadding: const EdgeInsets.all(10), errorStyle: const TextStyle(height: 0, fontSize: 0), focusedBorder: focusedBorder(), @@ -317,6 +276,7 @@ class _RuleAddDialogState extends State { } } +///请求重写规则列表 class RequestRuleList extends StatefulWidget { final RequestRewrites requestRewrites; @@ -396,21 +356,20 @@ class _RequestRuleListState extends State { SizedBox(width: 130, child: Text(list[index].name!, style: const TextStyle(fontSize: 13))), SizedBox( width: 40, - child: Transform.scale( + child: SwitchWidget( scale: 0.65, - child: SwitchWidget( - value: list[index].enabled, - onChanged: (val) { - list[index].enabled = val; - MultiWindow.invokeRefreshRewrite(Operation.update, index: index, rule: list[index]); - }))), + value: list[index].enabled, + onChanged: (val) { + list[index].enabled = val; + MultiWindow.invokeRefreshRewrite(Operation.update, index: index, rule: list[index]); + })), const SizedBox(width: 20), Expanded( child: Text(list[index].url, overflow: TextOverflow.ellipsis, style: const TextStyle(fontSize: 13))), SizedBox( width: 100, - child: Text(list[index].type.name, + child: Text(list[index].type.label, textAlign: TextAlign.center, style: const TextStyle(fontSize: 13))), ], ))); diff --git a/lib/ui/desktop/toolbar/setting/rewite/rewrite_replace.dart b/lib/ui/desktop/toolbar/setting/rewite/rewrite_replace.dart new file mode 100644 index 0000000..23d3107 --- /dev/null +++ b/lib/ui/desktop/toolbar/setting/rewite/rewrite_replace.dart @@ -0,0 +1,378 @@ +import 'package:flutter/material.dart'; +import 'package:network_proxy/network/util/request_rewrite.dart'; +import 'package:network_proxy/ui/component/widgets.dart'; + +/// 重写替换 +class RewriteReplaceDialog extends StatefulWidget { + final RequestRewriteRule? rule; + + const RewriteReplaceDialog({super.key, this.rule}); + + @override + State createState() => _RewriteReplaceState(); +} + +class _RewriteReplaceState extends State { + // final _formKey = GlobalKey(); + late RequestRewriteRule rule; + + @override + initState() { + super.initState(); + rule = widget.rule ?? RequestRewriteRule(true, url: '', type: RuleType.responseReplace); + } + + @override + Widget build(BuildContext context) { + return AlertDialog( + titlePadding: const EdgeInsets.all(0), + actionsPadding: const EdgeInsets.only(right: 10, bottom: 10), + contentPadding: const EdgeInsets.only(left: 10, right: 10, top: 0, bottom: 5), + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(10.0)), + actions: [ + TextButton( + onPressed: () { + Navigator.of(context).pop(rule); + }, + child: const Text("完成")), + TextButton(onPressed: () => Navigator.of(context).pop(), child: const Text("关闭")) + ], + title: ListTile( + title: Text(rule.type.label, + textAlign: TextAlign.center, style: const TextStyle(fontSize: 14, fontWeight: FontWeight.w500)), + subtitle: + Text(rule.url, textAlign: TextAlign.center, style: const TextStyle(fontSize: 12, color: Colors.grey))), + content: SizedBox(width: 500, height: 320, child: rewriteWidgets())); + } + + Widget rewriteWidgets() { + if (rule.type == RuleType.redirect) { + return TextFormField( + decoration: decoration('重定向到:', hintText: 'http://www.example.com/api'), + maxLines: 3, + style: const TextStyle(fontSize: 14), + initialValue: rule.redirectUrl, + onSaved: (val) => rule.redirectUrl = val, + validator: (val) { + if (val == null || val.trim().isEmpty) { + return '重定向URL不能为空'; + } + return null; + }); + } + + if (rule.type == RuleType.responseReplace || rule.type == RuleType.requestReplace) { + List tabs = rule.type == RuleType.responseReplace ? ["状态码", "响应头", "响应体"] : ["请求行", "请求头", "请求体"]; + return DefaultTabController( + length: tabs.length, + child: Scaffold( + appBar: TabBar( + tabs: tabs + .map((label) => Tab( + height: 38, + child: Row(mainAxisAlignment: MainAxisAlignment.center, children: [ + Text(label, style: const TextStyle(fontSize: 14, fontWeight: FontWeight.w500)), + const SizedBox(width: 5), + const Dot() + ]))) + .toList()), + body: TabBarView(children: [ + Container( + padding: const EdgeInsets.all(10), + child: Column( + children: [ + Row(children: [ + const Text('请求方法'), + const SizedBox(width: 10), + SizedBox( + width: 100, + child: DropdownButtonFormField( + value: 'GET', + focusColor: Colors.transparent, + itemHeight: 48, + decoration: const InputDecoration( + contentPadding: EdgeInsets.all(10), isDense: true, border: InputBorder.none), + items: ['GET', 'POST', 'PUT', 'DELETE', 'PATCH', 'HEAD', 'OPTIONS'] + .map((e) => DropdownMenuItem( + value: e, + child: Text(e, style: const TextStyle(fontSize: 13, fontWeight: FontWeight.w500)))) + .toList(), + onChanged: (val) {})), + Expanded( + child: Row(mainAxisAlignment: MainAxisAlignment.end, children: [ + const Text('启用'), + const SizedBox(width: 10), + SwitchWidget(value: true, scale: 0.65, onChanged: (val) {}) + ])), + ]), + const SizedBox(height: 15), + textField("Path", "", "示例: /api/v1/user"), + const SizedBox(height: 15), + textField("URL参数", rule.queryParam, "示例: id=1&name=2"), + ], + ), + ), + Container( + padding: const EdgeInsets.all(10), + child: Column(children: [ + Row( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + const Text('响应头列表'), + const SizedBox(width: 10), + Expanded( + child: Row(mainAxisAlignment: MainAxisAlignment.end, children: [ + const Text('启用'), + const SizedBox(width: 10), + SwitchWidget(value: true, scale: 0.65, onChanged: (val) {}) + ])) + ]), + const Headers() + ])), + Container( + padding: const EdgeInsets.all(10), + child: Column(children: [ + Row(children: [ + // const Text('类型'), + // const SizedBox(width: 10), //文本或文件 + // SizedBox( + // width: 80, + // child: DropdownButtonFormField( + // value: '文本', + // focusColor: Colors.transparent, + // itemHeight: 48, + // decoration: const InputDecoration( + // contentPadding: EdgeInsets.all(10), isDense: true, border: InputBorder.none), + // items: ['文本', '文件'] + // .map((e) => DropdownMenuItem( + // value: e, + // child: Text(e, style: const TextStyle(fontSize: 13, fontWeight: FontWeight.w500)))) + // .toList(), + // onChanged: (val) {})), + Expanded( + child: Row(mainAxisAlignment: MainAxisAlignment.end, children: [ + const Text('启用'), + const SizedBox(width: 10), + SwitchWidget(value: true, scale: 0.65, onChanged: (val) {}) + ])), + ]), + const SizedBox(height: 5), + TextFormField( + initialValue: rule.responseBody, + style: const TextStyle(fontSize: 14), + maxLines: 10, + decoration: decoration('响应体替换为:', hintText: '示例: {"code":"200","data":{}}'), + onSaved: (val) => rule.responseBody = val) + ])) + ]), + ), + ); + } + + return Container(); + // return [ + // TextFormField( + // initialValue: rule.queryParam, + // decoration: decoration('URL参数替换为:'), + // maxLines: 1, + // onSaved: (val) => rule.queryParam = val), + // const SizedBox(height: 5), + // TextFormField( + // initialValue: rule.requestBody, + // decoration: decoration('请求体替换为:'), + // minLines: 1, + // maxLines: 5, + // onSaved: (val) => rule.requestBody = val), + // const SizedBox(height: 5), + // TextFormField( + // initialValue: rule.responseBody, + // minLines: 3, + // maxLines: 10, + // decoration: decoration('响应体替换为:', hintText: '{"code":"200","data":{}}'), + // onSaved: (val) => rule.responseBody = val) + // ]; + } + + Widget textField(String label, dynamic value, String hint) { + return Row(children: [ + SizedBox(width: 80, child: Text(label)), + Expanded( + child: TextFormField( + initialValue: value, + validator: (val) => val?.isNotEmpty == true ? null : "", + onChanged: (val) => value = val, + decoration: InputDecoration( + hintText: hint, + hintStyle: TextStyle(color: Colors.grey.shade500, fontSize: 14), + contentPadding: const EdgeInsets.all(10), + errorStyle: const TextStyle(height: 0, fontSize: 0), + focusedBorder: focusedBorder(), + isDense: true, + border: const OutlineInputBorder()), + )) + ]); + } + + Widget statusCodeEdit() { + return Container( + padding: const EdgeInsets.all(10), + child: Column(children: [ + Row(crossAxisAlignment: CrossAxisAlignment.center, children: [ + const Text('状态码'), + const SizedBox(width: 10), + SizedBox( + width: 100, + child: TextFormField( + style: const TextStyle(fontSize: 14), + decoration: InputDecoration( + contentPadding: const EdgeInsets.all(10), + errorStyle: const TextStyle(height: 0, fontSize: 0), + focusedBorder: focusedBorder(), + isDense: true, + border: const OutlineInputBorder()), + validator: (val) { + if (val == null || val.trim().isEmpty) { + return '状态码不能为空'; + } + return null; + }, + )), + Expanded( + child: Row(mainAxisAlignment: MainAxisAlignment.end, children: [ + const Text('启用'), + const SizedBox(width: 10), + SwitchWidget(value: true, scale: 0.65, onChanged: (val) {}) + ])), + const SizedBox(width: 10), + ]) + ])); + } + + InputDecoration decoration(String label, {String? hintText}) { + Color color = Theme.of(context).colorScheme.primary; + // Color color = Colors.blueAccent; + return InputDecoration( + floatingLabelBehavior: FloatingLabelBehavior.always, + labelText: label, + hintText: hintText, + isDense: true, + border: OutlineInputBorder(borderSide: BorderSide(width: 0.8, color: color)), + enabledBorder: OutlineInputBorder(borderSide: BorderSide(width: 1.5, color: color)), + focusedBorder: OutlineInputBorder(borderSide: BorderSide(width: 2, color: color))); + } + + InputBorder focusedBorder() { + return OutlineInputBorder(borderSide: BorderSide(color: Theme.of(context).primaryColor, width: 2)); + } +} + +///请求头 +class Headers extends StatefulWidget { + final Map? headers; + + const Headers({super.key, this.headers}); + + @override + State createState() { + return HeadersState(); + } +} + +class HeadersState extends State with AutomaticKeepAliveClientMixin { + final Map _headers = {}; + + @override + bool get wantKeepAlive => true; + + @override + void initState() { + super.initState(); + if (widget.headers == null) { + return; + } + widget.headers?.forEach((name, value) { + _headers[TextEditingController(text: name)] = TextEditingController(text: value); + }); + } + + ///获取所有请求头 + Map getHeaders() { + var headers = {}; + _headers.forEach((name, value) { + if (name.text.isEmpty) { + return; + } + headers[name.text] = value.text; + }); + return headers; + } + + @override + Widget build(BuildContext context) { + super.build(context); + + var list = [ + ..._buildRows(), + ]; + + list.add(TextButton( + child: const Text("添加Header", textAlign: TextAlign.center), + onPressed: () { + setState(() { + _headers[TextEditingController()] = TextEditingController(); + }); + }, + )); + + return Padding( + padding: const EdgeInsets.only(top: 10), + child: ListView.separated( + shrinkWrap: true, + separatorBuilder: (context, index) => + index == list.length ? const SizedBox() : const Divider(thickness: 0.2), + itemBuilder: (context, index) => list[index], + itemCount: list.length)); + } + + List _buildRows() { + List list = []; + + _headers.forEach((key, val) { + list.add(_row( + _cell(key, isKey: true), + _cell(val), + Padding( + padding: const EdgeInsets.only(right: 15), + child: InkWell( + onTap: () { + setState(() { + _headers.remove(key); + }); + }, + child: const Icon(Icons.remove_circle, size: 16))))); + }); + + return list; + } + + Widget _cell(TextEditingController val, {bool isKey = false}) { + return Container( + padding: const EdgeInsets.only(right: 5), + child: TextFormField( + style: TextStyle(fontSize: 12, fontWeight: isKey ? FontWeight.w500 : null), + controller: val, + minLines: 1, + maxLines: 3, + decoration: InputDecoration(isDense: true, border: InputBorder.none, hintText: isKey ? "Key" : "Value"))); + } + + Widget _row(Widget key, Widget val, Widget? op) { + return Row(children: [ + Expanded(flex: 4, child: key), + const Text(": ", style: TextStyle(color: Colors.deepOrangeAccent)), + Expanded(flex: 6, child: val), + op ?? const SizedBox() + ]); + } +} diff --git a/lib/ui/desktop/toolbar/ssl/ssl.dart b/lib/ui/desktop/toolbar/ssl/ssl.dart index bf0dab0..6bbbbf9 100644 --- a/lib/ui/desktop/toolbar/ssl/ssl.dart +++ b/lib/ui/desktop/toolbar/ssl/ssl.dart @@ -22,7 +22,7 @@ class _SslState extends State { @override Widget build(BuildContext context) { var surfaceTintColor = - Brightness.dark == Theme.of(context).brightness ? null : Theme.of(context).colorScheme.background; + Brightness.dark == Theme.of(context).brightness ? null : Theme.of(context).colorScheme.background; return PopupMenuButton( icon: Icon(Icons.https, color: widget.proxyServer.enableSsl ? null : Colors.red), @@ -124,7 +124,6 @@ class _SslState extends State { context: context, builder: (BuildContext context) { return SimpleDialog( - shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(10.0)), contentPadding: const EdgeInsets.all(16), title: const Row(children: [ Text("电脑HTTPS抓包配置", style: TextStyle(fontSize: 16)), diff --git a/lib/ui/mobile/setting/request_rewrite.dart b/lib/ui/mobile/setting/request_rewrite.dart index a7fa1e0..17bd1d2 100644 --- a/lib/ui/mobile/setting/request_rewrite.dart +++ b/lib/ui/mobile/setting/request_rewrite.dart @@ -144,7 +144,7 @@ class _RewriteRuleState extends State { @override void initState() { super.initState(); - rule = widget.rule ?? RequestRewriteRule(true, url: ''); + rule = widget.rule ?? RequestRewriteRule(true, url: '', type: RuleType.responseReplace); enableNotifier = ValueNotifier(rule.enabled == true); }