This commit is contained in:
2026-03-27 14:31:17 +03:00
parent 123c7d0eb4
commit 06653d3c3b
2 changed files with 224 additions and 39 deletions
+217 -38
View File
@@ -5,6 +5,7 @@ import 'package:uuid/uuid.dart';
import '../../core/constants.dart'; import '../../core/constants.dart';
import '../../core/l10n/locale_provider.dart'; import '../../core/l10n/locale_provider.dart';
import '../../core/services/haptic_service.dart'; import '../../core/services/haptic_service.dart';
import '../../shared/models/account.dart';
import '../../shared/models/transaction.dart'; import '../../shared/models/transaction.dart';
import '../dashboard/provider.dart'; import '../dashboard/provider.dart';
import '../settings/provider.dart'; import '../settings/provider.dart';
@@ -375,6 +376,10 @@ class _AddTransactionScreenState extends ConsumerState<AddTransactionScreen>
final categories = ref.watch(availableCategoriesProvider(widget.initial)); final categories = ref.watch(availableCategoriesProvider(widget.initial));
final overrideCurrency = state.overrideCurrency; final overrideCurrency = state.overrideCurrency;
final isDark = Theme.of(context).brightness == Brightness.dark; final isDark = Theme.of(context).brightness == Brightness.dark;
final isEditing = state.isEditing;
final activeAccount = ref.watch(activeAccountProvider);
final isAccountLocked = activeAccount != null;
final isTransfer = state.type == TransactionType.transfer;
return Scaffold( return Scaffold(
backgroundColor: Theme.of(context).scaffoldBackgroundColor, backgroundColor: Theme.of(context).scaffoldBackgroundColor,
@@ -428,50 +433,99 @@ class _AddTransactionScreenState extends ConsumerState<AddTransactionScreen>
ListView( ListView(
padding: const EdgeInsets.all(20), padding: const EdgeInsets.all(20),
children: [ children: [
TypeToggle( Stack(
selected: state.type, children: [
onChanged: (type) => ref IgnorePointer(
.read(addTransactionProvider(widget.initial).notifier) ignoring: isEditing,
.setType(type), child: TypeToggle(
isDark: isDark, selected: state.type,
onChanged: (type) => ref
.read(
addTransactionProvider(widget.initial).notifier,
)
.setType(type),
isDark: isDark,
),
),
if (isEditing)
Positioned(
top: 8,
right: 8,
child: Icon(
Icons.lock_outline,
size: 16,
color: Theme.of(
context,
).colorScheme.onSurface.withOpacity(0.4),
),
),
],
), ),
const SizedBox(height: 16), const SizedBox(height: 16),
AccountRow( if (isTransfer)
initial: widget.initial, AccountRow(
showFromDropdown: _showFromAccountDropdown, initial: widget.initial,
showToDropdown: _showToAccountDropdown, showFromDropdown: _showFromAccountDropdown,
onToggleFromDropdown: () => setState(() { showToDropdown: _showToAccountDropdown,
_showFromAccountDropdown = !_showFromAccountDropdown; onToggleFromDropdown: () => setState(() {
_fromAccountError = null; _showFromAccountDropdown = !_showFromAccountDropdown;
}), _fromAccountError = null;
onToggleToDropdown: () => setState(() { }),
_showToAccountDropdown = !_showToAccountDropdown; onToggleToDropdown: () => setState(() {
_toAccountError = null; _showToAccountDropdown = !_showToAccountDropdown;
}), _toAccountError = null;
fromIndicatorKey: _fromAccountIndicatorKey, }),
toIndicatorKey: _toAccountIndicatorKey, fromIndicatorKey: _fromAccountIndicatorKey,
fromAccountError: _fromAccountError, toIndicatorKey: _toAccountIndicatorKey,
toAccountError: _toAccountError, fromAccountError: _fromAccountError,
isDark: isDark, toAccountError: _toAccountError,
), isDark: isDark,
const SizedBox(height: 24), isAccountLocked: isAccountLocked,
),
if (isTransfer) const SizedBox(height: 24),
SectionLabel(s.amount), SectionLabel(s.amount),
const SizedBox(height: 8), const SizedBox(height: 8),
AmountInput( Row(
controller: _amountController, crossAxisAlignment: CrossAxisAlignment.start,
currencySymbol: overrideCurrency, children: [
currencyCode: state.overrideCurrencyCode, Expanded(
showError: _showError, child: AmountInput(
borderColorAnimation: _borderColorAnimation, controller: _amountController,
isDark: isDark, currencySymbol: overrideCurrency,
onChanged: (v) { currencyCode: state.overrideCurrencyCode,
final parsed = double.tryParse(v); showError: _showError,
ref borderColorAnimation: _borderColorAnimation,
.read(addTransactionProvider(widget.initial).notifier) isDark: isDark,
.setAmount(parsed); onChanged: (v) {
}, final parsed = double.tryParse(v);
ref
.read(
addTransactionProvider(
widget.initial,
).notifier,
)
.setAmount(parsed);
},
),
),
if (!isTransfer) ...[
const SizedBox(width: 12),
_InlineAccountSelector(
initial: widget.initial,
showDropdown: _showFromAccountDropdown,
onToggleDropdown: () => setState(() {
_showFromAccountDropdown =
!_showFromAccountDropdown;
_fromAccountError = null;
}),
indicatorKey: _fromAccountIndicatorKey,
isDark: isDark,
isLocked: isAccountLocked,
),
],
],
), ),
const SizedBox(height: 20), const SizedBox(height: 20),
@@ -589,6 +643,131 @@ class _AddTransactionScreenState extends ConsumerState<AddTransactionScreen>
} }
} }
class _InlineAccountSelector extends ConsumerWidget {
final Transaction? initial;
final bool showDropdown;
final VoidCallback onToggleDropdown;
final GlobalKey indicatorKey;
final bool isDark;
final bool isLocked;
const _InlineAccountSelector({
required this.initial,
required this.showDropdown,
required this.onToggleDropdown,
required this.indicatorKey,
required this.isDark,
required this.isLocked,
});
@override
Widget build(BuildContext context, WidgetRef ref) {
final accountsAsync = ref.watch(accountsProvider);
final state = ref.watch(addTransactionProvider(initial));
final activeAccount = ref.watch(activeAccountProvider);
return accountsAsync.when(
data: (accounts) {
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 Opacity(
opacity: isLocked ? 0.7 : 1.0,
child: IgnorePointer(
ignoring: isLocked,
child: GestureDetector(
onTap: onToggleDropdown,
child: Container(
key: indicatorKey,
height: 56,
padding: const EdgeInsets.symmetric(horizontal: 14),
decoration: BoxDecoration(
color: Theme.of(context).colorScheme.surface,
borderRadius: BorderRadius.circular(12),
border: Border.all(
color: isDark
? Colors.white.withOpacity(0.1)
: const Color(0xFFDDDDEE),
width: 1,
),
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Icon(
Icons.account_balance_wallet_rounded,
size: 16,
color: Theme.of(
context,
).colorScheme.onSurface.withOpacity(0.6),
),
const SizedBox(width: 8),
Text(
displayAccount?.name ?? 'Account',
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
color: Theme.of(context).colorScheme.onSurface,
fontWeight: FontWeight.w500,
fontSize: 14,
),
),
if (!isLocked) ...[
const SizedBox(width: 4),
Icon(
showDropdown
? Icons.arrow_drop_up
: Icons.arrow_drop_down,
size: 20,
color: Theme.of(
context,
).colorScheme.onSurface.withOpacity(0.6),
),
],
],
),
),
),
),
);
},
loading: () => Container(
height: 56,
width: 140,
decoration: BoxDecoration(
color: Theme.of(context).colorScheme.surface,
borderRadius: BorderRadius.circular(12),
),
),
error: (_, __) => Container(
height: 56,
width: 140,
decoration: BoxDecoration(
color: Theme.of(context).colorScheme.surface,
borderRadius: BorderRadius.circular(12),
),
),
);
}
}
class _ToAccountDropdownOverlay extends ConsumerWidget { class _ToAccountDropdownOverlay extends ConsumerWidget {
final Transaction? initial; final Transaction? initial;
final VoidCallback onClose; final VoidCallback onClose;
@@ -16,6 +16,7 @@ class AccountRow extends ConsumerWidget {
final String? fromAccountError; final String? fromAccountError;
final String? toAccountError; final String? toAccountError;
final bool isDark; final bool isDark;
final bool isAccountLocked;
const AccountRow({ const AccountRow({
super.key, super.key,
@@ -29,6 +30,7 @@ class AccountRow extends ConsumerWidget {
this.fromAccountError, this.fromAccountError,
this.toAccountError, this.toAccountError,
required this.isDark, required this.isDark,
this.isAccountLocked = false,
}); });
@override @override
@@ -79,6 +81,7 @@ class AccountRow extends ConsumerWidget {
fromAccountError: fromAccountError, fromAccountError: fromAccountError,
toAccountError: toAccountError, toAccountError: toAccountError,
isDark: isDark, isDark: isDark,
isAccountLocked: isAccountLocked,
) )
else else
_SingleAccountSelector( _SingleAccountSelector(
@@ -226,6 +229,7 @@ class _TransferAccountRow extends ConsumerWidget {
final String? fromAccountError; final String? fromAccountError;
final String? toAccountError; final String? toAccountError;
final bool isDark; final bool isDark;
final bool isAccountLocked;
const _TransferAccountRow({ const _TransferAccountRow({
required this.initial, required this.initial,
@@ -239,6 +243,7 @@ class _TransferAccountRow extends ConsumerWidget {
this.fromAccountError, this.fromAccountError,
this.toAccountError, this.toAccountError,
required this.isDark, required this.isDark,
this.isAccountLocked = false,
}); });
@override @override
@@ -285,10 +290,11 @@ class _TransferAccountRow extends ConsumerWidget {
account: fromAccount, account: fromAccount,
label: 'From', label: 'From',
showDropdown: showFromDropdown, showDropdown: showFromDropdown,
onToggle: onToggleFromDropdown, onToggle: isAccountLocked ? null : onToggleFromDropdown,
indicatorKey: fromIndicatorKey, indicatorKey: fromIndicatorKey,
error: fromAccountError, error: fromAccountError,
isDark: isDark, isDark: isDark,
disabled: isAccountLocked,
), ),
), ),
Padding( Padding(