Files
windows-application/lib/screens/config_editor_screen.dart

393 lines
14 KiB
Dart
Raw Normal View History

2026-01-22 15:14:27 +08:00
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,
),
),
],
),
),
],
),
);
}
}