add some basic funcition
This commit is contained in:
275
lib/main.dart
275
lib/main.dart
@ -2,21 +2,34 @@ import 'package:flutter/material.dart';
|
|||||||
import 'package:file_picker/file_picker.dart';
|
import 'package:file_picker/file_picker.dart';
|
||||||
import 'package:process_run/shell.dart';
|
import 'package:process_run/shell.dart';
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
|
import 'dart:convert';
|
||||||
|
import 'package:path_provider/path_provider.dart';
|
||||||
|
import 'package:path/path.dart' as path;
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
runApp(NaiveProxyApp());
|
runApp(NaiveProxyApp());
|
||||||
}
|
}
|
||||||
|
|
||||||
class NaiveProxyApp extends StatelessWidget {
|
class ServerConfig {
|
||||||
const NaiveProxyApp({super.key});
|
final String name;
|
||||||
|
final String configPath;
|
||||||
|
final int? ping;
|
||||||
|
|
||||||
|
ServerConfig({
|
||||||
|
required this.name,
|
||||||
|
required this.configPath,
|
||||||
|
this.ping
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
class NaiveProxyApp extends StatelessWidget {
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return MaterialApp(
|
return MaterialApp(
|
||||||
title: 'NaiveProxy启动器',
|
title: 'NaiveProxy Launcher',
|
||||||
theme: ThemeData(
|
theme: ThemeData(
|
||||||
primarySwatch: Colors.blue,
|
colorScheme: ColorScheme.fromSeed(seedColor: Colors.purple),
|
||||||
visualDensity: VisualDensity.adaptivePlatformDensity,
|
useMaterial3: true,
|
||||||
),
|
),
|
||||||
home: NaiveProxyHomePage(),
|
home: NaiveProxyHomePage(),
|
||||||
);
|
);
|
||||||
@ -24,89 +37,152 @@ class NaiveProxyApp extends StatelessWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class NaiveProxyHomePage extends StatefulWidget {
|
class NaiveProxyHomePage extends StatefulWidget {
|
||||||
const NaiveProxyHomePage({super.key});
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
_NaiveProxyHomePageState createState() => _NaiveProxyHomePageState();
|
_NaiveProxyHomePageState createState() => _NaiveProxyHomePageState();
|
||||||
}
|
}
|
||||||
|
|
||||||
class _NaiveProxyHomePageState extends State<NaiveProxyHomePage> {
|
class _NaiveProxyHomePageState extends State<NaiveProxyHomePage> {
|
||||||
String? _configFilePath;
|
List<ServerConfig> _servers = [];
|
||||||
String _status = '未选择配置文件';
|
ServerConfig? _selectedServer;
|
||||||
|
bool _isProxyRunning = false;
|
||||||
|
Process? _naiveProcess;
|
||||||
|
List<String> _logs = [];
|
||||||
|
ScrollController _logController = ScrollController();
|
||||||
|
|
||||||
Future<void> _pickConfigFile() async {
|
@override
|
||||||
FilePickerResult? result = await FilePicker.platform.pickFiles(
|
void initState() {
|
||||||
type: FileType.custom,
|
super.initState();
|
||||||
allowedExtensions: ['json'],
|
_loadServers();
|
||||||
);
|
}
|
||||||
|
|
||||||
|
Future<void> _loadServers() async {
|
||||||
|
// 获取配置目录
|
||||||
|
Directory configDir = await _getNaiveFlyConfigDirectory();
|
||||||
|
|
||||||
|
// 确保目录存在
|
||||||
|
if (!configDir.existsSync()) {
|
||||||
|
configDir.createSync(recursive: true);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 读取所有JSON文件
|
||||||
|
List<FileSystemEntity> files = configDir.listSync()
|
||||||
|
.where((file) => file.path.endsWith('.json'))
|
||||||
|
.toList();
|
||||||
|
|
||||||
if (result != null) {
|
|
||||||
setState(() {
|
setState(() {
|
||||||
_configFilePath = result.files.single.path;
|
_servers = files.map((file) {
|
||||||
_status = '已选择文件: ${_configFilePath!.split('/').last}';
|
String name = path.basenameWithoutExtension(file.path);
|
||||||
|
return ServerConfig(
|
||||||
|
name: name,
|
||||||
|
configPath: file.path
|
||||||
|
);
|
||||||
|
}).toList();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<Directory> _getNaiveFlyConfigDirectory() async {
|
||||||
|
if (Platform.isWindows) {
|
||||||
|
final appDataDir = Platform.environment['APPDATA'];
|
||||||
|
return Directory(path.join(appDataDir!, 'NaiveFly'));
|
||||||
|
} else {
|
||||||
|
// 假设是Unix类系统(Linux/macOS)
|
||||||
|
final homeDir = Platform.environment['HOME'];
|
||||||
|
return Directory(path.join(homeDir!, '.config', 'naivefly'));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _launchNaiveProxy() async {
|
Future<void> _launchNaiveProxy() async {
|
||||||
if (_configFilePath == null) {
|
if (_isProxyRunning) {
|
||||||
_showErrorDialog('请先导入配置文件');
|
_stopNaiveProxy();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_selectedServer == null) {
|
||||||
|
_showErrorDialog('Please select a server');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// 检查naiveproxy是否已安装
|
// 检查naiveproxy是否已安装
|
||||||
var shell = Shell();
|
var whichResult = await Shell().run('which naive');
|
||||||
var whichResult = await shell.run('which naive');
|
|
||||||
|
|
||||||
if (whichResult.isEmpty) {
|
if (whichResult.isEmpty) {
|
||||||
_showErrorDialog('NaiveProxy未安装');
|
_showErrorDialog('NaiveProxy is not installed');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 尝试执行naive
|
// 启动naive进程
|
||||||
var result = await shell.run('naive $_configFilePath');
|
_naiveProcess = await Process.start('naive', [_selectedServer!.configPath]);
|
||||||
|
|
||||||
// 显示执行结果
|
setState(() {
|
||||||
_showResultDialog('NaiveProxy已启动', result.outText);
|
_isProxyRunning = true;
|
||||||
|
_logs.clear();
|
||||||
|
_addLog('NaiveProxy started: ${_selectedServer!.name}');
|
||||||
|
});
|
||||||
|
|
||||||
|
// 监听进程输出
|
||||||
|
_naiveProcess!.stdout.listen((List<int> event) {
|
||||||
|
setState(() {
|
||||||
|
_addLog('Output: ${String.fromCharCodes(event)}');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// 监听进程错误输出
|
||||||
|
_naiveProcess!.stderr.listen((List<int> event) {
|
||||||
|
setState(() {
|
||||||
|
_addLog('Message: ${String.fromCharCodes(event)}');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// 监听进程退出
|
||||||
|
_naiveProcess!.exitCode.then((int code) {
|
||||||
|
setState(() {
|
||||||
|
_isProxyRunning = false;
|
||||||
|
_addLog('NaiveProxy exited with code: $code');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
_showErrorDialog('启动失败:${e.toString()}');
|
_showErrorDialog('Launch failed: ${e.toString()}');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void _stopNaiveProxy() {
|
||||||
|
if (_naiveProcess != null) {
|
||||||
|
_naiveProcess!.kill();
|
||||||
|
setState(() {
|
||||||
|
_isProxyRunning = false;
|
||||||
|
_addLog('NaiveProxy manually stopped');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void _addLog(String message) {
|
||||||
|
setState(() {
|
||||||
|
_logs.add(message);
|
||||||
|
// 自动滚动到底部
|
||||||
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||||
|
if (_logController.hasClients) {
|
||||||
|
_logController.animateTo(
|
||||||
|
_logController.position.maxScrollExtent,
|
||||||
|
duration: Duration(milliseconds: 300),
|
||||||
|
curve: Curves.easeOut,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
void _showErrorDialog(String message) {
|
void _showErrorDialog(String message) {
|
||||||
showDialog(
|
showDialog(
|
||||||
context: context,
|
context: context,
|
||||||
builder: (BuildContext context) {
|
builder: (BuildContext context) {
|
||||||
return AlertDialog(
|
return AlertDialog(
|
||||||
title: Text('错误'),
|
title: Text('Error'),
|
||||||
content: Text(message),
|
content: Text(message),
|
||||||
actions: <Widget>[
|
actions: <Widget>[
|
||||||
TextButton(
|
TextButton(
|
||||||
child: Text('确定'),
|
child: Text('OK'),
|
||||||
onPressed: () {
|
|
||||||
Navigator.of(context).pop();
|
|
||||||
},
|
|
||||||
),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
void _showResultDialog(String title, String message) {
|
|
||||||
showDialog(
|
|
||||||
context: context,
|
|
||||||
builder: (BuildContext context) {
|
|
||||||
return AlertDialog(
|
|
||||||
title: Text(title),
|
|
||||||
content: SingleChildScrollView(
|
|
||||||
child: Text(message),
|
|
||||||
),
|
|
||||||
actions: <Widget>[
|
|
||||||
TextButton(
|
|
||||||
child: Text('确定'),
|
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
Navigator.of(context).pop();
|
Navigator.of(context).pop();
|
||||||
},
|
},
|
||||||
@ -121,43 +197,90 @@ class _NaiveProxyHomePageState extends State<NaiveProxyHomePage> {
|
|||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
title: Text('NaiveProxy启动器'),
|
title: Text('NaiveProxy Launcher'),
|
||||||
centerTitle: true,
|
centerTitle: true,
|
||||||
|
actions: [
|
||||||
|
IconButton(
|
||||||
|
icon: Icon(Icons.refresh),
|
||||||
|
onPressed: _loadServers,
|
||||||
|
)
|
||||||
|
],
|
||||||
),
|
),
|
||||||
body: Center(
|
body: Column(
|
||||||
child: Column(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
SizedBox(
|
// 服务器列表
|
||||||
width: 300,
|
Container(
|
||||||
child: Text(
|
height: 200,
|
||||||
_status,
|
child: ListView.builder(
|
||||||
style: TextStyle(fontSize: 16),
|
itemCount: _servers.length,
|
||||||
textAlign: TextAlign.center,
|
itemBuilder: (context, index) {
|
||||||
|
ServerConfig server = _servers[index];
|
||||||
|
return ListTile(
|
||||||
|
title: Text(server.name),
|
||||||
|
subtitle: Text(server.configPath),
|
||||||
|
trailing: Text('${server.ping ?? 'N/A'} ms',
|
||||||
|
style: TextStyle(color: Colors.green),
|
||||||
|
),
|
||||||
|
selected: _selectedServer == server,
|
||||||
|
onTap: () {
|
||||||
|
setState(() {
|
||||||
|
_selectedServer = server;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
);
|
||||||
|
},
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
SizedBox(height: 20),
|
|
||||||
ElevatedButton.icon(
|
// 启动按钮
|
||||||
icon: Icon(Icons.file_upload),
|
Padding(
|
||||||
label: Text('导入配置文件'),
|
padding: const EdgeInsets.all(16.0),
|
||||||
onPressed: _pickConfigFile,
|
child: ElevatedButton.icon(
|
||||||
style: ElevatedButton.styleFrom(
|
icon: Icon(_isProxyRunning ? Icons.stop : Icons.rocket_launch),
|
||||||
padding: EdgeInsets.symmetric(horizontal: 20, vertical: 12),
|
label: Text(_isProxyRunning ? 'Stop Proxy' : 'Start NaiveProxy'),
|
||||||
),
|
|
||||||
),
|
|
||||||
SizedBox(height: 20),
|
|
||||||
ElevatedButton.icon(
|
|
||||||
icon: Icon(Icons.rocket_launch),
|
|
||||||
label: Text('启动NaiveProxy'),
|
|
||||||
onPressed: _launchNaiveProxy,
|
onPressed: _launchNaiveProxy,
|
||||||
style: ElevatedButton.styleFrom(
|
style: ElevatedButton.styleFrom(
|
||||||
backgroundColor: Colors.green,
|
backgroundColor: _isProxyRunning ? Colors.red : Colors.green,
|
||||||
padding: EdgeInsets.symmetric(horizontal: 20, vertical: 12),
|
padding: EdgeInsets.symmetric(horizontal: 20, vertical: 12),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
),
|
||||||
|
|
||||||
|
// 日志输出区域
|
||||||
|
Expanded(
|
||||||
|
child: Container(
|
||||||
|
margin: EdgeInsets.all(16),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
border: Border.all(color: Colors.grey),
|
||||||
|
borderRadius: BorderRadius.circular(8),
|
||||||
|
),
|
||||||
|
child: ListView.builder(
|
||||||
|
controller: _logController,
|
||||||
|
itemCount: _logs.length,
|
||||||
|
itemBuilder: (context, index) {
|
||||||
|
return Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
|
||||||
|
child: Text(
|
||||||
|
_logs[index],
|
||||||
|
style: TextStyle(
|
||||||
|
fontFamily: 'monospace',
|
||||||
|
fontSize: 12,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
_stopNaiveProxy();
|
||||||
|
_logController.dispose();
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
66
pubspec.lock
66
pubspec.lock
@ -177,13 +177,69 @@ packages:
|
|||||||
source: hosted
|
source: hosted
|
||||||
version: "1.16.0"
|
version: "1.16.0"
|
||||||
path:
|
path:
|
||||||
dependency: transitive
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: path
|
name: path
|
||||||
sha256: "75cca69d1490965be98c73ceaea117e8a04dd21217b37b292c9ddbec0d955bc5"
|
sha256: "75cca69d1490965be98c73ceaea117e8a04dd21217b37b292c9ddbec0d955bc5"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.9.1"
|
version: "1.9.1"
|
||||||
|
path_provider:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: path_provider
|
||||||
|
sha256: "50c5dd5b6e1aaf6fb3a78b33f6aa3afca52bf903a8a5298f53101fdaee55bbcd"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.1.5"
|
||||||
|
path_provider_android:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: path_provider_android
|
||||||
|
sha256: "0ca7359dad67fd7063cb2892ab0c0737b2daafd807cf1acecd62374c8fae6c12"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.2.16"
|
||||||
|
path_provider_foundation:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: path_provider_foundation
|
||||||
|
sha256: "4843174df4d288f5e29185bd6e72a6fbdf5a4a4602717eed565497429f179942"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.4.1"
|
||||||
|
path_provider_linux:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: path_provider_linux
|
||||||
|
sha256: f7a1fe3a634fe7734c8d3f2766ad746ae2a2884abe22e241a8b301bf5cac3279
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.2.1"
|
||||||
|
path_provider_platform_interface:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: path_provider_platform_interface
|
||||||
|
sha256: "88f5779f72ba699763fa3a3b06aa4bf6de76c8e5de842cf6f29e2e06476c2334"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.1.2"
|
||||||
|
path_provider_windows:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: path_provider_windows
|
||||||
|
sha256: bd6f00dbd873bfb70d0761682da2b3a2c2fccc2b9e84c495821639601d81afe7
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.3.0"
|
||||||
|
platform:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: platform
|
||||||
|
sha256: "5d6b1b0036a5f331ebc77c850ebc8506cbc1e9416c27e59b439f917a902a4984"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "3.1.6"
|
||||||
plugin_platform_interface:
|
plugin_platform_interface:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -301,6 +357,14 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "5.12.0"
|
version: "5.12.0"
|
||||||
|
xdg_directories:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: xdg_directories
|
||||||
|
sha256: "7a3f37b05d989967cdddcbb571f1ea834867ae2faa29725fd085180e0883aa15"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.1.0"
|
||||||
yaml:
|
yaml:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|||||||
@ -37,6 +37,9 @@ dependencies:
|
|||||||
|
|
||||||
file_picker: ^9.2.2
|
file_picker: ^9.2.2
|
||||||
process_run: ^1.2.4
|
process_run: ^1.2.4
|
||||||
|
path_provider: ^2.1.5
|
||||||
|
path: ^1.9.1
|
||||||
|
# system_tray: ^2.0.3
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
flutter_test:
|
flutter_test:
|
||||||
sdk: flutter
|
sdk: flutter
|
||||||
|
|||||||
Reference in New Issue
Block a user