mirror of
https://github.com/koloideal/Casha.git
synced 2026-06-10 02:15:29 +03:00
update
This commit is contained in:
@@ -77,10 +77,16 @@ class _AddTransactionScreenState extends ConsumerState<AddTransactionScreen>
|
||||
_noteController.text = widget.initial!.note ?? '';
|
||||
} else {
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
final activeAccount = ref.read(activeAccountProvider);
|
||||
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
|
||||
.read(addTransactionProvider(null).notifier)
|
||||
.setCurrency(curr.symbol, curr.code);
|
||||
.setCurrency(currencySymbol, currencyCode);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -155,18 +161,24 @@ class _AddTransactionScreenState extends ConsumerState<AddTransactionScreen>
|
||||
|
||||
try {
|
||||
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);
|
||||
int accountId;
|
||||
|
||||
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;
|
||||
} else {
|
||||
print('No active account. Fetching main account...');
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -190,7 +202,9 @@ class _AddTransactionScreenState extends ConsumerState<AddTransactionScreen>
|
||||
print('Update completed');
|
||||
} else {
|
||||
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) {
|
||||
print('!!! Provider returned failure: ${res.errorOrNull}');
|
||||
@@ -343,8 +357,12 @@ class _AddTransactionScreenState extends ConsumerState<AddTransactionScreen>
|
||||
Expanded(
|
||||
child: accountsAsync.when(
|
||||
data: (accounts) {
|
||||
final displayAccount = activeAccount ??
|
||||
accounts.firstWhere((a) => a.isMain, orElse: () => accounts.first);
|
||||
final displayAccount =
|
||||
activeAccount ??
|
||||
accounts.firstWhere(
|
||||
(a) => a.isMain,
|
||||
orElse: () => accounts.first,
|
||||
);
|
||||
|
||||
return Container(
|
||||
height: 56,
|
||||
@@ -369,7 +387,8 @@ class _AddTransactionScreenState extends ConsumerState<AddTransactionScreen>
|
||||
Flexible(
|
||||
child: Text(
|
||||
displayAccount.name,
|
||||
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
|
||||
style: Theme.of(context).textTheme.bodyMedium
|
||||
?.copyWith(
|
||||
color: const Color(0xFF7C6DED),
|
||||
fontWeight: FontWeight.w600,
|
||||
fontSize: 14,
|
||||
@@ -408,14 +427,21 @@ class _AddTransactionScreenState extends ConsumerState<AddTransactionScreen>
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
border: isDark
|
||||
? null
|
||||
: Border.all(color: const Color(0xFFDDDDEE), width: 1),
|
||||
: Border.all(
|
||||
color: const Color(0xFFDDDDEE),
|
||||
width: 1,
|
||||
),
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: GestureDetector(
|
||||
onTap: () => ref
|
||||
.read(addTransactionProvider(widget.initial).notifier)
|
||||
.read(
|
||||
addTransactionProvider(
|
||||
widget.initial,
|
||||
).notifier,
|
||||
)
|
||||
.setType(TransactionType.income),
|
||||
child: AnimatedContainer(
|
||||
duration: const Duration(milliseconds: 200),
|
||||
@@ -430,7 +456,10 @@ class _AddTransactionScreenState extends ConsumerState<AddTransactionScreen>
|
||||
Icons.arrow_downward_rounded,
|
||||
color: state.type == TransactionType.income
|
||||
? AppColors.income
|
||||
: Theme.of(context).colorScheme.onSurface.withOpacity(0.4),
|
||||
: Theme.of(context)
|
||||
.colorScheme
|
||||
.onSurface
|
||||
.withOpacity(0.4),
|
||||
size: 20,
|
||||
),
|
||||
),
|
||||
@@ -440,7 +469,11 @@ class _AddTransactionScreenState extends ConsumerState<AddTransactionScreen>
|
||||
Expanded(
|
||||
child: GestureDetector(
|
||||
onTap: () => ref
|
||||
.read(addTransactionProvider(widget.initial).notifier)
|
||||
.read(
|
||||
addTransactionProvider(
|
||||
widget.initial,
|
||||
).notifier,
|
||||
)
|
||||
.setType(TransactionType.expense),
|
||||
child: AnimatedContainer(
|
||||
duration: const Duration(milliseconds: 200),
|
||||
@@ -455,7 +488,10 @@ class _AddTransactionScreenState extends ConsumerState<AddTransactionScreen>
|
||||
Icons.arrow_upward_rounded,
|
||||
color: state.type == TransactionType.expense
|
||||
? AppColors.expense
|
||||
: Theme.of(context).colorScheme.onSurface.withOpacity(0.4),
|
||||
: Theme.of(context)
|
||||
.colorScheme
|
||||
.onSurface
|
||||
.withOpacity(0.4),
|
||||
size: 20,
|
||||
),
|
||||
),
|
||||
|
||||
@@ -6,6 +6,7 @@ import '../../../core/l10n/app_strings.dart';
|
||||
import '../../../core/l10n/locale_provider.dart';
|
||||
import '../../../core/services/card_color_service.dart';
|
||||
import '../../../core/services/haptic_service.dart';
|
||||
import '../../../shared/models/account.dart';
|
||||
import '../../../shared/models/transaction.dart';
|
||||
import '../../settings/provider.dart';
|
||||
import '../provider.dart';
|
||||
@@ -37,6 +38,7 @@ class _AccountEditorOverlayState extends State<AccountEditorOverlay> {
|
||||
bool _showCurrencyDropdown = false;
|
||||
bool _showLimitError = false;
|
||||
bool _showDeleteDialog = false;
|
||||
bool _showDuplicateError = false;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
@@ -77,6 +79,17 @@ class _AccountEditorOverlayState extends State<AccountEditorOverlay> {
|
||||
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
|
||||
Widget build(BuildContext context) {
|
||||
final mq = MediaQuery.of(widget.context);
|
||||
@@ -96,14 +109,18 @@ class _AccountEditorOverlayState extends State<AccountEditorOverlay> {
|
||||
if (!dash.isAddingAccount) {
|
||||
if (dash.editingAccount != null) {
|
||||
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) {
|
||||
final converted = exchangeService.convert(
|
||||
t.amount,
|
||||
t.currencyCode,
|
||||
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 {
|
||||
// Fallback for edge cases
|
||||
@@ -136,6 +153,57 @@ class _AccountEditorOverlayState extends State<AccountEditorOverlay> {
|
||||
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
|
||||
Positioned(
|
||||
top: cardTop,
|
||||
@@ -187,7 +255,9 @@ class _AccountEditorOverlayState extends State<AccountEditorOverlay> {
|
||||
Positioned(
|
||||
top: editorPanelTop + 62,
|
||||
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(
|
||||
elevation: 12,
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
@@ -196,13 +266,16 @@ class _AccountEditorOverlayState extends State<AccountEditorOverlay> {
|
||||
color: Theme.of(widget.context).colorScheme.surface,
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
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,
|
||||
),
|
||||
),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
children:
|
||||
[
|
||||
('USD', '\$'),
|
||||
('EUR', '€'),
|
||||
('BYN', 'Br'),
|
||||
@@ -223,7 +296,10 @@ class _AccountEditorOverlayState extends State<AccountEditorOverlay> {
|
||||
},
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 8),
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 10,
|
||||
vertical: 8,
|
||||
),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
@@ -232,7 +308,9 @@ class _AccountEditorOverlayState extends State<AccountEditorOverlay> {
|
||||
style: TextStyle(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: isSelected ? const Color(0xFF7C6DED) : null,
|
||||
color: isSelected
|
||||
? const Color(0xFF7C6DED)
|
||||
: null,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 6),
|
||||
@@ -242,7 +320,10 @@ class _AccountEditorOverlayState extends State<AccountEditorOverlay> {
|
||||
fontSize: 11,
|
||||
color: isSelected
|
||||
? 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),
|
||||
@@ -263,12 +344,17 @@ class _AccountEditorOverlayState extends State<AccountEditorOverlay> {
|
||||
),
|
||||
// Top Right Buttons (Delete & Close)
|
||||
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,
|
||||
child: Row( // REMOVED SafeArea to fix the vertical offset
|
||||
child: Row(
|
||||
// REMOVED SafeArea to fix the vertical offset
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
if (!dash.isAddingAccount && (ref.watch(accountsProvider).valueOrNull?.length ?? 0) > 1) ...[
|
||||
if (!dash.isAddingAccount &&
|
||||
(ref.watch(accountsProvider).valueOrNull?.length ?? 0) >
|
||||
1) ...[
|
||||
GestureDetector(
|
||||
onTap: () => setState(() => _showDeleteDialog = true),
|
||||
child: Container(
|
||||
@@ -329,7 +415,9 @@ class _AccountEditorOverlayState extends State<AccountEditorOverlay> {
|
||||
child: Material(
|
||||
color: Colors.transparent,
|
||||
child: AlertDialog(
|
||||
backgroundColor: Theme.of(widget.context).colorScheme.surface,
|
||||
backgroundColor: Theme.of(
|
||||
widget.context,
|
||||
).colorScheme.surface,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
),
|
||||
@@ -339,7 +427,8 @@ class _AccountEditorOverlayState extends State<AccountEditorOverlay> {
|
||||
),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () => setState(() => _showDeleteDialog = false),
|
||||
onPressed: () =>
|
||||
setState(() => _showDeleteDialog = false),
|
||||
child: const Text('Cancel'),
|
||||
),
|
||||
TextButton(
|
||||
@@ -350,19 +439,32 @@ class _AccountEditorOverlayState extends State<AccountEditorOverlay> {
|
||||
|
||||
dash.closeAccountOverlay(apply: false);
|
||||
|
||||
final txs = ref.read(transactionsProvider).valueOrNull ?? [];
|
||||
final accountTxs = txs.where((t) => t.accountId == accountId).toList();
|
||||
final txs =
|
||||
ref
|
||||
.read(transactionsProvider)
|
||||
.valueOrNull ??
|
||||
[];
|
||||
final accountTxs = txs
|
||||
.where((t) => t.accountId == accountId)
|
||||
.toList();
|
||||
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)) {
|
||||
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,
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
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,
|
||||
),
|
||||
boxShadow: [
|
||||
@@ -408,7 +512,9 @@ class _AccountEditorOverlayState extends State<AccountEditorOverlay> {
|
||||
style: TextStyle(
|
||||
fontSize: 11,
|
||||
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),
|
||||
@@ -420,41 +526,70 @@ class _AccountEditorOverlayState extends State<AccountEditorOverlay> {
|
||||
flex: 3,
|
||||
child: TextField(
|
||||
controller: _nameController,
|
||||
buildCounter: (context, {required currentLength, required isFocused, maxLength}) => null,
|
||||
buildCounter:
|
||||
(
|
||||
context, {
|
||||
required currentLength,
|
||||
required isFocused,
|
||||
maxLength,
|
||||
}) => null,
|
||||
style: const TextStyle(fontSize: 13),
|
||||
decoration: InputDecoration(
|
||||
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,
|
||||
fillColor: Theme.of(widget.context).colorScheme.onSurface.withOpacity(0.05),
|
||||
fillColor: Theme.of(
|
||||
widget.context,
|
||||
).colorScheme.onSurface.withOpacity(0.05),
|
||||
border: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
borderSide: BorderSide(
|
||||
color: (_showLimitError || _nameController.text.trim().isEmpty)
|
||||
color:
|
||||
(_showLimitError ||
|
||||
_showDuplicateError ||
|
||||
_nameController.text.trim().isEmpty)
|
||||
? Colors.red
|
||||
: Theme.of(widget.context).colorScheme.onSurface.withOpacity(0.15),
|
||||
: Theme.of(
|
||||
widget.context,
|
||||
).colorScheme.onSurface.withOpacity(0.15),
|
||||
width: 1.5,
|
||||
),
|
||||
),
|
||||
enabledBorder: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
borderSide: BorderSide(
|
||||
color: (_showLimitError || _nameController.text.trim().isEmpty)
|
||||
color:
|
||||
(_showLimitError ||
|
||||
_showDuplicateError ||
|
||||
_nameController.text.trim().isEmpty)
|
||||
? Colors.red
|
||||
: Theme.of(widget.context).colorScheme.onSurface.withOpacity(0.15),
|
||||
: Theme.of(
|
||||
widget.context,
|
||||
).colorScheme.onSurface.withOpacity(0.15),
|
||||
width: 1.5,
|
||||
),
|
||||
),
|
||||
focusedBorder: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
borderSide: BorderSide(
|
||||
color: (_showLimitError || _nameController.text.trim().isEmpty)
|
||||
color:
|
||||
(_showLimitError ||
|
||||
_showDuplicateError ||
|
||||
_nameController.text.trim().isEmpty)
|
||||
? Colors.red
|
||||
: const Color(0xFF7C6DED),
|
||||
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(
|
||||
height: double.infinity,
|
||||
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),
|
||||
border: Border.all(
|
||||
color: _showCurrencyDropdown
|
||||
? const Color(0xFF7C6DED)
|
||||
: Theme.of(widget.context).colorScheme.onSurface.withOpacity(0.15),
|
||||
: Theme.of(
|
||||
widget.context,
|
||||
).colorScheme.onSurface.withOpacity(0.15),
|
||||
width: 1.5,
|
||||
),
|
||||
),
|
||||
@@ -483,15 +622,30 @@ class _AccountEditorOverlayState extends State<AccountEditorOverlay> {
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Text(
|
||||
[('USD', '\$'), ('EUR', '€'), ('BYN', 'Br'), ('RUB', '₽')]
|
||||
.firstWhere((c) => c.$1 == _selectedCurrency).$2,
|
||||
style: const TextStyle(fontSize: 15, fontWeight: FontWeight.w600),
|
||||
[
|
||||
('USD', '\$'),
|
||||
('EUR', '€'),
|
||||
('BYN', 'Br'),
|
||||
('RUB', '₽'),
|
||||
]
|
||||
.firstWhere(
|
||||
(c) => c.$1 == _selectedCurrency,
|
||||
)
|
||||
.$2,
|
||||
style: const TextStyle(
|
||||
fontSize: 15,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 4),
|
||||
Icon(
|
||||
_showCurrencyDropdown ? Icons.arrow_drop_up : Icons.arrow_drop_down,
|
||||
_showCurrencyDropdown
|
||||
? Icons.arrow_drop_up
|
||||
: Icons.arrow_drop_down,
|
||||
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,
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
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,
|
||||
),
|
||||
boxShadow: [
|
||||
@@ -569,7 +725,9 @@ class _AccountEditorOverlayState extends State<AccountEditorOverlay> {
|
||||
isDimmed: isSolid,
|
||||
onTap: () {
|
||||
dash.setState(() {
|
||||
if (isSolid) dash.tempGradientType = CardColorService.defaultGradient;
|
||||
if (isSolid)
|
||||
dash.tempGradientType =
|
||||
CardColorService.defaultGradient;
|
||||
dash.editingPrimary = true;
|
||||
});
|
||||
setPanelState(() {});
|
||||
@@ -586,7 +744,9 @@ class _AccountEditorOverlayState extends State<AccountEditorOverlay> {
|
||||
isDimmed: isSolid,
|
||||
onTap: () {
|
||||
dash.setState(() {
|
||||
if (isSolid) dash.tempGradientType = CardColorService.defaultGradient;
|
||||
if (isSolid)
|
||||
dash.tempGradientType =
|
||||
CardColorService.defaultGradient;
|
||||
dash.editingPrimary = false;
|
||||
});
|
||||
setPanelState(() {});
|
||||
@@ -598,16 +758,17 @@ class _AccountEditorOverlayState extends State<AccountEditorOverlay> {
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8),
|
||||
child: Container(
|
||||
width: 1,
|
||||
color: Theme.of(widget.context)
|
||||
.colorScheme
|
||||
.onSurface
|
||||
.withOpacity(0.15),
|
||||
color: Theme.of(
|
||||
widget.context,
|
||||
).colorScheme.onSurface.withOpacity(0.15),
|
||||
margin: const EdgeInsets.symmetric(vertical: 4),
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: GestureDetector(
|
||||
onTap: isSolid ? null : () {
|
||||
onTap: isSolid
|
||||
? null
|
||||
: () {
|
||||
dash.setState(() {
|
||||
dash.tempGradientType = GradientType.solid;
|
||||
dash.editingPrimary = true;
|
||||
@@ -618,7 +779,9 @@ class _AccountEditorOverlayState extends State<AccountEditorOverlay> {
|
||||
child: Container(
|
||||
height: double.infinity,
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 4, vertical: 6),
|
||||
horizontal: 4,
|
||||
vertical: 6,
|
||||
),
|
||||
decoration: BoxDecoration(
|
||||
color: isSolid
|
||||
? const Color(0xFF7C6DED).withOpacity(0.15)
|
||||
@@ -627,10 +790,9 @@ class _AccountEditorOverlayState extends State<AccountEditorOverlay> {
|
||||
border: Border.all(
|
||||
color: isSolid
|
||||
? const Color(0xFF7C6DED)
|
||||
: Theme.of(widget.context)
|
||||
.colorScheme
|
||||
.onSurface
|
||||
.withOpacity(0.2),
|
||||
: Theme.of(
|
||||
widget.context,
|
||||
).colorScheme.onSurface.withOpacity(0.2),
|
||||
width: 1.5,
|
||||
),
|
||||
),
|
||||
@@ -662,9 +824,8 @@ class _AccountEditorOverlayState extends State<AccountEditorOverlay> {
|
||||
child: LayoutBuilder(
|
||||
builder: (lbCtx, constraints) {
|
||||
const reservedBelow = 78.0;
|
||||
final spectrumH =
|
||||
(constraints.maxHeight - reservedBelow).clamp(
|
||||
40.0, double.infinity);
|
||||
final spectrumH = (constraints.maxHeight - reservedBelow)
|
||||
.clamp(40.0, double.infinity);
|
||||
|
||||
return Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
@@ -704,7 +865,8 @@ class _AccountEditorOverlayState extends State<AccountEditorOverlay> {
|
||||
GestureDetector(
|
||||
onTap: () {
|
||||
dash.setState(
|
||||
() => dash.editingPrimary = true);
|
||||
() => dash.editingPrimary = true,
|
||||
);
|
||||
setPanelState(() {});
|
||||
dash.overlayEntry?.markNeedsBuild();
|
||||
},
|
||||
@@ -721,10 +883,12 @@ class _AccountEditorOverlayState extends State<AccountEditorOverlay> {
|
||||
border: dash.editingPrimary
|
||||
? Border.all(
|
||||
color: Colors.white,
|
||||
width: 2)
|
||||
width: 2,
|
||||
)
|
||||
: Border.all(
|
||||
color: Colors.transparent,
|
||||
width: 2),
|
||||
width: 2,
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 5),
|
||||
@@ -742,7 +906,8 @@ class _AccountEditorOverlayState extends State<AccountEditorOverlay> {
|
||||
.withOpacity(
|
||||
dash.editingPrimary
|
||||
? 0.8
|
||||
: 0.4),
|
||||
: 0.4,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
@@ -752,8 +917,9 @@ class _AccountEditorOverlayState extends State<AccountEditorOverlay> {
|
||||
if (!isSolid)
|
||||
GestureDetector(
|
||||
onTap: () {
|
||||
dash.setState(() =>
|
||||
dash.editingPrimary = false);
|
||||
dash.setState(
|
||||
() => dash.editingPrimary = false,
|
||||
);
|
||||
setPanelState(() {});
|
||||
dash.overlayEntry?.markNeedsBuild();
|
||||
},
|
||||
@@ -774,7 +940,8 @@ class _AccountEditorOverlayState extends State<AccountEditorOverlay> {
|
||||
.withOpacity(
|
||||
!dash.editingPrimary
|
||||
? 0.8
|
||||
: 0.4),
|
||||
: 0.4,
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 5),
|
||||
@@ -788,11 +955,13 @@ class _AccountEditorOverlayState extends State<AccountEditorOverlay> {
|
||||
border: !dash.editingPrimary
|
||||
? Border.all(
|
||||
color: Colors.white,
|
||||
width: 2)
|
||||
width: 2,
|
||||
)
|
||||
: Border.all(
|
||||
color:
|
||||
Colors.transparent,
|
||||
width: 2),
|
||||
width: 2,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
@@ -827,10 +996,12 @@ class _AccountEditorOverlayState extends State<AccountEditorOverlay> {
|
||||
GradientType.solid => '',
|
||||
};
|
||||
final icon = switch (type) {
|
||||
GradientType.linear => Icons.trending_flat_rounded,
|
||||
GradientType.linear =>
|
||||
Icons.trending_flat_rounded,
|
||||
GradientType.linearReverse =>
|
||||
Icons.swap_horiz_rounded,
|
||||
GradientType.radial => Icons.blur_circular_rounded,
|
||||
GradientType.radial =>
|
||||
Icons.blur_circular_rounded,
|
||||
GradientType.sweep => Icons.rotate_right_rounded,
|
||||
GradientType.solid => Icons.square_rounded,
|
||||
};
|
||||
@@ -840,18 +1011,21 @@ class _AccountEditorOverlayState extends State<AccountEditorOverlay> {
|
||||
child: GestureDetector(
|
||||
onTap: () {
|
||||
dash.setState(
|
||||
() => dash.tempGradientType = type);
|
||||
() => dash.tempGradientType = type,
|
||||
);
|
||||
setPanelState(() {});
|
||||
dash.overlayEntry?.markNeedsBuild();
|
||||
},
|
||||
child: AnimatedContainer(
|
||||
duration: const Duration(milliseconds: 150),
|
||||
padding:
|
||||
const EdgeInsets.symmetric(vertical: 5),
|
||||
padding: const EdgeInsets.symmetric(
|
||||
vertical: 5,
|
||||
),
|
||||
decoration: BoxDecoration(
|
||||
color: isSelected
|
||||
? const Color(0xFF7C6DED)
|
||||
.withOpacity(0.15)
|
||||
? const Color(
|
||||
0xFF7C6DED,
|
||||
).withOpacity(0.15)
|
||||
: Theme.of(widget.context)
|
||||
.colorScheme
|
||||
.onSurface
|
||||
@@ -867,14 +1041,16 @@ class _AccountEditorOverlayState extends State<AccountEditorOverlay> {
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Icon(icon,
|
||||
Icon(
|
||||
icon,
|
||||
size: 15,
|
||||
color: isSelected
|
||||
? const Color(0xFF7C6DED)
|
||||
: Theme.of(widget.context)
|
||||
.colorScheme
|
||||
.onSurface
|
||||
.withOpacity(0.45)),
|
||||
.withOpacity(0.45),
|
||||
),
|
||||
const SizedBox(height: 2),
|
||||
Text(
|
||||
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.tempPrimaryHSV = HSVColor.fromColor(defP);
|
||||
dash.tempSecondaryHSV = HSVColor.fromColor(defS);
|
||||
dash.tempGradientType = CardColorService.defaultGradient;
|
||||
dash.tempGradientType =
|
||||
CardColorService.defaultGradient;
|
||||
});
|
||||
setPanelState(() {});
|
||||
dash.overlayEntry?.markNeedsBuild();
|
||||
},
|
||||
icon: const Icon(Icons.restart_alt_rounded, size: 15),
|
||||
label: Text(s.reset,
|
||||
style: const TextStyle(fontSize: 13)),
|
||||
label: Text(
|
||||
s.reset,
|
||||
style: const TextStyle(fontSize: 13),
|
||||
),
|
||||
style: OutlinedButton.styleFrom(
|
||||
foregroundColor: Theme.of(widget.context)
|
||||
.colorScheme
|
||||
.onSurface
|
||||
.withOpacity(0.7),
|
||||
foregroundColor: Theme.of(
|
||||
widget.context,
|
||||
).colorScheme.onSurface.withOpacity(0.7),
|
||||
side: BorderSide(
|
||||
color: Theme.of(widget.context)
|
||||
.colorScheme
|
||||
.onSurface
|
||||
.withOpacity(0.2),
|
||||
color: Theme.of(
|
||||
widget.context,
|
||||
).colorScheme.onSurface.withOpacity(0.2),
|
||||
),
|
||||
padding: const EdgeInsets.symmetric(vertical: 10),
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(12)),
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
@@ -953,29 +1132,53 @@ class _AccountEditorOverlayState extends State<AccountEditorOverlay> {
|
||||
onPressed: _nameController.text.trim().isEmpty
|
||||
? 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();
|
||||
dash.closeAccountOverlay(apply: true);
|
||||
},
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: const Color(0xFF7C6DED),
|
||||
foregroundColor: Colors.white,
|
||||
disabledBackgroundColor: Theme.of(widget.context)
|
||||
.colorScheme
|
||||
.onSurface
|
||||
.withOpacity(0.12),
|
||||
disabledForegroundColor: Theme.of(widget.context)
|
||||
.colorScheme
|
||||
.onSurface
|
||||
.withOpacity(0.38),
|
||||
disabledBackgroundColor: Theme.of(
|
||||
widget.context,
|
||||
).colorScheme.onSurface.withOpacity(0.12),
|
||||
disabledForegroundColor: Theme.of(
|
||||
widget.context,
|
||||
).colorScheme.onSurface.withOpacity(0.38),
|
||||
padding: const EdgeInsets.symmetric(vertical: 10),
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(12)),
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
),
|
||||
child: Text(
|
||||
dash.isAddingAccount ? s.addAccount : s.apply,
|
||||
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));
|
||||
final textColor = isSelected
|
||||
? 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(
|
||||
onTap: onTap,
|
||||
@@ -1025,12 +1230,11 @@ class PanelTab extends StatelessWidget {
|
||||
width: double.infinity,
|
||||
padding: const EdgeInsets.symmetric(horizontal: 4, vertical: 8),
|
||||
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),
|
||||
border: Border.all(
|
||||
color: borderColor,
|
||||
width: 1.5,
|
||||
),
|
||||
border: Border.all(color: borderColor, width: 1.5),
|
||||
),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
@@ -1057,8 +1261,9 @@ class PanelTab extends StatelessWidget {
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
fontWeight:
|
||||
isSelected ? FontWeight.w600 : FontWeight.normal,
|
||||
fontWeight: isSelected
|
||||
? FontWeight.w600
|
||||
: FontWeight.normal,
|
||||
color: textColor,
|
||||
),
|
||||
),
|
||||
|
||||
Reference in New Issue
Block a user