rwadurian/frontend/mobile-app/lib/features/mining/presentation/pages/mining_page.dart

555 lines
17 KiB
Dart
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import 'dart:io';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:flutter_svg/flutter_svg.dart';
import '../../../../core/di/injection_container.dart';
/// 挖矿状态枚举
enum MiningStatus {
pending, // 待开启
mining, // 挖矿中
paused, // 已暂停
}
/// 矿机页面 - 显示挖矿状态和控制
/// 展示用户序列号、社区信息和挖矿开关
class MiningPage extends ConsumerStatefulWidget {
const MiningPage({super.key});
@override
ConsumerState<MiningPage> createState() => _MiningPageState();
}
class _MiningPageState extends ConsumerState<MiningPage> {
// 当前挖矿状态
MiningStatus _miningStatus = MiningStatus.pending;
// 用户数据(从存储加载)
String _serialNumber = '--';
String? _avatarSvg;
String? _avatarUrl;
String? _localAvatarPath; // 本地头像文件路径
// 授权数据(从 authorization-service 获取)
String _community = '--';
String _province = '--';
String _city = '--';
@override
void initState() {
super.initState();
// 先同步检查本地头像,再异步加载其他数据
_checkLocalAvatarSync();
_loadUserData();
_loadAuthorizationData();
}
/// 同步检查本地头像文件(在 build 之前快速获取)
void _checkLocalAvatarSync() {
WidgetsBinding.instance.addPostFrameCallback((_) async {
final accountService = ref.read(accountServiceProvider);
final localPath = await accountService.getLocalAvatarPath();
if (mounted && localPath != null && _localAvatarPath == null) {
setState(() {
_localAvatarPath = localPath;
});
}
});
}
/// 加载用户数据
Future<void> _loadUserData() async {
final accountService = ref.read(accountServiceProvider);
// 并行加载所有数据
final results = await Future.wait([
accountService.getUserSerialNum(),
accountService.getAvatarSvg(),
accountService.getAvatarUrl(),
accountService.getLocalAvatarPath(),
]);
final serialNum = results[0] as int?;
final avatarSvg = results[1] as String?;
final avatarUrl = results[2] as String?;
final localAvatarPath = results[3] as String?;
if (mounted) {
setState(() {
_serialNumber = serialNum?.toString() ?? '--';
_avatarSvg = avatarSvg;
_avatarUrl = avatarUrl;
_localAvatarPath = localAvatarPath;
});
// 如果有远程URL但没有本地缓存后台下载并缓存
if (avatarUrl != null && avatarUrl.isNotEmpty && localAvatarPath == null) {
_downloadAndCacheAvatar(avatarUrl);
}
}
}
/// 后台下载并缓存头像
Future<void> _downloadAndCacheAvatar(String url) async {
final accountService = ref.read(accountServiceProvider);
final localPath = await accountService.downloadAndCacheAvatar(url);
if (mounted && localPath != null) {
setState(() {
_localAvatarPath = localPath;
});
}
}
/// 加载授权数据(社区、省、市)
Future<void> _loadAuthorizationData() async {
try {
debugPrint('[MiningPage] 开始加载授权数据...');
final authorizationService = ref.read(authorizationServiceProvider);
final summary = await authorizationService.getMyAuthorizationSummary();
if (mounted) {
setState(() {
_community = summary.communityName ?? '--';
_city = summary.cityCompanyName ?? '--';
_province = summary.provinceCompanyName ?? '--';
});
debugPrint('[MiningPage] 授权数据加载成功:');
debugPrint('[MiningPage] 社区: $_community');
debugPrint('[MiningPage] 市公司: $_city');
debugPrint('[MiningPage] 省公司: $_province');
}
} catch (e, stackTrace) {
debugPrint('[MiningPage] 加载授权数据失败: $e');
debugPrint('[MiningPage] 堆栈: $stackTrace');
// 失败时保持默认值 '--'
}
}
/// 显示帮助信息
void _showHelpInfo() {
showDialog(
context: context,
builder: (context) => AlertDialog(
title: const Text('矿机说明'),
content: const Text('矿机是您参与挖矿的核心工具。\n\n'
'开启挖矿后,您将开始获得收益。\n\n'
'收益与您的算力和团队规模相关。'),
actions: [
TextButton(
onPressed: () => Navigator.pop(context),
child: const Text('知道了'),
),
],
),
);
}
/// 切换挖矿状态
void _toggleMining() {
setState(() {
if (_miningStatus == MiningStatus.pending) {
_miningStatus = MiningStatus.mining;
} else if (_miningStatus == MiningStatus.mining) {
_miningStatus = MiningStatus.paused;
} else {
_miningStatus = MiningStatus.mining;
}
});
}
/// 获取状态文本
String _getStatusText() {
switch (_miningStatus) {
case MiningStatus.pending:
return '挖矿待开启';
case MiningStatus.mining:
return '挖矿中';
case MiningStatus.paused:
return '挖矿已暂停';
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.white,
body: Container(
width: double.infinity,
height: double.infinity,
decoration: const BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
colors: [
Color(0xFFFFF5E6),
Color(0xFFFFE4B5),
],
),
),
child: SafeArea(
child: Column(
children: [
// 顶部标题栏
_buildAppBar(),
// 用户信息区域
_buildUserInfo(),
const SizedBox(height: 24),
// 挖矿状态区域
Expanded(
child: _buildMiningStatusArea(),
),
],
),
),
),
);
}
/// 构建顶部标题栏
Widget _buildAppBar() {
return Container(
height: 56,
padding: const EdgeInsets.symmetric(horizontal: 16),
child: Row(
children: [
// 占位
const SizedBox(width: 48),
// 标题
const Expanded(
child: Text(
'矿机',
style: TextStyle(
fontSize: 18,
fontFamily: 'Inter',
fontWeight: FontWeight.w700,
height: 1.25,
letterSpacing: -0.27,
color: Color(0xFF5D4037),
),
textAlign: TextAlign.center,
),
),
// 帮助按钮
GestureDetector(
onTap: _showHelpInfo,
child: Container(
width: 48,
height: 48,
alignment: Alignment.center,
child: Container(
width: 24,
height: 24,
decoration: BoxDecoration(
shape: BoxShape.circle,
border: Border.all(
color: const Color(0xFF8B5A2B),
width: 2,
),
),
child: const Center(
child: Text(
'i',
style: TextStyle(
fontSize: 14,
fontWeight: FontWeight.w600,
color: Color(0xFF8B5A2B),
),
),
),
),
),
),
],
),
);
}
/// 构建用户信息区域
Widget _buildUserInfo() {
// 构建省市显示文本
String locationText = '';
if (_province != '--' && _city != '--') {
locationText = '$_province · $_city';
} else if (_province != '--') {
locationText = _province;
} else if (_city != '--') {
locationText = _city;
}
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 16),
child: Row(
children: [
// 头像
Container(
width: 80,
height: 80,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(40),
color: const Color(0xFFFFF5E6),
),
child: ClipRRect(
borderRadius: BorderRadius.circular(40),
child: _buildAvatarContent(),
),
),
const SizedBox(width: 16),
// 用户信息
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'序列号$_serialNumber',
style: const TextStyle(
fontSize: 20,
fontFamily: 'Inter',
fontWeight: FontWeight.w700,
height: 1.25,
letterSpacing: -0.3,
color: Color(0xFF5D4037),
),
),
const SizedBox(height: 4),
Text(
'社区: $_community${locationText.isNotEmpty ? ' / $locationText' : ''}',
style: const TextStyle(
fontSize: 14,
fontFamily: 'Inter',
height: 1.5,
color: Color(0xFF8B5A2B),
),
overflow: TextOverflow.ellipsis,
),
],
),
),
],
),
);
}
/// 构建挖矿状态区域
Widget _buildMiningStatusArea() {
return Padding(
padding: const EdgeInsets.all(16),
child: Container(
width: double.infinity,
decoration: BoxDecoration(
color: const Color(0x66FFFFFF),
borderRadius: BorderRadius.circular(12),
),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
// 挖矿开关按钮
GestureDetector(
onTap: _toggleMining,
child: Container(
width: 100,
height: 100,
decoration: BoxDecoration(
shape: BoxShape.circle,
color: _miningStatus == MiningStatus.mining
? const Color(0xFFD4AF37)
: Colors.transparent,
border: Border.all(
color: _miningStatus == MiningStatus.mining
? const Color(0xFFD4AF37)
: const Color(0xFFD4AF37).withOpacity(0.5),
width: 3,
),
),
child: Icon(
Icons.power_settings_new,
size: 48,
color: _miningStatus == MiningStatus.mining
? Colors.white
: const Color(0xFFD4AF37).withOpacity(0.5),
),
),
),
const SizedBox(height: 16),
// 状态文本
Text(
_getStatusText(),
style: const TextStyle(
fontSize: 18,
fontFamily: 'Inter',
fontWeight: FontWeight.w500,
height: 1.56,
color: Color(0xFF8B5A2B),
),
),
// 挖矿中显示额外信息
if (_miningStatus == MiningStatus.mining) ...[
const SizedBox(height: 24),
_buildMiningStats(),
],
],
),
),
);
}
/// 构建挖矿统计信息
Widget _buildMiningStats() {
return Container(
padding: const EdgeInsets.all(16),
margin: const EdgeInsets.symmetric(horizontal: 16),
decoration: BoxDecoration(
color: const Color(0x33D4AF37),
borderRadius: BorderRadius.circular(8),
),
child: Column(
children: [
_buildStatRow('个人算力', '100 H/s'),
const SizedBox(height: 8),
_buildStatRow('团队算力', '1,000 H/s'),
const SizedBox(height: 8),
_buildStatRow('今日收益', '0.00 DST'),
const SizedBox(height: 8),
_buildStatRow('累计收益', '0.00 DST'),
],
),
);
}
/// 构建统计行
Widget _buildStatRow(String label, String value) {
return Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
label,
style: const TextStyle(
fontSize: 14,
fontFamily: 'Inter',
height: 1.5,
color: Color(0xFF8B5A2B),
),
),
Text(
value,
style: const TextStyle(
fontSize: 14,
fontFamily: 'Inter',
fontWeight: FontWeight.w600,
height: 1.5,
color: Color(0xFF5D4037),
),
),
],
);
}
/// 构建头像内容优先本地文件其次网络URL最后SVG
Widget _buildAvatarContent() {
// 1. 优先显示本地缓存的头像文件
if (_localAvatarPath != null && _localAvatarPath!.isNotEmpty) {
final file = File(_localAvatarPath!);
// 同步检查文件是否存在
if (file.existsSync()) {
return Image.file(
file,
width: 80,
height: 80,
fit: BoxFit.cover,
gaplessPlayback: true, // 防止图片切换时闪烁
errorBuilder: (context, error, stackTrace) {
// 本地文件加载失败尝试网络URL
return _buildNetworkOrSvgAvatar();
},
);
}
}
// 2. 没有本地缓存尝试网络URL或SVG
return _buildNetworkOrSvgAvatar();
}
/// 构建网络URL或SVG头像
Widget _buildNetworkOrSvgAvatar() {
// 尝试显示网络图片URL
if (_avatarUrl != null && _avatarUrl!.isNotEmpty) {
return Image.network(
_avatarUrl!,
width: 80,
height: 80,
fit: BoxFit.cover,
loadingBuilder: (context, child, loadingProgress) {
if (loadingProgress == null) return child;
return const Center(
child: SizedBox(
width: 24,
height: 24,
child: CircularProgressIndicator(
strokeWidth: 2,
valueColor: AlwaysStoppedAnimation<Color>(Color(0xFFD4AF37)),
),
),
);
},
errorBuilder: (context, error, stackTrace) {
// 加载失败时显示SVG或默认头像
return _buildSvgOrDefaultAvatar();
},
);
}
// 显示SVG或默认头像
return _buildSvgOrDefaultAvatar();
}
/// 构建SVG或默认头像
/// 注意_avatarSvg 可能存储的是 URL用户上传的图片或 SVG 字符串(随机生成的头像)
Widget _buildSvgOrDefaultAvatar() {
if (_avatarSvg != null && _avatarSvg!.isNotEmpty) {
// 检测是否是 URL用户上传的头像图片
if (_avatarSvg!.startsWith('http://') || _avatarSvg!.startsWith('https://')) {
return Image.network(
_avatarSvg!,
width: 80,
height: 80,
fit: BoxFit.cover,
loadingBuilder: (context, child, loadingProgress) {
if (loadingProgress == null) return child;
return const Center(
child: SizedBox(
width: 24,
height: 24,
child: CircularProgressIndicator(
strokeWidth: 2,
valueColor: AlwaysStoppedAnimation<Color>(Color(0xFFD4AF37)),
),
),
);
},
errorBuilder: (context, error, stackTrace) {
return const Icon(
Icons.person,
size: 40,
color: Color(0xFF8B5A2B),
);
},
);
}
// 检测是否是 SVG 字符串(随机生成的 SVG 头像)
if (_avatarSvg!.contains('<svg') || _avatarSvg!.startsWith('<?xml')) {
return SvgPicture.string(
_avatarSvg!,
width: 80,
height: 80,
fit: BoxFit.cover,
);
}
}
return const Icon(
Icons.person,
size: 40,
color: Color(0xFF8B5A2B),
);
}
}