From f51aa44cd998295c1318e53760f73232d9dd1daa Mon Sep 17 00:00:00 2001 From: hailin Date: Mon, 19 Jan 2026 19:42:52 -0800 Subject: [PATCH] =?UTF-8?q?feat(frontend):=20K=E7=BA=BF=E5=9B=BE=E7=BB=84?= =?UTF-8?q?=E4=BB=B6=E6=94=AF=E6=8C=81=E6=B7=B1=E8=89=B2=E6=A8=A1=E5=BC=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - kline_chart_widget.dart: 使用 AppColors 动态颜色,传递 isDark 参数 - kline_painter.dart: 添加 isDark 参数,网格/文字/十字线颜色随主题变化 - kline_volume_painter.dart: 添加 isDark 参数,成交量图颜色随主题变化 Co-Authored-By: Claude Opus 4.5 --- .../kline_chart/kline_chart_widget.dart | 57 ++++++++++--------- .../widgets/kline_chart/kline_painter.dart | 25 +++++--- .../kline_chart/kline_volume_painter.dart | 20 ++++--- 3 files changed, 60 insertions(+), 42 deletions(-) diff --git a/frontend/mining-app/lib/presentation/widgets/kline_chart/kline_chart_widget.dart b/frontend/mining-app/lib/presentation/widgets/kline_chart/kline_chart_widget.dart index 81c5a803..a567eeef 100644 --- a/frontend/mining-app/lib/presentation/widgets/kline_chart/kline_chart_widget.dart +++ b/frontend/mining-app/lib/presentation/widgets/kline_chart/kline_chart_widget.dart @@ -2,6 +2,7 @@ import 'dart:math' as math; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import '../../../domain/entities/kline.dart'; +import '../../../core/constants/app_colors.dart'; import 'kline_painter.dart'; import 'kline_indicator_painter.dart'; import 'kline_volume_painter.dart'; @@ -35,12 +36,10 @@ class KlineChartWidget extends StatefulWidget { } class _KlineChartWidgetState extends State { - // 颜色定义 - static const Color _orange = Color(0xFFFF6B00); + // 品牌色彩(不随主题变化) + static const Color _orange = AppColors.orange; static const Color _green = Color(0xFF10B981); static const Color _red = Color(0xFFEF4444); - static const Color _grayText = Color(0xFF6B7280); - static const Color _borderGray = Color(0xFFE5E7EB); // 缩放和滑动状态 - 基于candleWidth的缩放模式(大厂标准做法) double _candleWidth = 8.0; // 单根K线宽度(像素) @@ -141,7 +140,7 @@ class _KlineChartWidgetState extends State { Widget _buildNormalChart() { return Container( decoration: BoxDecoration( - color: Colors.white, + color: AppColors.cardOf(context), borderRadius: BorderRadius.circular(16), ), child: Column( @@ -159,20 +158,20 @@ class _KlineChartWidgetState extends State { Widget _buildFullScreenChart() { return Scaffold( - backgroundColor: Colors.white, + backgroundColor: AppColors.backgroundOf(context), appBar: AppBar( - backgroundColor: Colors.white, + backgroundColor: AppColors.cardOf(context), elevation: 0, leading: IconButton( - icon: const Icon(Icons.close, color: Color(0xFF1F2937)), + icon: Icon(Icons.close, color: AppColors.textPrimaryOf(context)), onPressed: widget.onFullScreenToggle, ), - title: const Text( + title: Text( 'K线图', style: TextStyle( fontSize: 18, fontWeight: FontWeight.bold, - color: Color(0xFF1F2937), + color: AppColors.textPrimaryOf(context), ), ), centerTitle: true, @@ -244,15 +243,16 @@ class _KlineChartWidgetState extends State { } Widget _buildIndicatorChip(String label, bool isSelected, VoidCallback onTap) { + final isDark = AppColors.isDark(context); return GestureDetector( onTap: onTap, child: Container( padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4), decoration: BoxDecoration( - color: isSelected ? _orange.withOpacity(0.1) : Colors.transparent, + color: isSelected ? _orange.withOpacity(isDark ? 0.2 : 0.1) : Colors.transparent, borderRadius: BorderRadius.circular(4), border: Border.all( - color: isSelected ? _orange : _borderGray, + color: isSelected ? _orange : AppColors.borderOf(context), ), ), child: Text( @@ -260,7 +260,7 @@ class _KlineChartWidgetState extends State { style: TextStyle( fontSize: 11, fontWeight: isSelected ? FontWeight.bold : FontWeight.normal, - color: isSelected ? _orange : _grayText, + color: isSelected ? _orange : AppColors.textSecondaryOf(context), ), ), ), @@ -271,13 +271,13 @@ class _KlineChartWidgetState extends State { return Container( padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), decoration: BoxDecoration( - border: Border(bottom: BorderSide(color: _borderGray)), + border: Border(bottom: BorderSide(color: AppColors.borderOf(context))), ), child: SingleChildScrollView( scrollDirection: Axis.horizontal, child: Row( children: [ - const Text('主图:', style: TextStyle(fontSize: 12, color: _grayText)), + Text('主图:', style: TextStyle(fontSize: 12, color: AppColors.textSecondaryOf(context))), const SizedBox(width: 8), _buildIndicatorChip('MA', _selectedMainIndicator == 0, () { setState(() => _selectedMainIndicator = 0); @@ -291,7 +291,7 @@ class _KlineChartWidgetState extends State { setState(() => _selectedMainIndicator = 2); }), const SizedBox(width: 16), - const Text('副图:', style: TextStyle(fontSize: 12, color: _grayText)), + Text('副图:', style: TextStyle(fontSize: 12, color: AppColors.textSecondaryOf(context))), const SizedBox(width: 8), _buildIndicatorChip('MACD', _selectedSubIndicator == 0, () { setState(() => _selectedSubIndicator = 0); @@ -342,9 +342,9 @@ class _KlineChartWidgetState extends State { child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ - Icon(Icons.show_chart, size: 48, color: _grayText.withOpacity(0.5)), + Icon(Icons.show_chart, size: 48, color: AppColors.textMutedOf(context)), const SizedBox(height: 8), - const Text('暂无K线数据', style: TextStyle(color: _grayText)), + Text('暂无K线数据', style: TextStyle(color: AppColors.textSecondaryOf(context))), ], ), ); @@ -389,6 +389,7 @@ class _KlineChartWidgetState extends State { bollData: _selectedMainIndicator == 2 ? _getVisibleBollData(bollData, visibleData.startIndex) : null, crossLineIndex: _showCrossLine ? _crossLineIndex - visibleData.startIndex : -1, candleWidth: visibleData.candleWidth, + isDark: AppColors.isDark(context), ), ), // 十字线信息 @@ -428,6 +429,7 @@ class _KlineChartWidgetState extends State { klines: visibleData.klines, crossLineIndex: _showCrossLine ? _crossLineIndex - visibleData.startIndex : -1, candleWidth: visibleData.candleWidth, + isDark: AppColors.isDark(context), ), ), ), @@ -469,16 +471,16 @@ class _KlineChartWidgetState extends State { child: Container( padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 6), decoration: BoxDecoration( - color: isSelected ? _orange : Colors.white, + color: isSelected ? _orange : AppColors.cardOf(context), borderRadius: BorderRadius.circular(9999), - border: isSelected ? null : Border.all(color: _borderGray), + border: isSelected ? null : Border.all(color: AppColors.borderOf(context)), ), child: Text( widget.timeRanges[index], style: TextStyle( fontSize: 12, fontWeight: FontWeight.w500, - color: isSelected ? Colors.white : _grayText, + color: isSelected ? Colors.white : AppColors.textSecondaryOf(context), ), ), ), @@ -655,18 +657,19 @@ class _KlineChartWidgetState extends State { final volume = double.tryParse(kline.volume) ?? 0; final isUp = close >= open; + final isDark = AppColors.isDark(context); return Positioned( left: 8, top: 8, child: Container( padding: const EdgeInsets.all(8), decoration: BoxDecoration( - color: Colors.white.withOpacity(0.95), + color: AppColors.cardOf(context).withOpacity(0.95), borderRadius: BorderRadius.circular(8), - border: Border.all(color: _borderGray), + border: Border.all(color: AppColors.borderOf(context)), boxShadow: [ BoxShadow( - color: Colors.black.withOpacity(0.1), + color: Colors.black.withOpacity(isDark ? 0.3 : 0.1), blurRadius: 4, ), ], @@ -677,7 +680,7 @@ class _KlineChartWidgetState extends State { children: [ Text( _formatDateTime(kline.time), - style: const TextStyle(fontSize: 10, color: _grayText), + style: TextStyle(fontSize: 10, color: AppColors.textSecondaryOf(context)), ), const SizedBox(height: 4), Row( @@ -698,7 +701,7 @@ class _KlineChartWidgetState extends State { ], ), const SizedBox(height: 2), - _buildInfoItem('量', _formatVolume(volume), _grayText), + _buildInfoItem('量', _formatVolume(volume), AppColors.textSecondaryOf(context)), ], ), ), @@ -711,7 +714,7 @@ class _KlineChartWidgetState extends State { children: [ Text( '$label: ', - style: const TextStyle(fontSize: 10, color: _grayText), + style: TextStyle(fontSize: 10, color: AppColors.textSecondaryOf(context)), ), Text( value, diff --git a/frontend/mining-app/lib/presentation/widgets/kline_chart/kline_painter.dart b/frontend/mining-app/lib/presentation/widgets/kline_chart/kline_painter.dart index d65e92b5..666aae3a 100644 --- a/frontend/mining-app/lib/presentation/widgets/kline_chart/kline_painter.dart +++ b/frontend/mining-app/lib/presentation/widgets/kline_chart/kline_painter.dart @@ -11,12 +11,15 @@ class KlinePainter extends CustomPainter { final Map>? bollData; final int crossLineIndex; final double? candleWidth; // 可选的K线宽度,不传则自动计算 + final bool isDark; // 深色模式 static const Color _green = Color(0xFF10B981); static const Color _red = Color(0xFFEF4444); - static const Color _gridColor = Color(0xFFE5E7EB); - static const Color _textColor = Color(0xFF6B7280); - static const Color _crossLineColor = Color(0xFF9CA3AF); + + // 根据主题动态获取颜色 + Color get _gridColor => isDark ? const Color(0xFF374151) : const Color(0xFFE5E7EB); + Color get _textColor => isDark ? const Color(0xFF9CA3AF) : const Color(0xFF6B7280); + Color get _crossLineColor => isDark ? const Color(0xFF6B7280) : const Color(0xFF9CA3AF); // MA线颜色 static const List _maColors = [ @@ -38,6 +41,7 @@ class KlinePainter extends CustomPainter { this.bollData, this.crossLineIndex = -1, this.candleWidth, + this.isDark = false, }); @override @@ -248,7 +252,7 @@ class KlinePainter extends CustomPainter { final price = maxPrice - priceStep * i; textPainter.text = TextSpan( text: _formatPrice(price), - style: const TextStyle(color: _textColor, fontSize: 9), + style: TextStyle(color: _textColor, fontSize: 9), ); textPainter.layout(); textPainter.paint(canvas, Offset(size.width - rightPadding + 4, y - textPainter.height / 2)); @@ -344,12 +348,16 @@ class KlinePainter extends CustomPainter { // 价格标签 final textPainter = TextPainter( - text: TextSpan( - text: _formatPrice(close), - style: const TextStyle(color: Colors.white, fontSize: 9), + text: const TextSpan( + text: '', + style: TextStyle(color: Colors.white, fontSize: 9), ), textDirection: ui.TextDirection.ltr, ); + textPainter.text = TextSpan( + text: _formatPrice(close), + style: const TextStyle(color: Colors.white, fontSize: 9), + ); textPainter.layout(); final labelRect = RRect.fromRectAndRadius( @@ -443,6 +451,7 @@ class KlinePainter extends CustomPainter { oldDelegate.emaData != emaData || oldDelegate.bollData != bollData || oldDelegate.crossLineIndex != crossLineIndex || - oldDelegate.candleWidth != candleWidth; + oldDelegate.candleWidth != candleWidth || + oldDelegate.isDark != isDark; } } diff --git a/frontend/mining-app/lib/presentation/widgets/kline_chart/kline_volume_painter.dart b/frontend/mining-app/lib/presentation/widgets/kline_chart/kline_volume_painter.dart index c9f92fe6..f2155951 100644 --- a/frontend/mining-app/lib/presentation/widgets/kline_chart/kline_volume_painter.dart +++ b/frontend/mining-app/lib/presentation/widgets/kline_chart/kline_volume_painter.dart @@ -8,16 +8,21 @@ class KlineVolumePainter extends CustomPainter { final List klines; final int crossLineIndex; final double? candleWidth; + final bool isDark; // 深色模式 static const Color _green = Color(0xFF10B981); static const Color _red = Color(0xFFEF4444); - static const Color _gridColor = Color(0xFFE5E7EB); - static const Color _textColor = Color(0xFF6B7280); + + // 根据主题动态获取颜色 + Color get _gridColor => isDark ? const Color(0xFF374151) : const Color(0xFFE5E7EB); + Color get _textColor => isDark ? const Color(0xFF9CA3AF) : const Color(0xFF6B7280); + Color get _crossLineColor => isDark ? const Color(0xFF6B7280) : const Color(0xFF9CA3AF); KlineVolumePainter({ required this.klines, this.crossLineIndex = -1, this.candleWidth, + this.isDark = false, }); @override @@ -55,7 +60,7 @@ class KlineVolumePainter extends CustomPainter { final textPainter = TextPainter( text: TextSpan( text: 'VOL', - style: const TextStyle(color: _textColor, fontSize: 9), + style: TextStyle(color: _textColor, fontSize: 9), ), textDirection: ui.TextDirection.ltr, ); @@ -66,7 +71,7 @@ class KlineVolumePainter extends CustomPainter { final maxVolText = TextPainter( text: TextSpan( text: _formatVolume(maxVolume), - style: const TextStyle(color: _textColor, fontSize: 8), + style: TextStyle(color: _textColor, fontSize: 8), ), textDirection: ui.TextDirection.ltr, ); @@ -106,7 +111,7 @@ class KlineVolumePainter extends CustomPainter { Offset(x, 0), Offset(x, size.height), Paint() - ..color = const Color(0xFF9CA3AF) + ..color = _crossLineColor ..strokeWidth = 0.5, ); @@ -115,7 +120,7 @@ class KlineVolumePainter extends CustomPainter { final volText = TextPainter( text: TextSpan( text: _formatVolume(volume), - style: const TextStyle(color: _textColor, fontSize: 9, fontWeight: FontWeight.bold), + style: TextStyle(color: _textColor, fontSize: 9, fontWeight: FontWeight.bold), ), textDirection: ui.TextDirection.ltr, ); @@ -135,6 +140,7 @@ class KlineVolumePainter extends CustomPainter { bool shouldRepaint(covariant KlineVolumePainter oldDelegate) { return oldDelegate.klines != klines || oldDelegate.crossLineIndex != crossLineIndex || - oldDelegate.candleWidth != candleWidth; + oldDelegate.candleWidth != candleWidth || + oldDelegate.isDark != isDark; } }