feat: add 'Copy as fetch' functionality (#685)

This commit is contained in:
wanghongenpin
2026-05-10 21:03:41 +08:00
parent 1447cd529a
commit a950c71d81
10 changed files with 79 additions and 8 deletions

View File

@@ -159,6 +159,7 @@
"copyRequestResponse": "Copy Request and Response",
"copyCurl": "Copy cURL",
"copyAsPythonRequests": "Copy as Python Requests",
"copyAsFetch": "Copy as fetch",
"delete": "Delete",
"rename": "Rename",
"repeat": "Repeat",

View File

@@ -1014,6 +1014,12 @@ abstract class AppLocalizations {
/// **'Copy as Python Requests'**
String get copyAsPythonRequests;
/// No description provided for @copyAsFetch.
///
/// In en, this message translates to:
/// **'Copy as fetch'**
String get copyAsFetch;
/// No description provided for @delete.
///
/// In en, this message translates to:

View File

@@ -473,6 +473,9 @@ class AppLocalizationsEn extends AppLocalizations {
@override
String get copyAsPythonRequests => 'Copy as Python Requests';
@override
String get copyAsFetch => 'Copy as fetch';
@override
String get delete => 'Delete';

View File

@@ -471,6 +471,9 @@ class AppLocalizationsZh extends AppLocalizations {
@override
String get copyAsPythonRequests => '复制 Python Requests';
@override
String get copyAsFetch => '复制 fetch';
@override
String get delete => '删除';
@@ -1559,6 +1562,9 @@ class AppLocalizationsZhHant extends AppLocalizationsZh {
@override
String get copyAsPythonRequests => '複製 Python Requests';
@override
String get copyAsFetch => '複製 fetch';
@override
String get delete => '刪除';

View File

@@ -160,6 +160,7 @@
"copyRequestResponse": "复制 请求和响应",
"copyCurl": "复制 cURL",
"copyAsPythonRequests": "复制 Python Requests",
"copyAsFetch": "复制 fetch",
"delete": "删除",
"rename": "重命名",
"repeat": "重放",

View File

@@ -152,6 +152,7 @@
"copyRequestResponse": "複製 請求和回應",
"copyCurl": "複製 cURL",
"copyAsPythonRequests": "複製 Python Requests",
"copyAsFetch": "複製 fetch",
"delete": "刪除",
"rename": "重新命名",
"repeat": "重放",

View File

@@ -45,8 +45,10 @@ class ShareWidget extends StatelessWidget {
FlutterToastr.show(localizations.emptyData, context);
return;
}
Share.share(request!.requestUrl,
subject: localizations.proxyPinSoftware, sharePositionOrigin: await _sharePositionOrigin(context));
SharePlus.instance.share(ShareParams(
text: request!.requestUrl,
subject: localizations.proxyPinSoftware,
sharePositionOrigin: await _sharePositionOrigin(context)));
},
),
PopupMenuItem(
@@ -60,10 +62,11 @@ class ShareWidget extends StatelessWidget {
var file = XFile.fromData(utf8.encode(copyRequest(request!, response)),
name: localizations.captureDetail, mimeType: "txt");
Share.shareXFiles([file],
SharePlus.instance.share(ShareParams(
files: [file],
fileNameOverrides: ['request.txt'],
text: localizations.proxyPinSoftware,
sharePositionOrigin: await _sharePositionOrigin(context));
sharePositionOrigin: await _sharePositionOrigin(context)));
}),
PopupMenuItem(
padding: const EdgeInsets.only(left: 10, right: 2),
@@ -75,10 +78,22 @@ class ShareWidget extends StatelessWidget {
var text = curlRequest(request!);
var file = XFile.fromData(utf8.encode(text), name: "cURL.txt", mimeType: "txt");
Share.shareXFiles([file],
SharePlus.instance.share(ShareParams(
files: [file],
fileNameOverrides: ["cURL.txt"],
text: localizations.proxyPinSoftware,
sharePositionOrigin: await _sharePositionOrigin(context));
sharePositionOrigin: await _sharePositionOrigin(context)));
}),
PopupMenuItem(
padding: const EdgeInsets.only(left: 10, right: 2),
child: Text("${localizations.share} Fetch API"),
onTap: () async {
if (request == null) {
return;
}
var text = copyAsFetch(request!);
SharePlus.instance
.share(ShareParams(text: text, sharePositionOrigin: await _sharePositionOrigin(context)));
}),
PopupMenuItem(
enabled: QuickShareService.isRemoteConnected(proxyServer),

View File

@@ -204,6 +204,13 @@ class _RequestWidgetState extends State<RequestWidget> {
.then((value) => FlutterToastr.show(localizations.copied, rootNavigator: true, context));
},
),
MenuItem(
label: localizations.copyAsFetch,
onClick: (_) {
Clipboard.setData(ClipboardData(text: copyAsFetch(widget.request)))
.then((value) => FlutterToastr.show(localizations.copied, rootNavigator: true, context));
},
),
])),
MenuItem.separator(),
MenuItem(

View File

@@ -546,8 +546,11 @@ class _MobileAppBar extends StatelessWidget implements PreferredSizeWidget {
return AppBar(
leading: bottomNavigation ? const SizedBox() : null,
systemOverlayStyle:
Platform.isAndroid ? SystemUiOverlayStyle(systemNavigationBarColor: ColorScheme.of(context).surface) : null,
systemOverlayStyle: Platform.isAndroid
? SystemUiOverlayStyle(
systemNavigationBarColor: ColorScheme.of(context).surface,
statusBarColor: ColorScheme.of(context).surface)
: null,
title: MobileSearch(
key: MobileApp.searchStateKey, onSearch: (val) => MobileApp.requestStateKey.currentState?.search(val)),
actions: [

View File

@@ -17,6 +17,34 @@
import 'package:proxypin/network/http/http.dart';
import 'package:proxypin/network/http/http_headers.dart';
import 'package:proxypin/utils/lang.dart';
import 'dart:convert';
/// 复制为 fetch 请求
String copyAsFetch(HttpRequest request) {
final headers = request.headers.entries
.where((entry) => entry.key.toLowerCase() != 'content-length')
.toList();
final sb = StringBuffer();
sb.writeln('fetch(${jsonEncode(request.requestUrl)}, {');
sb.writeln(' method: ${jsonEncode(request.method.name.toUpperCase())},');
if (headers.isNotEmpty) {
sb.writeln(' headers: {');
for (final entry in headers) {
sb.writeln(' ${jsonEncode(entry.key)}: ${jsonEncode(entry.value)},');
}
sb.writeln(' },');
}
if (request.bodyAsString.isNotEmpty) {
sb.writeln(' body: ${jsonEncode(request.bodyAsString)},');
}
sb.writeln('});');
return sb.toString();
}
///复制cURL请求
String curlRequest(HttpRequest request) {