add some basic funcition

This commit is contained in:
2025-03-28 15:03:46 +08:00
parent eaf8bbabb5
commit bb2c25babc
3 changed files with 274 additions and 84 deletions

View File

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

View File

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

View File

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