Add breakpoint export and import (#669)(#660)(#386)

This commit is contained in:
wanghongenpin
2026-02-25 16:41:12 +08:00
parent c17066748f
commit 905d8932bd
7 changed files with 131 additions and 4 deletions

View File

@@ -62,6 +62,7 @@
"importFailed": "Import failed",
"export": "Export",
"exportSuccess": "Export successful",
"exportFailed": "Export failed",
"deleteSuccess": "Delete successful",
"send": "Send",
"fail": "fail",

View File

@@ -456,6 +456,12 @@ abstract class AppLocalizations {
/// **'Export successful'**
String get exportSuccess;
/// No description provided for @exportFailed.
///
/// In en, this message translates to:
/// **'Export failed'**
String get exportFailed;
/// No description provided for @deleteSuccess.
///
/// In en, this message translates to:

View File

@@ -188,6 +188,9 @@ class AppLocalizationsEn extends AppLocalizations {
@override
String get exportSuccess => 'Export successful';
@override
String get exportFailed => 'Export failed';
@override
String get deleteSuccess => 'Delete successful';

View File

@@ -188,6 +188,9 @@ class AppLocalizationsZh extends AppLocalizations {
@override
String get exportSuccess => '导出成功';
@override
String get exportFailed => '导出失败';
@override
String get deleteSuccess => '删除成功';

View File

@@ -63,6 +63,7 @@
"importFailed": "导入失败",
"export": "导出",
"exportSuccess": "导出成功",
"exportFailed": "导出失败",
"deleteSuccess": "删除成功",
"send": "发送",
"fail": "失败",

View File

@@ -1,4 +1,8 @@
import 'dart:convert';
import 'dart:io';
import 'package:desktop_multi_window/desktop_multi_window.dart';
import 'package:file_picker/file_picker.dart';
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
@@ -41,6 +45,60 @@ class _RequestBreakpointPageState extends State<RequestBreakpointPage> {
await _refreshConfig();
}
Future<void> _import() async {
String? path;
if (Platform.isMacOS) {
path = await DesktopMultiWindow.invokeMethod(0, "pickFiles", {
"allowedExtensions": ['json']
});
if (widget.windowId != null) WindowController.fromWindowId(widget.windowId!).show();
} else {
FilePickerResult? result =
await FilePicker.platform.pickFiles(type: FileType.custom, allowedExtensions: ['json']);
path = result?.files.single.path;
}
if (path == null) return;
File file = File(path);
try {
String content = await file.readAsString();
List<dynamic> list = jsonDecode(content);
var rules = list.map((e) => RequestBreakpointRule.fromJson(e)).toList();
for (var rule in rules) {
manager?.list.add(rule);
}
await _save();
setState(() {
this.rules = manager!.list;
});
if (mounted) CustomToast.success(localizations.importSuccess).show(context);
} catch (e) {
if (mounted) CustomToast.error(localizations.importFailed).show(context);
}
}
Future<void> _export(List<RequestBreakpointRule> exportRules) async {
if (exportRules.isEmpty) return;
String? outputFile;
if (Platform.isMacOS) {
outputFile = await DesktopMultiWindow.invokeMethod(0, "saveFile", {"fileName": 'request_breakpoint_rules.json'});
if (widget.windowId != null) WindowController.fromWindowId(widget.windowId!).show();
} else {
outputFile = await FilePicker.platform.saveFile(fileName: 'request_breakpoint_rules.json');
}
if (outputFile == null) return;
File file = File(outputFile);
try {
var json = exportRules.map((e) => e.toJson()).toList();
await file.writeAsString(jsonEncode(json));
if (mounted) CustomToast.success(localizations.exportSuccess).show(context);
} catch (e) {
if (mounted) CustomToast.error(localizations.exportFailed).show(context);
}
}
@override
void initState() {
super.initState();
@@ -114,7 +172,12 @@ class _RequestBreakpointPageState extends State<RequestBreakpointPage> {
Expanded(
child: Row(mainAxisAlignment: MainAxisAlignment.end, children: [
TextButton.icon(
icon: const Icon(Icons.add, size: 18), label: Text(localizations.add), onPressed: _editRule)
icon: const Icon(Icons.add, size: 18), label: Text(localizations.add), onPressed: _editRule),
const SizedBox(width: 5),
TextButton.icon(
icon: const Icon(Icons.input_rounded, size: 18),
onPressed: _import,
label: Text(localizations.import)),
])),
const SizedBox(width: 15)
]),
@@ -266,6 +329,22 @@ class _RequestBreakpointPageState extends State<RequestBreakpointPage> {
}
},
),
PopupMenuItem(
height: 32,
child: Text(localizations.export),
onTap: () async {
if (selected.isEmpty) return;
var list = selected.toList();
List<RequestBreakpointRule> exportRules = [];
for (var i in list) {
exportRules.add(rules[i]);
}
await _export(exportRules);
setState(() {
selected.clear();
});
},
),
PopupMenuItem(
height: 32,
child: Text(localizations.delete),

View File

@@ -1,5 +1,6 @@
import 'dart:collection';
import 'dart:convert';
import 'dart:io';
import 'package:file_picker/file_picker.dart';
import 'package:flutter/material.dart';
@@ -65,7 +66,7 @@ class _RequestBreakpointPageState extends State<MobileRequestBreakpointPage> {
child: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [
Row(children: [
SizedBox(
width: isEN ? 280 : 250,
width: isEN ? 230 : 160,
child: ListTile(
title: Text("${localizations.enable} ${localizations.breakpoint}"),
contentPadding: const EdgeInsets.only(left: 2),
@@ -82,8 +83,15 @@ class _RequestBreakpointPageState extends State<MobileRequestBreakpointPage> {
const SizedBox(width: 10),
Expanded(
child: Row(mainAxisAlignment: MainAxisAlignment.end, children: [
TextButton.icon(
icon: const Icon(Icons.add, size: 18), label: Text(localizations.add), onPressed: _editRule),
IconButton(
icon: Icon(Icons.add, size: 22, color: Theme.of(context).colorScheme.primary),
onPressed: _editRule,
tooltip: localizations.add),
const SizedBox(width: 5),
IconButton(
icon: Icon(Icons.input_rounded, size: 22, color: Theme.of(context).colorScheme.primary),
onPressed: _import,
tooltip: localizations.import),
])),
const SizedBox(width: 15)
]),
@@ -195,6 +203,32 @@ class _RequestBreakpointPageState extends State<MobileRequestBreakpointPage> {
}
}
Future<void> _import() async {
try {
FilePickerResult? result = await FilePicker.platform.pickFiles(
type: FileType.custom,
allowedExtensions: ['json'],
);
if (result == null || result.files.isEmpty) return;
File file = File(result.files.single.path!);
String content = await file.readAsString();
List<dynamic> list = jsonDecode(content);
var newRules = list.map((e) => RequestBreakpointRule.fromJson(e)).toList();
for (var rule in newRules) {
manager?.list.add(rule);
}
await _save();
setState(() {
rules = manager!.list;
});
if (mounted) FlutterToastr.show(localizations.importSuccess, context);
} catch (e) {
logger.e('Import failed', error: e);
if (mounted) FlutterToastr.show(localizations.importFailed, context);
}
}
Stack _buildSelectionFooter() {
final l10n = localizations;
return Stack(children: [