mirror of
https://github.com/wanghongenpin/proxypin.git
synced 2026-03-15 04:23:17 +08:00
Popup menu UI optimization & request highlighting
This commit is contained in:
@@ -275,6 +275,7 @@
|
||||
|
||||
"iosVpnBackgroundAudio": "After turning on packet capture, exit to the background. In order to maintain the main UI thread for network communication, a silent audio playback will be enabled to keep the main thread running. Otherwise, it will only run in the background for 30 seconds. Do you agree to play audio in the background after turning on packet capture?",
|
||||
|
||||
"markRead": "Mark as read",
|
||||
"highlight": "Highlight",
|
||||
"blue" : "Blue",
|
||||
"green" : "Green",
|
||||
|
||||
@@ -274,6 +274,7 @@
|
||||
"material3": "Material3是谷歌开源设计系统的最新版本",
|
||||
"iosVpnBackgroundAudio": "开启抓包后,退出到后台。为了维护主UI线程的网络通信,将启用静音音频播放以保持主线程运行。否则,它将只在后台运行30秒。您同意在启用抓包后在后台播放音频吗?",
|
||||
|
||||
"markRead": "标记已读",
|
||||
"highlight": "高亮",
|
||||
"blue": "蓝色",
|
||||
"green": "绿色",
|
||||
|
||||
@@ -52,7 +52,7 @@ class QeCodeScanView extends StatefulWidget {
|
||||
}
|
||||
|
||||
class _QrReaderViewState extends State<QeCodeScanView> with TickerProviderStateMixin {
|
||||
final int animationTime = 1500;
|
||||
final int animationTime = 2000;
|
||||
QrReaderViewController? _controller;
|
||||
AnimationController? _animationController;
|
||||
|
||||
@@ -96,22 +96,21 @@ class _QrReaderViewState extends State<QeCodeScanView> with TickerProviderStateM
|
||||
..addStatusListener((state) {
|
||||
if (!mounted) {
|
||||
stop();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (state == AnimationStatus.completed) {
|
||||
Future.delayed(Duration(seconds: 1), () {
|
||||
_animationController?.reverse(from: 1.5);
|
||||
_animationController?.reverse();
|
||||
});
|
||||
} else if (state == AnimationStatus.dismissed) {
|
||||
Future.delayed(Duration(seconds: 1), () {
|
||||
_animationController?.forward(from: 1.5);
|
||||
_animationController?.forward();
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
_animationController?.forward(from: 0.0);
|
||||
_animationController?.forward();
|
||||
}
|
||||
|
||||
void stop() {
|
||||
@@ -132,10 +131,12 @@ class _QrReaderViewState extends State<QeCodeScanView> with TickerProviderStateM
|
||||
setState(() {});
|
||||
}
|
||||
|
||||
Future<bool> setFlashlight() async {
|
||||
openFlashlight = await _controller?.setFlashlight() ?? false;
|
||||
setState(() {});
|
||||
return openFlashlight;
|
||||
setFlashlight() async {
|
||||
if (!isScan) return false;
|
||||
_controller?.setFlashlight();
|
||||
setState(() {
|
||||
openFlashlight = !openFlashlight;
|
||||
});
|
||||
}
|
||||
|
||||
scanImage(String path) {
|
||||
|
||||
@@ -27,6 +27,7 @@ class ShareWidget extends StatelessWidget {
|
||||
itemBuilder: (BuildContext context) {
|
||||
return [
|
||||
PopupMenuItem(
|
||||
padding: const EdgeInsets.only(left: 10, right: 2),
|
||||
child: Text(localizations.shareUrl),
|
||||
onTap: () {
|
||||
if (request == null) {
|
||||
@@ -37,7 +38,7 @@ class ShareWidget extends StatelessWidget {
|
||||
},
|
||||
),
|
||||
PopupMenuItem(
|
||||
padding: const EdgeInsets.only(left: 10),
|
||||
padding: const EdgeInsets.only(left: 10, right: 2),
|
||||
child: Text(localizations.shareRequestResponse),
|
||||
onTap: () {
|
||||
if (request == null) {
|
||||
@@ -49,7 +50,7 @@ class ShareWidget extends StatelessWidget {
|
||||
Share.shareXFiles([file], fileNameOverrides: ['request.txt'], text: localizations.proxyPinSoftware);
|
||||
}),
|
||||
PopupMenuItem(
|
||||
padding: const EdgeInsets.only(left: 10),
|
||||
padding: const EdgeInsets.only(left: 10, right: 2),
|
||||
child: Text(localizations.shareCurl),
|
||||
onTap: () {
|
||||
if (request == null) {
|
||||
|
||||
@@ -18,7 +18,6 @@ import 'package:flutter/services.dart';
|
||||
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/script_manager.dart';
|
||||
import 'package:network_proxy/network/http/http.dart';
|
||||
import 'package:network_proxy/network/http/websocket.dart';
|
||||
import 'package:network_proxy/storage/favorites.dart';
|
||||
@@ -28,7 +27,6 @@ import 'package:network_proxy/ui/component/utils.dart';
|
||||
import 'package:network_proxy/ui/component/widgets.dart';
|
||||
import 'package:network_proxy/ui/configuration.dart';
|
||||
import 'package:network_proxy/ui/mobile/request/request_editor.dart';
|
||||
import 'package:network_proxy/ui/mobile/setting/script.dart';
|
||||
import 'package:network_proxy/utils/lang.dart';
|
||||
import 'package:network_proxy/utils/platform.dart';
|
||||
import 'package:network_proxy/utils/python.dart';
|
||||
@@ -146,23 +144,6 @@ class NetworkTabState extends State<NetworkTabController> with SingleTickerProvi
|
||||
request: widget.request.get(), proxyServer: widget.proxyServer)));
|
||||
});
|
||||
}),
|
||||
PopupMenuItem(
|
||||
child: Text(localizations.script),
|
||||
onTap: () {
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) async {
|
||||
var scriptManager = await ScriptManager.instance;
|
||||
var request = widget.request.get();
|
||||
var url = '${request?.remoteDomain()}${request?.path()}';
|
||||
var scriptItem = (scriptManager).list.firstWhereOrNull((it) => it.url == url);
|
||||
String? script = scriptItem == null ? null : await scriptManager.getScript(scriptItem);
|
||||
|
||||
if (!context.mounted) return;
|
||||
|
||||
Navigator.of(context).push(MaterialPageRoute(
|
||||
builder: (context) => ScriptEdit(
|
||||
scriptItem: scriptItem, script: script, url: scriptItem?.url ?? url)));
|
||||
});
|
||||
}),
|
||||
CustomPopupMenuItem(
|
||||
padding: const EdgeInsets.only(left: 10),
|
||||
child: Text(localizations.copyAsPythonRequests),
|
||||
|
||||
@@ -22,14 +22,19 @@ import 'package:flutter/services.dart';
|
||||
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/request_rewrite_manager.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';
|
||||
import 'package:network_proxy/network/http_client.dart';
|
||||
import 'package:network_proxy/storage/favorites.dart';
|
||||
import 'package:network_proxy/ui/component/utils.dart';
|
||||
import 'package:network_proxy/ui/component/widgets.dart';
|
||||
import 'package:network_proxy/ui/content/panel.dart';
|
||||
import 'package:network_proxy/ui/mobile/request/repeat.dart';
|
||||
import 'package:network_proxy/ui/mobile/request/request_editor.dart';
|
||||
import 'package:network_proxy/ui/mobile/setting/request_rewrite.dart';
|
||||
import 'package:network_proxy/ui/mobile/setting/script.dart';
|
||||
import 'package:network_proxy/utils/curl.dart';
|
||||
import 'package:network_proxy/utils/lang.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
@@ -100,6 +105,7 @@ class _FavoriteItem extends StatefulWidget {
|
||||
|
||||
class _FavoriteItemState extends State<_FavoriteItem> {
|
||||
late HttpRequest request;
|
||||
bool selected = false;
|
||||
|
||||
AppLocalizations get localizations => AppLocalizations.of(context)!;
|
||||
|
||||
@@ -125,8 +131,13 @@ class _FavoriteItemState extends State<_FavoriteItem> {
|
||||
TextSpan(children: [
|
||||
TextSpan(text: '${request.method.name} ', style: const TextStyle(fontSize: 14, color: Colors.teal)),
|
||||
TextSpan(
|
||||
text: '${request.remoteDomain()}${request.path()}'.fixAutoLines(),
|
||||
style: TextStyle(fontSize: 14, color: Colors.blueAccent.shade200)),
|
||||
text: request.remoteDomain(),
|
||||
style: TextStyle(fontSize: 14, color: Colors.blue),
|
||||
),
|
||||
TextSpan(
|
||||
text: request.path(),
|
||||
style: TextStyle(fontSize: 14, color: Colors.green),
|
||||
),
|
||||
if (request.requestUri?.query.isNotEmpty == true)
|
||||
TextSpan(
|
||||
text: '?${request.requestUri?.query}',
|
||||
@@ -136,83 +147,171 @@ class _FavoriteItemState extends State<_FavoriteItem> {
|
||||
var time = formatDate(request.requestTime, [mm, '-', d, ' ', HH, ':', nn, ':', ss]);
|
||||
String subtitle =
|
||||
'$time - [${response?.status.code ?? ''}] ${response?.contentType.name.toUpperCase() ?? ''} ${response?.costTime() ?? ''} ';
|
||||
return ListTile(
|
||||
onLongPress: menu,
|
||||
minLeadingWidth: 25,
|
||||
leading: getIcon(response),
|
||||
title: title,
|
||||
subtitle: Text.rich(
|
||||
maxLines: 1,
|
||||
TextSpan(children: [
|
||||
TextSpan(text: '#${widget.index} ', style: const TextStyle(fontSize: 12, color: Colors.teal)),
|
||||
TextSpan(text: subtitle, style: const TextStyle(fontSize: 12)),
|
||||
])),
|
||||
dense: true,
|
||||
onTap: onClick);
|
||||
|
||||
return GestureDetector(
|
||||
onLongPressStart: menu,
|
||||
child: ListTile(
|
||||
selected: selected,
|
||||
minLeadingWidth: 25,
|
||||
leading: getIcon(response),
|
||||
title: title,
|
||||
subtitle: Text.rich(
|
||||
maxLines: 1,
|
||||
TextSpan(children: [
|
||||
TextSpan(text: '#${widget.index} ', style: const TextStyle(fontSize: 12, color: Colors.teal)),
|
||||
TextSpan(text: subtitle, style: const TextStyle(fontSize: 12)),
|
||||
])),
|
||||
dense: true,
|
||||
onTap: onClick));
|
||||
}
|
||||
|
||||
///右键菜单
|
||||
menu() {
|
||||
showModalBottomSheet(
|
||||
shape: const RoundedRectangleBorder(borderRadius: BorderRadius.vertical(top: Radius.circular(10))),
|
||||
context: context,
|
||||
isScrollControlled: true,
|
||||
builder: (ctx) {
|
||||
return Wrap(alignment: WrapAlignment.center, children: [
|
||||
menuItem(localizations.copyUrl, () => request.requestUrl),
|
||||
const Divider(thickness: 0.5, height: 5),
|
||||
menuItem(localizations.copyCurl, () => curlRequest(request)),
|
||||
const Divider(thickness: 0.5, height: 5),
|
||||
TextButton(
|
||||
child: SizedBox(width: double.infinity, child: Text(localizations.rename, textAlign: TextAlign.center)),
|
||||
onPressed: () {
|
||||
Navigator.of(context).pop();
|
||||
rename(widget.favorite);
|
||||
}),
|
||||
const Divider(thickness: 0.5, height: 5),
|
||||
TextButton(
|
||||
child: SizedBox(width: double.infinity, child: Text(localizations.repeat, textAlign: TextAlign.center)),
|
||||
onPressed: () {
|
||||
onRepeat(request);
|
||||
Navigator.of(context).pop();
|
||||
}),
|
||||
const Divider(thickness: 0.5, height: 5),
|
||||
TextButton(
|
||||
child: SizedBox(
|
||||
width: double.infinity, child: Text(localizations.customRepeat, textAlign: TextAlign.center)),
|
||||
onPressed: () => showCustomRepeat(request)),
|
||||
const Divider(thickness: 0.5, height: 5),
|
||||
TextButton(
|
||||
child:
|
||||
SizedBox(width: double.infinity, child: Text(localizations.editRequest, textAlign: TextAlign.center)),
|
||||
onPressed: () {
|
||||
Navigator.of(context).pop();
|
||||
Navigator.of(context).push(MaterialPageRoute(
|
||||
builder: (context) => MobileRequestEditor(request: request, proxyServer: widget.proxyServer)));
|
||||
}),
|
||||
const Divider(thickness: 0.5, height: 5),
|
||||
TextButton(
|
||||
child: SizedBox(
|
||||
width: double.infinity, child: Text(localizations.deleteFavorite, textAlign: TextAlign.center)),
|
||||
onPressed: () {
|
||||
widget.onRemove?.call(widget.favorite);
|
||||
FlutterToastr.show(localizations.deleteSuccess, context);
|
||||
Navigator.of(context).pop();
|
||||
}),
|
||||
Container(color: Theme.of(context).hoverColor, height: 8),
|
||||
TextButton(
|
||||
child: Container(
|
||||
height: 45,
|
||||
width: double.infinity,
|
||||
padding: const EdgeInsets.only(top: 10),
|
||||
child: Text(localizations.cancel, textAlign: TextAlign.center)),
|
||||
onPressed: () {
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
),
|
||||
]);
|
||||
},
|
||||
);
|
||||
menu(details) {
|
||||
setState(() {
|
||||
selected = true;
|
||||
});
|
||||
|
||||
var globalPosition = details.globalPosition;
|
||||
MediaQueryData mediaQuery = MediaQuery.of(context);
|
||||
var position = RelativeRect.fromLTRB(globalPosition.dx, globalPosition.dy, globalPosition.dx, globalPosition.dy);
|
||||
// Trigger haptic feedback
|
||||
HapticFeedback.mediumImpact();
|
||||
|
||||
showMenu(
|
||||
context: context,
|
||||
constraints: BoxConstraints(maxWidth: mediaQuery.size.width * 0.88),
|
||||
position: position,
|
||||
items: [
|
||||
//复制url
|
||||
PopupMenuContainer(
|
||||
child: Column(
|
||||
children: [
|
||||
Align(
|
||||
alignment: Alignment.centerLeft,
|
||||
child: Padding(
|
||||
padding: EdgeInsets.only(left: 20, top: 5),
|
||||
child: Text(localizations.selectAction, style: Theme.of(context).textTheme.bodyLarge)),
|
||||
),
|
||||
//copy
|
||||
menuItem(
|
||||
left: itemButton(
|
||||
onPressed: () {
|
||||
Clipboard.setData(ClipboardData(text: request.requestUrl)).then((value) {
|
||||
if (mounted) {
|
||||
FlutterToastr.show(localizations.copied, context);
|
||||
Navigator.maybePop(context);
|
||||
}
|
||||
});
|
||||
},
|
||||
label: localizations.copyUrl,
|
||||
icon: Icons.link,
|
||||
iconSize: 22),
|
||||
right: itemButton(
|
||||
onPressed: () {
|
||||
Clipboard.setData(ClipboardData(text: curlRequest(request))).then((value) {
|
||||
if (mounted) {
|
||||
FlutterToastr.show(localizations.copied, context);
|
||||
Navigator.maybePop(context);
|
||||
}
|
||||
});
|
||||
},
|
||||
label: localizations.copyCurl,
|
||||
icon: Icons.code),
|
||||
),
|
||||
//repeat
|
||||
menuItem(
|
||||
left: itemButton(
|
||||
onPressed: () {
|
||||
onRepeat(request);
|
||||
Navigator.maybePop(context);
|
||||
},
|
||||
label: localizations.repeat,
|
||||
icon: Icons.repeat_one),
|
||||
right: itemButton(
|
||||
onPressed: () => showCustomRepeat(request), label: localizations.customRepeat, icon: Icons.repeat),
|
||||
),
|
||||
//favorite and edit
|
||||
menuItem(
|
||||
left: itemButton(
|
||||
onPressed: () {
|
||||
Navigator.of(context).pop();
|
||||
rename(widget.favorite);
|
||||
},
|
||||
label: localizations.rename,
|
||||
icon: Icons.drive_file_rename_outline),
|
||||
right: itemButton(
|
||||
onPressed: () async {
|
||||
Navigator.pop(context);
|
||||
|
||||
var pageRoute = MaterialPageRoute(
|
||||
builder: (context) => MobileRequestEditor(request: request, proxyServer: widget.proxyServer));
|
||||
Navigator.push(context, pageRoute);
|
||||
},
|
||||
label: localizations.editRequest,
|
||||
icon: Icons.edit_outlined),
|
||||
),
|
||||
|
||||
//script and rewrite
|
||||
menuItem(
|
||||
left: itemButton(
|
||||
onPressed: () async {
|
||||
Navigator.maybePop(context);
|
||||
|
||||
var scriptManager = await ScriptManager.instance;
|
||||
var url = '${request.remoteDomain()}${request.path()}';
|
||||
var scriptItem = (scriptManager).list.firstWhereOrNull((it) => it.url == url);
|
||||
String? script = scriptItem == null ? null : await scriptManager.getScript(scriptItem);
|
||||
|
||||
var pageRoute = MaterialPageRoute(
|
||||
builder: (context) =>
|
||||
ScriptEdit(scriptItem: scriptItem, script: script, url: scriptItem?.url ?? url));
|
||||
if (mounted) Navigator.push(context, pageRoute);
|
||||
},
|
||||
label: localizations.script,
|
||||
icon: Icons.javascript_outlined),
|
||||
right: itemButton(
|
||||
onPressed: () async {
|
||||
Navigator.maybePop(context);
|
||||
bool isRequest = request.response == null;
|
||||
var requestRewrites = await RequestRewrites.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 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 : request.response?.bodyAsString}));
|
||||
}
|
||||
|
||||
var pageRoute = MaterialPageRoute(builder: (_) => RewriteRule(rule: rule, items: rewriteItems));
|
||||
if (mounted) Navigator.push(context, pageRoute);
|
||||
},
|
||||
label: localizations.requestRewrite,
|
||||
icon: Icons.replay_outlined),
|
||||
),
|
||||
SizedBox(height: 2),
|
||||
Row(mainAxisAlignment: MainAxisAlignment.center, children: [
|
||||
itemButton(
|
||||
onPressed: () {
|
||||
widget.onRemove?.call(widget.favorite);
|
||||
FlutterToastr.show(localizations.deleteSuccess, context);
|
||||
Navigator.maybePop(context);
|
||||
},
|
||||
label: localizations.deleteFavorite,
|
||||
icon: Icons.delete_outline),
|
||||
SizedBox(width: 10),
|
||||
]),
|
||||
],
|
||||
)),
|
||||
]).then((value) {
|
||||
selected = false;
|
||||
if (mounted) setState(() {});
|
||||
});
|
||||
}
|
||||
|
||||
//显示高级重发
|
||||
@@ -233,17 +332,6 @@ class _FavoriteItemState extends State<_FavoriteItem> {
|
||||
}
|
||||
}
|
||||
|
||||
Widget menuItem(String title, String Function() callback) {
|
||||
return TextButton(
|
||||
child: SizedBox(width: double.infinity, child: Text(title, textAlign: TextAlign.center)),
|
||||
onPressed: () {
|
||||
Clipboard.setData(ClipboardData(text: callback.call())).then((value) {
|
||||
FlutterToastr.show(localizations.copied, context);
|
||||
Navigator.of(context).pop();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
//重命名
|
||||
rename(Favorite item) {
|
||||
String? name = item.name;
|
||||
@@ -283,4 +371,20 @@ class _FavoriteItemState extends State<_FavoriteItem> {
|
||||
title: Text(localizations.captureDetail, style: const TextStyle(fontSize: 16)));
|
||||
}));
|
||||
}
|
||||
|
||||
Widget itemButton(
|
||||
{required String label, required IconData icon, required Function() onPressed, double iconSize = 20}) {
|
||||
var style = Theme.of(context).textTheme.bodyMedium;
|
||||
return TextButton.icon(
|
||||
onPressed: onPressed, label: Text(label, style: style), icon: Icon(icon, size: iconSize, color: style?.color));
|
||||
}
|
||||
|
||||
Widget menuItem({required Widget left, required Widget right}) {
|
||||
return Row(
|
||||
children: [
|
||||
SizedBox(width: 130, child: Align(alignment: Alignment.centerLeft, child: left)),
|
||||
Expanded(child: Align(alignment: Alignment.centerLeft, child: right))
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -181,7 +181,8 @@ class _MobileHistoryState extends State<MobileHistory> {
|
||||
Widget buildItem(HistoryStorage storage, int index, HistoryItem item) {
|
||||
return InkWell(
|
||||
onTapDown: (detail) async {
|
||||
HapticFeedback.heavyImpact();
|
||||
HapticFeedback.mediumImpact();
|
||||
|
||||
showContextMenu(context, detail.globalPosition.translate(-50, index == 0 ? -100 : 100), items: [
|
||||
PopupMenuItem(child: Text(localizations.rename), onTap: () => renameHistory(storage, item)),
|
||||
PopupMenuItem(child: Text(localizations.share), onTap: () => export(storage, item)),
|
||||
|
||||
@@ -21,6 +21,8 @@ import 'package:flutter/services.dart';
|
||||
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/request_rewrite_manager.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';
|
||||
import 'package:network_proxy/network/http_client.dart';
|
||||
@@ -31,6 +33,8 @@ import 'package:network_proxy/ui/component/widgets.dart';
|
||||
import 'package:network_proxy/ui/content/panel.dart';
|
||||
import 'package:network_proxy/ui/mobile/request/repeat.dart';
|
||||
import 'package:network_proxy/ui/mobile/request/request_editor.dart';
|
||||
import 'package:network_proxy/ui/mobile/setting/request_rewrite.dart';
|
||||
import 'package:network_proxy/ui/mobile/setting/script.dart';
|
||||
import 'package:network_proxy/ui/mobile/widgets/highlight.dart';
|
||||
import 'package:network_proxy/utils/curl.dart';
|
||||
import 'package:network_proxy/utils/lang.dart';
|
||||
@@ -65,6 +69,7 @@ class RequestRowState extends State<RequestRow> {
|
||||
late HttpRequest request;
|
||||
HttpResponse? response;
|
||||
bool selected = false;
|
||||
Color? highlightColor; //高亮颜色
|
||||
|
||||
AppLocalizations get localizations => AppLocalizations.of(context)!;
|
||||
|
||||
@@ -81,6 +86,14 @@ class RequestRowState extends State<RequestRow> {
|
||||
super.initState();
|
||||
}
|
||||
|
||||
Color? color(String url) {
|
||||
if (highlightColor != null) {
|
||||
return highlightColor;
|
||||
}
|
||||
|
||||
return KeywordHighlight.getHighlightColor(url);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
String url = widget.displayDomain ? request.requestUrl : request.path();
|
||||
@@ -93,7 +106,7 @@ class RequestRowState extends State<RequestRow> {
|
||||
|
||||
var subTitle = '$time - [${response?.status.code ?? ''}] $contentType $packagesSize ${response?.costTime() ?? ''}';
|
||||
|
||||
var highlightColor = KeywordHighlight.getHighlightColor(url);
|
||||
var highlightColor = color(url);
|
||||
|
||||
return GestureDetector(
|
||||
onLongPressStart: menu,
|
||||
@@ -115,7 +128,6 @@ class RequestRowState extends State<RequestRow> {
|
||||
contentPadding:
|
||||
Platform.isIOS ? const EdgeInsets.symmetric(horizontal: 8) : const EdgeInsets.only(left: 3, right: 5),
|
||||
onTap: () {
|
||||
|
||||
Navigator.of(this.context).push(MaterialPageRoute(builder: (context) {
|
||||
return NetworkTabController(
|
||||
proxyServer: widget.proxyServer,
|
||||
@@ -163,6 +175,7 @@ class RequestRowState extends State<RequestRow> {
|
||||
var position = RelativeRect.fromLTRB(globalPosition.dx, globalPosition.dy, globalPosition.dx, globalPosition.dy);
|
||||
// Trigger haptic feedback
|
||||
HapticFeedback.mediumImpact();
|
||||
|
||||
showMenu(
|
||||
context: context,
|
||||
constraints: BoxConstraints(maxWidth: mediaQuery.size.width * 0.88),
|
||||
@@ -180,7 +193,7 @@ class RequestRowState extends State<RequestRow> {
|
||||
),
|
||||
//copy
|
||||
menuItem(
|
||||
left: button(
|
||||
left: itemButton(
|
||||
onPressed: () {
|
||||
Clipboard.setData(ClipboardData(text: request.requestUrl)).then((value) {
|
||||
if (mounted) {
|
||||
@@ -192,7 +205,7 @@ class RequestRowState extends State<RequestRow> {
|
||||
label: localizations.copyUrl,
|
||||
icon: Icons.link,
|
||||
iconSize: 22),
|
||||
right: button(
|
||||
right: itemButton(
|
||||
onPressed: () {
|
||||
Clipboard.setData(ClipboardData(text: curlRequest(request))).then((value) {
|
||||
if (mounted) {
|
||||
@@ -206,19 +219,19 @@ class RequestRowState extends State<RequestRow> {
|
||||
),
|
||||
//repeat
|
||||
menuItem(
|
||||
left: button(
|
||||
left: itemButton(
|
||||
onPressed: () {
|
||||
onRepeat(request);
|
||||
Navigator.maybePop(context);
|
||||
},
|
||||
label: localizations.repeat,
|
||||
icon: Icons.repeat_one),
|
||||
right: button(
|
||||
right: itemButton(
|
||||
onPressed: () => showCustomRepeat(request), label: localizations.customRepeat, icon: Icons.repeat),
|
||||
),
|
||||
//favorite and edit
|
||||
menuItem(
|
||||
left: button(
|
||||
left: itemButton(
|
||||
onPressed: () {
|
||||
FavoriteStorage.addFavorite(widget.request);
|
||||
FlutterToastr.show(localizations.addSuccess, context);
|
||||
@@ -226,9 +239,9 @@ class RequestRowState extends State<RequestRow> {
|
||||
},
|
||||
label: localizations.favorite,
|
||||
icon: Icons.favorite_outline),
|
||||
right: button(
|
||||
onPressed: () {
|
||||
Navigator.pop(context);
|
||||
right: itemButton(
|
||||
onPressed: () async {
|
||||
await Navigator.maybePop(context);
|
||||
|
||||
var pageRoute = MaterialPageRoute(
|
||||
builder: (context) =>
|
||||
@@ -242,24 +255,77 @@ class RequestRowState extends State<RequestRow> {
|
||||
label: localizations.editRequest,
|
||||
icon: Icons.edit_outlined),
|
||||
),
|
||||
//script and rewrite
|
||||
menuItem(
|
||||
left: itemButton(
|
||||
onPressed: () async {
|
||||
Navigator.maybePop(context);
|
||||
|
||||
// menuItem(
|
||||
// left: button(
|
||||
// onPressed: () {}, label: localizations.script, icon: Icons.javascript_outlined, iconSize: 24),
|
||||
// right: button(onPressed: () {}, label: localizations.requestRewrite, icon: Icons.replay_outlined),
|
||||
// ),
|
||||
// menuItem(
|
||||
// left: TextButton.icon(
|
||||
// onPressed: () {},
|
||||
// label: Text(localizations.highlight),
|
||||
// icon: const Icon(Icons.highlight_outlined, size: 20)),
|
||||
// right: TextButton.icon(
|
||||
// onPressed: () {},
|
||||
// label: Text(localizations.requestRewrite),
|
||||
// icon: const Icon(Icons.highlight_remove, size: 20)),
|
||||
// ),
|
||||
var scriptManager = await ScriptManager.instance;
|
||||
var url = '${request.remoteDomain()}${request.path()}';
|
||||
var scriptItem = (scriptManager).list.firstWhereOrNull((it) => it.url == url);
|
||||
String? script = scriptItem == null ? null : await scriptManager.getScript(scriptItem);
|
||||
|
||||
var pageRoute = MaterialPageRoute(
|
||||
builder: (context) =>
|
||||
ScriptEdit(scriptItem: scriptItem, script: script, url: scriptItem?.url ?? url));
|
||||
if (mounted) {
|
||||
Navigator.push(context, pageRoute);
|
||||
} else {
|
||||
NavigatorHelper.push(pageRoute);
|
||||
}
|
||||
},
|
||||
label: localizations.script,
|
||||
icon: Icons.javascript_outlined),
|
||||
right: itemButton(
|
||||
onPressed: () async {
|
||||
Navigator.maybePop(context);
|
||||
bool isRequest = response == null;
|
||||
var requestRewrites = await RequestRewrites.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 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));
|
||||
|
||||
if (mounted) {
|
||||
Navigator.push(context, pageRoute);
|
||||
} else {
|
||||
NavigatorHelper.push(pageRoute);
|
||||
}
|
||||
},
|
||||
label: localizations.requestRewrite,
|
||||
icon: Icons.replay_outlined),
|
||||
),
|
||||
menuItem(
|
||||
left: itemButton(
|
||||
onPressed: () {
|
||||
highlightColor = Theme.of(context).colorScheme.primary;
|
||||
Navigator.maybePop(context);
|
||||
},
|
||||
label: localizations.highlight,
|
||||
icon: Icons.highlight_outlined),
|
||||
right: itemButton(
|
||||
onPressed: () {
|
||||
highlightColor = Colors.grey;
|
||||
Navigator.maybePop(context);
|
||||
},
|
||||
label: localizations.markRead,
|
||||
icon: Icons.mark_chat_read_outlined),
|
||||
),
|
||||
SizedBox(height: 2),
|
||||
Row(mainAxisAlignment: MainAxisAlignment.center, children: [
|
||||
button(
|
||||
itemButton(
|
||||
onPressed: () {
|
||||
widget.onRemove?.call(request);
|
||||
FlutterToastr.show(localizations.deleteSuccess, context);
|
||||
@@ -278,8 +344,8 @@ class RequestRowState extends State<RequestRow> {
|
||||
}
|
||||
|
||||
//显示高级重发
|
||||
showCustomRepeat(HttpRequest request) {
|
||||
Navigator.pop(context);
|
||||
showCustomRepeat(HttpRequest request) async {
|
||||
await Navigator.maybePop(context);
|
||||
var pageRoute = MaterialPageRoute(
|
||||
builder: (context) => futureWidget(SharedPreferences.getInstance(),
|
||||
(prefs) => MobileCustomRepeat(onRepeat: () => onRepeat(request), prefs: prefs)));
|
||||
@@ -300,7 +366,8 @@ class RequestRowState extends State<RequestRow> {
|
||||
}
|
||||
}
|
||||
|
||||
Widget button({required String label, required IconData icon, required Function() onPressed, double iconSize = 20}) {
|
||||
Widget itemButton(
|
||||
{required String label, required IconData icon, required Function() onPressed, double iconSize = 20}) {
|
||||
var style = Theme.of(context).textTheme.bodyMedium;
|
||||
return TextButton.icon(
|
||||
onPressed: onPressed, label: Text(label, style: style), icon: Icon(icon, size: iconSize, color: style?.color));
|
||||
|
||||
@@ -143,8 +143,7 @@ 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 = GlobalKey();
|
||||
indexes[view.elementAt(index)] = key;
|
||||
GlobalKey<RequestRowState> key = indexes[view.elementAt(index)] ??= GlobalKey();
|
||||
return RequestRow(
|
||||
index: view.length - index,
|
||||
key: key,
|
||||
|
||||
@@ -17,6 +17,7 @@ import 'dart:collection';
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:file_selector/file_selector.dart';
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
||||
@@ -24,7 +25,6 @@ import 'package:flutter_toastr/flutter_toastr.dart';
|
||||
import 'package:network_proxy/network/bin/configuration.dart';
|
||||
import 'package:network_proxy/network/util/logger.dart';
|
||||
import 'package:network_proxy/ui/component/utils.dart';
|
||||
import 'package:network_proxy/ui/component/widgets.dart';
|
||||
import 'package:share_plus/share_plus.dart';
|
||||
|
||||
import '../../../network/components/host_filter.dart';
|
||||
@@ -312,10 +312,12 @@ class _DomainListState extends State<DomainList> {
|
||||
|
||||
return List.generate(list.length, (index) {
|
||||
return InkWell(
|
||||
enableFeedback: false,
|
||||
highlightColor: Colors.transparent,
|
||||
splashColor: Colors.transparent,
|
||||
hoverColor: primaryColor.withOpacity(0.3),
|
||||
onLongPress: () => showMenus(index), // menus
|
||||
onLongPress: () => showMenus(index),
|
||||
// menus
|
||||
onDoubleTap: () => showEdit(index),
|
||||
onTap: () {
|
||||
if (multiple) {
|
||||
@@ -366,45 +368,51 @@ class _DomainListState extends State<DomainList> {
|
||||
setState(() {
|
||||
selected.add(index);
|
||||
});
|
||||
HapticFeedback.mediumImpact();
|
||||
|
||||
showModalBottomSheet(
|
||||
shape: const RoundedRectangleBorder(borderRadius: BorderRadius.vertical(top: Radius.circular(10))),
|
||||
showCupertinoModalPopup(
|
||||
context: context,
|
||||
enableDrag: true,
|
||||
builder: (ctx) {
|
||||
return Wrap(alignment: WrapAlignment.center, children: [
|
||||
BottomSheetItem(
|
||||
text: localizations.multiple,
|
||||
onPressed: () {
|
||||
setState(() => multiple = true);
|
||||
}),
|
||||
const Divider(thickness: 0.5, height: 5),
|
||||
BottomSheetItem(text: localizations.copy, onPressed: () {
|
||||
Clipboard.setData(ClipboardData(text: widget.hostList.list[index].pattern.replaceAll(".*", "*")));
|
||||
FlutterToastr.show(localizations.copied, context);
|
||||
}),
|
||||
const Divider(thickness: 0.5, height: 5),
|
||||
BottomSheetItem(text: localizations.edit, onPressed: () => showEdit(index)),
|
||||
const Divider(thickness: 0.5, height: 5),
|
||||
BottomSheetItem(onPressed: () => export([index]), text: localizations.share),
|
||||
const Divider(thickness: 0.5, height: 5),
|
||||
BottomSheetItem(
|
||||
text: localizations.delete,
|
||||
onPressed: () {
|
||||
widget.hostList.removeIndex([index]);
|
||||
onChanged();
|
||||
}),
|
||||
Container(color: Theme.of(context).hoverColor, height: 8),
|
||||
TextButton(
|
||||
child: Container(
|
||||
height: 45,
|
||||
width: double.infinity,
|
||||
padding: const EdgeInsets.only(top: 10),
|
||||
child: Text(localizations.cancel, textAlign: TextAlign.center)),
|
||||
onPressed: () {
|
||||
Navigator.of(context).pop();
|
||||
}),
|
||||
]);
|
||||
builder: (BuildContext context) {
|
||||
return CupertinoActionSheet(
|
||||
actions: [
|
||||
CupertinoActionSheetAction(
|
||||
child: Text(localizations.multiple),
|
||||
onPressed: () {
|
||||
setState(() => multiple = true);
|
||||
Navigator.of(context).pop();
|
||||
}),
|
||||
CupertinoActionSheetAction(
|
||||
onPressed: () {
|
||||
Clipboard.setData(ClipboardData(text: widget.hostList.list[index].pattern.replaceAll(".*", "*")));
|
||||
FlutterToastr.show(localizations.copied, context);
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
child: Text(localizations.copy)),
|
||||
CupertinoActionSheetAction(
|
||||
onPressed: () {
|
||||
Navigator.of(context).pop();
|
||||
showEdit(index);
|
||||
},
|
||||
child: Text(localizations.edit)),
|
||||
CupertinoActionSheetAction(
|
||||
onPressed: () {
|
||||
export([index]);
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
child: Text(localizations.share)),
|
||||
CupertinoActionSheetAction(
|
||||
onPressed: () {
|
||||
widget.hostList.removeIndex([index]);
|
||||
onChanged();
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
child: Text(localizations.delete)),
|
||||
],
|
||||
cancelButton: CupertinoActionSheetAction(
|
||||
onPressed: () {
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
child: Text(localizations.cancel)));
|
||||
}).then((value) {
|
||||
if (multiple) {
|
||||
return;
|
||||
|
||||
@@ -27,6 +27,7 @@ class About extends StatelessWidget {
|
||||
AppLocalizations localizations = AppLocalizations.of(context)!;
|
||||
bool isCN = Localizations.localeOf(context) == const Locale.fromSubtags(languageCode: 'zh');
|
||||
|
||||
String gitHub = "https://github.com/wanghongenpin/network_proxy_flutter";
|
||||
return Scaffold(
|
||||
appBar: AppBar(title: Text(localizations.about, style: const TextStyle(fontSize: 16)), centerTitle: true),
|
||||
body: Column(
|
||||
@@ -43,24 +44,21 @@ class About extends StatelessWidget {
|
||||
title: const Text("Github"),
|
||||
trailing: const Icon(Icons.arrow_right),
|
||||
onTap: () {
|
||||
launchUrl(Uri.parse("https://github.com/wanghongenpin/network_proxy_flutter"),
|
||||
mode: LaunchMode.externalApplication);
|
||||
launchUrl(Uri.parse(gitHub), mode: LaunchMode.externalApplication);
|
||||
}),
|
||||
ListTile(
|
||||
title: Text(localizations.feedback),
|
||||
trailing: const Icon(Icons.arrow_right),
|
||||
onTap: () {
|
||||
launchUrl(Uri.parse("https://github.com/wanghongenpin/network_proxy_flutter/issues"),
|
||||
mode: LaunchMode.externalApplication);
|
||||
launchUrl(Uri.parse("{$gitHub}/issues"), mode: LaunchMode.externalApplication);
|
||||
}),
|
||||
ListTile(
|
||||
title: Text(isCN ? "下载地址" : "Download"),
|
||||
trailing: const Icon(Icons.arrow_right),
|
||||
onTap: () {
|
||||
launchUrl(
|
||||
Uri.parse(isCN
|
||||
? "https://gitee.com/wanghongenpin/network-proxy-flutter/releases"
|
||||
: "https://github.com/wanghongenpin/network_proxy_flutter/releases"),
|
||||
Uri.parse(
|
||||
isCN ? "https://gitee.com/wanghongenpin/network-proxy-flutter/releases" : "$gitHub/releases"),
|
||||
mode: LaunchMode.externalApplication);
|
||||
})
|
||||
],
|
||||
|
||||
@@ -344,7 +344,7 @@ class _RemoteDevicePageState extends State<RemoteDevicePage> {
|
||||
Navigator.pop(context);
|
||||
}
|
||||
},
|
||||
child: Text(localizations.connected)),
|
||||
child: Text(localizations.connectRemote)),
|
||||
],
|
||||
);
|
||||
});
|
||||
@@ -380,7 +380,12 @@ class _RemoteDevicePageState extends State<RemoteDevicePage> {
|
||||
}
|
||||
|
||||
///
|
||||
bool doConnecting = false;
|
||||
|
||||
///连接远程设备
|
||||
Future<bool> doConnect(String host, int port, {bool? ipProxy}) async {
|
||||
if (doConnecting) return false;
|
||||
doConnecting = true;
|
||||
try {
|
||||
var response = await HttpClients.get("http://$host:$port/ping", timeout: const Duration(milliseconds: 3000));
|
||||
if (response.bodyAsString == "pong") {
|
||||
@@ -422,6 +427,8 @@ class _RemoteDevicePageState extends State<RemoteDevicePage> {
|
||||
});
|
||||
}
|
||||
return false;
|
||||
} finally {
|
||||
doConnecting = false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -35,7 +35,7 @@ dependencies:
|
||||
flutter_js: ^0.8.1
|
||||
flutter_code_editor: ^0.3.2
|
||||
flutter_desktop_context_menu: ^0.2.0
|
||||
file_picker: ^8.1.2
|
||||
file_picker: ^8.1.3
|
||||
file_selector: ^1.0.3
|
||||
win32audio: ^1.3.1
|
||||
device_info_plus: ^11.1.0
|
||||
|
||||
Reference in New Issue
Block a user