mirror of
https://github.com/koloideal/Casha.git
synced 2026-06-10 10:25:28 +03:00
stableee
This commit is contained in:
@@ -30,7 +30,6 @@ class AddTransactionState {
|
||||
});
|
||||
|
||||
factory AddTransactionState.fromTransaction(Transaction tx) {
|
||||
// Override type to transfer when category is 'Transfer'
|
||||
final resolvedType = (tx.category == 'Transfer')
|
||||
? TransactionType.transfer
|
||||
: tx.type;
|
||||
|
||||
@@ -206,7 +206,6 @@ class _AddTransactionScreenState extends ConsumerState<AddTransactionScreen>
|
||||
final amount = double.parse(parsed);
|
||||
final state = ref.read(addTransactionProvider(widget.initial));
|
||||
|
||||
// Validate transfer
|
||||
if (state.type == TransactionType.transfer) {
|
||||
bool hasError = false;
|
||||
|
||||
@@ -471,14 +470,11 @@ class _AddTransactionScreenState extends ConsumerState<AddTransactionScreen>
|
||||
onPressed: () async {
|
||||
Navigator.pop(ctx);
|
||||
|
||||
// Always delete the record we were given
|
||||
await ref
|
||||
.read(transactionsProvider.notifier)
|
||||
.delete(widget.initial!.id);
|
||||
|
||||
// If this is a Transfer, also delete the counterpart
|
||||
if (widget.initial!.category == 'Transfer') {
|
||||
// Use the pre-populated IDs from initState if available
|
||||
final counterpartId =
|
||||
widget.initial!.type == TransactionType.expense
|
||||
? _transferIncomeRecordId
|
||||
@@ -489,7 +485,6 @@ class _AddTransactionScreenState extends ConsumerState<AddTransactionScreen>
|
||||
.read(transactionsProvider.notifier)
|
||||
.delete(counterpartId);
|
||||
} else {
|
||||
// Fallback: search manually
|
||||
final allTxs =
|
||||
ref.read(transactionsProvider).valueOrNull ??
|
||||
[];
|
||||
@@ -893,10 +888,9 @@ class _ToAccountDropdownOverlay extends ConsumerWidget {
|
||||
.selectedAccountId;
|
||||
final toAccountId = ref.read(addTransactionProvider(initial)).toAccountId;
|
||||
|
||||
// Calculate position from trigger key
|
||||
double top = 340;
|
||||
double left = 20;
|
||||
double triggerWidth = 200; // fallback width
|
||||
double triggerWidth = 200;
|
||||
|
||||
if (triggerKey?.currentContext != null) {
|
||||
final triggerBox =
|
||||
|
||||
@@ -134,10 +134,9 @@ class AccountDropdownOverlay extends ConsumerWidget {
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final accountsAsync = ref.watch(accountsProvider);
|
||||
|
||||
// Calculate position from trigger key
|
||||
double top = 76;
|
||||
double left = 20;
|
||||
double triggerWidth = 200; // fallback width
|
||||
double triggerWidth = 200;
|
||||
|
||||
if (triggerKey?.currentContext != null) {
|
||||
final triggerBox =
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import '../../../core/constants.dart';
|
||||
import '../../../shared/models/transaction.dart';
|
||||
|
||||
class SubmitButton extends StatelessWidget {
|
||||
|
||||
@@ -12,15 +12,6 @@ import '../../shared/models/account.dart';
|
||||
import '../../shared/services/storage_service.dart';
|
||||
import '../settings/provider.dart';
|
||||
|
||||
// BUG FOUND: lib/features/dashboard/provider.dart
|
||||
// Description: CardColorsNotifier calls an async `_load()` in the constructor without awaiting it.
|
||||
// If the user triggers `save()` before `_load()` completes, the late `_load()` can
|
||||
// overwrite the newly saved colors/gradient types.
|
||||
// Reproduction: Open the app (cold start), open the card color editor immediately, press Apply
|
||||
// before the initial load finishes.
|
||||
// Suggested fix: Track a generation/token for in-flight loads and ignore stale load results
|
||||
// after any state mutation (save/reset/theme-change).
|
||||
|
||||
final sharedPreferencesProvider = Provider<SharedPreferences>((ref) {
|
||||
throw UnimplementedError('Override in main');
|
||||
});
|
||||
@@ -113,15 +104,13 @@ class TransactionsNotifier
|
||||
}
|
||||
}
|
||||
|
||||
// Returns a map: transactionId -> paired Transaction (its counterpart)
|
||||
final transferPairsProvider = Provider<Map<String, Transaction>>((ref) {
|
||||
final txs = ref.watch(transactionsProvider).valueOrNull ?? [];
|
||||
final transfers = txs.where((t) => t.category == 'Transfer').toList();
|
||||
final Map<String, Transaction> pairs = {};
|
||||
|
||||
for (final tx in transfers) {
|
||||
if (pairs.containsKey(tx.id)) continue; // already paired
|
||||
// Find counterpart: opposite type, same amount, same date (minute precision), same note
|
||||
if (pairs.containsKey(tx.id)) continue;
|
||||
final counterpart = transfers.firstWhereOrNull(
|
||||
(other) =>
|
||||
other.id != tx.id &&
|
||||
@@ -156,18 +145,15 @@ final timeFilterProvider = StateProvider<TimeFilter>(
|
||||
(ref) => TimeFilter.lastMonth,
|
||||
);
|
||||
|
||||
// Base filtered transactions by active account
|
||||
final accountFilteredTransactionsProvider = Provider<List<Transaction>>((ref) {
|
||||
final txsAsync = ref.watch(transactionsProvider);
|
||||
final txs = txsAsync.valueOrNull ?? [];
|
||||
final activeAccount = ref.watch(activeAccountProvider);
|
||||
|
||||
// If activeAccount is null (Total Balance page), return all transactions
|
||||
if (activeAccount == null) {
|
||||
return txs;
|
||||
}
|
||||
|
||||
// Filter by account ID
|
||||
return txs.where((t) => t.accountId == activeAccount.id).toList();
|
||||
});
|
||||
|
||||
@@ -214,18 +200,15 @@ final totalBalanceProvider = Provider<double>((ref) {
|
||||
});
|
||||
|
||||
final totalIncomeProvider = Provider<double>((ref) {
|
||||
// Watch the filtered transactions directly
|
||||
final txs = ref.watch(accountFilteredTransactionsProvider);
|
||||
final filtered = txs.where(
|
||||
(t) => t.type == TransactionType.income && t.category != 'Transfer',
|
||||
);
|
||||
|
||||
// Watch the dependencies that change on swipe!
|
||||
final index = ref.watch(activeAccountIndexProvider);
|
||||
final accountsAsync = ref.watch(accountsProvider);
|
||||
final globalCurrency = ref.watch(currencyProvider).code;
|
||||
|
||||
// Resolve target currency synchronously based on the current swipe index
|
||||
String targetCurrency = globalCurrency;
|
||||
if (index > 0) {
|
||||
final accounts = accountsAsync.valueOrNull ?? [];
|
||||
@@ -341,12 +324,9 @@ final filteredTransactionsProvider = Provider<List<Transaction>>((ref) {
|
||||
|
||||
filtered.sort((a, b) => b.date.compareTo(a.date));
|
||||
|
||||
// Deduplicate transfers for Total Balance view
|
||||
if (activeAccount == null) {
|
||||
filtered = filtered.where((t) {
|
||||
if (t.category != 'Transfer') return true;
|
||||
// On Total Balance: show only expense side of complete pairs
|
||||
// If income side has a known pair, hide it
|
||||
if (t.type == TransactionType.income && transferPairs.containsKey(t.id)) {
|
||||
return false;
|
||||
}
|
||||
@@ -361,7 +341,6 @@ final recentTransactionsProvider = Provider<List<Transaction>>((ref) {
|
||||
return ref.watch(filteredTransactionsProvider).take(20).toList();
|
||||
});
|
||||
|
||||
// Watches the list of all accounts
|
||||
final accountsProvider = StreamProvider<List<Account>>((ref) async* {
|
||||
final repository = ref.watch(accountRepositoryProvider);
|
||||
while (true) {
|
||||
@@ -370,15 +349,13 @@ final accountsProvider = StreamProvider<List<Account>>((ref) async* {
|
||||
}
|
||||
});
|
||||
|
||||
// Ephemeral UI state — active carousel index, starts at 0, not persisted
|
||||
final activeAccountIndexProvider = StateProvider<int>((ref) => 0);
|
||||
|
||||
// Returns the currently active Account based on carousel index
|
||||
final activeAccountProvider = Provider<Account?>((ref) {
|
||||
final index = ref.watch(activeAccountIndexProvider);
|
||||
final accountsAsync = ref.watch(accountsProvider);
|
||||
|
||||
if (index == 0) return null; // 0 means "Total Balance"
|
||||
if (index == 0) return null;
|
||||
|
||||
return accountsAsync.when(
|
||||
data: (accounts) {
|
||||
@@ -416,7 +393,6 @@ final cardColorsProvider =
|
||||
return notifier;
|
||||
});
|
||||
|
||||
// Account-specific color provider
|
||||
final accountCardColorsProvider =
|
||||
StateNotifierProvider.family<CardColorsNotifier, CardColors, int>((
|
||||
ref,
|
||||
@@ -457,7 +433,7 @@ class CardColorsNotifier extends StateNotifier<CardColors> {
|
||||
final (c1, c2, lightG, darkG) = await CardColorService.load(
|
||||
accountId: accountId,
|
||||
);
|
||||
if (currentGeneration != _loadGeneration) return; // stale
|
||||
if (currentGeneration != _loadGeneration) return;
|
||||
state = CardColors(c1, c2, lightG, darkG);
|
||||
}
|
||||
|
||||
@@ -467,7 +443,6 @@ class CardColorsNotifier extends StateNotifier<CardColors> {
|
||||
GradientType lightGradient,
|
||||
GradientType darkGradient,
|
||||
) async {
|
||||
// Invalidate any in-flight load so it can't overwrite this save.
|
||||
_loadGeneration++;
|
||||
state = CardColors(primary, secondary, lightGradient, darkGradient);
|
||||
await CardColorService.save(
|
||||
@@ -506,20 +481,17 @@ class CardColorsNotifier extends StateNotifier<CardColors> {
|
||||
final previousBrightness = _resolve(previous);
|
||||
final nextBrightness = _resolve(next);
|
||||
|
||||
// No change in actual brightness
|
||||
if (previousBrightness == nextBrightness) return;
|
||||
|
||||
final oldDefaults = _defaultsFor(previousBrightness);
|
||||
final newDefaults = _defaultsFor(nextBrightness);
|
||||
|
||||
// Check if current colors match old theme defaults
|
||||
final isUsingOldDefaults =
|
||||
state.primary == oldDefaults.primary &&
|
||||
state.secondary == oldDefaults.secondary &&
|
||||
state.gradientTypeForBrightness(previousBrightness) ==
|
||||
oldDefaults.gradient;
|
||||
|
||||
// Only auto-switch if using default colors
|
||||
if (isUsingOldDefaults) {
|
||||
_loadGeneration++;
|
||||
state = CardColors(
|
||||
|
||||
@@ -44,7 +44,6 @@ class _DashboardScreenState extends ConsumerState<DashboardScreen> {
|
||||
HSVColor savedSecondaryHSV = HSVColor.fromColor(
|
||||
CardColorService.defaultSecondary,
|
||||
);
|
||||
// Per-theme gradient types (light/dark), persisted separately.
|
||||
GradientType tempLightGradientType = CardColorService.defaultGradientLight;
|
||||
GradientType tempDarkGradientType = CardColorService.defaultGradientDark;
|
||||
GradientType savedLightGradientType = CardColorService.defaultGradientLight;
|
||||
@@ -52,7 +51,6 @@ class _DashboardScreenState extends ConsumerState<DashboardScreen> {
|
||||
|
||||
OverlayEntry? overlayEntry;
|
||||
|
||||
// Account editing state
|
||||
Account? editingAccount;
|
||||
String tempAccountName = '';
|
||||
String tempAccountCurrency = 'USD';
|
||||
@@ -192,8 +190,6 @@ class _DashboardScreenState extends ConsumerState<DashboardScreen> {
|
||||
accountId: newId,
|
||||
);
|
||||
} else if (editingAccount != null) {
|
||||
// Existing edit logic
|
||||
// Save colors
|
||||
await ref
|
||||
.read(accountCardColorsProvider(editingAccount!.id).notifier)
|
||||
.save(
|
||||
@@ -203,7 +199,6 @@ class _DashboardScreenState extends ConsumerState<DashboardScreen> {
|
||||
tempDarkGradientType,
|
||||
);
|
||||
|
||||
// Update account name and currency
|
||||
final updatedAccount = Account(
|
||||
id: editingAccount!.id,
|
||||
name: tempAccountName.trim(),
|
||||
@@ -216,7 +211,6 @@ class _DashboardScreenState extends ConsumerState<DashboardScreen> {
|
||||
await ref.read(accountRepositoryProvider).update(updatedAccount);
|
||||
}
|
||||
} else {
|
||||
// Restore original values on cancel
|
||||
setState(() {
|
||||
tempPrimary = savedPrimary;
|
||||
tempSecondary = savedSecondary;
|
||||
@@ -370,8 +364,8 @@ class _DashboardScreenState extends ConsumerState<DashboardScreen> {
|
||||
previewSecondary: editingCard ? tempSecondary : null,
|
||||
previewGradientType: editingCard
|
||||
? (Theme.of(context).brightness == Brightness.dark
|
||||
? tempDarkGradientType
|
||||
: tempLightGradientType)
|
||||
? tempDarkGradientType
|
||||
: tempLightGradientType)
|
||||
: null,
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
@@ -457,9 +451,9 @@ class _AccountsInfoBlock extends ConsumerWidget {
|
||||
final s = ref.watch(stringsProvider);
|
||||
final onSurface = Theme.of(context).colorScheme.onSurface;
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(bottom: 60),
|
||||
padding: const EdgeInsets.only(bottom: 60),
|
||||
child: ConstrainedBox(
|
||||
constraints: const BoxConstraints(maxWidth: 260),
|
||||
constraints: const BoxConstraints(maxWidth: 260),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
@@ -484,17 +478,26 @@ class _AccountsInfoBlock extends ConsumerWidget {
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
_InfoRow(icon: Icons.swap_horiz_rounded, text: s.accountsInfoBalance),
|
||||
_InfoRow(
|
||||
icon: Icons.swap_horiz_rounded,
|
||||
text: s.accountsInfoBalance,
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
_InfoRow(icon: Icons.touch_app_rounded, text: s.accountsInfoCustomize),
|
||||
_InfoRow(
|
||||
icon: Icons.touch_app_rounded,
|
||||
text: s.accountsInfoCustomize,
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
_InfoRow(icon: Icons.lock_outline_rounded, text: s.accountsInfoLimit),
|
||||
_InfoRow(
|
||||
icon: Icons.lock_outline_rounded,
|
||||
text: s.accountsInfoLimit,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class _InfoRow extends StatelessWidget {
|
||||
final IconData icon;
|
||||
|
||||
@@ -90,7 +90,6 @@ class _AccountEditorOverlayState extends State<AccountEditorOverlay> {
|
||||
final editorPanelTop = cardTop + cardHeight + 20;
|
||||
final colorPanelTop = editorPanelTop + editorPanelHeight + 12;
|
||||
const colorPanelHeight = 410.0;
|
||||
// Preview card in overlay should match BalanceCardCarousel sizing.
|
||||
|
||||
return Consumer(
|
||||
builder: (context, ref, _) {
|
||||
@@ -203,25 +202,24 @@ class _AccountEditorOverlayState extends State<AccountEditorOverlay> {
|
||||
child: SizedBox(
|
||||
height: cardHeight,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8),
|
||||
child: BalanceCard(
|
||||
balance: previewBalance,
|
||||
currencyInfo: CurrencyInfo(
|
||||
currencyMap[dash.tempAccountCurrency]?.symbol ??
|
||||
'\$',
|
||||
dash.tempAccountCurrency,
|
||||
),
|
||||
onLongPress: null,
|
||||
accountName: dash.tempAccountName,
|
||||
previewPrimary: dash.tempPrimary,
|
||||
previewSecondary: dash.tempSecondary,
|
||||
previewGradientType:
|
||||
Theme.of(widget.context).brightness ==
|
||||
Brightness.dark
|
||||
? dash.tempDarkGradientType
|
||||
: dash.tempLightGradientType,
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8),
|
||||
child: BalanceCard(
|
||||
balance: previewBalance,
|
||||
currencyInfo: CurrencyInfo(
|
||||
currencyMap[dash.tempAccountCurrency]?.symbol ?? '\$',
|
||||
dash.tempAccountCurrency,
|
||||
),
|
||||
onLongPress: null,
|
||||
accountName: dash.tempAccountName,
|
||||
previewPrimary: dash.tempPrimary,
|
||||
previewSecondary: dash.tempSecondary,
|
||||
previewGradientType:
|
||||
Theme.of(widget.context).brightness ==
|
||||
Brightness.dark
|
||||
? dash.tempDarkGradientType
|
||||
: dash.tempLightGradientType,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
@@ -299,75 +297,75 @@ class _AccountEditorOverlayState extends State<AccountEditorOverlay> {
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: kDisplayCurrencies.map((entry) {
|
||||
final isSelected = entry.$1 == _selectedCurrency;
|
||||
return InkWell(
|
||||
onTap: () {
|
||||
setState(() {
|
||||
_selectedCurrency = entry.$1;
|
||||
dash.setState(() {
|
||||
dash.tempAccountCurrency = entry.$1;
|
||||
});
|
||||
dash.overlayEntry?.markNeedsBuild();
|
||||
_showCurrencyDropdown = false;
|
||||
});
|
||||
},
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 8,
|
||||
vertical: 8,
|
||||
),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
entry.$1 == 'BYN'
|
||||
? BynSign(
|
||||
fontSize: 14,
|
||||
color: isSelected
|
||||
? const Color(0xFF7C6DED)
|
||||
: Theme.of(
|
||||
widget.context,
|
||||
).colorScheme.onSurface,
|
||||
)
|
||||
: Text(
|
||||
entry.$2,
|
||||
style: TextStyle(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: isSelected
|
||||
? const Color(0xFF7C6DED)
|
||||
: null,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 4),
|
||||
Flexible(
|
||||
child: Text(
|
||||
entry.$1,
|
||||
final isSelected = entry.$1 == _selectedCurrency;
|
||||
return InkWell(
|
||||
onTap: () {
|
||||
setState(() {
|
||||
_selectedCurrency = entry.$1;
|
||||
dash.setState(() {
|
||||
dash.tempAccountCurrency = entry.$1;
|
||||
});
|
||||
dash.overlayEntry?.markNeedsBuild();
|
||||
_showCurrencyDropdown = false;
|
||||
});
|
||||
},
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 8,
|
||||
vertical: 8,
|
||||
),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
entry.$1 == 'BYN'
|
||||
? BynSign(
|
||||
fontSize: 14,
|
||||
color: isSelected
|
||||
? const Color(0xFF7C6DED)
|
||||
: Theme.of(
|
||||
widget.context,
|
||||
).colorScheme.onSurface,
|
||||
)
|
||||
: Text(
|
||||
entry.$2,
|
||||
style: TextStyle(
|
||||
fontSize: 11,
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: isSelected
|
||||
? const Color(0xFF7C6DED)
|
||||
: Theme.of(widget.context)
|
||||
.colorScheme
|
||||
.onSurface
|
||||
.withOpacity(0.6),
|
||||
: null,
|
||||
),
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
const SizedBox(width: 4),
|
||||
Flexible(
|
||||
child: Text(
|
||||
entry.$1,
|
||||
style: TextStyle(
|
||||
fontSize: 11,
|
||||
color: isSelected
|
||||
? const Color(0xFF7C6DED)
|
||||
: Theme.of(widget.context)
|
||||
.colorScheme
|
||||
.onSurface
|
||||
.withOpacity(0.6),
|
||||
),
|
||||
if (isSelected) ...[
|
||||
const SizedBox(width: 4),
|
||||
const Icon(
|
||||
Icons.check_rounded,
|
||||
size: 14,
|
||||
color: Color(0xFF7C6DED),
|
||||
),
|
||||
],
|
||||
],
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}).toList(),
|
||||
if (isSelected) ...[
|
||||
const SizedBox(width: 4),
|
||||
const Icon(
|
||||
Icons.check_rounded,
|
||||
size: 14,
|
||||
color: Color(0xFF7C6DED),
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}).toList(),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
@@ -41,7 +41,6 @@ class _BalanceCardCarouselState extends ConsumerState<BalanceCardCarousel> {
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
// 0.92 позволяет видеть края предыдущей/следующей карточки
|
||||
final savedIndex = ref.read(activeAccountIndexProvider);
|
||||
_pageController = PageController(
|
||||
viewportFraction: 0.92,
|
||||
@@ -68,14 +67,12 @@ class _BalanceCardCarouselState extends ConsumerState<BalanceCardCarousel> {
|
||||
children: [
|
||||
SizedBox(
|
||||
height: 230,
|
||||
// OverflowBox позволяет PageView игнорировать паддинги родителя (DashboardScreen)
|
||||
// и растянуться на всю ширину экрана
|
||||
child: OverflowBox(
|
||||
maxWidth: MediaQuery.of(context).size.width,
|
||||
child: PageView.builder(
|
||||
controller: _pageController,
|
||||
clipBehavior:
|
||||
Clip.none, // Не обрезает карточку при 3D-наклоне
|
||||
Clip.none,
|
||||
itemCount: totalPages,
|
||||
onPageChanged: (index) {
|
||||
ref.read(activeAccountIndexProvider.notifier).state = index;
|
||||
@@ -105,7 +102,6 @@ class _BalanceCardCarouselState extends ConsumerState<BalanceCardCarousel> {
|
||||
accountCardColorsProvider(account.id),
|
||||
);
|
||||
|
||||
// Calculate this specific account's balance
|
||||
final txs =
|
||||
ref.watch(transactionsProvider).valueOrNull ?? [];
|
||||
final accountTxs = txs
|
||||
@@ -119,7 +115,7 @@ class _BalanceCardCarouselState extends ConsumerState<BalanceCardCarousel> {
|
||||
t.amount,
|
||||
t.currencyCode,
|
||||
account
|
||||
.currency, // target is the account's own currency
|
||||
.currency,
|
||||
);
|
||||
return t.type == TransactionType.income
|
||||
? sum + converted
|
||||
@@ -128,7 +124,7 @@ class _BalanceCardCarouselState extends ConsumerState<BalanceCardCarousel> {
|
||||
|
||||
cardWidget = BalanceCard(
|
||||
balance:
|
||||
accountBalance, // Use the dynamically calculated balance!
|
||||
accountBalance,
|
||||
currencyInfo: CurrencyInfo(
|
||||
currencyMap[account.currency]?.symbol ?? '\$',
|
||||
account.currency,
|
||||
@@ -144,7 +140,6 @@ class _BalanceCardCarouselState extends ConsumerState<BalanceCardCarousel> {
|
||||
);
|
||||
}
|
||||
|
||||
// Отступ между карточками во время свайпа
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8),
|
||||
child: cardWidget,
|
||||
@@ -198,12 +193,12 @@ class AddAccountCard extends StatelessWidget {
|
||||
margin: const EdgeInsets.symmetric(
|
||||
horizontal: 10,
|
||||
vertical: 10,
|
||||
), // Reduced margins for larger size
|
||||
),
|
||||
child: CustomPaint(
|
||||
painter: _DashedBorderPainter(),
|
||||
child: Container(
|
||||
width: double.infinity,
|
||||
height: 205, // Increased height
|
||||
height: 205,
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context).colorScheme.surface.withOpacity(0.4),
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
@@ -213,7 +208,7 @@ class AddAccountCard extends StatelessWidget {
|
||||
children: [
|
||||
Icon(
|
||||
Icons.add_rounded,
|
||||
size: 36, // Slightly bigger icon
|
||||
size: 36,
|
||||
color: Theme.of(
|
||||
context,
|
||||
).colorScheme.onSurface.withOpacity(0.5),
|
||||
|
||||
@@ -94,7 +94,6 @@ class _FullScreenBlurOverlayState extends State<FullScreenBlurOverlay> {
|
||||
child: _buildPanel(panelHeight),
|
||||
),
|
||||
),
|
||||
// Close Button - Top Right
|
||||
Positioned(
|
||||
top: cardTop - 20,
|
||||
right: 20,
|
||||
@@ -367,7 +366,7 @@ class _FullScreenBlurOverlayState extends State<FullScreenBlurOverlay> {
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
SizedBox(
|
||||
height: 36, //
|
||||
height: 36,
|
||||
child: ColorPickerSlider(
|
||||
TrackType.hue,
|
||||
currentHSV,
|
||||
|
||||
@@ -42,7 +42,6 @@ class TransactionTile extends ConsumerWidget {
|
||||
? Icons.swap_horiz_rounded
|
||||
: (AppCategories.icons[transaction.category] ?? Icons.category_rounded);
|
||||
|
||||
// Check if we're on Total Balance page
|
||||
final activeAccount = ref.watch(activeAccountProvider);
|
||||
final displayCurrency =
|
||||
activeAccount?.currency ?? ref.watch(currencyProvider).code;
|
||||
@@ -57,19 +56,16 @@ class TransactionTile extends ConsumerWidget {
|
||||
: 0.0;
|
||||
final displaySymbol = currencyMap[displayCurrency]?.symbol ?? '';
|
||||
|
||||
// Look up the account name by matching transaction.accountId
|
||||
final accounts = ref.watch(accountsProvider).valueOrNull ?? [];
|
||||
final txAccount = accounts.firstWhereOrNull(
|
||||
(a) => a.id == transaction.accountId,
|
||||
);
|
||||
|
||||
// Build account label with 10-character limit
|
||||
String accountLabel = txAccount?.name ?? '';
|
||||
if (accountLabel.length > 10) {
|
||||
accountLabel = '${accountLabel.substring(0, 10)}...';
|
||||
}
|
||||
|
||||
// Transfer pairing logic
|
||||
final pairs = ref.watch(transferPairsProvider);
|
||||
final counterpart = pairs[transaction.id];
|
||||
|
||||
@@ -381,11 +377,9 @@ class TransactionTile extends ConsumerWidget {
|
||||
bool isIncome,
|
||||
Account? activeAccount,
|
||||
) {
|
||||
// Total Balance view with Transfer expense: no prefix
|
||||
if (isTransfer && activeAccount == null && !isIncome) {
|
||||
return '';
|
||||
}
|
||||
// All other cases: show + or −
|
||||
return isIncome ? '+ ' : '\u2212 ';
|
||||
}
|
||||
|
||||
@@ -396,15 +390,12 @@ class TransactionTile extends ConsumerWidget {
|
||||
Account? activeAccount,
|
||||
Color defaultColor,
|
||||
) {
|
||||
// Total Balance view with Transfer expense: neutral color
|
||||
if (isTransfer && activeAccount == null && !isIncome) {
|
||||
return Theme.of(context).colorScheme.onSurface.withOpacity(0.8);
|
||||
}
|
||||
// Transfer in account view or Total Balance income: use income/expense colors
|
||||
if (isTransfer) {
|
||||
return isIncome ? AppColors.income : AppColors.expense;
|
||||
}
|
||||
// Non-transfer: use default color
|
||||
return defaultColor;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user