import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; import 'package:sxchnge_wallet/sxchnge_wallet.dart'; import 'paste.dart'; import '../generated/i18n/app_localizations.dart'; class BankFields extends StatefulWidget { const BankFields({ super.key, required this.onChanged, this.initial, this.errorField, }); final ValueChanged onChanged; final RussianBankDestination? initial; final String? errorField; @override State createState() => _BankFieldsState(); } class _BankFieldsState extends State { late final first = TextEditingController(text: widget.initial?.firstName); late final last = TextEditingController(text: widget.initial?.lastName); late final patronymic = TextEditingController( text: widget.initial?.patronymic, ); late final account = TextEditingController(text: widget.initial?.account); final query = TextEditingController(); late final search = BankSearchProvider( context.read().service, ); late Bank? bank = widget.initial?.bank; String? pastedHolder; bool conflict = false; Map pasted = {}; bool get mismatch => bank != null && ((pasted['bank'] != null && pasted['bank'] != bank!.name) || (pasted['correspondent'] != null && pasted['correspondent'] != bank!.correspondent)); void changed() => widget.onChanged( bank == null ? null : RussianBankDestination( firstName: first.text, lastName: last.text, patronymic: patronymic.text, bank: bank!, account: account.text, ), ); Future paste() async { final parsed = await showDialog( context: context, builder: (_) => const BankPasteDialog(), ); if (!mounted || parsed == null) return; for (final entry in { 'firstName': first, 'lastName': last, 'patronymic': patronymic, 'account': account, }.entries) { if (parsed.conflicts.contains(entry.key)) { entry.value.clear(); } else if (parsed.values[entry.key] case final String value) { entry.value.text = value; } } setState(() { pasted = parsed.values; conflict = parsed.conflicts.isNotEmpty; pastedHolder = parsed.values['holder']; }); if (parsed.conflicts.contains('bik')) { setState(() => bank = null); } if (parsed.values['bik'] case final String bik) { query.text = bik; search.search(bik); setState(() => bank = null); } changed(); } @override void dispose() { for (final c in [first, last, patronymic, account, query]) { c.dispose(); } search.dispose(); super.dispose(); } Widget field( String label, TextEditingController c, { String? id, bool numeric = false, String? error, }) => Padding( padding: const EdgeInsets.only(bottom: 16), child: TextField( key: id == null ? null : ValueKey(id), controller: c, keyboardType: numeric ? TextInputType.number : TextInputType.name, decoration: InputDecoration(labelText: label, errorText: error), onChanged: (_) => changed(), ), ); @override Widget build(BuildContext context) { final l = AppLocalizations.of(context)!; return Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ Text(l.dHolder, style: Theme.of(context).textTheme.titleMedium), const SizedBox(height: 16), LayoutBuilder( builder: (context, b) { final a = field( l.dFirst, first, id: 'destination-first', error: widget.errorField == 'holder' ? l.dInvalid : null, ); final z = field(l.dLast, last, id: 'destination-last'); return b.maxWidth >= 480 ? Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ Expanded(child: a), const SizedBox(width: 12), Expanded(child: z), ], ) : Column(children: [a, z]); }, ), field(l.dPatronymic, patronymic), if (pastedHolder != null) SelectableText(pastedHolder!), if (conflict) Text(l.dPasteConflict), Text(l.dBankDetails, style: Theme.of(context).textTheme.titleMedium), Align( alignment: Alignment.centerLeft, child: TextButton.icon( onPressed: paste, icon: const Icon(Icons.content_paste), label: Text(l.dPaste), ), ), ListenableBuilder( listenable: search, builder: (context, _) => Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ if (bank == null) ...[ TextField( controller: query, decoration: InputDecoration(labelText: l.dBankSearch), onChanged: search.search, ), if (search.loading) const LinearProgressIndicator(), for (final b in search.results) ListTile( title: Text(b.name), subtitle: Text('${b.bik} ยท ${b.city}'), onTap: () { setState(() => bank = b); changed(); }, ), if (search.error != null) Text( search.error == DestinationIssue.unavailable ? l.dBankFailure : l.dBankUnknown, ), if (search.error == DestinationIssue.unavailable) TextButton( onPressed: () => search.search(query.text), child: Text(l.wRefresh), ), ] else ...[ ListTile( contentPadding: EdgeInsets.zero, title: Text(bank!.name), subtitle: Text('${l.dBik} ${bank!.bik}'), trailing: TextButton( onPressed: () { setState(() => bank = null); changed(); }, child: Text(l.dChangeBank), ), ), ExpansionTile( title: Text(l.dBankDetails), children: [ SelectableText( '${l.dCorrespondent}: ${bank!.correspondent}', ), ], ), if (!bank!.supported) Text(l.dUnsupported), ], ], ), ), const SizedBox(height: 16), field( l.dAccount, account, id: 'destination-account', numeric: true, error: widget.errorField == 'account' ? l.dInvalid : null, ), ], ); } }