feat(app): график цены — периоды, режимы, бенчмарки и мои сделки
Переключатель периода (7д…все и свой диапазон), режимы «Цена ₽ / ₽ + НКД / %», наложение бенчмарков в процентах (у ценового индекса пометка «без дивидендов»), изменение за период, заливка, подписи min/max, курсор с подсказкой и точки покупок и продаж. Ось X по датам, подписи осей без наложений.
This commit is contained in:
@@ -1,13 +1,11 @@
|
||||
import 'package:decimal/decimal.dart';
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:fintracker_api/fintracker_api.dart';
|
||||
import 'package:fl_chart/fl_chart.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
|
||||
import '../../core/api/api_client.dart';
|
||||
import '../../core/auth/auth_controller.dart';
|
||||
import '../../core/theme/chart_colors.dart';
|
||||
import '../../core/utils/ru_date.dart';
|
||||
import '../../core/widgets/async_value_view.dart';
|
||||
import '../../core/widgets/asset_icon.dart';
|
||||
@@ -17,6 +15,7 @@ import '../../core/widgets/stale_banner.dart';
|
||||
import '../../core/widgets/help_tip.dart';
|
||||
import 'instrument_edit_dialog.dart';
|
||||
import 'labels.dart';
|
||||
import 'price_history.dart';
|
||||
import 'providers.dart';
|
||||
|
||||
double _d(String s) => Decimal.parse(s).toDouble();
|
||||
@@ -87,15 +86,25 @@ class InstrumentPage extends ConsumerWidget {
|
||||
StaleBanner(fetchedAt: cached.fetchedAt!),
|
||||
_Header(instrument: d.instrument, holding: d.holding),
|
||||
const SizedBox(height: 16),
|
||||
_Section(
|
||||
title: 'Цена',
|
||||
child: d.prices.isEmpty
|
||||
? const EmptyState(
|
||||
icon: Icons.show_chart,
|
||||
message: 'Цен нет — стоимость позиции неизвестна.',
|
||||
)
|
||||
: _PriceChart(prices: d.prices),
|
||||
),
|
||||
if (d.prices.isEmpty)
|
||||
const _Section(
|
||||
title: 'История цены',
|
||||
child: EmptyState(
|
||||
icon: Icons.show_chart,
|
||||
message: 'Цен нет — стоимость позиции неизвестна.',
|
||||
),
|
||||
)
|
||||
else
|
||||
PriceHistory(
|
||||
instrumentId: instrumentId,
|
||||
label: d.instrument.ticker ?? d.instrument.name,
|
||||
isBond: d.instrument.assetClass == 'bond',
|
||||
trades: [
|
||||
for (final e in d.events)
|
||||
if (e.kind == EventKind.buy || e.kind == EventKind.sell)
|
||||
e.tradeDate,
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
_Section(
|
||||
title: 'Лоты',
|
||||
@@ -287,61 +296,6 @@ class _Fact extends StatelessWidget {
|
||||
}
|
||||
}
|
||||
|
||||
class _PriceChart extends StatelessWidget {
|
||||
const _PriceChart({required this.prices});
|
||||
|
||||
final List<PricePoint> prices;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final theme = Theme.of(context);
|
||||
return SizedBox(
|
||||
height: 200,
|
||||
child: LineChart(
|
||||
LineChartData(
|
||||
gridData: const FlGridData(show: true, drawVerticalLine: false),
|
||||
borderData: FlBorderData(show: false),
|
||||
titlesData: FlTitlesData(
|
||||
topTitles: const AxisTitles(),
|
||||
rightTitles: const AxisTitles(),
|
||||
leftTitles: const AxisTitles(
|
||||
sideTitles: SideTitles(showTitles: true, reservedSize: 52),
|
||||
),
|
||||
bottomTitles: AxisTitles(
|
||||
sideTitles: SideTitles(
|
||||
showTitles: true,
|
||||
reservedSize: 28,
|
||||
interval: (prices.length / 4).clamp(1, double.infinity),
|
||||
getTitlesWidget: (value, meta) {
|
||||
final i = value.round();
|
||||
if (i < 0 || i >= prices.length)
|
||||
return const SizedBox.shrink();
|
||||
return Text(
|
||||
ruMonthYearShort(prices[i].d),
|
||||
style: theme.textTheme.bodySmall,
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
lineBarsData: [
|
||||
LineChartBarData(
|
||||
spots: [
|
||||
for (var i = 0; i < prices.length; i++)
|
||||
FlSpot(i.toDouble(), _d(prices[i].close)),
|
||||
],
|
||||
isCurved: false,
|
||||
color: ChartColors.slot1Blue,
|
||||
barWidth: 2,
|
||||
dotData: FlDotData(show: prices.length < 2),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _LotsTable extends StatelessWidget {
|
||||
const _LotsTable({required this.lots});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user