Rewrite request modification to add regular matching test

This commit is contained in:
wanghongenpin
2024-10-24 00:31:01 +08:00
parent be30d20239
commit abaf62ebbc
26 changed files with 486 additions and 302 deletions

View File

@@ -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",

View File

@@ -104,6 +104,9 @@
"enableSelect": "启用选择",
"disableSelect": "禁用选择",
"deleteSelect": "删除选择",
"testData": "测试数据",
"noChangesDetected": "未检测到变更",
"enterMatchData": "输入待匹配的数据",
"modifyRequestHeader": "修改请求头",
"headerName": "请求头名称",

View File

@@ -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;

View File

@@ -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);
}

View File

@@ -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();

View File

@@ -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);
}
//请求头

View File

@@ -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;

View File

@@ -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 = {

View File

@@ -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 {

View File

@@ -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;
}

View File

@@ -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,

View File

@@ -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 (_) {}

View File

@@ -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();
});
}

View File

@@ -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);

View File

@@ -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);
}
}

View File

@@ -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(() {});
}

View File

@@ -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(

View File

@@ -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) {

View File

@@ -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;

View File

@@ -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}) {

View File

@@ -126,9 +126,9 @@ class RequestListState extends State<RequestListWidget> {
///清理
clean() {
setState(() {
container.clear();
domainListKey.currentState?.clean();
requestSequenceKey.currentState?.clean();
container.clear();
});
}

View File

@@ -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}) {

View File

@@ -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,

View File

@@ -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,

View File

@@ -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) {

View File

@@ -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(() {});
}