feat(app): экран «События»
Лента брокерского леджера: фильтры по счёту, типу, датам и поиск, бесконечная прокрутка с явной кнопкой «Ещё», карточка события в bottom sheet с переходом на инструмент. Паттерн контроллера и пагинации взят у транзакций один в один — это тот же список с фильтрами, и второй способ делать одно и то же был бы просто вторым способом его чинить. Пункт навигации стоит между «Портфелем» и «Потоками», а не рядом с «Операциями»: это инвестиционная лента, а в операциях лежит ZenMoney. eventKindLabels переиспользован из portfolio/labels.dart, где он уже жил ради карточки инструмента, а не скопирован: два словаря подписей для одного енума разъезжаются на первом же новом типе события. Бэкенд не менялся — /events с фильтрами и пагинацией закрывает экран целиком, клиент не перегенерировался.
This commit is contained in:
@@ -0,0 +1,74 @@
|
||||
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),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user