From 6e0a30c1b79d50d86d70217e5377e4fab5f48fc2 Mon Sep 17 00:00:00 2001 From: wanghongenpin Date: Mon, 10 Nov 2025 23:21:32 +0800 Subject: [PATCH] Save Request (#561) --- lib/ui/component/share.dart | 85 ------------- lib/ui/content/menu.dart | 222 +++++++++++++++++++++++++++++++++ lib/ui/content/panel.dart | 67 +--------- lib/ui/mobile/menu/drawer.dart | 1 + 4 files changed, 225 insertions(+), 150 deletions(-) delete mode 100644 lib/ui/component/share.dart create mode 100644 lib/ui/content/menu.dart diff --git a/lib/ui/component/share.dart b/lib/ui/component/share.dart deleted file mode 100644 index dbd7271..0000000 --- a/lib/ui/component/share.dart +++ /dev/null @@ -1,85 +0,0 @@ -import 'dart:convert'; - -import 'package:flutter/material.dart'; -import 'package:proxypin/l10n/app_localizations.dart'; -import 'package:flutter_toastr/flutter_toastr.dart'; -import 'package:proxypin/network/bin/server.dart'; -import 'package:proxypin/network/http/http.dart'; -import 'package:proxypin/ui/component/utils.dart'; -import 'package:proxypin/utils/curl.dart'; -import 'package:proxypin/utils/platform.dart'; -import 'package:share_plus/share_plus.dart'; - -///分享按钮 -class ShareWidget extends StatelessWidget { - final ProxyServer? proxyServer; - final HttpRequest? request; - final HttpResponse? response; - -const ShareWidget({super.key, required this.proxyServer, this.request, this.response}); - - @override - Widget build(BuildContext context) { - AppLocalizations localizations = AppLocalizations.of(context)!; - - return PopupMenuButton( - icon: const Icon(Icons.share, size: 24), - offset: const Offset(0, 30), - itemBuilder: (BuildContext context) { - return [ - PopupMenuItem( - padding: const EdgeInsets.only(left: 10, right: 2), - child: Text(localizations.shareUrl), - onTap: () async { - if (request == null) { - FlutterToastr.show("Request is empty", context); - return; - } - Share.share(request!.requestUrl, - subject: localizations.proxyPinSoftware, sharePositionOrigin: await _sharePositionOrigin(context)); - }, - ), - PopupMenuItem( - padding: const EdgeInsets.only(left: 10, right: 2), - child: Text(localizations.shareRequestResponse), - onTap: () async { - if (request == null) { - FlutterToastr.show("Request is empty", context); - return; - } - var file = XFile.fromData(utf8.encode(copyRequest(request!, response)), - name: localizations.captureDetail, mimeType: "txt"); - - Share.shareXFiles([file], - fileNameOverrides: ['request.txt'], - text: localizations.proxyPinSoftware, - sharePositionOrigin: await _sharePositionOrigin(context)); - }), - PopupMenuItem( - padding: const EdgeInsets.only(left: 10, right: 2), - child: Text(localizations.shareCurl), - onTap: () async { - if (request == null) { - return; - } - var text = curlRequest(request!); - var file = XFile.fromData(utf8.encode(text), name: "cURL.txt", mimeType: "txt"); - - Share.shareXFiles([file], - fileNameOverrides: ["cURL.txt"], - text: localizations.proxyPinSoftware, - sharePositionOrigin: await _sharePositionOrigin(context)); - }), - ]; - }, - ); - } - - Future _sharePositionOrigin(BuildContext context) async { - RenderBox? box; - if (await Platforms.isIpad() && context.mounted) { - box = context.findRenderObject() as RenderBox?; - } - return box == null ? null : box.localToGlobal(Offset.zero) & box.size; - } -} diff --git a/lib/ui/content/menu.dart b/lib/ui/content/menu.dart new file mode 100644 index 0000000..45437c3 --- /dev/null +++ b/lib/ui/content/menu.dart @@ -0,0 +1,222 @@ +import 'dart:convert'; + +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:proxypin/l10n/app_localizations.dart'; +import 'package:flutter_toastr/flutter_toastr.dart'; +import 'package:proxypin/network/bin/server.dart'; +import 'package:proxypin/network/http/http.dart'; +import 'package:proxypin/storage/favorites.dart'; +import 'package:proxypin/ui/component/utils.dart'; +import 'package:proxypin/utils/curl.dart'; +import 'package:proxypin/utils/platform.dart'; +import 'package:share_plus/share_plus.dart'; + +import '../../utils/export_request.dart'; +import '../../utils/python.dart'; +import '../component/widgets.dart'; +import '../mobile/menu/bottom_navigation.dart'; +import '../mobile/request/request_editor.dart'; +import '../mobile/setting/request_map.dart'; + +///分享按钮 +class ShareWidget extends StatelessWidget { + final ProxyServer? proxyServer; + final HttpRequest? request; + final HttpResponse? response; + + const ShareWidget({super.key, required this.proxyServer, this.request, this.response}); + + @override + Widget build(BuildContext context) { + AppLocalizations localizations = AppLocalizations.of(context)!; + + return PopupMenuButton( + icon: const Icon(Icons.share, size: 24), + offset: const Offset(0, 30), + itemBuilder: (BuildContext context) { + return [ + PopupMenuItem( + padding: const EdgeInsets.only(left: 10, right: 2), + child: Text(localizations.shareUrl), + onTap: () async { + if (request == null) { + FlutterToastr.show("Request is empty", context); + return; + } + Share.share(request!.requestUrl, + subject: localizations.proxyPinSoftware, sharePositionOrigin: await _sharePositionOrigin(context)); + }, + ), + PopupMenuItem( + padding: const EdgeInsets.only(left: 10, right: 2), + child: Text(localizations.shareRequestResponse), + onTap: () async { + if (request == null) { + FlutterToastr.show("Request is empty", context); + return; + } + var file = XFile.fromData(utf8.encode(copyRequest(request!, response)), + name: localizations.captureDetail, mimeType: "txt"); + + Share.shareXFiles([file], + fileNameOverrides: ['request.txt'], + text: localizations.proxyPinSoftware, + sharePositionOrigin: await _sharePositionOrigin(context)); + }), + PopupMenuItem( + padding: const EdgeInsets.only(left: 10, right: 2), + child: Text(localizations.shareCurl), + onTap: () async { + if (request == null) { + return; + } + var text = curlRequest(request!); + var file = XFile.fromData(utf8.encode(text), name: "cURL.txt", mimeType: "txt"); + + Share.shareXFiles([file], + fileNameOverrides: ["cURL.txt"], + text: localizations.proxyPinSoftware, + sharePositionOrigin: await _sharePositionOrigin(context)); + }), + ]; + }, + ); + } + + Future _sharePositionOrigin(BuildContext context) async { + RenderBox? box; + if (await Platforms.isIpad() && context.mounted) { + box = context.findRenderObject() as RenderBox?; + } + return box == null ? null : box.localToGlobal(Offset.zero) & box.size; + } +} + +class DetailMenuWidget extends StatelessWidget { + final HttpRequest? request; + + const DetailMenuWidget({ + super.key, + this.request, + }); + + @override + Widget build(BuildContext context) { + final localizations = AppLocalizations.of(context)!; + return PopupMenuButton( + offset: const Offset(0, 30), + padding: const EdgeInsets.all(0), + itemBuilder: (context) => [ + PopupMenuItem( + child: Text(localizations.favorite), + onTap: () { + if (request == null) return; + + FavoriteStorage.addFavorite(request!); + FlutterToastr.show(localizations.addSuccess, context); + }), + PopupMenuItem( + child: Text(localizations.save), + onTap: () { + if (request == null) return; + + showDialog( + context: context, + builder: (menuContext) { + return AlertDialog( + title: Text(localizations.save), + content: SingleChildScrollView( + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + ListTile( + visualDensity: const VisualDensity(vertical: -3), + contentPadding: const EdgeInsets.symmetric(horizontal: 12, vertical: 2), + title: Text(localizations.request), + onTap: () { + Navigator.of(menuContext).pop(); + WidgetsBinding.instance.addPostFrameCallback((_) { + exportRequest(request!); + }); + }, + ), + ListTile( + visualDensity: const VisualDensity(vertical: -3), + contentPadding: const EdgeInsets.symmetric(horizontal: 12, vertical: 2), + title: Text(localizations.requestBody), + onTap: () { + Navigator.of(menuContext).pop(); + WidgetsBinding.instance.addPostFrameCallback((_) { + exportRequestBody(request!); + }); + }, + ), + ListTile( + visualDensity: const VisualDensity(vertical: -3), + contentPadding: const EdgeInsets.symmetric(horizontal: 12, vertical: 2), + title: Text(localizations.response), + onTap: () { + Navigator.of(menuContext).pop(); + WidgetsBinding.instance.addPostFrameCallback((_) { + exportResponse(request?.response); + }); + }, + ), + ListTile( + visualDensity: const VisualDensity(vertical: -3), + contentPadding: const EdgeInsets.symmetric(horizontal: 12, vertical: 2), + title: Text(localizations.responseBody), + onTap: () { + Navigator.of(menuContext).pop(); + WidgetsBinding.instance.addPostFrameCallback((_) { + exportResponseBody(request?.response); + }); + }, + ), + ], + ), + )); + }); + }), + PopupMenuItem( + child: Text(localizations.requestEdit), + onTap: () { + WidgetsBinding.instance.addPostFrameCallback((_) { + Navigator.of(context).push(MaterialPageRoute( + builder: (context) => + MobileRequestEditor(request: request, proxyServer: ProxyServer.current))); + }); + }), + PopupMenuItem( + child: Text(localizations.requestMap), + onTap: () { + WidgetsBinding.instance.addPostFrameCallback((_) { + navigator( + context, MobileRequestMapEdit(url: request?.domainPath, title: request?.hostAndPort?.host)); + }); + }), + CustomPopupMenuItem( + padding: const EdgeInsets.only(left: 10), + child: Text(localizations.copyRawRequest), + onTap: () { + if (request == null) return; + + var text = copyRawRequest(request!); + Clipboard.setData(ClipboardData(text: text)); + FlutterToastr.show(localizations.copied, context); + }), + CustomPopupMenuItem( + padding: const EdgeInsets.only(left: 10), + child: Text(localizations.copyAsPythonRequests), + onTap: () { + if (request == null) return; + + var text = copyAsPythonRequests(request!); + Clipboard.setData(ClipboardData(text: text)); + FlutterToastr.show(localizations.copied, context); + }) + ], + child: const SizedBox(height: 38, width: 38, child: Icon(Icons.more_vert, size: 28))); + } +} diff --git a/lib/ui/content/panel.dart b/lib/ui/content/panel.dart index 5f247d3..c955259 100644 --- a/lib/ui/content/panel.dart +++ b/lib/ui/content/panel.dart @@ -17,25 +17,18 @@ import 'package:desktop_multi_window/desktop_multi_window.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; -import 'package:flutter_toastr/flutter_toastr.dart'; import 'package:proxypin/l10n/app_localizations.dart'; import 'package:proxypin/network/bin/server.dart'; import 'package:proxypin/network/http/http.dart'; -import 'package:proxypin/storage/favorites.dart'; -import 'package:proxypin/ui/component/share.dart'; import 'package:proxypin/ui/component/state_component.dart'; import 'package:proxypin/ui/component/utils.dart'; -import 'package:proxypin/ui/component/widgets.dart'; import 'package:proxypin/ui/configuration.dart'; import 'package:proxypin/ui/content/web_socket.dart'; -import 'package:proxypin/ui/mobile/menu/drawer.dart'; -import 'package:proxypin/ui/mobile/request/request_editor.dart'; -import 'package:proxypin/ui/mobile/setting/request_map.dart'; import 'package:proxypin/utils/lang.dart'; import 'package:proxypin/utils/platform.dart'; -import 'package:proxypin/utils/python.dart'; import 'body.dart'; +import 'menu.dart'; ///网络请求详情页 ///@Author: wanghongen @@ -159,63 +152,7 @@ class NetworkTabState extends State with SingleTickerProvi ShareWidget( proxyServer: widget.proxyServer, request: widget.request.get(), response: widget.response.get()), const SizedBox(width: 3), - PopupMenuButton( - offset: const Offset(0, 30), - padding: const EdgeInsets.all(0), - itemBuilder: (context) => [ - PopupMenuItem( - child: Text(localizations.favorite), - onTap: () { - var request = widget.request.get(); - if (request == null) return; - - FavoriteStorage.addFavorite(request); - FlutterToastr.show(localizations.addSuccess, context); - }), - PopupMenuItem( - child: Text(localizations.requestEdit), - onTap: () { - WidgetsBinding.instance.addPostFrameCallback((_) { - Navigator.of(context).push(MaterialPageRoute( - builder: (context) => MobileRequestEditor( - request: widget.request.get(), proxyServer: widget.proxyServer))); - }); - }), - PopupMenuItem( - child: Text(localizations.requestMap), - onTap: () { - WidgetsBinding.instance.addPostFrameCallback((_) { - navigator( - context, - MobileRequestMapEdit( - url: widget.request.get()?.domainPath, - title: widget.request.get()?.hostAndPort?.host)); - }); - }), - CustomPopupMenuItem( - padding: const EdgeInsets.only(left: 10), - child: Text(localizations.copyRawRequest), - onTap: () { - var request = widget.request.get(); - if (request == null) return; - - var text = copyRawRequest(request); - Clipboard.setData(ClipboardData(text: text)); - FlutterToastr.show(localizations.copied, context); - }), - CustomPopupMenuItem( - padding: const EdgeInsets.only(left: 10), - child: Text(localizations.copyAsPythonRequests), - onTap: () { - var request = widget.request.get(); - if (request == null) return; - - var text = copyAsPythonRequests(request); - Clipboard.setData(ClipboardData(text: text)); - FlutterToastr.show(localizations.copied, context); - }) - ], - child: const SizedBox(height: 38, width: 38, child: Icon(Icons.more_vert, size: 28))), + DetailMenuWidget(request: widget.request.get()), const SizedBox(width: 10), ], ); diff --git a/lib/ui/mobile/menu/drawer.dart b/lib/ui/mobile/menu/drawer.dart index e2ad72f..cc4098b 100644 --- a/lib/ui/mobile/menu/drawer.dart +++ b/lib/ui/mobile/menu/drawer.dart @@ -56,6 +56,7 @@ class DrawerWidget extends StatelessWidget { return Drawer( backgroundColor: Theme.of(context).cardColor, child: ListView( + padding: EdgeInsets.zero, children: [ DrawerHeader( decoration: BoxDecoration(color: Theme.of(context).colorScheme.primaryContainer),