import 'package:fintracker_api/fintracker_api.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import '../accounts/accounts_page.dart' show accountKindLabel; import '../accounts/providers.dart'; class PortfolioDraft { const PortfolioDraft({required this.name, required this.accountIds}); final String name; final Set accountIds; } /// Create/edit form for a portfolio: a name and the accounts it is made of. Returns the /// draft to save, or null when cancelled. class PortfolioEditDialog extends ConsumerStatefulWidget { const PortfolioEditDialog({super.key, this.initial}); final PortfolioOut? initial; @override ConsumerState createState() => _PortfolioEditDialogState(); } class _PortfolioEditDialogState extends ConsumerState { final _formKey = GlobalKey(); late final _name = TextEditingController(text: widget.initial?.name ?? ''); late final Set _selected = {...?widget.initial?.accountIds}; @override void dispose() { _name.dispose(); super.dispose(); } @override Widget build(BuildContext context) { final accounts = ref.watch(accountsProvider).valueOrNull?.data ?? const []; // Brokers first: that is what a portfolio to rebalance is made of. An archived account // stays listed only while the portfolio already contains it, so it can be removed. final shown = [ for (final a in accounts) if (!a.archived || _selected.contains(a.id)) a, ]..sort((a, b) { final byBroker = (b.kind == AccountKind.broker ? 1 : 0) - (a.kind == AccountKind.broker ? 1 : 0); return byBroker != 0 ? byBroker : a.name.compareTo(b.name); }); return AlertDialog( title: Text(widget.initial == null ? 'Новый портфель' : 'Портфель'), content: SizedBox( width: 420, child: Form( key: _formKey, child: Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [ TextFormField( controller: _name, decoration: const InputDecoration(labelText: 'Название'), validator: (v) => (v ?? '').trim().isEmpty ? 'Обязательное поле' : null, ), const SizedBox(height: 16), Text( 'Счета в портфеле', style: Theme.of(context).textTheme.titleSmall, ), const SizedBox(height: 4), Flexible( child: shown.isEmpty ? const Padding( padding: EdgeInsets.symmetric(vertical: 16), child: Text('Счетов ещё нет — нужна синхронизация.'), ) : ListView( shrinkWrap: true, children: [ for (final a in shown) CheckboxListTile( contentPadding: EdgeInsets.zero, controlAffinity: ListTileControlAffinity.leading, dense: true, value: _selected.contains(a.id), title: Text(a.name), subtitle: Text( '${accountKindLabel(a.kind)} · ${a.currency}', ), onChanged: (v) => setState(() { if (v ?? false) { _selected.add(a.id); } else { _selected.remove(a.id); } }), ), ], ), ), ], ), ), ), actions: [ TextButton( onPressed: () => Navigator.of(context).pop(), child: const Text('Отмена'), ), FilledButton( onPressed: () { if (!(_formKey.currentState?.validate() ?? false)) return; Navigator.of(context).pop( PortfolioDraft( name: _name.text.trim(), accountIds: {..._selected}, ), ); }, child: const Text('Сохранить'), ), ], ); } }