This commit is contained in:
2026-03-25 01:13:28 +03:00
parent ceea63812b
commit 76cffc4960
2 changed files with 569 additions and 328 deletions
+49 -13
View File
@@ -77,10 +77,16 @@ class _AddTransactionScreenState extends ConsumerState<AddTransactionScreen>
_noteController.text = widget.initial!.note ?? ''; _noteController.text = widget.initial!.note ?? '';
} else { } else {
WidgetsBinding.instance.addPostFrameCallback((_) { WidgetsBinding.instance.addPostFrameCallback((_) {
final activeAccount = ref.read(activeAccountProvider);
final curr = ref.read(currencyProvider); final curr = ref.read(currencyProvider);
// Use active account's currency if available, otherwise use global currency
final currencyCode = activeAccount?.currency ?? curr.code;
final currencySymbol = currencyMap[currencyCode]?.symbol ?? curr.symbol;
ref ref
.read(addTransactionProvider(null).notifier) .read(addTransactionProvider(null).notifier)
.setCurrency(curr.symbol, curr.code); .setCurrency(currencySymbol, currencyCode);
}); });
} }
} }
@@ -155,18 +161,24 @@ class _AddTransactionScreenState extends ConsumerState<AddTransactionScreen>
try { try {
print('--- SUBMIT CLICKED ---'); print('--- SUBMIT CLICKED ---');
print('Amount: $amount, Category: ${state.category}, Type: ${state.type.name}'); print(
'Amount: $amount, Category: ${state.category}, Type: ${state.type.name}',
);
final activeAccount = ref.read(activeAccountProvider); final activeAccount = ref.read(activeAccountProvider);
int accountId; int accountId;
if (activeAccount != null) { if (activeAccount != null) {
print('Using active account ID: ${activeAccount.id}, Name: ${activeAccount.name}'); print(
'Using active account ID: ${activeAccount.id}, Name: ${activeAccount.name}',
);
accountId = activeAccount.id; accountId = activeAccount.id;
} else { } else {
print('No active account. Fetching main account...'); print('No active account. Fetching main account...');
final mainAccount = await ref.read(accountRepositoryProvider).getMain(); final mainAccount = await ref.read(accountRepositoryProvider).getMain();
print('Main account fetched: ID=${mainAccount.id}, Name: ${mainAccount.name}'); print(
'Main account fetched: ID=${mainAccount.id}, Name: ${mainAccount.name}',
);
accountId = mainAccount.id; accountId = mainAccount.id;
} }
@@ -190,7 +202,9 @@ class _AddTransactionScreenState extends ConsumerState<AddTransactionScreen>
print('Update completed'); print('Update completed');
} else { } else {
final res = await ref.read(transactionsProvider.notifier).add(tx); final res = await ref.read(transactionsProvider.notifier).add(tx);
print('Add completed. Result: ${res.isSuccess ? "SUCCESS" : "FAILURE"}'); print(
'Add completed. Result: ${res.isSuccess ? "SUCCESS" : "FAILURE"}',
);
if (res.isFailure) { if (res.isFailure) {
print('!!! Provider returned failure: ${res.errorOrNull}'); print('!!! Provider returned failure: ${res.errorOrNull}');
@@ -343,8 +357,12 @@ class _AddTransactionScreenState extends ConsumerState<AddTransactionScreen>
Expanded( Expanded(
child: accountsAsync.when( child: accountsAsync.when(
data: (accounts) { data: (accounts) {
final displayAccount = activeAccount ?? final displayAccount =
accounts.firstWhere((a) => a.isMain, orElse: () => accounts.first); activeAccount ??
accounts.firstWhere(
(a) => a.isMain,
orElse: () => accounts.first,
);
return Container( return Container(
height: 56, height: 56,
@@ -369,7 +387,8 @@ class _AddTransactionScreenState extends ConsumerState<AddTransactionScreen>
Flexible( Flexible(
child: Text( child: Text(
displayAccount.name, displayAccount.name,
style: Theme.of(context).textTheme.bodyMedium?.copyWith( style: Theme.of(context).textTheme.bodyMedium
?.copyWith(
color: const Color(0xFF7C6DED), color: const Color(0xFF7C6DED),
fontWeight: FontWeight.w600, fontWeight: FontWeight.w600,
fontSize: 14, fontSize: 14,
@@ -408,14 +427,21 @@ class _AddTransactionScreenState extends ConsumerState<AddTransactionScreen>
borderRadius: BorderRadius.circular(12), borderRadius: BorderRadius.circular(12),
border: isDark border: isDark
? null ? null
: Border.all(color: const Color(0xFFDDDDEE), width: 1), : Border.all(
color: const Color(0xFFDDDDEE),
width: 1,
),
), ),
child: Row( child: Row(
children: [ children: [
Expanded( Expanded(
child: GestureDetector( child: GestureDetector(
onTap: () => ref onTap: () => ref
.read(addTransactionProvider(widget.initial).notifier) .read(
addTransactionProvider(
widget.initial,
).notifier,
)
.setType(TransactionType.income), .setType(TransactionType.income),
child: AnimatedContainer( child: AnimatedContainer(
duration: const Duration(milliseconds: 200), duration: const Duration(milliseconds: 200),
@@ -430,7 +456,10 @@ class _AddTransactionScreenState extends ConsumerState<AddTransactionScreen>
Icons.arrow_downward_rounded, Icons.arrow_downward_rounded,
color: state.type == TransactionType.income color: state.type == TransactionType.income
? AppColors.income ? AppColors.income
: Theme.of(context).colorScheme.onSurface.withOpacity(0.4), : Theme.of(context)
.colorScheme
.onSurface
.withOpacity(0.4),
size: 20, size: 20,
), ),
), ),
@@ -440,7 +469,11 @@ class _AddTransactionScreenState extends ConsumerState<AddTransactionScreen>
Expanded( Expanded(
child: GestureDetector( child: GestureDetector(
onTap: () => ref onTap: () => ref
.read(addTransactionProvider(widget.initial).notifier) .read(
addTransactionProvider(
widget.initial,
).notifier,
)
.setType(TransactionType.expense), .setType(TransactionType.expense),
child: AnimatedContainer( child: AnimatedContainer(
duration: const Duration(milliseconds: 200), duration: const Duration(milliseconds: 200),
@@ -455,7 +488,10 @@ class _AddTransactionScreenState extends ConsumerState<AddTransactionScreen>
Icons.arrow_upward_rounded, Icons.arrow_upward_rounded,
color: state.type == TransactionType.expense color: state.type == TransactionType.expense
? AppColors.expense ? AppColors.expense
: Theme.of(context).colorScheme.onSurface.withOpacity(0.4), : Theme.of(context)
.colorScheme
.onSurface
.withOpacity(0.4),
size: 20, size: 20,
), ),
), ),
@@ -6,6 +6,7 @@ import '../../../core/l10n/app_strings.dart';
import '../../../core/l10n/locale_provider.dart'; import '../../../core/l10n/locale_provider.dart';
import '../../../core/services/card_color_service.dart'; import '../../../core/services/card_color_service.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 '../../settings/provider.dart'; import '../../settings/provider.dart';
import '../provider.dart'; import '../provider.dart';
@@ -37,6 +38,7 @@ class _AccountEditorOverlayState extends State<AccountEditorOverlay> {
bool _showCurrencyDropdown = false; bool _showCurrencyDropdown = false;
bool _showLimitError = false; bool _showLimitError = false;
bool _showDeleteDialog = false; bool _showDeleteDialog = false;
bool _showDuplicateError = false;
@override @override
void initState() { void initState() {
@@ -77,6 +79,17 @@ class _AccountEditorOverlayState extends State<AccountEditorOverlay> {
super.dispose(); super.dispose();
} }
bool _isDuplicateName(List<Account> accounts, String name) {
final trimmed = name.trim().toLowerCase();
return accounts.any((a) {
// When editing: ignore the account being edited itself
if (dash.editingAccount != null && a.id == dash.editingAccount!.id) {
return false;
}
return a.name.trim().toLowerCase() == trimmed;
});
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final mq = MediaQuery.of(widget.context); final mq = MediaQuery.of(widget.context);
@@ -96,14 +109,18 @@ class _AccountEditorOverlayState extends State<AccountEditorOverlay> {
if (!dash.isAddingAccount) { if (!dash.isAddingAccount) {
if (dash.editingAccount != null) { if (dash.editingAccount != null) {
final txs = ref.watch(accountFilteredTransactionsProvider); final txs = ref.watch(accountFilteredTransactionsProvider);
final accountTxs = txs.where((t) => t.accountId == dash.editingAccount!.id); final accountTxs = txs.where(
(t) => t.accountId == dash.editingAccount!.id,
);
previewBalance = accountTxs.fold(0.0, (sum, t) { previewBalance = accountTxs.fold(0.0, (sum, t) {
final converted = exchangeService.convert( final converted = exchangeService.convert(
t.amount, t.amount,
t.currencyCode, t.currencyCode,
dash.tempAccountCurrency, // convert directly from tx currency to selected dropdown currency dash.tempAccountCurrency, // convert directly from tx currency to selected dropdown currency
); );
return t.type == TransactionType.income ? sum + converted : sum - converted; return t.type == TransactionType.income
? sum + converted
: sum - converted;
}); });
} else { } else {
// Fallback for edge cases // Fallback for edge cases
@@ -136,6 +153,57 @@ class _AccountEditorOverlayState extends State<AccountEditorOverlay> {
child: const SizedBox.expand(), child: const SizedBox.expand(),
), ),
), ),
// Duplicate Name Error Alert (Top)
if (_showDuplicateError)
Positioned(
top: mq.padding.top + 8,
left: 20,
right: 20,
child: GestureDetector(
onTap: () {}, // Prevent tap-through
child: Container(
padding: const EdgeInsets.symmetric(
horizontal: 16,
vertical: 12,
),
decoration: BoxDecoration(
color: Colors.red.shade50,
borderRadius: BorderRadius.circular(16),
border: Border.all(
color: Colors.red.shade400,
width: 2,
),
boxShadow: [
BoxShadow(
color: Colors.red.withOpacity(0.3),
blurRadius: 12,
offset: const Offset(0, 4),
),
],
),
child: Row(
children: [
Icon(
Icons.error_outline_rounded,
color: Colors.red.shade700,
size: 22,
),
const SizedBox(width: 12),
Expanded(
child: Text(
'Account name already exists',
style: TextStyle(
fontSize: 13,
color: Colors.red.shade900,
fontWeight: FontWeight.w600,
),
),
),
],
),
),
),
),
// Preview Card // Preview Card
Positioned( Positioned(
top: cardTop, top: cardTop,
@@ -187,7 +255,9 @@ class _AccountEditorOverlayState extends State<AccountEditorOverlay> {
Positioned( Positioned(
top: editorPanelTop + 62, top: editorPanelTop + 62,
right: 34, // 20 (panel padding) + 14 (inner padding) right: 34, // 20 (panel padding) + 14 (inner padding)
width: (MediaQuery.of(context).size.width - 68) * 0.25, // 25% of the inner row width width:
(MediaQuery.of(context).size.width - 68) *
0.25, // 25% of the inner row width
child: Material( child: Material(
elevation: 12, elevation: 12,
borderRadius: BorderRadius.circular(12), borderRadius: BorderRadius.circular(12),
@@ -196,13 +266,16 @@ class _AccountEditorOverlayState extends State<AccountEditorOverlay> {
color: Theme.of(widget.context).colorScheme.surface, color: Theme.of(widget.context).colorScheme.surface,
borderRadius: BorderRadius.circular(12), borderRadius: BorderRadius.circular(12),
border: Border.all( border: Border.all(
color: Theme.of(widget.context).colorScheme.onSurface.withOpacity(0.15), color: Theme.of(
widget.context,
).colorScheme.onSurface.withOpacity(0.15),
width: 1.5, width: 1.5,
), ),
), ),
child: Column( child: Column(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: [ children:
[
('USD', '\$'), ('USD', '\$'),
('EUR', ''), ('EUR', ''),
('BYN', 'Br'), ('BYN', 'Br'),
@@ -223,7 +296,10 @@ class _AccountEditorOverlayState extends State<AccountEditorOverlay> {
}, },
borderRadius: BorderRadius.circular(12), borderRadius: BorderRadius.circular(12),
child: Padding( child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 8), padding: const EdgeInsets.symmetric(
horizontal: 10,
vertical: 8,
),
child: Row( child: Row(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: [ children: [
@@ -232,7 +308,9 @@ class _AccountEditorOverlayState extends State<AccountEditorOverlay> {
style: TextStyle( style: TextStyle(
fontSize: 14, fontSize: 14,
fontWeight: FontWeight.w600, fontWeight: FontWeight.w600,
color: isSelected ? const Color(0xFF7C6DED) : null, color: isSelected
? const Color(0xFF7C6DED)
: null,
), ),
), ),
const SizedBox(width: 6), const SizedBox(width: 6),
@@ -242,7 +320,10 @@ class _AccountEditorOverlayState extends State<AccountEditorOverlay> {
fontSize: 11, fontSize: 11,
color: isSelected color: isSelected
? const Color(0xFF7C6DED) ? const Color(0xFF7C6DED)
: Theme.of(widget.context).colorScheme.onSurface.withOpacity(0.6), : Theme.of(widget.context)
.colorScheme
.onSurface
.withOpacity(0.6),
), ),
), ),
const SizedBox(width: 4), const SizedBox(width: 4),
@@ -263,12 +344,17 @@ class _AccountEditorOverlayState extends State<AccountEditorOverlay> {
), ),
// Top Right Buttons (Delete & Close) // Top Right Buttons (Delete & Close)
Positioned( Positioned(
top: cardTop - 20, // Center buttons exactly on the top edge of the card top:
cardTop -
20, // Center buttons exactly on the top edge of the card
right: 20, right: 20,
child: Row( // REMOVED SafeArea to fix the vertical offset child: Row(
// REMOVED SafeArea to fix the vertical offset
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: [ children: [
if (!dash.isAddingAccount && (ref.watch(accountsProvider).valueOrNull?.length ?? 0) > 1) ...[ if (!dash.isAddingAccount &&
(ref.watch(accountsProvider).valueOrNull?.length ?? 0) >
1) ...[
GestureDetector( GestureDetector(
onTap: () => setState(() => _showDeleteDialog = true), onTap: () => setState(() => _showDeleteDialog = true),
child: Container( child: Container(
@@ -329,7 +415,9 @@ class _AccountEditorOverlayState extends State<AccountEditorOverlay> {
child: Material( child: Material(
color: Colors.transparent, color: Colors.transparent,
child: AlertDialog( child: AlertDialog(
backgroundColor: Theme.of(widget.context).colorScheme.surface, backgroundColor: Theme.of(
widget.context,
).colorScheme.surface,
shape: RoundedRectangleBorder( shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(20), borderRadius: BorderRadius.circular(20),
), ),
@@ -339,7 +427,8 @@ class _AccountEditorOverlayState extends State<AccountEditorOverlay> {
), ),
actions: [ actions: [
TextButton( TextButton(
onPressed: () => setState(() => _showDeleteDialog = false), onPressed: () =>
setState(() => _showDeleteDialog = false),
child: const Text('Cancel'), child: const Text('Cancel'),
), ),
TextButton( TextButton(
@@ -350,19 +439,32 @@ class _AccountEditorOverlayState extends State<AccountEditorOverlay> {
dash.closeAccountOverlay(apply: false); dash.closeAccountOverlay(apply: false);
final txs = ref.read(transactionsProvider).valueOrNull ?? []; final txs =
final accountTxs = txs.where((t) => t.accountId == accountId).toList(); ref
.read(transactionsProvider)
.valueOrNull ??
[];
final accountTxs = txs
.where((t) => t.accountId == accountId)
.toList();
for (final t in accountTxs) { for (final t in accountTxs) {
await ref.read(transactionsProvider.notifier).delete(t.id); await ref
.read(transactionsProvider.notifier)
.delete(t.id);
} }
await ref.read(accountRepositoryProvider).delete(accountId); await ref
.read(accountRepositoryProvider)
.delete(accountId);
if (ref.read(hapticEnabledProvider)) { if (ref.read(hapticEnabledProvider)) {
HapticService.medium(); HapticService.medium();
} }
}, },
child: const Text('Delete', style: TextStyle(color: Colors.red)), child: const Text(
'Delete',
style: TextStyle(color: Colors.red),
),
), ),
], ],
), ),
@@ -386,7 +488,9 @@ class _AccountEditorOverlayState extends State<AccountEditorOverlay> {
color: Theme.of(widget.context).colorScheme.surface, color: Theme.of(widget.context).colorScheme.surface,
borderRadius: BorderRadius.circular(20), borderRadius: BorderRadius.circular(20),
border: Border.all( border: Border.all(
color: Theme.of(widget.context).colorScheme.onSurface.withOpacity(0.1), color: Theme.of(
widget.context,
).colorScheme.onSurface.withOpacity(0.1),
width: 1.5, width: 1.5,
), ),
boxShadow: [ boxShadow: [
@@ -408,7 +512,9 @@ class _AccountEditorOverlayState extends State<AccountEditorOverlay> {
style: TextStyle( style: TextStyle(
fontSize: 11, fontSize: 11,
fontWeight: FontWeight.w600, fontWeight: FontWeight.w600,
color: Theme.of(widget.context).colorScheme.onSurface.withOpacity(0.6), color: Theme.of(
widget.context,
).colorScheme.onSurface.withOpacity(0.6),
), ),
), ),
const SizedBox(height: 8), const SizedBox(height: 8),
@@ -420,41 +526,70 @@ class _AccountEditorOverlayState extends State<AccountEditorOverlay> {
flex: 3, flex: 3,
child: TextField( child: TextField(
controller: _nameController, controller: _nameController,
buildCounter: (context, {required currentLength, required isFocused, maxLength}) => null, buildCounter:
(
context, {
required currentLength,
required isFocused,
maxLength,
}) => null,
style: const TextStyle(fontSize: 13), style: const TextStyle(fontSize: 13),
decoration: InputDecoration( decoration: InputDecoration(
hintText: 'Account name', hintText: 'Account name',
hintStyle: TextStyle(fontSize: 13, color: Theme.of(widget.context).colorScheme.onSurface.withOpacity(0.4)), hintStyle: TextStyle(
fontSize: 13,
color: Theme.of(
widget.context,
).colorScheme.onSurface.withOpacity(0.4),
),
filled: true, filled: true,
fillColor: Theme.of(widget.context).colorScheme.onSurface.withOpacity(0.05), fillColor: Theme.of(
widget.context,
).colorScheme.onSurface.withOpacity(0.05),
border: OutlineInputBorder( border: OutlineInputBorder(
borderRadius: BorderRadius.circular(12), borderRadius: BorderRadius.circular(12),
borderSide: BorderSide( borderSide: BorderSide(
color: (_showLimitError || _nameController.text.trim().isEmpty) color:
(_showLimitError ||
_showDuplicateError ||
_nameController.text.trim().isEmpty)
? Colors.red ? Colors.red
: Theme.of(widget.context).colorScheme.onSurface.withOpacity(0.15), : Theme.of(
widget.context,
).colorScheme.onSurface.withOpacity(0.15),
width: 1.5, width: 1.5,
), ),
), ),
enabledBorder: OutlineInputBorder( enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(12), borderRadius: BorderRadius.circular(12),
borderSide: BorderSide( borderSide: BorderSide(
color: (_showLimitError || _nameController.text.trim().isEmpty) color:
(_showLimitError ||
_showDuplicateError ||
_nameController.text.trim().isEmpty)
? Colors.red ? Colors.red
: Theme.of(widget.context).colorScheme.onSurface.withOpacity(0.15), : Theme.of(
widget.context,
).colorScheme.onSurface.withOpacity(0.15),
width: 1.5, width: 1.5,
), ),
), ),
focusedBorder: OutlineInputBorder( focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(12), borderRadius: BorderRadius.circular(12),
borderSide: BorderSide( borderSide: BorderSide(
color: (_showLimitError || _nameController.text.trim().isEmpty) color:
(_showLimitError ||
_showDuplicateError ||
_nameController.text.trim().isEmpty)
? Colors.red ? Colors.red
: const Color(0xFF7C6DED), : const Color(0xFF7C6DED),
width: 1.5, width: 1.5,
), ),
), ),
contentPadding: const EdgeInsets.symmetric(horizontal: 10, vertical: 12), contentPadding: const EdgeInsets.symmetric(
horizontal: 10,
vertical: 12,
),
), ),
), ),
), ),
@@ -469,12 +604,16 @@ class _AccountEditorOverlayState extends State<AccountEditorOverlay> {
child: Container( child: Container(
height: double.infinity, height: double.infinity,
decoration: BoxDecoration( decoration: BoxDecoration(
color: Theme.of(widget.context).colorScheme.onSurface.withOpacity(0.05), color: Theme.of(
widget.context,
).colorScheme.onSurface.withOpacity(0.05),
borderRadius: BorderRadius.circular(12), borderRadius: BorderRadius.circular(12),
border: Border.all( border: Border.all(
color: _showCurrencyDropdown color: _showCurrencyDropdown
? const Color(0xFF7C6DED) ? const Color(0xFF7C6DED)
: Theme.of(widget.context).colorScheme.onSurface.withOpacity(0.15), : Theme.of(
widget.context,
).colorScheme.onSurface.withOpacity(0.15),
width: 1.5, width: 1.5,
), ),
), ),
@@ -483,15 +622,30 @@ class _AccountEditorOverlayState extends State<AccountEditorOverlay> {
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
children: [ children: [
Text( Text(
[('USD', '\$'), ('EUR', ''), ('BYN', 'Br'), ('RUB', '')] [
.firstWhere((c) => c.$1 == _selectedCurrency).$2, ('USD', '\$'),
style: const TextStyle(fontSize: 15, fontWeight: FontWeight.w600), ('EUR', ''),
('BYN', 'Br'),
('RUB', ''),
]
.firstWhere(
(c) => c.$1 == _selectedCurrency,
)
.$2,
style: const TextStyle(
fontSize: 15,
fontWeight: FontWeight.w600,
),
), ),
const SizedBox(width: 4), const SizedBox(width: 4),
Icon( Icon(
_showCurrencyDropdown ? Icons.arrow_drop_up : Icons.arrow_drop_down, _showCurrencyDropdown
? Icons.arrow_drop_up
: Icons.arrow_drop_down,
size: 20, size: 20,
color: Theme.of(widget.context).colorScheme.onSurface.withOpacity(0.6), color: Theme.of(
widget.context,
).colorScheme.onSurface.withOpacity(0.6),
), ),
], ],
), ),
@@ -516,7 +670,9 @@ class _AccountEditorOverlayState extends State<AccountEditorOverlay> {
color: Theme.of(widget.context).colorScheme.surface, color: Theme.of(widget.context).colorScheme.surface,
borderRadius: BorderRadius.circular(20), borderRadius: BorderRadius.circular(20),
border: Border.all( border: Border.all(
color: Theme.of(widget.context).colorScheme.onSurface.withOpacity(0.1), color: Theme.of(
widget.context,
).colorScheme.onSurface.withOpacity(0.1),
width: 1.5, width: 1.5,
), ),
boxShadow: [ boxShadow: [
@@ -569,7 +725,9 @@ class _AccountEditorOverlayState extends State<AccountEditorOverlay> {
isDimmed: isSolid, isDimmed: isSolid,
onTap: () { onTap: () {
dash.setState(() { dash.setState(() {
if (isSolid) dash.tempGradientType = CardColorService.defaultGradient; if (isSolid)
dash.tempGradientType =
CardColorService.defaultGradient;
dash.editingPrimary = true; dash.editingPrimary = true;
}); });
setPanelState(() {}); setPanelState(() {});
@@ -586,7 +744,9 @@ class _AccountEditorOverlayState extends State<AccountEditorOverlay> {
isDimmed: isSolid, isDimmed: isSolid,
onTap: () { onTap: () {
dash.setState(() { dash.setState(() {
if (isSolid) dash.tempGradientType = CardColorService.defaultGradient; if (isSolid)
dash.tempGradientType =
CardColorService.defaultGradient;
dash.editingPrimary = false; dash.editingPrimary = false;
}); });
setPanelState(() {}); setPanelState(() {});
@@ -598,16 +758,17 @@ class _AccountEditorOverlayState extends State<AccountEditorOverlay> {
padding: const EdgeInsets.symmetric(horizontal: 8), padding: const EdgeInsets.symmetric(horizontal: 8),
child: Container( child: Container(
width: 1, width: 1,
color: Theme.of(widget.context) color: Theme.of(
.colorScheme widget.context,
.onSurface ).colorScheme.onSurface.withOpacity(0.15),
.withOpacity(0.15),
margin: const EdgeInsets.symmetric(vertical: 4), margin: const EdgeInsets.symmetric(vertical: 4),
), ),
), ),
Expanded( Expanded(
child: GestureDetector( child: GestureDetector(
onTap: isSolid ? null : () { onTap: isSolid
? null
: () {
dash.setState(() { dash.setState(() {
dash.tempGradientType = GradientType.solid; dash.tempGradientType = GradientType.solid;
dash.editingPrimary = true; dash.editingPrimary = true;
@@ -618,7 +779,9 @@ class _AccountEditorOverlayState extends State<AccountEditorOverlay> {
child: Container( child: Container(
height: double.infinity, height: double.infinity,
padding: const EdgeInsets.symmetric( padding: const EdgeInsets.symmetric(
horizontal: 4, vertical: 6), horizontal: 4,
vertical: 6,
),
decoration: BoxDecoration( decoration: BoxDecoration(
color: isSolid color: isSolid
? const Color(0xFF7C6DED).withOpacity(0.15) ? const Color(0xFF7C6DED).withOpacity(0.15)
@@ -627,10 +790,9 @@ class _AccountEditorOverlayState extends State<AccountEditorOverlay> {
border: Border.all( border: Border.all(
color: isSolid color: isSolid
? const Color(0xFF7C6DED) ? const Color(0xFF7C6DED)
: Theme.of(widget.context) : Theme.of(
.colorScheme widget.context,
.onSurface ).colorScheme.onSurface.withOpacity(0.2),
.withOpacity(0.2),
width: 1.5, width: 1.5,
), ),
), ),
@@ -662,9 +824,8 @@ class _AccountEditorOverlayState extends State<AccountEditorOverlay> {
child: LayoutBuilder( child: LayoutBuilder(
builder: (lbCtx, constraints) { builder: (lbCtx, constraints) {
const reservedBelow = 78.0; const reservedBelow = 78.0;
final spectrumH = final spectrumH = (constraints.maxHeight - reservedBelow)
(constraints.maxHeight - reservedBelow).clamp( .clamp(40.0, double.infinity);
40.0, double.infinity);
return Column( return Column(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
@@ -704,7 +865,8 @@ class _AccountEditorOverlayState extends State<AccountEditorOverlay> {
GestureDetector( GestureDetector(
onTap: () { onTap: () {
dash.setState( dash.setState(
() => dash.editingPrimary = true); () => dash.editingPrimary = true,
);
setPanelState(() {}); setPanelState(() {});
dash.overlayEntry?.markNeedsBuild(); dash.overlayEntry?.markNeedsBuild();
}, },
@@ -721,10 +883,12 @@ class _AccountEditorOverlayState extends State<AccountEditorOverlay> {
border: dash.editingPrimary border: dash.editingPrimary
? Border.all( ? Border.all(
color: Colors.white, color: Colors.white,
width: 2) width: 2,
)
: Border.all( : Border.all(
color: Colors.transparent, color: Colors.transparent,
width: 2), width: 2,
),
), ),
), ),
const SizedBox(width: 5), const SizedBox(width: 5),
@@ -742,7 +906,8 @@ class _AccountEditorOverlayState extends State<AccountEditorOverlay> {
.withOpacity( .withOpacity(
dash.editingPrimary dash.editingPrimary
? 0.8 ? 0.8
: 0.4), : 0.4,
),
), ),
), ),
], ],
@@ -752,8 +917,9 @@ class _AccountEditorOverlayState extends State<AccountEditorOverlay> {
if (!isSolid) if (!isSolid)
GestureDetector( GestureDetector(
onTap: () { onTap: () {
dash.setState(() => dash.setState(
dash.editingPrimary = false); () => dash.editingPrimary = false,
);
setPanelState(() {}); setPanelState(() {});
dash.overlayEntry?.markNeedsBuild(); dash.overlayEntry?.markNeedsBuild();
}, },
@@ -774,7 +940,8 @@ class _AccountEditorOverlayState extends State<AccountEditorOverlay> {
.withOpacity( .withOpacity(
!dash.editingPrimary !dash.editingPrimary
? 0.8 ? 0.8
: 0.4), : 0.4,
),
), ),
), ),
const SizedBox(width: 5), const SizedBox(width: 5),
@@ -788,11 +955,13 @@ class _AccountEditorOverlayState extends State<AccountEditorOverlay> {
border: !dash.editingPrimary border: !dash.editingPrimary
? Border.all( ? Border.all(
color: Colors.white, color: Colors.white,
width: 2) width: 2,
)
: Border.all( : Border.all(
color: color:
Colors.transparent, Colors.transparent,
width: 2), width: 2,
),
), ),
), ),
], ],
@@ -827,10 +996,12 @@ class _AccountEditorOverlayState extends State<AccountEditorOverlay> {
GradientType.solid => '', GradientType.solid => '',
}; };
final icon = switch (type) { final icon = switch (type) {
GradientType.linear => Icons.trending_flat_rounded, GradientType.linear =>
Icons.trending_flat_rounded,
GradientType.linearReverse => GradientType.linearReverse =>
Icons.swap_horiz_rounded, Icons.swap_horiz_rounded,
GradientType.radial => Icons.blur_circular_rounded, GradientType.radial =>
Icons.blur_circular_rounded,
GradientType.sweep => Icons.rotate_right_rounded, GradientType.sweep => Icons.rotate_right_rounded,
GradientType.solid => Icons.square_rounded, GradientType.solid => Icons.square_rounded,
}; };
@@ -840,18 +1011,21 @@ class _AccountEditorOverlayState extends State<AccountEditorOverlay> {
child: GestureDetector( child: GestureDetector(
onTap: () { onTap: () {
dash.setState( dash.setState(
() => dash.tempGradientType = type); () => dash.tempGradientType = type,
);
setPanelState(() {}); setPanelState(() {});
dash.overlayEntry?.markNeedsBuild(); dash.overlayEntry?.markNeedsBuild();
}, },
child: AnimatedContainer( child: AnimatedContainer(
duration: const Duration(milliseconds: 150), duration: const Duration(milliseconds: 150),
padding: padding: const EdgeInsets.symmetric(
const EdgeInsets.symmetric(vertical: 5), vertical: 5,
),
decoration: BoxDecoration( decoration: BoxDecoration(
color: isSelected color: isSelected
? const Color(0xFF7C6DED) ? const Color(
.withOpacity(0.15) 0xFF7C6DED,
).withOpacity(0.15)
: Theme.of(widget.context) : Theme.of(widget.context)
.colorScheme .colorScheme
.onSurface .onSurface
@@ -867,14 +1041,16 @@ class _AccountEditorOverlayState extends State<AccountEditorOverlay> {
child: Column( child: Column(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: [ children: [
Icon(icon, Icon(
icon,
size: 15, size: 15,
color: isSelected color: isSelected
? const Color(0xFF7C6DED) ? const Color(0xFF7C6DED)
: Theme.of(widget.context) : Theme.of(widget.context)
.colorScheme .colorScheme
.onSurface .onSurface
.withOpacity(0.45)), .withOpacity(0.45),
),
const SizedBox(height: 2), const SizedBox(height: 2),
Text( Text(
label, label,
@@ -897,7 +1073,8 @@ class _AccountEditorOverlayState extends State<AccountEditorOverlay> {
), ),
), ),
); );
}).toList(), })
.toList(),
), ),
), ),
), ),
@@ -921,28 +1098,30 @@ class _AccountEditorOverlayState extends State<AccountEditorOverlay> {
dash.tempSecondary = defS; dash.tempSecondary = defS;
dash.tempPrimaryHSV = HSVColor.fromColor(defP); dash.tempPrimaryHSV = HSVColor.fromColor(defP);
dash.tempSecondaryHSV = HSVColor.fromColor(defS); dash.tempSecondaryHSV = HSVColor.fromColor(defS);
dash.tempGradientType = CardColorService.defaultGradient; dash.tempGradientType =
CardColorService.defaultGradient;
}); });
setPanelState(() {}); setPanelState(() {});
dash.overlayEntry?.markNeedsBuild(); dash.overlayEntry?.markNeedsBuild();
}, },
icon: const Icon(Icons.restart_alt_rounded, size: 15), icon: const Icon(Icons.restart_alt_rounded, size: 15),
label: Text(s.reset, label: Text(
style: const TextStyle(fontSize: 13)), s.reset,
style: const TextStyle(fontSize: 13),
),
style: OutlinedButton.styleFrom( style: OutlinedButton.styleFrom(
foregroundColor: Theme.of(widget.context) foregroundColor: Theme.of(
.colorScheme widget.context,
.onSurface ).colorScheme.onSurface.withOpacity(0.7),
.withOpacity(0.7),
side: BorderSide( side: BorderSide(
color: Theme.of(widget.context) color: Theme.of(
.colorScheme widget.context,
.onSurface ).colorScheme.onSurface.withOpacity(0.2),
.withOpacity(0.2),
), ),
padding: const EdgeInsets.symmetric(vertical: 10), padding: const EdgeInsets.symmetric(vertical: 10),
shape: RoundedRectangleBorder( shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12)), borderRadius: BorderRadius.circular(12),
),
), ),
), ),
), ),
@@ -953,29 +1132,53 @@ class _AccountEditorOverlayState extends State<AccountEditorOverlay> {
onPressed: _nameController.text.trim().isEmpty onPressed: _nameController.text.trim().isEmpty
? null ? null
: () { : () {
// Add haptic feedback here final accounts =
ProviderScope.containerOf(
widget.context,
).read(accountsProvider).valueOrNull ??
[];
if (_isDuplicateName(
accounts,
_nameController.text,
)) {
setState(() => _showDuplicateError = true);
Future.delayed(
const Duration(seconds: 3),
() {
if (mounted)
setState(
() => _showDuplicateError = false,
);
},
);
return; // block saving
}
HapticService.light(); HapticService.light();
dash.closeAccountOverlay(apply: true); dash.closeAccountOverlay(apply: true);
}, },
style: ElevatedButton.styleFrom( style: ElevatedButton.styleFrom(
backgroundColor: const Color(0xFF7C6DED), backgroundColor: const Color(0xFF7C6DED),
foregroundColor: Colors.white, foregroundColor: Colors.white,
disabledBackgroundColor: Theme.of(widget.context) disabledBackgroundColor: Theme.of(
.colorScheme widget.context,
.onSurface ).colorScheme.onSurface.withOpacity(0.12),
.withOpacity(0.12), disabledForegroundColor: Theme.of(
disabledForegroundColor: Theme.of(widget.context) widget.context,
.colorScheme ).colorScheme.onSurface.withOpacity(0.38),
.onSurface
.withOpacity(0.38),
padding: const EdgeInsets.symmetric(vertical: 10), padding: const EdgeInsets.symmetric(vertical: 10),
shape: RoundedRectangleBorder( shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12)), borderRadius: BorderRadius.circular(12),
),
), ),
child: Text( child: Text(
dash.isAddingAccount ? s.addAccount : s.apply, dash.isAddingAccount ? s.addAccount : s.apply,
style: const TextStyle( style: const TextStyle(
fontWeight: FontWeight.w700, fontSize: 14)), fontWeight: FontWeight.w700,
fontSize: 14,
),
),
), ),
), ),
], ],
@@ -1013,7 +1216,9 @@ class PanelTab extends StatelessWidget {
: (isDark ? Colors.white24 : const Color(0xFFCCCCDD)); : (isDark ? Colors.white24 : const Color(0xFFCCCCDD));
final textColor = isSelected final textColor = isSelected
? const Color(0xFF7C6DED) ? const Color(0xFF7C6DED)
: (isDark ? Colors.white60 : Theme.of(context).colorScheme.onSurface.withOpacity(0.5)); : (isDark
? Colors.white60
: Theme.of(context).colorScheme.onSurface.withOpacity(0.5));
return GestureDetector( return GestureDetector(
onTap: onTap, onTap: onTap,
@@ -1025,12 +1230,11 @@ class PanelTab extends StatelessWidget {
width: double.infinity, width: double.infinity,
padding: const EdgeInsets.symmetric(horizontal: 4, vertical: 8), padding: const EdgeInsets.symmetric(horizontal: 4, vertical: 8),
decoration: BoxDecoration( decoration: BoxDecoration(
color: isSelected ? const Color(0xFF7C6DED).withOpacity(0.15) : Colors.transparent, color: isSelected
? const Color(0xFF7C6DED).withOpacity(0.15)
: Colors.transparent,
borderRadius: BorderRadius.circular(10), borderRadius: BorderRadius.circular(10),
border: Border.all( border: Border.all(color: borderColor, width: 1.5),
color: borderColor,
width: 1.5,
),
), ),
child: Row( child: Row(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
@@ -1057,8 +1261,9 @@ class PanelTab extends StatelessWidget {
textAlign: TextAlign.center, textAlign: TextAlign.center,
style: TextStyle( style: TextStyle(
fontSize: 12, fontSize: 12,
fontWeight: fontWeight: isSelected
isSelected ? FontWeight.w600 : FontWeight.normal, ? FontWeight.w600
: FontWeight.normal,
color: textColor, color: textColor,
), ),
), ),