mirror of
https://github.com/wanghongenpin/proxypin.git
synced 2026-03-19 05:19:47 +08:00
Rewrite request modification to add regular matching test
This commit is contained in:
@@ -104,6 +104,9 @@
|
||||
"enableSelect": "Enable Select",
|
||||
"disableSelect": "Disable Select",
|
||||
"deleteSelect": "Delete Select",
|
||||
"testData": "Test Data",
|
||||
"noChangesDetected": "No changes detected",
|
||||
"enterMatchData": "Enter the data to be matched",
|
||||
|
||||
"modifyRequestHeader": "Modify Header",
|
||||
"headerName": "Header Name",
|
||||
|
||||
@@ -104,6 +104,9 @@
|
||||
"enableSelect": "启用选择",
|
||||
"disableSelect": "禁用选择",
|
||||
"deleteSelect": "删除选择",
|
||||
"testData": "测试数据",
|
||||
"noChangesDetected": "未检测到变更",
|
||||
"enterMatchData": "输入待匹配的数据",
|
||||
|
||||
"modifyRequestHeader": "修改请求头",
|
||||
"headerName": "请求头名称",
|
||||
|
||||
@@ -230,7 +230,7 @@ class RequestRewriteManager {
|
||||
}
|
||||
|
||||
RequestRewriteRule getRequestRewriteRule(HttpRequest request, RuleType type) {
|
||||
var url = '${request.remoteDomain()}${request.path()}';
|
||||
var url = request.domainPath;
|
||||
for (var rule in rules) {
|
||||
if (rule.match(url) && rule.type == type) {
|
||||
return rule;
|
||||
|
||||
@@ -236,7 +236,7 @@ async function onResponse(context, request, response) {
|
||||
if (!enabled) {
|
||||
return request;
|
||||
}
|
||||
var url = '${request.remoteDomain()}${request.path()}';
|
||||
var url = request.domainPath;
|
||||
for (var item in list) {
|
||||
if (item.enabled && item.match(url)) {
|
||||
var context = jsonEncode(scriptContext(item));
|
||||
@@ -265,7 +265,7 @@ async function onResponse(context, request, response) {
|
||||
}
|
||||
|
||||
var request = response.request!;
|
||||
var url = '${request.remoteDomain()}${request.path()}';
|
||||
var url = request.domainPath;
|
||||
for (var item in list) {
|
||||
if (item.enabled && item.match(url)) {
|
||||
var context = jsonEncode(request.attributes['scriptContext'] ?? scriptContext(item));
|
||||
@@ -297,7 +297,6 @@ async function onResponse(context, request, response) {
|
||||
if (jsResult.isPromise || jsResult.rawResult is Future) {
|
||||
jsResult = await flutterJs.handlePromise(jsResult);
|
||||
}
|
||||
|
||||
} catch (e) {
|
||||
throw SignalException(jsResult.stringResult);
|
||||
}
|
||||
|
||||
@@ -121,7 +121,7 @@ class HttpProxyChannelHandler extends ChannelHandler<HttpRequest> {
|
||||
return;
|
||||
}
|
||||
|
||||
var uri = '${httpRequest.remoteDomain()}${httpRequest.path()}';
|
||||
var uri = httpRequest.domainPath;
|
||||
//脚本替换
|
||||
var scriptManager = await ScriptManager.instance;
|
||||
HttpRequest? request = await scriptManager.runScript(httpRequest);
|
||||
@@ -282,7 +282,7 @@ class HttpResponseProxyHandler extends ChannelHandler<HttpResponse> {
|
||||
listener?.onResponse(channelContext, msg);
|
||||
|
||||
//屏蔽响应
|
||||
var uri = '${request?.remoteDomain()}${request?.path()}';
|
||||
var uri = request?.domainPath ?? '';
|
||||
var blockResponse = (await RequestBlockManager.instance).enableBlockResponse(uri);
|
||||
if (blockResponse) {
|
||||
channel.close();
|
||||
|
||||
@@ -156,8 +156,8 @@ abstract class HttpCodec<T extends HttpMessage> implements Codec<T> {
|
||||
initialLine(builder, message);
|
||||
|
||||
List<int>? body = message.body;
|
||||
if (message.headers.isGzip) {
|
||||
body = gzipEncode(body!);
|
||||
if (message.headers.isGzip && body != null) {
|
||||
body = gzipEncode(body);
|
||||
}
|
||||
|
||||
//请求头
|
||||
|
||||
@@ -56,7 +56,7 @@ abstract class HttpMessage {
|
||||
String? remoteHost;
|
||||
int? remotePort;
|
||||
|
||||
String requestId = (DateTime.now().millisecondsSinceEpoch + Random().nextInt(99999)).toRadixString(36);
|
||||
String requestId = (DateTime.now().millisecondsSinceEpoch + Random().nextInt(999999)).toRadixString(36);
|
||||
int? streamId; // http2 streamId
|
||||
HttpMessage(this.protocolVersion);
|
||||
|
||||
@@ -151,7 +151,11 @@ class HttpRequest extends HttpMessage {
|
||||
}
|
||||
}
|
||||
|
||||
String path() {
|
||||
///域名+路径
|
||||
String get domainPath => '${remoteDomain()}$path';
|
||||
|
||||
/// 请求的path
|
||||
String get path {
|
||||
try {
|
||||
var requestPath = Uri.parse(requestUrl).path;
|
||||
return requestPath.isEmpty ? "" : requestPath;
|
||||
|
||||
@@ -34,7 +34,7 @@ class ProxyHelper {
|
||||
//请求本服务
|
||||
static localRequest(HttpRequest msg, Channel channel) async {
|
||||
//获取配置
|
||||
if (msg.path() == '/config') {
|
||||
if (msg.path == '/config') {
|
||||
final requestRewrites = await RequestRewriteManager.instance;
|
||||
var response = HttpResponse(HttpStatus.ok, protocolVersion: msg.protocolVersion);
|
||||
var body = {
|
||||
|
||||
@@ -13,14 +13,12 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
import 'dart:io';
|
||||
import 'dart:math';
|
||||
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:network_proxy/ui/component/json/theme.dart';
|
||||
import 'package:network_proxy/ui/component/utils.dart';
|
||||
|
||||
class JsonText extends StatelessWidget {
|
||||
class JsonText extends StatefulWidget {
|
||||
final ColorTheme colorTheme;
|
||||
final dynamic json;
|
||||
final String indent;
|
||||
@@ -28,6 +26,31 @@ class JsonText extends StatelessWidget {
|
||||
|
||||
const JsonText({super.key, required this.json, this.indent = ' ', required this.colorTheme, this.scrollController});
|
||||
|
||||
@override
|
||||
State<StatefulWidget> createState() => _JsonTextSate();
|
||||
}
|
||||
|
||||
class _JsonTextSate extends State<JsonText> {
|
||||
late ColorTheme colorTheme;
|
||||
late dynamic json;
|
||||
late String indent;
|
||||
late ScrollController? scrollController;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
colorTheme = widget.colorTheme;
|
||||
json = widget.json;
|
||||
indent = widget.indent;
|
||||
scrollController = trackingScroll(widget.scrollController);
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
scrollController?.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
var jsnParser = JsnParser(json, colorTheme, indent);
|
||||
@@ -49,7 +72,7 @@ class JsonText extends StatelessWidget {
|
||||
height: MediaQuery.of(context).size.height - 160,
|
||||
child: ListView.builder(
|
||||
physics: const BouncingScrollPhysics(),
|
||||
controller: trackingScroll(),
|
||||
controller: scrollController,
|
||||
cacheExtent: 1000,
|
||||
itemBuilder: (context, index) => textList[index],
|
||||
itemCount: textList.length));
|
||||
@@ -60,32 +83,6 @@ class JsonText extends StatelessWidget {
|
||||
return Container();
|
||||
});
|
||||
}
|
||||
|
||||
///滚动条
|
||||
ScrollController trackingScroll() {
|
||||
var trackingScroll = TrackingScrollController();
|
||||
double offset = 0;
|
||||
trackingScroll.addListener(() {
|
||||
if (trackingScroll.offset < -10 || (trackingScroll.offset < 30 && trackingScroll.offset < offset)) {
|
||||
if (scrollController != null && scrollController!.offset >= 0) {
|
||||
scrollController?.jumpTo(scrollController!.offset - max((offset - trackingScroll.offset), 15));
|
||||
}
|
||||
}
|
||||
offset = trackingScroll.offset;
|
||||
});
|
||||
|
||||
if (Platform.isIOS) {
|
||||
scrollController?.addListener(() {
|
||||
if (scrollController!.offset >= scrollController!.position.maxScrollExtent) {
|
||||
scrollController?.jumpTo(scrollController!.position.maxScrollExtent);
|
||||
trackingScroll
|
||||
.jumpTo(trackingScroll.offset + (scrollController!.offset - scrollController!.position.maxScrollExtent));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return trackingScroll;
|
||||
}
|
||||
}
|
||||
|
||||
class JsnParser {
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
*/
|
||||
|
||||
import 'dart:io';
|
||||
import 'dart:math';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
@@ -204,3 +205,27 @@ Future<T?> showConfirmDialog<T>(BuildContext context, {String? title, String? co
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
///滚动条
|
||||
ScrollController? trackingScroll(ScrollController? scrollController) {
|
||||
if (scrollController == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
var trackingScroll = TrackingScrollController();
|
||||
double offset = 0;
|
||||
trackingScroll.addListener(() {
|
||||
if (trackingScroll.offset < 30 && trackingScroll.offset < offset && scrollController.offset > 0) {
|
||||
//往上滚动
|
||||
scrollController.jumpTo(scrollController.offset - max(offset - trackingScroll.offset, 15));
|
||||
} else if (trackingScroll.offset > 0 &&
|
||||
trackingScroll.offset > offset &&
|
||||
scrollController.offset < scrollController.position.maxScrollExtent) {
|
||||
//往下滚动
|
||||
scrollController.jumpTo(scrollController.offset + max(trackingScroll.offset - offset, 15));
|
||||
}
|
||||
|
||||
offset = trackingScroll.offset;
|
||||
});
|
||||
return trackingScroll;
|
||||
}
|
||||
|
||||
@@ -226,7 +226,8 @@ class HttpBodyState extends State<HttpBodyWidget> {
|
||||
if (!mounted) return;
|
||||
|
||||
if (Platforms.isMobile()) {
|
||||
Navigator.push(context, MaterialPageRoute(builder: (_) => RewriteRule(rule: rule, items: rewriteItems)));
|
||||
Navigator.push(
|
||||
context, MaterialPageRoute(builder: (_) => RewriteRule(rule: rule, items: rewriteItems, request: request)));
|
||||
} else {
|
||||
showDialog(
|
||||
context: context,
|
||||
|
||||
@@ -285,7 +285,7 @@ class NetworkTabState extends State<NetworkTabController> with SingleTickerProvi
|
||||
}
|
||||
|
||||
var scrollController = ScrollController(); //处理body也有滚动条问题
|
||||
var path = widget.request.get()?.path() ?? '';
|
||||
var path = widget.request.get()?.path ?? '';
|
||||
try {
|
||||
path = Uri.decodeFull(path);
|
||||
} catch (_) {}
|
||||
|
||||
@@ -149,10 +149,10 @@ class DesktopRequestListState extends State<DesktopRequestListWidget> with Autom
|
||||
///清理
|
||||
clean() {
|
||||
setState(() {
|
||||
container.clear();
|
||||
domainListKey.currentState?.clean();
|
||||
requestSequenceKey.currentState?.clean();
|
||||
widget.panel.change(null, null);
|
||||
container.clear();
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -84,7 +84,7 @@ class _RequestWidgetState extends State<RequestWidget> {
|
||||
Widget build(BuildContext context) {
|
||||
var request = widget.request;
|
||||
var response = widget.response.get() ?? request.response;
|
||||
String path = widget.displayDomain ? '${request.remoteDomain()}${request.path()}' : request.path();
|
||||
String path = widget.displayDomain ? request.domainPath : request.path;
|
||||
String title = '${request.method.name} $path';
|
||||
|
||||
var time = formatDate(request.requestTime, [HH, ':', nn, ':', ss]);
|
||||
@@ -186,7 +186,7 @@ class _RequestWidgetState extends State<RequestWidget> {
|
||||
label: localizations.script,
|
||||
onClick: (_) async {
|
||||
var scriptManager = await ScriptManager.instance;
|
||||
var url = '${widget.request.remoteDomain()}${widget.request.path()}';
|
||||
var url = widget.request.domainPath;
|
||||
var scriptItem = (scriptManager).list.firstWhereOrNull((it) => it.url == url);
|
||||
|
||||
String? script = scriptItem == null ? null : await scriptManager.getScript(scriptItem);
|
||||
|
||||
@@ -460,6 +460,9 @@ class RewriteRuleEdit extends StatefulWidget {
|
||||
}
|
||||
|
||||
class _RewriteRuleEditState extends State<RewriteRuleEdit> {
|
||||
final rewriteReplaceKey = GlobalKey<RewriteReplaceState>();
|
||||
final rewriteUpdateKey = GlobalKey<RewriteUpdateState>();
|
||||
|
||||
late RequestRewriteRule rule;
|
||||
List<RewriteItem>? items;
|
||||
|
||||
@@ -467,9 +470,6 @@ class _RewriteRuleEditState extends State<RewriteRuleEdit> {
|
||||
late TextEditingController nameInput;
|
||||
late TextEditingController urlInput;
|
||||
|
||||
final rewriteReplaceKey = GlobalKey<RewriteReplaceState>();
|
||||
final rewriteUpdateKey = GlobalKey<RewriteUpdateState>();
|
||||
|
||||
AppLocalizations get localizations => AppLocalizations.of(context)!;
|
||||
|
||||
@override
|
||||
@@ -482,7 +482,6 @@ class _RewriteRuleEditState extends State<RewriteRuleEdit> {
|
||||
urlInput = TextEditingController(text: rule.url);
|
||||
|
||||
if (items == null && widget.request != null) {
|
||||
print('items == null && widget.request != null');
|
||||
items = fromRequestItems(widget.request!, ruleType);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -82,7 +82,7 @@ class RewriteUpdateState extends State<DesktopRewriteUpdate> {
|
||||
))
|
||||
],
|
||||
),
|
||||
UpdateList(items: items, ruleType: ruleType),
|
||||
UpdateList(items: items, ruleType: ruleType, request: widget.request),
|
||||
],
|
||||
);
|
||||
}
|
||||
@@ -221,12 +221,15 @@ class _RewriteUpdateAddState extends State<RewriteUpdateAddDialog> {
|
||||
controller: valueController),
|
||||
const SizedBox(height: 10),
|
||||
Row(children: [
|
||||
Align(alignment: Alignment.centerLeft, child: Text('测试数据', style: const TextStyle(fontSize: 14))),
|
||||
Align(
|
||||
alignment: Alignment.centerLeft,
|
||||
child: Text(localizations.testData, style: const TextStyle(fontSize: 14))),
|
||||
const SizedBox(width: 10),
|
||||
if (!isMatch) Text('未检测到变更', style: TextStyle(color: Colors.red, fontSize: 14))
|
||||
if (!isMatch)
|
||||
Text(localizations.noChangesDetected, style: TextStyle(color: Colors.red, fontSize: 14))
|
||||
]),
|
||||
const SizedBox(height: 5),
|
||||
formField('输入待匹配的数据', lines: 10, required: false, controller: dataController),
|
||||
formField(localizations.enterMatchData, lines: 10, required: false, controller: dataController),
|
||||
]))));
|
||||
}
|
||||
|
||||
@@ -294,7 +297,6 @@ class _RewriteUpdateAddState extends State<RewriteUpdateAddDialog> {
|
||||
}
|
||||
|
||||
var match = dataController.highlight(key, caseSensitive: rewriteType != RewriteType.updateHeader);
|
||||
print('onChangeMatch $match');
|
||||
isMatch = match;
|
||||
});
|
||||
});
|
||||
@@ -333,8 +335,9 @@ class _RewriteUpdateAddState extends State<RewriteUpdateAddDialog> {
|
||||
class UpdateList extends StatefulWidget {
|
||||
final List<RewriteItem> items;
|
||||
final RuleType ruleType;
|
||||
final HttpRequest? request;
|
||||
|
||||
const UpdateList({super.key, required this.items, required this.ruleType});
|
||||
const UpdateList({super.key, required this.items, required this.ruleType, this.request});
|
||||
|
||||
@override
|
||||
State<UpdateList> createState() => _UpdateListState();
|
||||
@@ -383,7 +386,8 @@ class _UpdateListState extends State<UpdateList> {
|
||||
hoverColor: primaryColor.withOpacity(0.3),
|
||||
onDoubleTap: () => showDialog(
|
||||
context: context,
|
||||
builder: (context) => RewriteUpdateAddDialog(item: list[index], ruleType: widget.ruleType))
|
||||
builder: (context) =>
|
||||
RewriteUpdateAddDialog(item: list[index], ruleType: widget.ruleType, request: widget.request))
|
||||
.then((value) {
|
||||
if (value != null) setState(() {});
|
||||
}),
|
||||
@@ -439,8 +443,8 @@ class _UpdateListState extends State<UpdateList> {
|
||||
showDialog(
|
||||
context: context,
|
||||
barrierDismissible: false,
|
||||
builder: (BuildContext context) =>
|
||||
RewriteUpdateAddDialog(item: widget.items[index], ruleType: widget.ruleType)).then((value) {
|
||||
builder: (BuildContext context) => RewriteUpdateAddDialog(
|
||||
item: widget.items[index], ruleType: widget.ruleType, request: widget.request)).then((value) {
|
||||
if (value != null) {
|
||||
setState(() {});
|
||||
}
|
||||
|
||||
@@ -214,7 +214,6 @@ class _ScriptConsoleState extends State<ScriptConsoleWidget> {
|
||||
super.initState();
|
||||
DesktopMultiWindow.invokeMethod(0, "registerConsoleLog", widget.windowId);
|
||||
DesktopMultiWindow.setMethodHandler((call, fromWindowId) async {
|
||||
// print("consoleLog $scrollEnd $fromWindowId ${call.arguments}");
|
||||
if (call.method == 'consoleLog') {
|
||||
setState(() {
|
||||
var logInfo = LogInfo(call.arguments['level'], call.arguments['output']);
|
||||
@@ -236,7 +235,6 @@ class _ScriptConsoleState extends State<ScriptConsoleWidget> {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
// print("script build");
|
||||
return Scaffold(
|
||||
backgroundColor: Theme.of(context).dialogBackgroundColor,
|
||||
appBar: AppBar(
|
||||
|
||||
@@ -92,7 +92,7 @@ class DrawerWidget extends StatelessWidget {
|
||||
onTap: () => navigator(context, FilterMenu(proxyServer: proxyServer))),
|
||||
ListTile(
|
||||
title: Text(localizations.requestRewrite),
|
||||
leading: const Icon(Icons.replay_outlined),
|
||||
leading: const Icon(Icons.edit_outlined),
|
||||
onTap: () async {
|
||||
var requestRewrites = await RequestRewriteManager.instance;
|
||||
if (context.mounted) {
|
||||
|
||||
@@ -94,7 +94,7 @@ class _MePageState extends State<MePage> {
|
||||
onTap: () => navigator(context, FilterMenu(proxyServer: proxyServer))),
|
||||
ListTile(
|
||||
title: Text(localizations.requestRewrite),
|
||||
leading: Icon(Icons.replay_outlined, color: color),
|
||||
leading: Icon(Icons.edit_outlined, color: color),
|
||||
trailing: const Icon(Icons.arrow_forward_ios, size: 16),
|
||||
onTap: () async {
|
||||
var requestRewrites = await RequestRewriteManager.instance;
|
||||
|
||||
@@ -24,6 +24,7 @@ import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
||||
import 'package:flutter_toastr/flutter_toastr.dart';
|
||||
import 'package:network_proxy/network/bin/server.dart';
|
||||
import 'package:network_proxy/network/components/rewrite/request_rewrite_manager.dart';
|
||||
import 'package:network_proxy/network/components/rewrite/rewrite_rule.dart';
|
||||
import 'package:network_proxy/network/components/script_manager.dart';
|
||||
import 'package:network_proxy/network/host_port.dart';
|
||||
import 'package:network_proxy/network/http/http.dart';
|
||||
@@ -136,7 +137,7 @@ class _FavoriteItemState extends State<_FavoriteItem> {
|
||||
style: TextStyle(fontSize: 14, color: Colors.blue),
|
||||
),
|
||||
TextSpan(
|
||||
text: request.path(),
|
||||
text: request.path,
|
||||
style: TextStyle(fontSize: 14, color: Colors.green),
|
||||
),
|
||||
if (request.requestUri?.query.isNotEmpty == true)
|
||||
@@ -249,7 +250,7 @@ class _FavoriteItemState extends State<_FavoriteItem> {
|
||||
Navigator.push(context, pageRoute);
|
||||
},
|
||||
label: localizations.editRequest,
|
||||
icon: Icons.edit_outlined),
|
||||
icon: Icons.replay_outlined),
|
||||
),
|
||||
|
||||
//script and rewrite
|
||||
@@ -257,9 +258,8 @@ class _FavoriteItemState extends State<_FavoriteItem> {
|
||||
left: itemButton(
|
||||
onPressed: () async {
|
||||
Navigator.maybePop(context);
|
||||
|
||||
var scriptManager = await ScriptManager.instance;
|
||||
var url = '${request.remoteDomain()}${request.path()}';
|
||||
var url = request.domainPath;
|
||||
var scriptItem = (scriptManager).list.firstWhereOrNull((it) => it.url == url);
|
||||
String? script = scriptItem == null ? null : await scriptManager.getScript(scriptItem);
|
||||
|
||||
@@ -276,11 +276,17 @@ class _FavoriteItemState extends State<_FavoriteItem> {
|
||||
bool isRequest = request.response == null;
|
||||
var requestRewrites = await RequestRewriteManager.instance;
|
||||
|
||||
var pageRoute = MaterialPageRoute(builder: (_) => RewriteRule());
|
||||
var ruleType = isRequest ? RuleType.requestReplace : RuleType.responseReplace;
|
||||
var rule = requestRewrites.getRequestRewriteRule(request, ruleType);
|
||||
|
||||
var rewriteItems = await requestRewrites.getRewriteItems(rule);
|
||||
|
||||
var pageRoute = MaterialPageRoute(
|
||||
builder: (_) => RewriteRule(rule: rule, items: rewriteItems, request: request));
|
||||
if (mounted) Navigator.push(context, pageRoute);
|
||||
},
|
||||
label: localizations.requestRewrite,
|
||||
icon: Icons.replay_outlined),
|
||||
icon: Icons.edit_outlined),
|
||||
),
|
||||
SizedBox(height: 2),
|
||||
Row(mainAxisAlignment: MainAxisAlignment.center, children: [
|
||||
@@ -362,9 +368,12 @@ class _FavoriteItemState extends State<_FavoriteItem> {
|
||||
|
||||
Widget itemButton(
|
||||
{required String label, required IconData icon, required Function() onPressed, double iconSize = 20}) {
|
||||
var style = Theme.of(context).textTheme.bodyMedium;
|
||||
var theme = Theme.of(context);
|
||||
var style = theme.textTheme.bodyMedium;
|
||||
return TextButton.icon(
|
||||
onPressed: onPressed, label: Text(label, style: style), icon: Icon(icon, size: iconSize, color: style?.color));
|
||||
onPressed: onPressed,
|
||||
label: Text(label, style: style),
|
||||
icon: Icon(icon, size: iconSize, color: theme.colorScheme.primary.withOpacity(0.65)));
|
||||
}
|
||||
|
||||
Widget menuItem({required Widget left, required Widget right}) {
|
||||
|
||||
@@ -126,9 +126,9 @@ class RequestListState extends State<RequestListWidget> {
|
||||
///清理
|
||||
clean() {
|
||||
setState(() {
|
||||
container.clear();
|
||||
domainListKey.currentState?.clean();
|
||||
requestSequenceKey.currentState?.clean();
|
||||
container.clear();
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -101,7 +101,7 @@ class RequestRowState extends State<RequestRow> {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
String url = widget.displayDomain ? request.requestUrl : request.path();
|
||||
String url = widget.displayDomain ? request.requestUrl : request.path;
|
||||
|
||||
var title = Strings.autoLineString('${request.method.name} $url');
|
||||
|
||||
@@ -250,7 +250,7 @@ class RequestRowState extends State<RequestRow> {
|
||||
Navigator.push(getContext(), pageRoute);
|
||||
},
|
||||
label: localizations.editRequest,
|
||||
icon: Icons.edit_outlined),
|
||||
icon: Icons.replay_outlined),
|
||||
),
|
||||
//script and rewrite
|
||||
menuItem(
|
||||
@@ -259,8 +259,8 @@ class RequestRowState extends State<RequestRow> {
|
||||
Navigator.maybePop(availableContext);
|
||||
|
||||
var scriptManager = await ScriptManager.instance;
|
||||
var url = '${request.remoteDomain()}${request.path()}';
|
||||
var scriptItem = (scriptManager).list.firstWhereOrNull((it) => it.url == url);
|
||||
var url = request.domainPath;
|
||||
var scriptItem = scriptManager.list.firstWhereOrNull((it) => it.url == url);
|
||||
String? script = scriptItem == null ? null : await scriptManager.getScript(scriptItem);
|
||||
|
||||
var pageRoute = MaterialPageRoute(
|
||||
@@ -278,24 +278,17 @@ class RequestRowState extends State<RequestRow> {
|
||||
var requestRewrites = await RequestRewriteManager.instance;
|
||||
|
||||
var ruleType = isRequest ? RuleType.requestReplace : RuleType.responseReplace;
|
||||
var url = '${request.remoteDomain()}${request.path()}';
|
||||
var rule = requestRewrites.rules.firstWhere((it) => it.matchUrl(url, ruleType),
|
||||
orElse: () => RequestRewriteRule(type: ruleType, url: url));
|
||||
var rule = requestRewrites.getRequestRewriteRule(request, ruleType);
|
||||
|
||||
var rewriteItems = await requestRewrites.getRewriteItems(rule);
|
||||
RewriteType rewriteType =
|
||||
isRequest ? RewriteType.replaceRequestBody : RewriteType.replaceResponseBody;
|
||||
// if (!rewriteItems?.any((element) => element.type == rewriteType)) {
|
||||
// rewriteItems.add(RewriteItem(rewriteType, true,
|
||||
// values: {'body': isRequest ? request.bodyAsString : response?.bodyAsString}));
|
||||
// }
|
||||
|
||||
var pageRoute = MaterialPageRoute(builder: (_) => RewriteRule(rule: rule, items: rewriteItems));
|
||||
var pageRoute = MaterialPageRoute(
|
||||
builder: (_) => RewriteRule(rule: rule, items: rewriteItems, request: request));
|
||||
var context = availableContext;
|
||||
if (context.mounted) Navigator.push(context, pageRoute);
|
||||
},
|
||||
label: localizations.requestRewrite,
|
||||
icon: Icons.replay_outlined),
|
||||
icon: Icons.edit_outlined),
|
||||
),
|
||||
menuItem(
|
||||
left: itemButton(
|
||||
@@ -353,9 +346,12 @@ class RequestRowState extends State<RequestRow> {
|
||||
|
||||
Widget itemButton(
|
||||
{required String label, required IconData icon, required Function() onPressed, double iconSize = 20}) {
|
||||
var style = Theme.of(context).textTheme.bodyMedium;
|
||||
var theme = Theme.of(context);
|
||||
var style = theme.textTheme.bodyMedium;
|
||||
return TextButton.icon(
|
||||
onPressed: onPressed, label: Text(label, style: style), icon: Icon(icon, size: iconSize, color: style?.color));
|
||||
onPressed: onPressed,
|
||||
label: Text(label, style: style),
|
||||
icon: Icon(icon, size: iconSize, color: theme.colorScheme.primary.withOpacity(0.65)));
|
||||
}
|
||||
|
||||
Widget menuItem({required Widget left, required Widget right}) {
|
||||
|
||||
@@ -26,8 +26,8 @@ class RequestSequence extends StatefulWidget {
|
||||
}
|
||||
|
||||
class RequestSequenceState extends State<RequestSequence> with AutomaticKeepAliveClientMixin {
|
||||
///请求和对应的row的映射
|
||||
Map<HttpRequest, GlobalKey<RequestRowState>> indexes = HashMap();
|
||||
///请求id和对应的row的映射
|
||||
Map<String, GlobalKey<RequestRowState>> indexes = HashMap();
|
||||
|
||||
///显示的请求列表 最新的在前面
|
||||
Queue<HttpRequest> view = Queue();
|
||||
@@ -71,7 +71,7 @@ class RequestSequenceState extends State<RequestSequence> with AutomaticKeepAliv
|
||||
|
||||
///添加响应
|
||||
addResponse(HttpResponse response) {
|
||||
var state = indexes.remove(response.request);
|
||||
var state = indexes.remove(response.request?.requestId);
|
||||
state?.currentState?.change(response);
|
||||
|
||||
if (searchModel == null || searchModel!.isEmpty || response.request == null) {
|
||||
@@ -145,10 +145,9 @@ class RequestSequenceState extends State<RequestSequence> with AutomaticKeepAliv
|
||||
Divider(thickness: 0.2, height: 0, color: Theme.of(context).dividerColor),
|
||||
itemCount: view.length,
|
||||
itemBuilder: (context, index) {
|
||||
GlobalKey<RequestRowState> key = indexes[view.elementAt(index)] ??= GlobalKey();
|
||||
return RequestRow(
|
||||
index: view.length - index,
|
||||
key: key,
|
||||
key: indexes[view.elementAt(index).requestId] ??= GlobalKey(),
|
||||
request: view.elementAt(index),
|
||||
proxyServer: widget.proxyServer,
|
||||
displayDomain: widget.displayDomain,
|
||||
|
||||
@@ -402,8 +402,9 @@ class _RequestRuleListState extends State<RequestRuleList> {
|
||||
class RewriteRule extends StatefulWidget {
|
||||
final RequestRewriteRule? rule;
|
||||
final List<RewriteItem>? items;
|
||||
final HttpRequest? request;
|
||||
|
||||
const RewriteRule({super.key, this.rule, this.items});
|
||||
const RewriteRule({super.key, this.rule, this.items, this.request});
|
||||
|
||||
@override
|
||||
State<StatefulWidget> createState() {
|
||||
@@ -423,6 +424,8 @@ class _RewriteRuleState extends State<RewriteRule> {
|
||||
|
||||
AppLocalizations get localizations => AppLocalizations.of(context)!;
|
||||
|
||||
final ScrollController scrollController = ScrollController();
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
@@ -432,12 +435,17 @@ class _RewriteRuleState extends State<RewriteRule> {
|
||||
|
||||
nameInput = TextEditingController(text: rule.name);
|
||||
urlInput = TextEditingController(text: rule.url);
|
||||
|
||||
if (items == null && widget.request != null) {
|
||||
items = fromRequestItems(widget.request!, ruleType);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
urlInput.dispose();
|
||||
nameInput.dispose();
|
||||
scrollController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@@ -491,52 +499,71 @@ class _RewriteRuleState extends State<RewriteRule> {
|
||||
),
|
||||
body: Padding(
|
||||
padding: const EdgeInsets.all(15),
|
||||
child: Form(
|
||||
key: formKey,
|
||||
child: ListView(children: <Widget>[
|
||||
Row(children: [
|
||||
SizedBox(
|
||||
width: 60,
|
||||
child: Text('${localizations.enable}:',
|
||||
style: const TextStyle(fontSize: 16, fontWeight: FontWeight.w500))),
|
||||
SwitchWidget(value: rule.enabled, onChanged: (val) => rule.enabled = val, scale: 0.8)
|
||||
]),
|
||||
textField('${localizations.name}:', nameInput, localizations.pleaseEnter),
|
||||
textField('URL:', urlInput, 'https://www.example.com/api/*',
|
||||
required: true, keyboardType: TextInputType.url),
|
||||
Row(children: [
|
||||
SizedBox(
|
||||
width: 60,
|
||||
child: Text('${localizations.action}:',
|
||||
style: const TextStyle(fontSize: 16, fontWeight: FontWeight.w500))),
|
||||
SizedBox(
|
||||
width: 165,
|
||||
height: 50,
|
||||
child: DropdownButtonFormField<RuleType>(
|
||||
onSaved: (val) => rule.type = val!,
|
||||
value: ruleType,
|
||||
decoration: const InputDecoration(
|
||||
errorStyle: TextStyle(height: 0, fontSize: 0), contentPadding: EdgeInsets.only()),
|
||||
items: RuleType.values
|
||||
.map((e) => DropdownMenuItem(value: e, child: Text(isCN ? e.label : e.name)))
|
||||
.toList(),
|
||||
onChanged: (val) {
|
||||
setState(() {
|
||||
ruleType = val!;
|
||||
items = ruleType == widget.rule?.type ? widget.items : [];
|
||||
rewriteReplaceKey.currentState?.initItems(ruleType, items);
|
||||
rewriteUpdateKey.currentState?.initItems(ruleType, items);
|
||||
});
|
||||
},
|
||||
)),
|
||||
const SizedBox(width: 10),
|
||||
]),
|
||||
const SizedBox(height: 10),
|
||||
rewriteRule()
|
||||
])),
|
||||
child: NestedScrollView(
|
||||
controller: scrollController,
|
||||
headerSliverBuilder: (BuildContext context, bool innerBoxIsScrolled) {
|
||||
return <Widget>[
|
||||
SliverToBoxAdapter(
|
||||
child: Form(
|
||||
key: formKey,
|
||||
child: Column(children: <Widget>[
|
||||
Row(children: [
|
||||
SizedBox(
|
||||
width: 60,
|
||||
child: Text('${localizations.enable}:',
|
||||
style: const TextStyle(fontSize: 16, fontWeight: FontWeight.w500))),
|
||||
SwitchWidget(value: rule.enabled, onChanged: (val) => rule.enabled = val, scale: 0.8)
|
||||
]),
|
||||
textField('${localizations.name}:', nameInput, localizations.pleaseEnter),
|
||||
textField('URL:', urlInput, 'https://www.example.com/api/*',
|
||||
required: true, keyboardType: TextInputType.url),
|
||||
Row(children: [
|
||||
SizedBox(
|
||||
width: 60,
|
||||
child: Text('${localizations.action}:',
|
||||
style: const TextStyle(fontSize: 16, fontWeight: FontWeight.w500))),
|
||||
SizedBox(
|
||||
width: 165,
|
||||
height: 50,
|
||||
child: DropdownButtonFormField<RuleType>(
|
||||
onSaved: (val) => rule.type = val!,
|
||||
value: ruleType,
|
||||
decoration: const InputDecoration(
|
||||
errorStyle: TextStyle(height: 0, fontSize: 0), contentPadding: EdgeInsets.only()),
|
||||
items: RuleType.values
|
||||
.map((e) => DropdownMenuItem(value: e, child: Text(isCN ? e.label : e.name)))
|
||||
.toList(),
|
||||
onChanged: onChangeType,
|
||||
)),
|
||||
const SizedBox(width: 10),
|
||||
]),
|
||||
const SizedBox(height: 10),
|
||||
]),
|
||||
))
|
||||
];
|
||||
},
|
||||
body: rewriteRule()),
|
||||
));
|
||||
}
|
||||
|
||||
void onChangeType(RuleType? val) async {
|
||||
if (ruleType == val) return;
|
||||
|
||||
ruleType = val!;
|
||||
items = [];
|
||||
|
||||
if (ruleType == widget.rule?.type) {
|
||||
items = widget.items;
|
||||
} else if (widget.request != null) {
|
||||
items?.addAll(fromRequestItems(widget.request!, ruleType));
|
||||
}
|
||||
|
||||
setState(() {
|
||||
rewriteReplaceKey.currentState?.initItems(ruleType, items);
|
||||
rewriteUpdateKey.currentState?.initItems(ruleType, items);
|
||||
});
|
||||
}
|
||||
|
||||
static List<RewriteItem> fromRequestItems(HttpRequest request, RuleType ruleType) {
|
||||
if (ruleType == RuleType.requestReplace) {
|
||||
//请求替换
|
||||
@@ -550,10 +577,11 @@ class _RewriteRuleState extends State<RewriteRule> {
|
||||
|
||||
Widget rewriteRule() {
|
||||
if (ruleType == RuleType.requestUpdate || ruleType == RuleType.responseUpdate) {
|
||||
return MobileRewriteUpdate(key: rewriteUpdateKey, items: items, ruleType: ruleType);
|
||||
return MobileRewriteUpdate(key: rewriteUpdateKey, items: items, ruleType: ruleType, request: widget.request);
|
||||
}
|
||||
|
||||
return MobileRewriteReplace(key: rewriteReplaceKey, items: items, ruleType: ruleType);
|
||||
return MobileRewriteReplace(
|
||||
scrollController: scrollController, key: rewriteReplaceKey, items: items, ruleType: ruleType);
|
||||
}
|
||||
|
||||
Widget textField(String label, TextEditingController controller, String hint,
|
||||
|
||||
@@ -20,6 +20,7 @@ import 'package:flutter/services.dart';
|
||||
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
||||
import 'package:network_proxy/network/components/rewrite/rewrite_rule.dart';
|
||||
import 'package:network_proxy/ui/component/state_component.dart';
|
||||
import 'package:network_proxy/ui/component/utils.dart';
|
||||
import 'package:network_proxy/ui/component/widgets.dart';
|
||||
import 'package:network_proxy/utils/lang.dart';
|
||||
|
||||
@@ -28,8 +29,9 @@ import 'package:network_proxy/utils/lang.dart';
|
||||
class MobileRewriteReplace extends StatefulWidget {
|
||||
final RuleType ruleType;
|
||||
final List<RewriteItem>? items;
|
||||
final ScrollController? scrollController;
|
||||
|
||||
const MobileRewriteReplace({super.key, this.items, required this.ruleType});
|
||||
const MobileRewriteReplace({super.key, this.items, required this.ruleType, this.scrollController});
|
||||
|
||||
@override
|
||||
State<MobileRewriteReplace> createState() => RewriteReplaceState();
|
||||
@@ -38,6 +40,7 @@ class MobileRewriteReplace extends StatefulWidget {
|
||||
class RewriteReplaceState extends State<MobileRewriteReplace> {
|
||||
final _headerKey = GlobalKey<HeadersState>();
|
||||
final bodyTextController = TextEditingController();
|
||||
late ScrollController? bodyScrollController;
|
||||
|
||||
late RuleType ruleType;
|
||||
List<RewriteItem> items = [];
|
||||
@@ -48,11 +51,13 @@ class RewriteReplaceState extends State<MobileRewriteReplace> {
|
||||
initState() {
|
||||
super.initState();
|
||||
initItems(widget.ruleType, widget.items);
|
||||
bodyScrollController = trackingScroll(widget.scrollController);
|
||||
}
|
||||
|
||||
@override
|
||||
dispose() {
|
||||
bodyTextController.dispose();
|
||||
bodyScrollController?.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@@ -118,19 +123,17 @@ class RewriteReplaceState extends State<MobileRewriteReplace> {
|
||||
? [localizations.requestLine, localizations.requestHeader, localizations.requestBody]
|
||||
: [localizations.statusCode, localizations.responseHeader, localizations.responseBody];
|
||||
|
||||
return Container(
|
||||
constraints: BoxConstraints(maxHeight: 660, minHeight: 350),
|
||||
child: DefaultTabController(
|
||||
length: tabs.length,
|
||||
initialIndex: tabs.length - 1,
|
||||
child: Scaffold(
|
||||
appBar: tabBar(tabs),
|
||||
body: TabBarView(children: [
|
||||
KeepAliveWrapper(child: requestEdited ? requestLine() : statusCodeEdit()),
|
||||
KeepAliveWrapper(child: headers()),
|
||||
KeepAliveWrapper(child: body())
|
||||
])),
|
||||
));
|
||||
return DefaultTabController(
|
||||
length: tabs.length,
|
||||
initialIndex: tabs.length - 1,
|
||||
child: Scaffold(
|
||||
appBar: tabBar(tabs),
|
||||
body: TabBarView(children: [
|
||||
KeepAliveWrapper(child: requestEdited ? requestLine() : statusCodeEdit()),
|
||||
KeepAliveWrapper(child: headers()),
|
||||
KeepAliveWrapper(child: body())
|
||||
])),
|
||||
);
|
||||
}
|
||||
|
||||
return Container();
|
||||
@@ -157,7 +160,7 @@ class RewriteReplaceState extends State<MobileRewriteReplace> {
|
||||
var rewriteItem = items.firstWhere(
|
||||
(item) => item.type == RewriteType.replaceRequestBody || item.type == RewriteType.replaceResponseBody);
|
||||
|
||||
return Column(children: [
|
||||
return ListView(physics: const ClampingScrollPhysics(), children: [
|
||||
Row(mainAxisAlignment: MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.center, children: [
|
||||
const SizedBox(width: 5),
|
||||
Text("${localizations.type}: "),
|
||||
@@ -196,8 +199,11 @@ class RewriteReplaceState extends State<MobileRewriteReplace> {
|
||||
else
|
||||
TextFormField(
|
||||
controller: bodyTextController,
|
||||
scrollPhysics: const BouncingScrollPhysics(),
|
||||
scrollController: bodyScrollController,
|
||||
style: const TextStyle(fontSize: 14),
|
||||
maxLines: 25,
|
||||
minLines: 20,
|
||||
maxLines: 23,
|
||||
decoration: decoration(localizations.replaceBodyWith,
|
||||
hintText: '${localizations.example} {"code":"200","data":{}}'),
|
||||
onChanged: (val) => rewriteItem.body = val)
|
||||
@@ -242,7 +248,7 @@ class RewriteReplaceState extends State<MobileRewriteReplace> {
|
||||
var rewriteItem = items.firstWhere(
|
||||
(item) => item.type == RewriteType.replaceRequestHeader || item.type == RewriteType.replaceResponseHeader);
|
||||
|
||||
return Column(children: [
|
||||
return ListView(physics: const ClampingScrollPhysics(), children: [
|
||||
Row(mainAxisAlignment: MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.center, children: [
|
||||
const Text('Header'),
|
||||
const SizedBox(width: 10),
|
||||
@@ -258,14 +264,15 @@ class RewriteReplaceState extends State<MobileRewriteReplace> {
|
||||
}))
|
||||
]))
|
||||
]),
|
||||
Headers(headers: rewriteItem.headers, key: _headerKey)
|
||||
Headers(headers: rewriteItem.headers, key: _headerKey, scrollController: widget.scrollController)
|
||||
]);
|
||||
}
|
||||
|
||||
///请求行
|
||||
Widget requestLine() {
|
||||
var rewriteItem = items.firstWhere((item) => item.type == RewriteType.replaceRequestLine);
|
||||
return Column(
|
||||
return ListView(
|
||||
physics: const ClampingScrollPhysics(),
|
||||
children: [
|
||||
Row(children: [
|
||||
Text(localizations.requestMethod),
|
||||
@@ -303,7 +310,7 @@ class RewriteReplaceState extends State<MobileRewriteReplace> {
|
||||
]),
|
||||
const SizedBox(height: 15),
|
||||
textField("Path", rewriteItem.path, "${localizations.example} /api/v1/user",
|
||||
onChanged: (val) => rewriteItem.values['path'] = val),
|
||||
onChanged: (val) => rewriteItem.path = val),
|
||||
const SizedBox(height: 15),
|
||||
textField("URL${localizations.param}", rewriteItem.queryParam, "${localizations.example} id=1&name=2",
|
||||
onChanged: (val) => rewriteItem.queryParam = val),
|
||||
@@ -347,42 +354,41 @@ class RewriteReplaceState extends State<MobileRewriteReplace> {
|
||||
Widget statusCodeEdit() {
|
||||
var rewriteItem = items.firstWhere((item) => item.type == RewriteType.replaceResponseStatus);
|
||||
|
||||
return Column(children: [
|
||||
Row(crossAxisAlignment: CrossAxisAlignment.center, children: [
|
||||
Text(localizations.statusCode),
|
||||
const SizedBox(width: 10),
|
||||
SizedBox(
|
||||
width: 100,
|
||||
child: TextFormField(
|
||||
style: const TextStyle(fontSize: 14),
|
||||
initialValue: rewriteItem.statusCode?.toString(),
|
||||
onChanged: (val) => rewriteItem.statusCode = int.tryParse(val),
|
||||
inputFormatters: [FilteringTextInputFormatter.digitsOnly],
|
||||
decoration: InputDecoration(
|
||||
contentPadding: const EdgeInsets.all(10),
|
||||
focusedBorder: focusedBorder(),
|
||||
isDense: true,
|
||||
border: const OutlineInputBorder()),
|
||||
)),
|
||||
Expanded(
|
||||
child: Row(mainAxisAlignment: MainAxisAlignment.end, children: [
|
||||
Text(localizations.enable),
|
||||
const SizedBox(width: 10),
|
||||
SwitchWidget(
|
||||
value: rewriteItem.enabled,
|
||||
scale: 0.65,
|
||||
onChanged: (val) => setState(() {
|
||||
rewriteItem.enabled = val;
|
||||
}))
|
||||
])),
|
||||
const SizedBox(width: 10),
|
||||
])
|
||||
]);
|
||||
return ListView(physics: const ClampingScrollPhysics(), children: [
|
||||
Row(crossAxisAlignment: CrossAxisAlignment.center, children: [
|
||||
Text(localizations.statusCode),
|
||||
const SizedBox(width: 10),
|
||||
SizedBox(
|
||||
width: 100,
|
||||
child: TextFormField(
|
||||
style: const TextStyle(fontSize: 14),
|
||||
initialValue: rewriteItem.statusCode?.toString(),
|
||||
onChanged: (val) => rewriteItem.statusCode = int.tryParse(val),
|
||||
inputFormatters: [FilteringTextInputFormatter.digitsOnly],
|
||||
decoration: InputDecoration(
|
||||
contentPadding: const EdgeInsets.all(10),
|
||||
focusedBorder: focusedBorder(),
|
||||
isDense: true,
|
||||
border: const OutlineInputBorder()),
|
||||
)),
|
||||
Expanded(
|
||||
child: Row(mainAxisAlignment: MainAxisAlignment.end, children: [
|
||||
Text(localizations.enable),
|
||||
const SizedBox(width: 10),
|
||||
SwitchWidget(
|
||||
value: rewriteItem.enabled,
|
||||
scale: 0.65,
|
||||
onChanged: (val) => setState(() {
|
||||
rewriteItem.enabled = 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,
|
||||
@@ -401,8 +407,9 @@ class RewriteReplaceState extends State<MobileRewriteReplace> {
|
||||
///请求头
|
||||
class Headers extends StatefulWidget {
|
||||
final Map<String, String>? headers;
|
||||
final ScrollController? scrollController;
|
||||
|
||||
const Headers({super.key, this.headers});
|
||||
const Headers({super.key, this.headers, this.scrollController});
|
||||
|
||||
@override
|
||||
State<StatefulWidget> createState() {
|
||||
@@ -465,27 +472,25 @@ class HeadersState extends State<Headers> with AutomaticKeepAliveClientMixin {
|
||||
Widget build(BuildContext context) {
|
||||
super.build(context);
|
||||
|
||||
var list = [
|
||||
..._buildRows(),
|
||||
];
|
||||
|
||||
list.add(TextButton(
|
||||
child: Text("${localizations.add}Header", textAlign: TextAlign.center),
|
||||
onPressed: () {
|
||||
setState(() {
|
||||
_headers[TextEditingController()] = TextEditingController();
|
||||
});
|
||||
},
|
||||
));
|
||||
var list = _buildRows();
|
||||
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(top: 10),
|
||||
child: ListView.separated(
|
||||
shrinkWrap: true,
|
||||
physics: const ClampingScrollPhysics(),
|
||||
separatorBuilder: (context, index) =>
|
||||
index == list.length ? const SizedBox() : const Divider(thickness: 0.2),
|
||||
itemBuilder: (context, index) => list[index],
|
||||
itemCount: list.length));
|
||||
itemBuilder: (context, index) => index < list.length?list[index]
|
||||
: TextButton(
|
||||
child: Text("${localizations.add}Header", textAlign: TextAlign.center),
|
||||
onPressed: () {
|
||||
setState(() {
|
||||
_headers[TextEditingController()] = TextEditingController();
|
||||
});
|
||||
},
|
||||
),
|
||||
itemCount: list.length + 1));
|
||||
}
|
||||
|
||||
List<Widget> _buildRows() {
|
||||
@@ -517,7 +522,11 @@ class HeadersState extends State<Headers> with AutomaticKeepAliveClientMixin {
|
||||
controller: val,
|
||||
minLines: 1,
|
||||
maxLines: 3,
|
||||
decoration: InputDecoration(isDense: true, border: InputBorder.none, hintText: isKey ? "Key" : "Value")));
|
||||
decoration: InputDecoration(
|
||||
isDense: true,
|
||||
border: InputBorder.none,
|
||||
hintStyle: TextStyle(fontSize: 12, color: Colors.grey),
|
||||
hintText: isKey ? "Key" : "Value")));
|
||||
}
|
||||
|
||||
Widget _row(Widget key, Widget val, Widget? op) {
|
||||
|
||||
@@ -18,14 +18,17 @@ import 'package:flutter/material.dart';
|
||||
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
||||
import 'package:flutter_toastr/flutter_toastr.dart';
|
||||
import 'package:network_proxy/network/components/rewrite/rewrite_rule.dart';
|
||||
import 'package:network_proxy/network/http/http.dart';
|
||||
import 'package:network_proxy/ui/component/text_field.dart';
|
||||
import 'package:network_proxy/ui/component/widgets.dart';
|
||||
import 'package:network_proxy/utils/lang.dart';
|
||||
|
||||
class MobileRewriteUpdate extends StatefulWidget {
|
||||
final RuleType ruleType;
|
||||
final List<RewriteItem>? items;
|
||||
final HttpRequest? request;
|
||||
|
||||
const MobileRewriteUpdate({super.key, required this.ruleType, this.items});
|
||||
const MobileRewriteUpdate({super.key, required this.ruleType, this.items, required this.request});
|
||||
|
||||
@override
|
||||
State<MobileRewriteUpdate> createState() => RewriteUpdateState();
|
||||
@@ -61,7 +64,8 @@ class RewriteUpdateState extends State<MobileRewriteUpdate> {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Column(
|
||||
return ListView(
|
||||
physics: ClampingScrollPhysics(),
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
@@ -76,13 +80,15 @@ class RewriteUpdateState extends State<MobileRewriteUpdate> {
|
||||
))
|
||||
],
|
||||
),
|
||||
UpdateList(items: items, ruleType: ruleType),
|
||||
UpdateList(items: items, ruleType: ruleType, request: widget.request),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
add() {
|
||||
showDialog(context: context, builder: (context) => RewriteUpdateAddDialog(ruleType: ruleType)).then((value) {
|
||||
Navigator.of(context)
|
||||
.push(MaterialPageRoute(builder: (context) => RewriteUpdateEdit(ruleType: ruleType, request: widget.request)))
|
||||
.then((value) {
|
||||
if (value != null) {
|
||||
setState(() {
|
||||
items.add(value);
|
||||
@@ -92,21 +98,26 @@ class RewriteUpdateState extends State<MobileRewriteUpdate> {
|
||||
}
|
||||
}
|
||||
|
||||
class RewriteUpdateAddDialog extends StatefulWidget {
|
||||
class RewriteUpdateEdit extends StatefulWidget {
|
||||
final RewriteItem? item;
|
||||
final RuleType ruleType;
|
||||
final HttpRequest? request;
|
||||
|
||||
const RewriteUpdateAddDialog({super.key, this.item, required this.ruleType});
|
||||
const RewriteUpdateEdit({super.key, this.item, required this.ruleType, this.request});
|
||||
|
||||
@override
|
||||
State<RewriteUpdateAddDialog> createState() => _RewriteUpdateAddState();
|
||||
State<RewriteUpdateEdit> createState() => _RewriteUpdateAddState();
|
||||
}
|
||||
|
||||
class _RewriteUpdateAddState extends State<RewriteUpdateAddDialog> {
|
||||
class _RewriteUpdateAddState extends State<RewriteUpdateEdit> {
|
||||
late RewriteType rewriteType;
|
||||
GlobalKey formKey = GlobalKey<FormState>();
|
||||
late RewriteItem rewriteItem;
|
||||
|
||||
var keyController = TextEditingController();
|
||||
var valueController = TextEditingController();
|
||||
var dataController = HighlightTextEditingController();
|
||||
|
||||
AppLocalizations get i18n => AppLocalizations.of(context)!;
|
||||
|
||||
@override
|
||||
@@ -114,6 +125,21 @@ class _RewriteUpdateAddState extends State<RewriteUpdateAddDialog> {
|
||||
super.initState();
|
||||
rewriteType = widget.item?.type ?? RewriteType.updateBody;
|
||||
rewriteItem = widget.item ?? RewriteItem(rewriteType, true);
|
||||
|
||||
keyController.text = rewriteItem.key ?? '';
|
||||
valueController.text = rewriteItem.value ?? '';
|
||||
|
||||
initTestData();
|
||||
keyController.addListener(onInputChangeMatch);
|
||||
dataController.addListener(onInputChangeMatch);
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
keyController.dispose();
|
||||
valueController.dispose();
|
||||
dataController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -134,84 +160,165 @@ class _RewriteUpdateAddState extends State<RewriteUpdateAddDialog> {
|
||||
|
||||
var typeList = widget.ruleType == RuleType.requestUpdate ? RewriteType.updateRequest : RewriteType.updateResponse;
|
||||
bool isCN = Localizations.localeOf(context).languageCode == "zh";
|
||||
return AlertDialog(
|
||||
title: Text(i18n.add,
|
||||
style: const TextStyle(fontSize: 15, fontWeight: FontWeight.w500), textAlign: TextAlign.center),
|
||||
actions: [
|
||||
TextButton(onPressed: () => Navigator.of(context).pop(), child: Text(i18n.cancel)),
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
if (!(formKey.currentState as FormState).validate()) {
|
||||
FlutterToastr.show(i18n.cannotBeEmpty, context, position: FlutterToastr.center);
|
||||
return;
|
||||
}
|
||||
(formKey.currentState as FormState).save();
|
||||
rewriteItem.type = rewriteType;
|
||||
Navigator.of(context).pop(rewriteItem);
|
||||
},
|
||||
child: Text(i18n.confirm)),
|
||||
],
|
||||
content: SizedBox(
|
||||
height: 243,
|
||||
child: Form(
|
||||
key: formKey,
|
||||
child: ListView(children: [
|
||||
Row(
|
||||
children: [
|
||||
Text(i18n.type),
|
||||
const SizedBox(width: 15),
|
||||
SizedBox(
|
||||
width: 140,
|
||||
child: DropdownButtonFormField<RewriteType>(
|
||||
value: rewriteType,
|
||||
focusColor: Colors.transparent,
|
||||
itemHeight: 48,
|
||||
decoration: const InputDecoration(
|
||||
contentPadding: EdgeInsets.all(10), isDense: true, border: InputBorder.none),
|
||||
items: typeList
|
||||
.map((e) => DropdownMenuItem(
|
||||
value: e,
|
||||
child: Text(e.getDescribe(isCN),
|
||||
style: const TextStyle(fontSize: 13, fontWeight: FontWeight.w500))))
|
||||
.toList(),
|
||||
onChanged: (val) {
|
||||
setState(() {
|
||||
rewriteType = val!;
|
||||
});
|
||||
})),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 15),
|
||||
textField(isUpdate ? i18n.match : i18n.name, rewriteItem.key, keyTips,
|
||||
required: !isDelete, onSaved: (val) => rewriteItem.key = val),
|
||||
const SizedBox(height: 15),
|
||||
textField(isUpdate ? i18n.replace : i18n.value, rewriteItem.value, valueTips,
|
||||
onSaved: (val) => rewriteItem.value = val),
|
||||
]))));
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
centerTitle: true,
|
||||
title: Text(i18n.requestRewriteRule, style: const TextStyle(fontSize: 15, fontWeight: FontWeight.w500)),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
if (!(formKey.currentState as FormState).validate()) {
|
||||
FlutterToastr.show(i18n.cannotBeEmpty, context, position: FlutterToastr.center);
|
||||
return;
|
||||
}
|
||||
(formKey.currentState as FormState).save();
|
||||
rewriteItem.key = keyController.text;
|
||||
rewriteItem.value = valueController.text;
|
||||
rewriteItem.type = rewriteType;
|
||||
Navigator.of(context).pop(rewriteItem);
|
||||
},
|
||||
child: Text(i18n.confirm)),
|
||||
SizedBox(width: 5)
|
||||
]),
|
||||
body: Form(
|
||||
key: formKey,
|
||||
child: ListView(padding: const EdgeInsets.all(10), children: [
|
||||
Row(
|
||||
children: [
|
||||
Text(i18n.type),
|
||||
const SizedBox(width: 15),
|
||||
SizedBox(
|
||||
width: 140,
|
||||
child: DropdownButtonFormField<RewriteType>(
|
||||
value: rewriteType,
|
||||
focusColor: Colors.transparent,
|
||||
itemHeight: 48,
|
||||
decoration: const InputDecoration(
|
||||
contentPadding: EdgeInsets.all(10), isDense: true, border: InputBorder.none),
|
||||
items: typeList
|
||||
.map((e) => DropdownMenuItem(
|
||||
value: e,
|
||||
child: Text(e.getDescribe(isCN),
|
||||
style: const TextStyle(fontSize: 13, fontWeight: FontWeight.w500))))
|
||||
.toList(),
|
||||
onChanged: (val) {
|
||||
setState(() {
|
||||
rewriteType = val!;
|
||||
initTestData();
|
||||
});
|
||||
})),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 15),
|
||||
textField(isUpdate ? i18n.match : i18n.name, keyTips, controller: keyController, required: !isDelete),
|
||||
const SizedBox(height: 15),
|
||||
textField(isUpdate ? i18n.replace : i18n.value, valueTips, controller: valueController),
|
||||
const SizedBox(height: 10),
|
||||
Row(children: [
|
||||
Align(alignment: Alignment.centerLeft, child: Text(i18n.testData, style: const TextStyle(fontSize: 14))),
|
||||
const SizedBox(width: 10),
|
||||
if (!isMatch) Text(i18n.noChangesDetected, style: TextStyle(color: Colors.red, fontSize: 14))
|
||||
]),
|
||||
const SizedBox(height: 5),
|
||||
formField(i18n.enterMatchData, lines: 15, required: false, controller: dataController),
|
||||
])));
|
||||
}
|
||||
|
||||
Widget textField(String label, String? val, String hint, {bool required = false, FormFieldSetter<String>? onSaved}) {
|
||||
initTestData() {
|
||||
dataController.highlightEnabled = rewriteType != RewriteType.addQueryParam && rewriteType != RewriteType.addHeader;
|
||||
bool isRemove = [RewriteType.removeHeader, RewriteType.removeQueryParam].contains(rewriteType);
|
||||
|
||||
valueController.removeListener(onInputChangeMatch);
|
||||
if (isRemove) {
|
||||
valueController.addListener(onInputChangeMatch);
|
||||
}
|
||||
|
||||
if (widget.request == null) return;
|
||||
|
||||
if (rewriteType == RewriteType.updateBody) {
|
||||
dataController.text = (widget.ruleType == RuleType.requestUpdate
|
||||
? widget.request?.bodyAsString
|
||||
: widget.request?.response?.bodyAsString) ??
|
||||
'';
|
||||
return;
|
||||
}
|
||||
|
||||
if (rewriteType == RewriteType.updateQueryParam || rewriteType == RewriteType.removeQueryParam) {
|
||||
dataController.text = Uri.decodeQueryComponent(widget.request?.requestUri?.query ?? '');
|
||||
return;
|
||||
}
|
||||
|
||||
if (rewriteType == RewriteType.updateHeader || rewriteType == RewriteType.removeHeader) {
|
||||
dataController.text = widget.request?.headers.toRawHeaders() ?? '';
|
||||
return;
|
||||
}
|
||||
|
||||
dataController.clear();
|
||||
}
|
||||
|
||||
bool onMatch = false; //是否正在匹配
|
||||
bool isMatch = true;
|
||||
|
||||
onInputChangeMatch() {
|
||||
if (onMatch || dataController.highlightEnabled == false) {
|
||||
return;
|
||||
}
|
||||
onMatch = true;
|
||||
|
||||
//高亮显示
|
||||
Future.delayed(const Duration(milliseconds: 500), () {
|
||||
onMatch = false;
|
||||
if (dataController.text.isEmpty) {
|
||||
if (isMatch) return;
|
||||
setState(() {
|
||||
isMatch = true;
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (!mounted) return;
|
||||
setState(() {
|
||||
bool isRemove = [RewriteType.removeHeader, RewriteType.removeQueryParam].contains(rewriteType);
|
||||
String key = keyController.text;
|
||||
if (isRemove && key.isNotEmpty) {
|
||||
if (rewriteType == RewriteType.removeHeader) {
|
||||
key = '$key: ${valueController.text}';
|
||||
} else {
|
||||
key = '$key=${valueController.text}';
|
||||
}
|
||||
}
|
||||
|
||||
var match = dataController.highlight(key, caseSensitive: rewriteType != RewriteType.updateHeader);
|
||||
isMatch = match;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
Widget textField(String label, String hint, {bool required = false, int? lines, TextEditingController? controller}) {
|
||||
return Row(children: [
|
||||
SizedBox(width: 55, child: Text(label)),
|
||||
Expanded(
|
||||
child: TextFormField(
|
||||
initialValue: val,
|
||||
style: const TextStyle(fontSize: 14),
|
||||
maxLines: 3,
|
||||
validator: (val) => val?.isNotEmpty == true || !required ? null : "",
|
||||
onSaved: onSaved,
|
||||
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()),
|
||||
))
|
||||
Expanded(child: formField(hint, required: required, lines: lines, controller: controller))
|
||||
]);
|
||||
}
|
||||
|
||||
Widget formField(String hint, {bool required = false, int? lines, TextEditingController? controller}) {
|
||||
return TextFormField(
|
||||
controller: controller,
|
||||
style: const TextStyle(fontSize: 14),
|
||||
minLines: lines ?? 1,
|
||||
maxLines: lines ?? 3,
|
||||
validator: (val) => val?.isNotEmpty == true || !required ? null : "",
|
||||
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()),
|
||||
);
|
||||
}
|
||||
|
||||
InputBorder focusedBorder() {
|
||||
return OutlineInputBorder(borderSide: BorderSide(color: Theme.of(context).colorScheme.primary, width: 2));
|
||||
}
|
||||
@@ -220,8 +327,9 @@ class _RewriteUpdateAddState extends State<RewriteUpdateAddDialog> {
|
||||
class UpdateList extends StatefulWidget {
|
||||
final List<RewriteItem> items;
|
||||
final RuleType ruleType;
|
||||
final HttpRequest? request;
|
||||
|
||||
const UpdateList({super.key, required this.items, required this.ruleType});
|
||||
const UpdateList({super.key, required this.items, required this.ruleType, this.request});
|
||||
|
||||
@override
|
||||
State<UpdateList> createState() => _UpdateListState();
|
||||
@@ -239,10 +347,8 @@ class _UpdateListState extends State<UpdateList> {
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
padding: const EdgeInsets.only(top: 10),
|
||||
constraints: const BoxConstraints(minHeight: 350),
|
||||
decoration: BoxDecoration(border: Border.all(color: Colors.grey.withOpacity(0.2))),
|
||||
child: SingleChildScrollView(
|
||||
child: Column(children: [
|
||||
child: Column(children: [
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
children: [
|
||||
@@ -254,7 +360,7 @@ class _UpdateListState extends State<UpdateList> {
|
||||
),
|
||||
const Divider(thickness: 0.5),
|
||||
Column(children: rows(widget.items))
|
||||
])));
|
||||
]));
|
||||
}
|
||||
|
||||
int selected = -1;
|
||||
@@ -267,9 +373,11 @@ class _UpdateListState extends State<UpdateList> {
|
||||
highlightColor: Colors.transparent,
|
||||
splashColor: Colors.transparent,
|
||||
hoverColor: primaryColor.withOpacity(0.3),
|
||||
onTap: () => showDialog(
|
||||
context: context,
|
||||
builder: (context) => RewriteUpdateAddDialog(item: list[index], ruleType: widget.ruleType))
|
||||
onTap: () => Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (context) =>
|
||||
RewriteUpdateEdit(item: list[index], ruleType: widget.ruleType, request: widget.request)))
|
||||
.then((value) {
|
||||
if (value != null) setState(() {});
|
||||
}),
|
||||
@@ -326,12 +434,14 @@ class _UpdateListState extends State<UpdateList> {
|
||||
return Wrap(alignment: WrapAlignment.center, children: [
|
||||
BottomSheetItem(
|
||||
text: i18n.modify,
|
||||
onPressed: () async {
|
||||
showDialog(
|
||||
context: context,
|
||||
barrierDismissible: false,
|
||||
builder: (BuildContext context) =>
|
||||
RewriteUpdateAddDialog(item: widget.items[index], ruleType: widget.ruleType)).then((value) {
|
||||
onPressed: () {
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (BuildContext context) => RewriteUpdateEdit(
|
||||
item: widget.items[index],
|
||||
ruleType: widget.ruleType,
|
||||
request: widget.request))).then((value) {
|
||||
if (value != null) {
|
||||
setState(() {});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user