Месячная сетка с дивидендами, купонами, амортизациями и погашениями по дням, выплаченными и ожидаемыми, с сводкой за месяц и списком выплат выбранного дня. Раскладка без прокрутки страницы: высота ячеек подстраивается под окно, на широком экране список справа. Прежний список остался вторым режимом.
74 lines
2.5 KiB
Dart
74 lines
2.5 KiB
Dart
import 'package:flutter/material.dart';
|
||
import 'package:go_router/go_router.dart';
|
||
|
||
import '../../core/utils/ru_date.dart';
|
||
import '../../core/widgets/money_text.dart';
|
||
import '../portfolio/labels.dart' show formatQty;
|
||
import 'data/income_api.dart';
|
||
import 'labels.dart';
|
||
|
||
/// One payment as a list row: what, when, on which basis, and how much. The basis chip is on
|
||
/// every row on purpose — it is what tells a declared payment from an extrapolated one.
|
||
class IncomeEntryRow extends StatelessWidget {
|
||
const IncomeEntryRow({required this.entry, super.key});
|
||
|
||
final IncomeEntry entry;
|
||
|
||
@override
|
||
Widget build(BuildContext context) {
|
||
final theme = Theme.of(context);
|
||
final details = [
|
||
incomeKindLabel(entry.kind),
|
||
if (entry.qty != null && entry.perUnit != null)
|
||
'${formatQty(entry.qty!)} × ${MoneyText.format(entry.perUnit!, entry.currency)}',
|
||
if (entry.recordDate != null) 'отсечка ${ruDate(entry.recordDate!)}',
|
||
if (entry.taxWithheld != null)
|
||
'налог ${MoneyText.format(entry.taxWithheld!, entry.currency)}',
|
||
].join(' · ');
|
||
|
||
return ListTile(
|
||
contentPadding: EdgeInsets.zero,
|
||
dense: true,
|
||
onTap: entry.instrumentId == null
|
||
? null
|
||
: () => context.push('/portfolio/instrument/${entry.instrumentId}'),
|
||
leading: Icon(
|
||
incomeKindIcon(entry.kind),
|
||
color: basisColor(entry.basis),
|
||
size: 20,
|
||
),
|
||
title: Row(
|
||
children: [
|
||
Flexible(child: Text(entry.title, overflow: TextOverflow.ellipsis)),
|
||
const SizedBox(width: 8),
|
||
BasisChip(basis: entry.basis),
|
||
],
|
||
),
|
||
subtitle: Text(
|
||
'${entry.expectedDate == null ? 'дата неизвестна' : ruDate(entry.expectedDate!)} · $details',
|
||
style: theme.textTheme.bodySmall,
|
||
),
|
||
trailing: Column(
|
||
mainAxisAlignment: MainAxisAlignment.center,
|
||
crossAxisAlignment: CrossAxisAlignment.end,
|
||
children: [
|
||
if (entry.amount != null)
|
||
MoneyText(
|
||
entry.amount!,
|
||
currency: entry.currency,
|
||
style: theme.textTheme.titleSmall,
|
||
),
|
||
// no FX rate for the date ⇒ no rouble figure. An em dash, never 0 ₽.
|
||
if (entry.currency != 'RUB')
|
||
Text(
|
||
entry.amountRub == null
|
||
? '— ₽ (нет курса)'
|
||
: MoneyText.format(entry.amountRub!, 'RUB'),
|
||
style: theme.textTheme.bodySmall,
|
||
),
|
||
],
|
||
),
|
||
);
|
||
}
|
||
}
|