mirror of
https://github.com/wanghongenpin/proxypin.git
synced 2026-03-15 04:23:17 +08:00
desktop request list sort
This commit is contained in:
2
.gitignore
vendored
2
.gitignore
vendored
@@ -5,9 +5,11 @@
|
||||
*.swp
|
||||
.DS_Store
|
||||
.atom/
|
||||
.build/
|
||||
.buildlog/
|
||||
.history
|
||||
.svn/
|
||||
.swiftpm/
|
||||
migrate_working_dir/
|
||||
Podfile.lock
|
||||
# IntelliJ related
|
||||
|
||||
@@ -151,6 +151,8 @@
|
||||
"editRequest": "Edit and Request",
|
||||
"reSendRequest": "The request has been resent",
|
||||
"viewExport": "View Export",
|
||||
"timeDesc": "Descending by time",
|
||||
"timeAsc": "Ascending by time",
|
||||
|
||||
"search": "Search",
|
||||
"clearSearch": "Clear Search",
|
||||
|
||||
@@ -151,6 +151,8 @@
|
||||
"editRequest": "编辑请求",
|
||||
"reSendRequest": "已重新发送请求",
|
||||
"viewExport": "视图导出",
|
||||
"timeDesc": "按时间降序",
|
||||
"timeAsc": "按时间升序",
|
||||
|
||||
"search": "搜索",
|
||||
"clearSearch": "清除搜索",
|
||||
|
||||
@@ -108,7 +108,7 @@ class ProxyHelper {
|
||||
..hostAndPort = hostAndPort;
|
||||
request.processInfo ??= channelContext.processInfo;
|
||||
|
||||
if (request.method == HttpMethod.connect && !request.requestUrl.startsWith("http")) {
|
||||
if (request.method == HttpMethod.connect && !request.uri.startsWith("http")) {
|
||||
request.uri = hostAndPort.domain;
|
||||
}
|
||||
|
||||
|
||||
@@ -86,41 +86,6 @@ class HistoryPageWidget extends StatelessWidget {
|
||||
localizations.historyRecordTitle(
|
||||
item.requestLength, item.name.substring(0, min(item.name.length, 25))),
|
||||
style: const TextStyle(fontSize: 14)),
|
||||
actions: [
|
||||
PopupMenuButton(
|
||||
offset: const Offset(0, 32),
|
||||
icon: const Icon(Icons.more_vert_outlined, size: 20),
|
||||
itemBuilder: (BuildContext context) {
|
||||
return <PopupMenuEntry>[
|
||||
CustomPopupMenuItem(
|
||||
height: 35,
|
||||
onTap: () {
|
||||
String fileName = '${item.name.contains("ProxyPin") ? '' : 'ProxyPin'}${item.name}.har'
|
||||
.replaceAll(" ", "_")
|
||||
.replaceAll(":", "_");
|
||||
requestListKey.currentState?.export(fileName);
|
||||
},
|
||||
child: IconText(
|
||||
icon: const Icon(Icons.share, size: 16),
|
||||
text: localizations.viewExport,
|
||||
textStyle: const TextStyle(fontSize: 13))),
|
||||
CustomPopupMenuItem(
|
||||
height: 35,
|
||||
onTap: () async {
|
||||
var requests = requestListKey.currentState?.currentView();
|
||||
if (requests == null) return;
|
||||
|
||||
//重发所有请求
|
||||
_repeatAllRequests(requests.toList(), proxyServer,
|
||||
context: context.mounted ? context : null);
|
||||
},
|
||||
child: IconText(
|
||||
icon: const Icon(Icons.repeat, size: 16),
|
||||
text: localizations.repeatAllRequests,
|
||||
textStyle: const TextStyle(fontSize: 13))),
|
||||
];
|
||||
}),
|
||||
],
|
||||
)),
|
||||
body: futureWidget(HistoryStorage.instance.then((value) => value.getRequests(item)), (data) {
|
||||
//shrinkWrap: false,
|
||||
@@ -130,26 +95,6 @@ class HistoryPageWidget extends StatelessWidget {
|
||||
}
|
||||
}
|
||||
|
||||
///重发所有请求
|
||||
void _repeatAllRequests(Iterable<HttpRequest> requests, ProxyServer proxyServer, {BuildContext? context}) async {
|
||||
var localizations = context == null ? null : AppLocalizations.of(context);
|
||||
|
||||
for (var request in requests) {
|
||||
var httpRequest = request.copy(uri: request.requestUrl);
|
||||
var proxyInfo = proxyServer.isRunning ? ProxyInfo.of("127.0.0.1", proxyServer.port) : null;
|
||||
try {
|
||||
await HttpClients.proxyRequest(httpRequest, proxyInfo: proxyInfo, timeout: const Duration(seconds: 3));
|
||||
if (context != null && context.mounted) {
|
||||
FlutterToastr.show(localizations!.reSendRequest, rootNavigator: true, context);
|
||||
}
|
||||
} catch (e) {
|
||||
if (context != null && context.mounted) {
|
||||
FlutterToastr.show('${localizations!.fail} $e', rootNavigator: true, context);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
///历史记录列表
|
||||
class _HistoryListWidget extends StatefulWidget {
|
||||
// 存储
|
||||
@@ -299,7 +244,7 @@ class _HistoryListState extends State<_HistoryListWidget> {
|
||||
onTap: () async {
|
||||
var requests = (await storage.getRequests(item)).reversed;
|
||||
//重发所有请求
|
||||
_repeatAllRequests(requests.toList(), proxyServer, context: rootContext.mounted ? rootContext : null);
|
||||
_repeatAllRequests(requests.toList());
|
||||
}),
|
||||
const PopupMenuDivider(height: 3),
|
||||
CustomPopupMenuItem(
|
||||
@@ -383,4 +328,24 @@ class _HistoryListState extends State<_HistoryListWidget> {
|
||||
if (mounted) FlutterToastr.show(localizations.exportSuccess, context);
|
||||
Future.delayed(const Duration(seconds: 30), () => item.requests = null);
|
||||
}
|
||||
|
||||
///重发所有请求
|
||||
void _repeatAllRequests(Iterable<HttpRequest> requests) async {
|
||||
var localizations = AppLocalizations.of(context);
|
||||
|
||||
for (var request in requests) {
|
||||
var httpRequest = request.copy(uri: request.requestUrl);
|
||||
var proxyInfo = proxyServer.isRunning ? ProxyInfo.of("127.0.0.1", proxyServer.port) : null;
|
||||
try {
|
||||
await HttpClients.proxyRequest(httpRequest, proxyInfo: proxyInfo, timeout: const Duration(seconds: 3));
|
||||
if (mounted) {
|
||||
FlutterToastr.show(localizations!.reSendRequest, rootNavigator: true, context);
|
||||
}
|
||||
} catch (e) {
|
||||
if (mounted) {
|
||||
FlutterToastr.show('${localizations!.fail} $e', rootNavigator: true, context);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -75,6 +75,8 @@ class DomainWidgetState extends State<DomainList> with AutomaticKeepAliveClientM
|
||||
//关键词高亮监听
|
||||
late VoidCallback highlightListener;
|
||||
|
||||
bool sortDesc = true;
|
||||
|
||||
changeState() {
|
||||
if (!changing) {
|
||||
changing = true;
|
||||
@@ -92,7 +94,7 @@ class DomainWidgetState extends State<DomainList> with AutomaticKeepAliveClientM
|
||||
var container = widget.list;
|
||||
for (var request in container.source) {
|
||||
DomainRequests domainRequests = getDomainRequests(request);
|
||||
domainRequests.addRequest(request.requestId, request);
|
||||
domainRequests.addRequest(request.requestId, request, sortDesc);
|
||||
}
|
||||
highlightListener = () {
|
||||
//回调时机在高亮设置页面dispose之后。所以需要在下一帧刷新,否则会报错
|
||||
@@ -172,10 +174,10 @@ class DomainWidgetState extends State<DomainList> with AutomaticKeepAliveClientM
|
||||
DomainRequests domainRequests = getDomainRequests(request);
|
||||
var isNew = domainRequests.body.isEmpty;
|
||||
|
||||
domainRequests.addRequest(request.requestId, request);
|
||||
domainRequests.addRequest(request.requestId, request, sortDesc);
|
||||
//搜索视图
|
||||
if (searchModel?.isNotEmpty == true && searchModel?.filter(request, null) == true) {
|
||||
searchView[host]?.addRequest(request.requestId, request);
|
||||
searchView[host]?.addRequest(request.requestId, request, sortDesc);
|
||||
}
|
||||
|
||||
if (isNew) {
|
||||
@@ -242,7 +244,7 @@ class DomainWidgetState extends State<DomainList> with AutomaticKeepAliveClientM
|
||||
if (searchModel?.isNotEmpty == true && searchModel?.filter(pathRow.request, response) == true) {
|
||||
var requests = searchView[domain];
|
||||
if (requests?.getRequest(response) == null) {
|
||||
requests?.addRequest(response.requestId, pathRow.request);
|
||||
requests?.addRequest(response.requestId, pathRow.request, sortDesc);
|
||||
}
|
||||
requests?.getRequest(response)?.setResponse(response);
|
||||
}
|
||||
@@ -264,7 +266,7 @@ class DomainWidgetState extends State<DomainList> with AutomaticKeepAliveClientM
|
||||
var container = widget.list;
|
||||
for (var request in container.source) {
|
||||
DomainRequests domainRequests = getDomainRequests(request);
|
||||
domainRequests.addRequest(request.requestId, request);
|
||||
domainRequests.addRequest(request.requestId, request, sortDesc);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -276,6 +278,17 @@ class DomainWidgetState extends State<DomainList> with AutomaticKeepAliveClientM
|
||||
}
|
||||
return container.expand((list) => list.body.map((it) => it.request)).toList();
|
||||
}
|
||||
|
||||
///排序
|
||||
sort(bool desc) {
|
||||
sortDesc = desc;
|
||||
containerMap.forEach((key, request) {
|
||||
var reversed = request.body.toList().reversed;
|
||||
request.body.clear();
|
||||
request.body.addAll(reversed);
|
||||
request.changeState();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
///标题和内容布局 标题是域名 内容是域名下请求
|
||||
@@ -302,15 +315,17 @@ class DomainRequests extends StatefulWidget {
|
||||
: super(key: GlobalKey<_DomainRequestsState>());
|
||||
|
||||
///添加请求
|
||||
void addRequest(String? requestId, HttpRequest request) {
|
||||
void addRequest(String? requestId, HttpRequest request, bool sortDesc) {
|
||||
if (requestMap.containsKey(requestId)) return;
|
||||
|
||||
var requestWidget = RequestWidget(request,
|
||||
index: body.length, proxyServer: proxyServer, displayDomain: false, remove: (it) => _remove(it));
|
||||
body.addFirst(requestWidget);
|
||||
sortDesc ? body.addFirst(requestWidget) : body.addLast(requestWidget);
|
||||
|
||||
if (requestId == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
requestMap[requestId] = requestWidget;
|
||||
changeState();
|
||||
}
|
||||
@@ -407,6 +422,7 @@ class _DomainRequestsState extends State<DomainRequests> {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
|
||||
return Column(children: [
|
||||
_hostWidget(widget.domain),
|
||||
Offstage(offstage: !selected, child: Column(children: widget.body.toList()))
|
||||
@@ -521,7 +537,7 @@ class HostWidget extends StatelessWidget {
|
||||
final String host;
|
||||
final Function()? onMenu;
|
||||
|
||||
HostWidget(this.host, {this.onMenu});
|
||||
const HostWidget(this.host, {super.key, this.onMenu});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
|
||||
@@ -21,12 +21,16 @@ import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
||||
import 'package:flutter_toastr/flutter_toastr.dart';
|
||||
import 'package:proxypin/network/bin/server.dart';
|
||||
import 'package:proxypin/network/channel.dart';
|
||||
import 'package:proxypin/network/host_port.dart';
|
||||
import 'package:proxypin/network/http/http.dart';
|
||||
import 'package:proxypin/network/http_client.dart';
|
||||
import 'package:proxypin/ui/component/widgets.dart';
|
||||
import 'package:proxypin/ui/content/panel.dart';
|
||||
import 'package:proxypin/ui/desktop/request/model/search_model.dart';
|
||||
import 'package:proxypin/ui/desktop/request/request_sequence.dart';
|
||||
import 'package:proxypin/ui/desktop/request/search.dart';
|
||||
import 'package:proxypin/utils/har.dart';
|
||||
import 'package:proxypin/utils/lang.dart';
|
||||
import 'package:proxypin/utils/listenable_list.dart';
|
||||
|
||||
import 'domians.dart';
|
||||
@@ -52,6 +56,8 @@ class DesktopRequestListState extends State<DesktopRequestListWidget> with Autom
|
||||
//请求列表容器
|
||||
ListenableList<HttpRequest> container = ListenableList();
|
||||
|
||||
bool sortDesc = true;
|
||||
|
||||
AppLocalizations get localizations => AppLocalizations.of(context)!;
|
||||
|
||||
@override
|
||||
@@ -83,9 +89,11 @@ class DesktopRequestListState extends State<DesktopRequestListWidget> with Autom
|
||||
length: tabs.length,
|
||||
child: Scaffold(
|
||||
appBar: AppBar(
|
||||
toolbarHeight: 40,
|
||||
title: SizedBox(height: 40, child: TabBar(tabs: tabs)),
|
||||
automaticallyImplyLeading: false),
|
||||
toolbarHeight: 40,
|
||||
title: SizedBox(height: 40, child: TabBar(tabs: tabs)),
|
||||
automaticallyImplyLeading: false,
|
||||
actions: [popupMenus()],
|
||||
),
|
||||
bottomNavigationBar: Search(onSearch: search),
|
||||
body: Padding(
|
||||
padding: const EdgeInsets.only(right: 5),
|
||||
@@ -104,6 +112,41 @@ class DesktopRequestListState extends State<DesktopRequestListWidget> with Autom
|
||||
]))));
|
||||
}
|
||||
|
||||
Widget popupMenus() {
|
||||
return PopupMenuButton(
|
||||
offset: const Offset(0, 32),
|
||||
icon: const Icon(Icons.more_vert_outlined, size: 20),
|
||||
itemBuilder: (BuildContext context) {
|
||||
return <PopupMenuEntry>[
|
||||
CustomPopupMenuItem(
|
||||
height: 35,
|
||||
onTap: () => export('ProxyPin_${DateTime.now().dateFormat()}.har'),
|
||||
child: IconText(
|
||||
icon: const Icon(Icons.share, size: 16),
|
||||
text: localizations.viewExport,
|
||||
textStyle: const TextStyle(fontSize: 13))),
|
||||
CustomPopupMenuItem(
|
||||
height: 35,
|
||||
onTap: () => repeatAllRequests(),
|
||||
child: IconText(
|
||||
icon: const Icon(Icons.repeat, size: 16),
|
||||
text: localizations.repeatAllRequests,
|
||||
textStyle: const TextStyle(fontSize: 13))),
|
||||
CustomPopupMenuItem(
|
||||
height: 35,
|
||||
onTap: () {
|
||||
sortDesc = !sortDesc;
|
||||
requestSequenceKey.currentState?.sort(sortDesc);
|
||||
domainListKey.currentState?.sort(sortDesc);
|
||||
},
|
||||
child: IconText(
|
||||
icon: const Icon(Icons.sort, size: 16),
|
||||
text: sortDesc ? localizations.timeDesc : localizations.timeAsc,
|
||||
textStyle: const TextStyle(fontSize: 13))),
|
||||
];
|
||||
});
|
||||
}
|
||||
|
||||
///添加请求
|
||||
add(Channel channel, HttpRequest request) {
|
||||
container.add(request);
|
||||
@@ -176,4 +219,28 @@ class DesktopRequestListState extends State<DesktopRequestListWidget> with Autom
|
||||
|
||||
if (mounted) FlutterToastr.show(AppLocalizations.of(context)!.exportSuccess, context);
|
||||
}
|
||||
|
||||
///重发所有请求
|
||||
void repeatAllRequests() async {
|
||||
var requests = currentView();
|
||||
if (requests == null) return;
|
||||
|
||||
var localizations = AppLocalizations.of(context);
|
||||
final proxyServer = widget.proxyServer;
|
||||
|
||||
for (var request in requests) {
|
||||
var httpRequest = request.copy(uri: request.requestUrl);
|
||||
var proxyInfo = proxyServer.isRunning ? ProxyInfo.of("127.0.0.1", proxyServer.port) : null;
|
||||
try {
|
||||
await HttpClients.proxyRequest(httpRequest, proxyInfo: proxyInfo, timeout: const Duration(seconds: 3));
|
||||
if (mounted) {
|
||||
FlutterToastr.show(localizations!.reSendRequest, rootNavigator: true, context);
|
||||
}
|
||||
} catch (e) {
|
||||
if (mounted) {
|
||||
FlutterToastr.show('${localizations!.fail} $e', rootNavigator: true, context);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -50,6 +50,8 @@ class RequestSequenceState extends State<RequestSequence> with AutomaticKeepAliv
|
||||
Queue<HttpRequest> view = Queue();
|
||||
bool changing = false;
|
||||
|
||||
bool sortDesc = true;
|
||||
|
||||
//搜索的内容
|
||||
SearchModel? searchModel;
|
||||
|
||||
@@ -147,7 +149,12 @@ class RequestSequenceState extends State<RequestSequence> with AutomaticKeepAliv
|
||||
return;
|
||||
}
|
||||
|
||||
view.addFirst(request);
|
||||
if (sortDesc) {
|
||||
view.addFirst(request);
|
||||
} else {
|
||||
view.addLast(request);
|
||||
}
|
||||
|
||||
changeState();
|
||||
}
|
||||
|
||||
@@ -190,4 +197,12 @@ class RequestSequenceState extends State<RequestSequence> with AutomaticKeepAliv
|
||||
view.addAll(widget.container.source.reversed);
|
||||
});
|
||||
}
|
||||
|
||||
///排序
|
||||
sort(bool desc) {
|
||||
sortDesc = desc;
|
||||
setState(() {
|
||||
view = Queue.of(view.toList().reversed);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,6 +33,8 @@ class RequestSequenceState extends State<RequestSequence> with AutomaticKeepAliv
|
||||
Queue<HttpRequest> view = Queue();
|
||||
bool changing = false;
|
||||
|
||||
bool sortDesc = true;
|
||||
|
||||
//搜索的内容
|
||||
SearchModel? searchModel;
|
||||
|
||||
@@ -65,7 +67,12 @@ class RequestSequenceState extends State<RequestSequence> with AutomaticKeepAliv
|
||||
return;
|
||||
}
|
||||
|
||||
view.addFirst(request);
|
||||
if (sortDesc) {
|
||||
view.addFirst(request);
|
||||
} else {
|
||||
view.addLast(request);
|
||||
}
|
||||
|
||||
changeState();
|
||||
}
|
||||
|
||||
@@ -164,4 +171,16 @@ class RequestSequenceState extends State<RequestSequence> with AutomaticKeepAliv
|
||||
PrimaryScrollController.maybeOf(context)
|
||||
?.animateTo(0, duration: const Duration(milliseconds: 300), curve: Curves.ease);
|
||||
}
|
||||
|
||||
///排序
|
||||
sort(bool desc) {
|
||||
if (sortDesc == desc) {
|
||||
return;
|
||||
}
|
||||
|
||||
sortDesc = desc;
|
||||
setState(() {
|
||||
view = Queue.of(view.toList().reversed);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -622,7 +622,7 @@
|
||||
"@executable_path/../Frameworks",
|
||||
);
|
||||
MACOSX_DEPLOYMENT_TARGET = "$(RECOMMENDED_MACOSX_DEPLOYMENT_TARGET)";
|
||||
MARKETING_VERSION = 1.0.0;
|
||||
MARKETING_VERSION = 1.1.7;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.proxy.pin;
|
||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||
SWIFT_VERSION = 5.0;
|
||||
@@ -763,7 +763,7 @@
|
||||
"@executable_path/../Frameworks",
|
||||
);
|
||||
MACOSX_DEPLOYMENT_TARGET = "$(RECOMMENDED_MACOSX_DEPLOYMENT_TARGET)";
|
||||
MARKETING_VERSION = 1.0.0;
|
||||
MARKETING_VERSION = 1.1.7;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.proxy.pin;
|
||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
||||
@@ -792,7 +792,7 @@
|
||||
"@executable_path/../Frameworks",
|
||||
);
|
||||
MACOSX_DEPLOYMENT_TARGET = "$(RECOMMENDED_MACOSX_DEPLOYMENT_TARGET)";
|
||||
MARKETING_VERSION = 1.0.0;
|
||||
MARKETING_VERSION = 1.1.7;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.proxy.pin;
|
||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||
SWIFT_VERSION = 5.0;
|
||||
|
||||
Reference in New Issue
Block a user