Files
Casha/lib/features/add_transaction/widgets/account_row.dart
T
2026-03-29 15:23:20 +03:00

438 lines
14 KiB
Dart

import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import '../../../shared/models/account.dart';
import '../../../shared/models/transaction.dart';
import '../../dashboard/provider.dart';
import '../provider.dart';
class AccountRow extends ConsumerWidget {
final Transaction? initial;
final bool showFromDropdown;
final bool showToDropdown;
final VoidCallback onToggleFromDropdown;
final VoidCallback onToggleToDropdown;
final GlobalKey fromIndicatorKey;
final GlobalKey toIndicatorKey;
final String? fromAccountError;
final String? toAccountError;
final bool isDark;
final bool isFromAccountLocked;
final bool isToAccountLocked;
const AccountRow({
super.key,
required this.initial,
required this.showFromDropdown,
required this.showToDropdown,
required this.onToggleFromDropdown,
required this.onToggleToDropdown,
required this.fromIndicatorKey,
required this.toIndicatorKey,
this.fromAccountError,
this.toAccountError,
required this.isDark,
this.isFromAccountLocked = false,
this.isToAccountLocked = false,
});
@override
Widget build(BuildContext context, WidgetRef ref) {
final state = ref.watch(addTransactionProvider(initial));
final accountsAsync = ref.watch(accountsProvider);
final accounts = accountsAsync.valueOrNull ?? [];
final isTransfer = state.type == TransactionType.transfer;
// Auto-select toAccount when only 2 accounts exist
if (isTransfer && accounts.length == 2 && state.selectedAccountId != null) {
final otherId = accounts
.firstWhere(
(a) => a.id != state.selectedAccountId,
orElse: () => accounts.first,
)
.id;
if (state.toAccountId != otherId) {
WidgetsBinding.instance.addPostFrameCallback((_) {
ref
.read(addTransactionProvider(initial).notifier)
.setToAccountId(otherId);
});
}
}
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'Account',
style: TextStyle(
fontSize: 13,
color: Theme.of(context).colorScheme.onSurface.withOpacity(0.6),
),
),
const SizedBox(height: 8),
if (isTransfer)
_TransferAccountRow(
initial: initial,
accounts: accounts,
showFromDropdown: showFromDropdown,
showToDropdown: showToDropdown,
onToggleFromDropdown: onToggleFromDropdown,
onToggleToDropdown: onToggleToDropdown,
fromIndicatorKey: fromIndicatorKey,
toIndicatorKey: toIndicatorKey,
fromAccountError: fromAccountError,
toAccountError: toAccountError,
isDark: isDark,
isFromAccountLocked: isFromAccountLocked,
isToAccountLocked: isToAccountLocked,
)
else
_SingleAccountSelector(
initial: initial,
accounts: accounts,
showDropdown: showFromDropdown,
onToggleDropdown: onToggleFromDropdown,
indicatorKey: fromIndicatorKey,
error: fromAccountError,
isDark: isDark,
),
],
);
}
}
class _SingleAccountSelector extends ConsumerWidget {
final Transaction? initial;
final List<Account> accounts;
final bool showDropdown;
final VoidCallback onToggleDropdown;
final GlobalKey indicatorKey;
final String? error;
final bool isDark;
const _SingleAccountSelector({
required this.initial,
required this.accounts,
required this.showDropdown,
required this.onToggleDropdown,
required this.indicatorKey,
this.error,
required this.isDark,
});
@override
Widget build(BuildContext context, WidgetRef ref) {
final state = ref.watch(addTransactionProvider(initial));
final activeAccount = ref.watch(activeAccountProvider);
final selectedAccountId = state.selectedAccountId;
final Account? displayAccount;
if (selectedAccountId != null) {
displayAccount = accounts.firstWhere(
(a) => a.id == selectedAccountId,
orElse: () => accounts.isNotEmpty
? accounts.first
: Account(
id: 0,
name: '',
currency: 'USD',
isMain: false,
sortOrder: 0,
createdAt: DateTime.now(),
),
);
} else {
displayAccount =
activeAccount ?? (accounts.isNotEmpty ? accounts.first : null);
}
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
GestureDetector(
onTap: onToggleDropdown,
child: Container(
key: indicatorKey,
height: 56,
padding: const EdgeInsets.symmetric(horizontal: 14),
decoration: BoxDecoration(
color: error != null
? const Color(0xFFE05C6B).withOpacity(0.1)
: Theme.of(context).colorScheme.surface,
borderRadius: BorderRadius.circular(12),
border: Border.all(
color: error != null
? const Color(0xFFE05C6B)
: isDark
? Colors.white.withOpacity(0.1)
: const Color(0xFFDDDDEE),
width: error != null ? 1.5 : 1,
),
),
child: Row(
children: [
Icon(
Icons.account_balance_wallet_rounded,
size: 18,
color: error != null
? const Color(0xFFE05C6B)
: Theme.of(
context,
).colorScheme.onSurface.withOpacity(0.6),
),
const SizedBox(width: 10),
Expanded(
child: Text(
displayAccount?.name ?? 'Select account',
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
color: displayAccount != null
? Theme.of(context).colorScheme.onSurface
: Theme.of(
context,
).colorScheme.onSurface.withOpacity(0.4),
fontWeight: FontWeight.w500,
fontSize: 14,
),
overflow: TextOverflow.ellipsis,
),
),
Icon(
showDropdown ? Icons.arrow_drop_up : Icons.arrow_drop_down,
size: 20,
color: Theme.of(
context,
).colorScheme.onSurface.withOpacity(0.6),
),
],
),
),
),
if (error != null) ...[
const SizedBox(height: 6),
Text(
error!,
style: const TextStyle(fontSize: 12, color: Color(0xFFE05C6B)),
),
],
],
);
}
}
class _TransferAccountRow extends ConsumerWidget {
final Transaction? initial;
final List<Account> accounts;
final bool showFromDropdown;
final bool showToDropdown;
final VoidCallback onToggleFromDropdown;
final VoidCallback onToggleToDropdown;
final GlobalKey fromIndicatorKey;
final GlobalKey toIndicatorKey;
final String? fromAccountError;
final String? toAccountError;
final bool isDark;
final bool isFromAccountLocked;
final bool isToAccountLocked;
const _TransferAccountRow({
required this.initial,
required this.accounts,
required this.showFromDropdown,
required this.showToDropdown,
required this.onToggleFromDropdown,
required this.onToggleToDropdown,
required this.fromIndicatorKey,
required this.toIndicatorKey,
this.fromAccountError,
this.toAccountError,
required this.isDark,
this.isFromAccountLocked = false,
this.isToAccountLocked = false,
});
@override
Widget build(BuildContext context, WidgetRef ref) {
final state = ref.watch(addTransactionProvider(initial));
final activeAccount = ref.watch(activeAccountProvider);
final selectedAccountId = state.selectedAccountId;
final toAccountId = state.toAccountId;
final Account? fromAccount;
if (selectedAccountId != null) {
fromAccount = accounts.firstWhere(
(a) => a.id == selectedAccountId,
orElse: () => accounts.isNotEmpty
? accounts.first
: Account(
id: 0,
name: '',
currency: 'USD',
isMain: false,
sortOrder: 0,
createdAt: DateTime.now(),
),
);
} else {
// If no account is explicitly selected and we're on Total Balance
// creating a new transfer — show empty, force user to choose
if (activeAccount == null && initial == null) {
fromAccount = null;
} else {
fromAccount =
activeAccount ?? (accounts.isNotEmpty ? accounts.first : null);
}
}
final Account? toAccount = toAccountId != null && accounts.isNotEmpty
? accounts.firstWhere(
(a) => a.id == toAccountId,
orElse: () => accounts.first,
)
: null;
final autoSelectEnabled = accounts.length == 2;
return Row(
children: [
Expanded(
child: _AccountHalf(
account: fromAccount,
label: 'From',
showDropdown: showFromDropdown,
onToggle: isFromAccountLocked ? null : onToggleFromDropdown,
indicatorKey: fromIndicatorKey,
error: fromAccountError,
isDark: isDark,
disabled: isFromAccountLocked,
),
),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 8),
child: Icon(
Icons.swap_horiz_rounded,
size: 24,
color: Theme.of(context).colorScheme.onSurface.withOpacity(0.4),
),
),
Expanded(
child: _AccountHalf(
account: toAccount,
label: 'To',
showDropdown: showToDropdown,
onToggle: (autoSelectEnabled || isToAccountLocked)
? null
: onToggleToDropdown,
indicatorKey: toIndicatorKey,
error: toAccountError,
isDark: isDark,
disabled: autoSelectEnabled || isToAccountLocked,
),
),
],
);
}
}
class _AccountHalf extends StatelessWidget {
final Account? account;
final String label;
final bool showDropdown;
final VoidCallback? onToggle;
final GlobalKey indicatorKey;
final String? error;
final bool isDark;
final bool disabled;
const _AccountHalf({
required this.account,
required this.label,
required this.showDropdown,
required this.onToggle,
required this.indicatorKey,
this.error,
required this.isDark,
this.disabled = false,
});
@override
Widget build(BuildContext context) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
GestureDetector(
onTap: disabled ? null : onToggle,
child: Container(
key: indicatorKey,
height: 56,
padding: const EdgeInsets.symmetric(horizontal: 12),
decoration: BoxDecoration(
color: error != null
? const Color(0xFFE05C6B).withOpacity(0.1)
: disabled
? Theme.of(context).colorScheme.surface.withOpacity(0.5)
: Theme.of(context).colorScheme.surface,
borderRadius: BorderRadius.circular(12),
border: Border.all(
color: error != null
? const Color(0xFFE05C6B)
: isDark
? Colors.white.withOpacity(disabled ? 0.05 : 0.1)
: const Color(0xFFDDDDEE),
width: error != null ? 1.5 : 1,
),
),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Text(
label,
style: TextStyle(
fontSize: 10,
color: Theme.of(context).colorScheme.onSurface
.withOpacity(disabled ? 0.3 : 0.5),
fontWeight: FontWeight.w500,
),
),
const Spacer(),
if (onToggle != null)
Icon(
showDropdown
? Icons.arrow_drop_up
: Icons.arrow_drop_down,
size: 16,
color: Theme.of(
context,
).colorScheme.onSurface.withOpacity(0.5),
),
],
),
const SizedBox(height: 2),
Text(
account?.name ?? 'Select',
style: TextStyle(
fontSize: 13,
fontWeight: FontWeight.w600,
color: account != null
? Theme.of(context).colorScheme.onSurface.withOpacity(
disabled ? 0.5 : 1.0,
)
: Theme.of(
context,
).colorScheme.onSurface.withOpacity(0.4),
),
overflow: TextOverflow.ellipsis,
),
],
),
),
),
],
);
}
}