Popup menu UI optimization & request highlighting

This commit is contained in:
wanghongenpin
2024-10-20 13:22:58 +08:00
parent a01b03d97b
commit 11666ea9fc
13 changed files with 366 additions and 197 deletions

View File

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

View File

@@ -274,6 +274,7 @@
"material3": "Material3是谷歌开源设计系统的最新版本",
"iosVpnBackgroundAudio": "开启抓包后退出到后台。为了维护主UI线程的网络通信将启用静音音频播放以保持主线程运行。否则它将只在后台运行30秒。您同意在启用抓包后在后台播放音频吗?",
"markRead": "标记已读",
"highlight": "高亮",
"blue": "蓝色",
"green": "绿色",

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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