云算力平台windows桌面应用
This commit is contained in:
392
lib/screens/config_editor_screen.dart
Normal file
392
lib/screens/config_editor_screen.dart
Normal file
@@ -0,0 +1,392 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import '../services/config_service.dart';
|
||||
import '../utils/ini_utils.dart';
|
||||
|
||||
class ConfigEditorScreen extends StatefulWidget {
|
||||
const ConfigEditorScreen({super.key});
|
||||
|
||||
@override
|
||||
State<ConfigEditorScreen> createState() => _ConfigEditorScreenState();
|
||||
}
|
||||
|
||||
class _ConfigEditorScreenState extends State<ConfigEditorScreen> {
|
||||
final ConfigService _configService = ConfigService();
|
||||
bool _isLoading = false;
|
||||
bool _isModified = false;
|
||||
|
||||
// 配置项数据
|
||||
final Map<String, Map<String, TextEditingController>> _controllers = {};
|
||||
final Map<String, Map<String, bool>> _enabledFlags = {};
|
||||
final List<String> _sections = [];
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_loadConfig();
|
||||
}
|
||||
|
||||
Future<void> _loadConfig() async {
|
||||
setState(() {
|
||||
_isLoading = true;
|
||||
});
|
||||
|
||||
try {
|
||||
final content = await _configService.readConfig();
|
||||
if (content.isNotEmpty) {
|
||||
_parseConfig(content);
|
||||
}
|
||||
} catch (e) {
|
||||
if (mounted) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(content: Text('加载配置失败: $e')),
|
||||
);
|
||||
}
|
||||
} finally {
|
||||
setState(() {
|
||||
_isLoading = false;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void _parseConfig(String content) {
|
||||
final config = parseIni(content);
|
||||
_controllers.clear();
|
||||
_enabledFlags.clear();
|
||||
_sections.clear();
|
||||
|
||||
// 定义所有可能的配置项
|
||||
final configStructure = {
|
||||
'client': ['server_url', 'update_url'],
|
||||
'lolminer': ['path'],
|
||||
'rigel': ['path'],
|
||||
'bzminer': ['path'],
|
||||
'proxy': ['proxy'],
|
||||
'sustain': ['enabled', 'algo', 'coin', 'miner', 'pool_url', 'wallet', 'worker_id', 'pool_user', 'wallet_mining'],
|
||||
};
|
||||
|
||||
for (final section in configStructure.keys) {
|
||||
_sections.add(section);
|
||||
_controllers[section] = {};
|
||||
_enabledFlags[section] = {};
|
||||
|
||||
for (final option in configStructure[section]!) {
|
||||
final value = config.get(section, option) ?? '';
|
||||
_controllers[section]![option] = TextEditingController(text: value);
|
||||
_controllers[section]![option]!.addListener(() {
|
||||
setState(() {
|
||||
_isModified = true;
|
||||
});
|
||||
});
|
||||
// 默认所有配置项都是禁用的(需要勾选才能编辑)
|
||||
_enabledFlags[section]![option] = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _saveConfig() async {
|
||||
final confirmed = await showDialog<bool>(
|
||||
context: context,
|
||||
builder: (context) => AlertDialog(
|
||||
title: const Text('确认保存'),
|
||||
content: const Text('确定要保存配置文件吗?'),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () => Navigator.pop(context, false),
|
||||
child: const Text('取消'),
|
||||
),
|
||||
TextButton(
|
||||
onPressed: () => Navigator.pop(context, true),
|
||||
child: const Text('确定'),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
|
||||
if (confirmed == true) {
|
||||
setState(() {
|
||||
_isLoading = true;
|
||||
});
|
||||
|
||||
try {
|
||||
final content = _generateConfigContent();
|
||||
final success = await _configService.saveConfig(content);
|
||||
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
_isLoading = false;
|
||||
_isModified = !success;
|
||||
});
|
||||
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: Text(success ? '保存成功' : '保存失败'),
|
||||
backgroundColor: success ? Colors.green : Colors.red,
|
||||
),
|
||||
);
|
||||
}
|
||||
} catch (e) {
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
_isLoading = false;
|
||||
});
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(content: Text('保存失败: $e')),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
String _generateConfigContent() {
|
||||
final buffer = StringBuffer();
|
||||
|
||||
// 添加注释
|
||||
buffer.writeln('#请确认您的主机上安装了下列挖矿软件,确认后可以打开注释,并修改其路径,如果没有安装,请勿打开注释');
|
||||
buffer.writeln('#请使用双\\\\,否则可能无法解析出准确的路径');
|
||||
buffer.writeln();
|
||||
|
||||
for (final section in _sections) {
|
||||
if (section == 'client') {
|
||||
buffer.writeln('[client]');
|
||||
_writeOption(buffer, section, 'server_url');
|
||||
_writeOption(buffer, section, 'update_url');
|
||||
buffer.writeln();
|
||||
buffer.writeln('#请确认您的主机上安装了下列挖矿软件,确认后可以打开注释,并修改其路径,如果没有安装,请勿打开注释');
|
||||
buffer.writeln('#请使用双\\\\,否则可能无法解析出准确的路径');
|
||||
} else if (section == 'lolminer' || section == 'rigel' || section == 'bzminer') {
|
||||
buffer.writeln('[$section]');
|
||||
_writeOption(buffer, section, 'path', commentPrefix: '# ');
|
||||
} else if (section == 'proxy') {
|
||||
buffer.writeln();
|
||||
buffer.writeln('#如果您的网络无法直接连通各个矿池,需要使用各大矿池专用网络,请打开proxy的注释');
|
||||
buffer.writeln('#打开此注释后会使用各大矿池的专用网络,每笔订单额外增加1%的网络费用');
|
||||
buffer.writeln('[proxy]');
|
||||
_writeOption(buffer, section, 'proxy', commentPrefix: '# ');
|
||||
} else if (section == 'sustain') {
|
||||
buffer.writeln();
|
||||
buffer.writeln('#持续挖矿开关,即在矿机没有租约期间是否自行挖矿');
|
||||
buffer.writeln('#开启此选项启动客户端后,客户端会自动根据下面配置开启挖矿任务,直到云算力平台有人租赁本台GPU主机');
|
||||
buffer.writeln('#当该租约结束后,本台GPU主机会自动切回下方配置的挖矿任务');
|
||||
buffer.writeln('[sustain]');
|
||||
_writeOption(buffer, section, 'enabled', commentPrefix: '#');
|
||||
_writeOption(buffer, section, 'algo', commentPrefix: '#');
|
||||
_writeOption(buffer, section, 'coin', commentPrefix: '#');
|
||||
_writeOption(buffer, section, 'miner', commentPrefix: '#');
|
||||
_writeOption(buffer, section, 'pool_url', commentPrefix: '#');
|
||||
_writeOption(buffer, section, 'wallet', commentPrefix: '#');
|
||||
_writeOption(buffer, section, 'worker_id', commentPrefix: '#');
|
||||
_writeOption(buffer, section, 'pool_user', commentPrefix: '#');
|
||||
_writeOption(buffer, section, 'wallet_mining', commentPrefix: '#');
|
||||
}
|
||||
buffer.writeln();
|
||||
}
|
||||
|
||||
return buffer.toString();
|
||||
}
|
||||
|
||||
void _writeOption(StringBuffer buffer, String section, String option, {String commentPrefix = ''}) {
|
||||
final controller = _controllers[section]?[option];
|
||||
final enabled = _enabledFlags[section]?[option] ?? false;
|
||||
final value = controller?.text ?? '';
|
||||
|
||||
if (value.isEmpty || !enabled) {
|
||||
// 如果值为空或未启用,使用注释形式
|
||||
if (option == 'path') {
|
||||
buffer.writeln('$commentPrefix$option=C:\\\\path\\\\${section}');
|
||||
} else if (option == 'proxy') {
|
||||
buffer.writeln('$commentPrefix$option=true');
|
||||
} else if (option == 'enabled') {
|
||||
buffer.writeln('$commentPrefix$option=true');
|
||||
} else if (option == 'algo') {
|
||||
buffer.writeln('$commentPrefix$option="算法"');
|
||||
} else if (option == 'coin') {
|
||||
buffer.writeln('$commentPrefix$option="币种"');
|
||||
} else if (option == 'miner') {
|
||||
buffer.writeln('$commentPrefix$option="挖矿软件名,此处使用的挖矿软件要使用上方已经配置路径的挖矿软件名,即bzminer/lolminer/rigel,只能填一个,自行选择"');
|
||||
} else if (option == 'pool_url') {
|
||||
buffer.writeln('$commentPrefix$option="挖矿地址"');
|
||||
} else if (option == 'wallet') {
|
||||
buffer.writeln('$commentPrefix$option="挖矿钱包"');
|
||||
} else if (option == 'worker_id') {
|
||||
buffer.writeln('$commentPrefix$option="矿工号"');
|
||||
} else if (option == 'pool_user') {
|
||||
buffer.writeln('$commentPrefix$option="挖矿账号名,f2pool/m2pool等不支持钱包挖矿的矿池需配置,其余支持钱包挖矿的矿池无需配置"');
|
||||
} else if (option == 'wallet_mining') {
|
||||
buffer.writeln('$commentPrefix$option=true #pool_user打开时同时打开本配置');
|
||||
} else {
|
||||
buffer.writeln('$commentPrefix$option=$value');
|
||||
}
|
||||
} else {
|
||||
// 如果值不为空且已启用,写入实际值
|
||||
if (option == 'path' || option == 'server_url' || option == 'update_url' ||
|
||||
option == 'pool_url' || option == 'wallet' || option == 'worker_id' ||
|
||||
option == 'pool_user' || option == 'algo' || option == 'coin' || option == 'miner') {
|
||||
buffer.writeln('$option=$value');
|
||||
} else {
|
||||
buffer.writeln('$option=$value');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
for (final section in _controllers.values) {
|
||||
for (final controller in section.values) {
|
||||
controller.dispose();
|
||||
}
|
||||
}
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: const Text('配置文件编辑'),
|
||||
actions: [
|
||||
if (_isModified)
|
||||
IconButton(
|
||||
icon: const Icon(Icons.save),
|
||||
onPressed: _isLoading ? null : _saveConfig,
|
||||
tooltip: '保存',
|
||||
),
|
||||
],
|
||||
),
|
||||
body: _isLoading && _controllers.isEmpty
|
||||
? const Center(child: CircularProgressIndicator())
|
||||
: SingleChildScrollView(
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
if (_isModified)
|
||||
Container(
|
||||
padding: const EdgeInsets.all(12.0),
|
||||
margin: const EdgeInsets.only(bottom: 16.0),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.orange.shade50,
|
||||
borderRadius: BorderRadius.circular(8.0),
|
||||
border: Border.all(color: Colors.orange),
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
Icon(Icons.info, color: Colors.orange.shade700),
|
||||
const SizedBox(width: 8),
|
||||
const Expanded(
|
||||
child: Text(
|
||||
'配置已修改,请点击保存按钮保存更改',
|
||||
style: TextStyle(fontWeight: FontWeight.w500),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
..._sections.map((section) => _buildSection(section)),
|
||||
const SizedBox(height: 16),
|
||||
ElevatedButton.icon(
|
||||
onPressed: _isLoading || !_isModified ? null : _saveConfig,
|
||||
icon: const Icon(Icons.save),
|
||||
label: const Text('保存配置'),
|
||||
style: ElevatedButton.styleFrom(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 12),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildSection(String section) {
|
||||
final sectionNames = {
|
||||
'client': '客户端配置',
|
||||
'lolminer': 'LolMiner 配置',
|
||||
'rigel': 'Rigel 配置',
|
||||
'bzminer': 'BzMiner 配置',
|
||||
'proxy': '代理配置',
|
||||
'sustain': '持续挖矿配置',
|
||||
};
|
||||
|
||||
return Card(
|
||||
margin: const EdgeInsets.only(bottom: 16.0),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
sectionNames[section] ?? section,
|
||||
style: const TextStyle(
|
||||
fontSize: 18,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
const Divider(),
|
||||
...(_controllers[section]?.keys.map((option) => _buildConfigField(section, option)) ?? []),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildConfigField(String section, String option) {
|
||||
final controller = _controllers[section]?[option];
|
||||
final enabled = _enabledFlags[section]?[option] ?? false;
|
||||
|
||||
final fieldNames = {
|
||||
'server_url': '服务器地址',
|
||||
'update_url': '更新服务器地址',
|
||||
'path': '软件路径',
|
||||
'proxy': '启用代理',
|
||||
'enabled': '启用持续挖矿',
|
||||
'algo': '算法',
|
||||
'coin': '币种',
|
||||
'miner': '挖矿软件',
|
||||
'pool_url': '矿池地址',
|
||||
'wallet': '钱包地址',
|
||||
'worker_id': '矿工号',
|
||||
'pool_user': '矿池用户名',
|
||||
'wallet_mining': '钱包挖矿',
|
||||
};
|
||||
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 8.0),
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Checkbox(
|
||||
value: enabled,
|
||||
onChanged: (value) {
|
||||
setState(() {
|
||||
_enabledFlags[section]![option] = value ?? false;
|
||||
_isModified = true;
|
||||
});
|
||||
},
|
||||
),
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
fieldNames[option] ?? option,
|
||||
style: const TextStyle(fontWeight: FontWeight.w500),
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
TextField(
|
||||
controller: controller,
|
||||
enabled: enabled,
|
||||
decoration: InputDecoration(
|
||||
border: const OutlineInputBorder(),
|
||||
hintText: '请输入${fieldNames[option] ?? option}',
|
||||
filled: !enabled,
|
||||
fillColor: enabled ? null : Colors.grey.shade200,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user