feat(app): экран «События»
Лента брокерского леджера: фильтры по счёту, типу, датам и поиск, бесконечная прокрутка с явной кнопкой «Ещё», карточка события в bottom sheet с переходом на инструмент. Паттерн контроллера и пагинации взят у транзакций один в один — это тот же список с фильтрами, и второй способ делать одно и то же был бы просто вторым способом его чинить. Пункт навигации стоит между «Портфелем» и «Потоками», а не рядом с «Операциями»: это инвестиционная лента, а в операциях лежит ZenMoney. eventKindLabels переиспользован из portfolio/labels.dart, где он уже жил ради карточки инструмента, а не скопирован: два словаря подписей для одного енума разъезжаются на первом же новом типе события. Бэкенд не менялся — /events с фильтрами и пагинацией закрывает экран целиком, клиент не перегенерировался.
This commit is contained in:
@@ -0,0 +1,65 @@
|
||||
import 'package:fintracker_api/fintracker_api.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import '../portfolio/labels.dart' show eventKindLabel;
|
||||
|
||||
export '../portfolio/labels.dart' show eventKindLabel, eventKindLabels;
|
||||
|
||||
/// The kinds worth offering as a filter, in the order a person scans for them: the two
|
||||
/// trades first, then the payouts, then the money moving in and out. The wire enum has
|
||||
/// more members (splits, amortization, the generated `unknown_default_open_api`), but a
|
||||
/// dropdown listing every one of them is a worse filter than a short one.
|
||||
const filterableEventKinds = [
|
||||
EventKind.buy,
|
||||
EventKind.sell,
|
||||
EventKind.dividend,
|
||||
EventKind.coupon,
|
||||
EventKind.interest,
|
||||
EventKind.commission,
|
||||
EventKind.tax,
|
||||
EventKind.deposit,
|
||||
EventKind.withdrawal,
|
||||
EventKind.transferIn,
|
||||
EventKind.transferOut,
|
||||
EventKind.fxExchange,
|
||||
EventKind.repayment,
|
||||
EventKind.other,
|
||||
];
|
||||
|
||||
/// Statuses as a person reads them. Only `confirmed` feeds analytics, so which of these a
|
||||
/// row carries is the answer to "why isn't this event in my numbers".
|
||||
const eventStatusLabels = {
|
||||
'confirmed': 'Подтверждено',
|
||||
'pending': 'Ожидает',
|
||||
'shadow': 'Теневое',
|
||||
'ignored': 'Игнорируется',
|
||||
};
|
||||
|
||||
String eventStatusLabel(EventStatus status) => eventStatusLabels[status.value] ?? status.value;
|
||||
|
||||
/// Icon and colour per kind, on the same principle as the transaction list: money coming
|
||||
/// in is green, money leaving is the error colour, everything structural is neutral.
|
||||
(IconData, Color) eventKindIcon(EventKind kind, ColorScheme scheme) => switch (kind) {
|
||||
EventKind.buy => (Icons.add_shopping_cart, scheme.primary),
|
||||
EventKind.sell => (Icons.sell_outlined, Colors.deepPurple),
|
||||
EventKind.dividend || EventKind.coupon || EventKind.interest => (
|
||||
Icons.payments_outlined,
|
||||
Colors.green,
|
||||
),
|
||||
EventKind.taxRefund => (Icons.assignment_return_outlined, Colors.green),
|
||||
EventKind.tax || EventKind.commission => (Icons.receipt_outlined, scheme.error),
|
||||
EventKind.deposit => (Icons.arrow_circle_down_outlined, Colors.green),
|
||||
EventKind.withdrawal => (Icons.arrow_circle_up_outlined, scheme.error),
|
||||
EventKind.transferIn || EventKind.transferOut => (Icons.swap_horiz, scheme.primary),
|
||||
EventKind.fxExchange => (Icons.currency_exchange, Colors.amber),
|
||||
EventKind.split || EventKind.amortization || EventKind.repayment => (
|
||||
Icons.call_split,
|
||||
scheme.outline,
|
||||
),
|
||||
EventKind.other || EventKind.unknownDefaultOpenApi => (Icons.help_outline, scheme.outline),
|
||||
};
|
||||
|
||||
/// `'Покупка · SBER'`, the one-line identity of a row. Events with no instrument behind
|
||||
/// them (deposits, fees) keep just the kind.
|
||||
String eventTitle(EventKind kind, String? ticker) =>
|
||||
ticker == null || ticker.isEmpty ? eventKindLabel(kind) : '${eventKindLabel(kind)} · $ticker';
|
||||
Reference in New Issue
Block a user