111
This commit is contained in:
@@ -0,0 +1,56 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import '../../../../core/theme/app_theme.dart';
|
||||
import '../../../../core/theme/app_theme_extension.dart';
|
||||
|
||||
/// K线周期选择器:15m / 1h / 4h / 1d / 1M
|
||||
class IntervalSelector extends StatelessWidget {
|
||||
final String selected;
|
||||
final ValueChanged<String> onChanged;
|
||||
|
||||
static const List<MapEntry<String, String>> intervals = [
|
||||
MapEntry('15m', '15分'),
|
||||
MapEntry('1h', '1时'),
|
||||
MapEntry('4h', '4时'),
|
||||
MapEntry('1d', '日线'),
|
||||
MapEntry('1M', '月线'),
|
||||
];
|
||||
|
||||
const IntervalSelector({
|
||||
super.key,
|
||||
required this.selected,
|
||||
required this.onChanged,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Row(
|
||||
children: intervals.map((e) {
|
||||
final isSelected = e.key == selected;
|
||||
return Expanded(
|
||||
child: GestureDetector(
|
||||
onTap: () => onChanged(e.key),
|
||||
child: Container(
|
||||
padding: const EdgeInsets.symmetric(vertical: 8),
|
||||
decoration: BoxDecoration(
|
||||
color: isSelected
|
||||
? context.colors.primary.withValues(alpha: 0.15)
|
||||
: Colors.transparent,
|
||||
borderRadius: BorderRadius.circular(6),
|
||||
),
|
||||
alignment: Alignment.center,
|
||||
child: Text(
|
||||
e.value,
|
||||
style: AppTextStyles.bodyMedium(context).copyWith(
|
||||
color: isSelected
|
||||
? context.colors.primary
|
||||
: context.appColors.onSurfaceMuted,
|
||||
fontWeight: isSelected ? FontWeight.w600 : FontWeight.w400,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}).toList(),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import '../../../../core/theme/app_theme.dart';
|
||||
import '../../../../core/theme/app_theme_extension.dart';
|
||||
import '../../../../data/models/kline_candle.dart';
|
||||
|
||||
/// K线 OHLC 信息栏
|
||||
class KlineStatsBar extends StatelessWidget {
|
||||
final KlineCandle? candle;
|
||||
|
||||
const KlineStatsBar({super.key, this.candle});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
if (candle == null) return const SizedBox.shrink();
|
||||
|
||||
final c = candle!;
|
||||
final change = c.closePrice - c.openPrice;
|
||||
final changePct = c.openPrice > 0 ? (change / c.openPrice * 100) : 0.0;
|
||||
final isUp = change >= 0;
|
||||
final color = isUp ? context.appColors.up : context.appColors.down;
|
||||
|
||||
return Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6),
|
||||
child: Row(
|
||||
children: [
|
||||
_statItem(context, '开', _fmt(c.openPrice), color),
|
||||
const SizedBox(width: 12),
|
||||
_statItem(context, '高', _fmt(c.highPrice), color),
|
||||
const SizedBox(width: 12),
|
||||
_statItem(context, '低', _fmt(c.lowPrice), color),
|
||||
const SizedBox(width: 12),
|
||||
_statItem(context, '收', _fmt(c.closePrice), color),
|
||||
const SizedBox(width: 12),
|
||||
_statItem(context, '量', _fmtVol(c.volume), color),
|
||||
const Spacer(),
|
||||
Text(
|
||||
'${isUp ? '+' : ''}${changePct.toStringAsFixed(2)}%',
|
||||
style: AppTextStyles.labelLarge(context).copyWith(
|
||||
color: color,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _statItem(BuildContext context, String label, String value, Color color) {
|
||||
return Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Text(label,
|
||||
style: AppTextStyles.bodySmall(context).copyWith(
|
||||
color: context.appColors.onSurfaceMuted,
|
||||
)),
|
||||
const SizedBox(width: 2),
|
||||
Text(value,
|
||||
style: AppTextStyles.bodySmall(context).copyWith(
|
||||
color: color,
|
||||
fontWeight: FontWeight.w500,
|
||||
)),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
String _fmt(double v) {
|
||||
if (v >= 1000) return v.toStringAsFixed(2);
|
||||
if (v >= 1) return v.toStringAsFixed(4);
|
||||
return v.toStringAsFixed(6);
|
||||
}
|
||||
|
||||
String _fmtVol(double v) {
|
||||
if (v >= 1000000) return '${(v / 1000000).toStringAsFixed(1)}M';
|
||||
if (v >= 1000) return '${(v / 1000).toStringAsFixed(1)}K';
|
||||
return v.toStringAsFixed(0);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user