Files
windows-application/lib/screens/main_screen.dart
2026-01-22 15:14:27 +08:00

427 lines
12 KiB
Dart

import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import '../providers/client_provider.dart';
import '../models/client_status.dart';
import 'log_viewer_screen.dart';
import 'config_editor_screen.dart';
import 'mining_info_screen.dart';
import 'dart:io';
class MainScreen extends StatelessWidget {
const MainScreen({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('云算力平台客户端'),
centerTitle: true,
),
body: Consumer<ClientProvider>(
builder: (context, provider, child) {
final clientInfo = provider.clientInfo;
if (provider.isLoading && clientInfo == null) {
return const Center(child: CircularProgressIndicator());
}
if (clientInfo == null) {
return const Center(child: Text('无法获取客户端信息'));
}
return SingleChildScrollView(
padding: const EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// 状态指示器
_buildStatusCard(clientInfo),
const SizedBox(height: 16),
// 基本信息卡片
_buildInfoCard(clientInfo),
const SizedBox(height: 16),
// 版本更新提示
if (provider.hasUpdate)
_buildUpdateCard(context, provider),
if (provider.hasUpdate) const SizedBox(height: 16),
// GPU信息卡片
_buildGPUCard(clientInfo.gpus),
const SizedBox(height: 16),
// 操作按钮
_buildActionButtons(context, clientInfo),
],
),
);
},
),
);
}
Widget _buildStatusCard(ClientInfo clientInfo) {
return Card(
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Row(
children: [
Container(
width: 20,
height: 20,
decoration: BoxDecoration(
color: clientInfo.status.color,
shape: BoxShape.circle,
),
),
const SizedBox(width: 12),
Text(
'当前状态: ${clientInfo.status.displayName}',
style: const TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
),
),
],
),
),
);
}
Widget _buildInfoCard(ClientInfo clientInfo) {
return Card(
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text(
'基本信息',
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
),
),
const Divider(),
_buildInfoRow('版本号', clientInfo.version),
_buildInfoRow('身份信息', clientInfo.auth),
_buildInfoRow('机器码', clientInfo.machineCode),
],
),
),
);
}
Widget _buildInfoRow(String label, String value) {
return Padding(
padding: const EdgeInsets.symmetric(vertical: 8.0),
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
SizedBox(
width: 100,
child: Text(
'$label:',
style: const TextStyle(fontWeight: FontWeight.w500),
),
),
Expanded(
child: Text(value),
),
],
),
);
}
Widget _buildGPUCard(List<GPUInfo> gpus) {
return Card(
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text(
'GPU信息',
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
),
),
const Divider(),
if (gpus.isEmpty)
const Text('未检测到GPU')
else
...gpus.map((gpu) => Padding(
padding: const EdgeInsets.symmetric(vertical: 4.0),
child: Text('GPU ${gpu.index}: ${gpu.displayName}'),
)),
],
),
),
);
}
Widget _buildUpdateCard(BuildContext context, ClientProvider provider) {
return Card(
color: Colors.orange.shade50,
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Icon(Icons.system_update, color: Colors.orange.shade700),
const SizedBox(width: 8),
const Text(
'发现新版本',
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
color: Colors.orange,
),
),
],
),
const SizedBox(height: 8),
Text('当前版本: ${provider.clientInfo?.version ?? '未知'}'),
Text('最新版本: ${provider.remoteVersion ?? '未知'}'),
const SizedBox(height: 12),
ElevatedButton.icon(
onPressed: () => _performUpdate(context, provider),
icon: const Icon(Icons.download),
label: const Text('立即更新'),
style: ElevatedButton.styleFrom(
backgroundColor: Colors.orange,
foregroundColor: Colors.white,
),
),
],
),
),
);
}
Future<void> _performUpdate(BuildContext context, ClientProvider provider) async {
final confirmed = await showDialog<bool>(
context: context,
builder: (context) => AlertDialog(
title: const Text('确认更新'),
content: const Text(
'更新将在下载完成后,下次启动时应用。\n'
'更新过程中请勿关闭程序。\n\n'
'确定要继续吗?',
),
actions: [
TextButton(
onPressed: () => Navigator.pop(context, false),
child: const Text('取消'),
),
TextButton(
onPressed: () => Navigator.pop(context, true),
child: const Text('确定'),
),
],
),
);
if (confirmed == true) {
// 显示进度对话框
showDialog(
context: context,
barrierDismissible: false,
builder: (context) => const AlertDialog(
content: Column(
mainAxisSize: MainAxisSize.min,
children: [
CircularProgressIndicator(),
SizedBox(height: 16),
Text('正在下载更新...'),
],
),
),
);
final success = await provider.performUpdate();
if (context.mounted) {
Navigator.pop(context); // 关闭进度对话框
showDialog(
context: context,
builder: (context) => AlertDialog(
title: Text(success ? '更新成功' : '更新失败'),
content: Text(
success
? '更新已下载完成,将在下次启动时应用。\n请重启程序以完成更新。'
: '更新下载失败,请检查网络连接或稍后重试。',
),
actions: [
TextButton(
onPressed: () => Navigator.pop(context),
child: const Text('确定'),
),
],
),
);
}
}
}
Widget _buildActionButtons(BuildContext context, ClientInfo clientInfo) {
return Wrap(
spacing: 12,
runSpacing: 12,
children: [
ElevatedButton.icon(
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(builder: (_) => const LogViewerScreen()),
);
},
icon: const Icon(Icons.description),
label: const Text('查看日志'),
),
ElevatedButton.icon(
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(builder: (_) => const ConfigEditorScreen()),
);
},
icon: const Icon(Icons.settings),
label: const Text('查看/修改配置'),
),
ElevatedButton.icon(
onPressed: clientInfo.status == ClientStatus.mining && clientInfo.miningInfo != null
? () {
Navigator.push(
context,
MaterialPageRoute(
builder: (_) => MiningInfoScreen(miningInfo: clientInfo.miningInfo!),
),
);
}
: null,
icon: const Icon(Icons.diamond),
label: const Text('挖矿信息'),
style: ElevatedButton.styleFrom(
backgroundColor: clientInfo.status == ClientStatus.mining && clientInfo.miningInfo != null
? null
: Colors.grey,
),
),
ElevatedButton.icon(
onPressed: () => _restartClient(context),
icon: const Icon(Icons.refresh),
label: const Text('重启'),
style: ElevatedButton.styleFrom(
backgroundColor: Colors.orange,
foregroundColor: Colors.white,
),
),
ElevatedButton.icon(
onPressed: () => _exitApp(context),
icon: const Icon(Icons.exit_to_app),
label: const Text('退出程序'),
style: ElevatedButton.styleFrom(
backgroundColor: Colors.red,
foregroundColor: Colors.white,
),
),
],
);
}
void _restartClient(BuildContext context) 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) {
// 显示加载对话框,禁用所有操作
showDialog(
context: context,
barrierDismissible: false,
builder: (context) => PopScope(
canPop: false, // 禁止返回
child: const AlertDialog(
content: Column(
mainAxisSize: MainAxisSize.min,
children: [
CircularProgressIndicator(),
SizedBox(height: 16),
Text('正在重启客户端...'),
],
),
),
),
);
final provider = Provider.of<ClientProvider>(context, listen: false);
bool success = false;
String errorMessage = '';
try {
await provider.restart();
success = true;
} catch (e) {
errorMessage = e.toString();
}
if (context.mounted) {
Navigator.pop(context); // 关闭加载对话框
// 显示结果
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(success ? '客户端重启成功' : '客户端重启失败: $errorMessage'),
backgroundColor: success ? Colors.green : Colors.red,
duration: const Duration(seconds: 3),
),
);
}
}
}
void _exitApp(BuildContext context) 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) {
exit(0);
}
}
}