Лента брокерского леджера: фильтры по счёту, типу, датам и поиск, бесконечная прокрутка с явной кнопкой «Ещё», карточка события в bottom sheet с переходом на инструмент. Паттерн контроллера и пагинации взят у транзакций один в один — это тот же список с фильтрами, и второй способ делать одно и то же был бы просто вторым способом его чинить. Пункт навигации стоит между «Портфелем» и «Потоками», а не рядом с «Операциями»: это инвестиционная лента, а в операциях лежит ZenMoney. eventKindLabels переиспользован из portfolio/labels.dart, где он уже жил ради карточки инструмента, а не скопирован: два словаря подписей для одного енума разъезжаются на первом же новом типе события. Бэкенд не менялся — /events с фильтрами и пагинацией закрывает экран целиком, клиент не перегенерировался.
75 lines
2.4 KiB
Dart
75 lines
2.4 KiB
Dart
import 'package:fintracker_api/fintracker_api.dart';
|
|
import 'package:flutter/material.dart';
|
|
|
|
import '../../core/utils/ru_date.dart';
|
|
import '../../core/widgets/money_text.dart';
|
|
import '../portfolio/labels.dart' show formatQty, signColor;
|
|
import 'labels.dart';
|
|
|
|
/// One row of the События list: kind and ticker, the date with the account and quantity
|
|
/// behind it, and the native amount with its RUB equivalent when the currency differs.
|
|
///
|
|
/// The RUB line can be missing while the native amount is there — that is a day with no
|
|
/// rate, not a zero — so it is simply left out rather than filled in.
|
|
class EventRow extends StatelessWidget {
|
|
const EventRow({
|
|
required this.event,
|
|
required this.accountName,
|
|
required this.onTap,
|
|
super.key,
|
|
});
|
|
|
|
final EventOut event;
|
|
final String? accountName;
|
|
final VoidCallback onTap;
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
final e = event;
|
|
final theme = Theme.of(context);
|
|
final scheme = theme.colorScheme;
|
|
final (icon, color) = eventKindIcon(e.kind, scheme);
|
|
final showRub = e.currency != 'RUB' && e.amountRub != null;
|
|
final subtitle = [
|
|
ruDate(e.tradeDate),
|
|
?accountName,
|
|
if (e.quantity != null) '${formatQty(e.quantity!)} шт.',
|
|
if (e.status != EventStatus.confirmed) eventStatusLabel(e.status),
|
|
].join(' · ');
|
|
|
|
return ListTile(
|
|
onTap: onTap,
|
|
leading: Icon(icon, color: color),
|
|
title: Row(
|
|
children: [
|
|
Flexible(child: Text(eventTitle(e.kind, e.ticker), overflow: TextOverflow.ellipsis)),
|
|
if (e.externalFlow) ...[
|
|
const SizedBox(width: 6),
|
|
Tooltip(
|
|
message: 'Внешний поток — учитывается в XIRR',
|
|
child: Icon(Icons.swap_vert, size: 14, color: scheme.outline),
|
|
),
|
|
],
|
|
],
|
|
),
|
|
subtitle: Text(subtitle),
|
|
trailing: Column(
|
|
mainAxisAlignment: MainAxisAlignment.center,
|
|
crossAxisAlignment: CrossAxisAlignment.end,
|
|
children: [
|
|
MoneyText(
|
|
e.amount,
|
|
currency: e.currency,
|
|
style: TextStyle(color: signColor(context, e.amount)),
|
|
),
|
|
if (showRub)
|
|
Text(
|
|
MoneyText.format(e.amountRub!, 'RUB'),
|
|
style: theme.textTheme.bodySmall?.copyWith(color: scheme.outline),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
}
|