This commit is contained in:
2026-03-23 11:44:21 +03:00
parent 4f87aa7598
commit b19b7fac4f
11 changed files with 254 additions and 161 deletions
+2 -3
View File
@@ -13,7 +13,7 @@ class AppDatabase extends _$AppDatabase {
AppDatabase() : super(_openConnection());
@override
int get schemaVersion => 3;
int get schemaVersion => 4;
@override
MigrationStrategy get migration => MigrationStrategy(
@@ -23,11 +23,10 @@ class AppDatabase extends _$AppDatabase {
await customStatement(
'INSERT INTO accounts (name, is_main, currency, sort_order, created_at) '
'VALUES (?, ?, ?, ?, ?)',
['Main', 1, 'USD', 0, DateTime.now().millisecondsSinceEpoch],
['main', 1, 'USD', 0, DateTime.now().millisecondsSinceEpoch],
);
}
if (from == 2) {
// Add currency column to existing accounts table
await customStatement(
'ALTER TABLE accounts ADD COLUMN currency TEXT NOT NULL DEFAULT "USD"',
);
+68 -1
View File
@@ -113,6 +113,17 @@ class $TransactionsTable extends Transactions
requiredDuringInsert: false,
defaultValue: const Constant('USD'),
);
static const VerificationMeta _accountIdMeta = const VerificationMeta(
'accountId',
);
@override
late final GeneratedColumn<int> accountId = GeneratedColumn<int>(
'account_id',
aliasedName,
false,
type: DriftSqlType.int,
requiredDuringInsert: true,
);
static const VerificationMeta _createdAtMeta = const VerificationMeta(
'createdAt',
);
@@ -137,6 +148,7 @@ class $TransactionsTable extends Transactions
lastOccurrence,
currency,
currencyCode,
accountId,
createdAt,
];
@override
@@ -224,6 +236,14 @@ class $TransactionsTable extends Transactions
),
);
}
if (data.containsKey('account_id')) {
context.handle(
_accountIdMeta,
accountId.isAcceptableOrUnknown(data['account_id']!, _accountIdMeta),
);
} else if (isInserting) {
context.missing(_accountIdMeta);
}
if (data.containsKey('created_at')) {
context.handle(
_createdAtMeta,
@@ -279,6 +299,10 @@ class $TransactionsTable extends Transactions
DriftSqlType.string,
data['${effectivePrefix}currency_code'],
)!,
accountId: attachedDatabase.typeMapping.read(
DriftSqlType.int,
data['${effectivePrefix}account_id'],
)!,
createdAt: attachedDatabase.typeMapping.read(
DriftSqlType.dateTime,
data['${effectivePrefix}created_at'],
@@ -303,6 +327,7 @@ class Transaction extends DataClass implements Insertable<Transaction> {
final DateTime? lastOccurrence;
final String currency;
final String currencyCode;
final int accountId;
final DateTime createdAt;
const Transaction({
required this.id,
@@ -315,6 +340,7 @@ class Transaction extends DataClass implements Insertable<Transaction> {
this.lastOccurrence,
required this.currency,
required this.currencyCode,
required this.accountId,
required this.createdAt,
});
@override
@@ -334,6 +360,7 @@ class Transaction extends DataClass implements Insertable<Transaction> {
}
map['currency'] = Variable<String>(currency);
map['currency_code'] = Variable<String>(currencyCode);
map['account_id'] = Variable<int>(accountId);
map['created_at'] = Variable<DateTime>(createdAt);
return map;
}
@@ -352,6 +379,7 @@ class Transaction extends DataClass implements Insertable<Transaction> {
: Value(lastOccurrence),
currency: Value(currency),
currencyCode: Value(currencyCode),
accountId: Value(accountId),
createdAt: Value(createdAt),
);
}
@@ -372,6 +400,7 @@ class Transaction extends DataClass implements Insertable<Transaction> {
lastOccurrence: serializer.fromJson<DateTime?>(json['lastOccurrence']),
currency: serializer.fromJson<String>(json['currency']),
currencyCode: serializer.fromJson<String>(json['currencyCode']),
accountId: serializer.fromJson<int>(json['accountId']),
createdAt: serializer.fromJson<DateTime>(json['createdAt']),
);
}
@@ -389,6 +418,7 @@ class Transaction extends DataClass implements Insertable<Transaction> {
'lastOccurrence': serializer.toJson<DateTime?>(lastOccurrence),
'currency': serializer.toJson<String>(currency),
'currencyCode': serializer.toJson<String>(currencyCode),
'accountId': serializer.toJson<int>(accountId),
'createdAt': serializer.toJson<DateTime>(createdAt),
};
}
@@ -404,6 +434,7 @@ class Transaction extends DataClass implements Insertable<Transaction> {
Value<DateTime?> lastOccurrence = const Value.absent(),
String? currency,
String? currencyCode,
int? accountId,
DateTime? createdAt,
}) => Transaction(
id: id ?? this.id,
@@ -418,6 +449,7 @@ class Transaction extends DataClass implements Insertable<Transaction> {
: this.lastOccurrence,
currency: currency ?? this.currency,
currencyCode: currencyCode ?? this.currencyCode,
accountId: accountId ?? this.accountId,
createdAt: createdAt ?? this.createdAt,
);
Transaction copyWithCompanion(TransactionsCompanion data) {
@@ -438,6 +470,7 @@ class Transaction extends DataClass implements Insertable<Transaction> {
currencyCode: data.currencyCode.present
? data.currencyCode.value
: this.currencyCode,
accountId: data.accountId.present ? data.accountId.value : this.accountId,
createdAt: data.createdAt.present ? data.createdAt.value : this.createdAt,
);
}
@@ -455,6 +488,7 @@ class Transaction extends DataClass implements Insertable<Transaction> {
..write('lastOccurrence: $lastOccurrence, ')
..write('currency: $currency, ')
..write('currencyCode: $currencyCode, ')
..write('accountId: $accountId, ')
..write('createdAt: $createdAt')
..write(')'))
.toString();
@@ -472,6 +506,7 @@ class Transaction extends DataClass implements Insertable<Transaction> {
lastOccurrence,
currency,
currencyCode,
accountId,
createdAt,
);
@override
@@ -488,6 +523,7 @@ class Transaction extends DataClass implements Insertable<Transaction> {
other.lastOccurrence == this.lastOccurrence &&
other.currency == this.currency &&
other.currencyCode == this.currencyCode &&
other.accountId == this.accountId &&
other.createdAt == this.createdAt);
}
@@ -502,6 +538,7 @@ class TransactionsCompanion extends UpdateCompanion<Transaction> {
final Value<DateTime?> lastOccurrence;
final Value<String> currency;
final Value<String> currencyCode;
final Value<int> accountId;
final Value<DateTime> createdAt;
final Value<int> rowid;
const TransactionsCompanion({
@@ -515,6 +552,7 @@ class TransactionsCompanion extends UpdateCompanion<Transaction> {
this.lastOccurrence = const Value.absent(),
this.currency = const Value.absent(),
this.currencyCode = const Value.absent(),
this.accountId = const Value.absent(),
this.createdAt = const Value.absent(),
this.rowid = const Value.absent(),
});
@@ -529,13 +567,15 @@ class TransactionsCompanion extends UpdateCompanion<Transaction> {
this.lastOccurrence = const Value.absent(),
this.currency = const Value.absent(),
this.currencyCode = const Value.absent(),
required int accountId,
this.createdAt = const Value.absent(),
this.rowid = const Value.absent(),
}) : id = Value(id),
amount = Value(amount),
category = Value(category),
type = Value(type),
date = Value(date);
date = Value(date),
accountId = Value(accountId);
static Insertable<Transaction> custom({
Expression<String>? id,
Expression<double>? amount,
@@ -547,6 +587,7 @@ class TransactionsCompanion extends UpdateCompanion<Transaction> {
Expression<DateTime>? lastOccurrence,
Expression<String>? currency,
Expression<String>? currencyCode,
Expression<int>? accountId,
Expression<DateTime>? createdAt,
Expression<int>? rowid,
}) {
@@ -561,6 +602,7 @@ class TransactionsCompanion extends UpdateCompanion<Transaction> {
if (lastOccurrence != null) 'last_occurrence': lastOccurrence,
if (currency != null) 'currency': currency,
if (currencyCode != null) 'currency_code': currencyCode,
if (accountId != null) 'account_id': accountId,
if (createdAt != null) 'created_at': createdAt,
if (rowid != null) 'rowid': rowid,
});
@@ -577,6 +619,7 @@ class TransactionsCompanion extends UpdateCompanion<Transaction> {
Value<DateTime?>? lastOccurrence,
Value<String>? currency,
Value<String>? currencyCode,
Value<int>? accountId,
Value<DateTime>? createdAt,
Value<int>? rowid,
}) {
@@ -591,6 +634,7 @@ class TransactionsCompanion extends UpdateCompanion<Transaction> {
lastOccurrence: lastOccurrence ?? this.lastOccurrence,
currency: currency ?? this.currency,
currencyCode: currencyCode ?? this.currencyCode,
accountId: accountId ?? this.accountId,
createdAt: createdAt ?? this.createdAt,
rowid: rowid ?? this.rowid,
);
@@ -629,6 +673,9 @@ class TransactionsCompanion extends UpdateCompanion<Transaction> {
if (currencyCode.present) {
map['currency_code'] = Variable<String>(currencyCode.value);
}
if (accountId.present) {
map['account_id'] = Variable<int>(accountId.value);
}
if (createdAt.present) {
map['created_at'] = Variable<DateTime>(createdAt.value);
}
@@ -651,6 +698,7 @@ class TransactionsCompanion extends UpdateCompanion<Transaction> {
..write('lastOccurrence: $lastOccurrence, ')
..write('currency: $currency, ')
..write('currencyCode: $currencyCode, ')
..write('accountId: $accountId, ')
..write('createdAt: $createdAt, ')
..write('rowid: $rowid')
..write(')'))
@@ -2276,6 +2324,7 @@ typedef $$TransactionsTableCreateCompanionBuilder =
Value<DateTime?> lastOccurrence,
Value<String> currency,
Value<String> currencyCode,
required int accountId,
Value<DateTime> createdAt,
Value<int> rowid,
});
@@ -2291,6 +2340,7 @@ typedef $$TransactionsTableUpdateCompanionBuilder =
Value<DateTime?> lastOccurrence,
Value<String> currency,
Value<String> currencyCode,
Value<int> accountId,
Value<DateTime> createdAt,
Value<int> rowid,
});
@@ -2354,6 +2404,11 @@ class $$TransactionsTableFilterComposer
builder: (column) => ColumnFilters(column),
);
ColumnFilters<int> get accountId => $composableBuilder(
column: $table.accountId,
builder: (column) => ColumnFilters(column),
);
ColumnFilters<DateTime> get createdAt => $composableBuilder(
column: $table.createdAt,
builder: (column) => ColumnFilters(column),
@@ -2419,6 +2474,11 @@ class $$TransactionsTableOrderingComposer
builder: (column) => ColumnOrderings(column),
);
ColumnOrderings<int> get accountId => $composableBuilder(
column: $table.accountId,
builder: (column) => ColumnOrderings(column),
);
ColumnOrderings<DateTime> get createdAt => $composableBuilder(
column: $table.createdAt,
builder: (column) => ColumnOrderings(column),
@@ -2470,6 +2530,9 @@ class $$TransactionsTableAnnotationComposer
builder: (column) => column,
);
GeneratedColumn<int> get accountId =>
$composableBuilder(column: $table.accountId, builder: (column) => column);
GeneratedColumn<DateTime> get createdAt =>
$composableBuilder(column: $table.createdAt, builder: (column) => column);
}
@@ -2515,6 +2578,7 @@ class $$TransactionsTableTableManager
Value<DateTime?> lastOccurrence = const Value.absent(),
Value<String> currency = const Value.absent(),
Value<String> currencyCode = const Value.absent(),
Value<int> accountId = const Value.absent(),
Value<DateTime> createdAt = const Value.absent(),
Value<int> rowid = const Value.absent(),
}) => TransactionsCompanion(
@@ -2528,6 +2592,7 @@ class $$TransactionsTableTableManager
lastOccurrence: lastOccurrence,
currency: currency,
currencyCode: currencyCode,
accountId: accountId,
createdAt: createdAt,
rowid: rowid,
),
@@ -2543,6 +2608,7 @@ class $$TransactionsTableTableManager
Value<DateTime?> lastOccurrence = const Value.absent(),
Value<String> currency = const Value.absent(),
Value<String> currencyCode = const Value.absent(),
required int accountId,
Value<DateTime> createdAt = const Value.absent(),
Value<int> rowid = const Value.absent(),
}) => TransactionsCompanion.insert(
@@ -2556,6 +2622,7 @@ class $$TransactionsTableTableManager
lastOccurrence: lastOccurrence,
currency: currency,
currencyCode: currencyCode,
accountId: accountId,
createdAt: createdAt,
rowid: rowid,
),
+1
View File
@@ -12,6 +12,7 @@ class Transactions extends Table {
DateTimeColumn get lastOccurrence => dateTime().nullable()();
TextColumn get currency => text().withDefault(const Constant('\$'))();
TextColumn get currencyCode => text().withDefault(const Constant('USD'))();
IntColumn get accountId => integer()();
DateTimeColumn get createdAt => dateTime().withDefault(currentDateAndTime)();
@override
+3 -13
View File
@@ -64,11 +64,8 @@ class AccountRepository {
..orderBy([(a) => OrderingTerm.asc(a.sortOrder)]))
.get();
print('AccountRepository.getAll(): rows.length = ${rows.length}');
// Fallback: insert default account if none exists
if (rows.isEmpty) {
print('AccountRepository.getAll(): inserting default account');
try {
await _db.into(_db.accounts).insert(
AccountsCompanion.insert(
@@ -78,18 +75,16 @@ class AccountRepository {
sortOrder: const Value(0),
),
);
print('AccountRepository.getAll(): default account inserted');
} catch (e) {
print('AccountRepository.getAll(): insert error: $e');
// Ignore if already exists
}
// Re-query after insert
rows = await (_db.select(_db.accounts)
..orderBy([(a) => OrderingTerm.asc(a.sortOrder)]))
.get();
print('AccountRepository.getAll(): after insert, rows.length = ${rows.length}');
}
final accounts = rows
return rows
.map((row) => model.Account(
id: row.id,
name: row.name,
@@ -99,12 +94,7 @@ class AccountRepository {
createdAt: row.createdAt,
))
.toList();
print('AccountRepository.getAll(): returning ${accounts.length} accounts');
return accounts;
} catch (e, stack) {
print('AccountRepository.getAll(): error: $e');
print('Stack: $stack');
} catch (e) {
// Return empty list on error
return [];
}
@@ -148,6 +148,7 @@ class TransactionRepository {
lastOccurrence: dbTransaction.lastOccurrence as DateTime?,
currency: dbTransaction.currency as String,
currencyCode: dbTransaction.currencyCode as String,
accountId: dbTransaction.accountId as int,
);
}
@@ -165,6 +166,7 @@ class TransactionRepository {
lastOccurrence: Value(transaction.lastOccurrence),
currency: Value(transaction.currency),
currencyCode: Value(transaction.currencyCode),
accountId: Value(transaction.accountId),
createdAt: Value(DateTime.now()),
);
}
@@ -182,6 +184,7 @@ class TransactionRepository {
lastOccurrence: Value(transaction.lastOccurrence),
currency: Value(transaction.currency),
currencyCode: Value(transaction.currencyCode),
accountId: Value(transaction.accountId),
);
}
+40 -18
View File
@@ -15,11 +15,18 @@ import 'provider.dart';
const _uuid = Uuid();
// Provider to get main account name
final mainAccountNameProvider = FutureProvider<String>((ref) async {
final repository = ref.watch(accountRepositoryProvider);
final mainAccount = await repository.getMain();
return mainAccount.name;
// Provider to get the account for new transactions
final transactionAccountProvider = Provider<({int id, String name})>((ref) {
final activeAccount = ref.watch(activeAccountProvider);
if (activeAccount != null) {
// User is on a specific account page
return (id: activeAccount.id, name: activeAccount.name);
}
// User is on Total Balance page, use Main account
// This will be resolved in the widget
return (id: 0, name: ''); // Placeholder, will be replaced
});
class AddTransactionScreen extends ConsumerStatefulWidget {
@@ -146,6 +153,11 @@ class _AddTransactionScreenState extends ConsumerState<AddTransactionScreen>
? null
: _noteController.text.trim();
// Get account ID: use active account or fallback to main
final activeAccount = ref.read(activeAccountProvider);
final accountId = activeAccount?.id ??
(await ref.read(accountRepositoryProvider).getMain()).id;
final tx = Transaction(
id: state.editingId ?? _uuid.v4(),
amount: amount,
@@ -155,6 +167,7 @@ class _AddTransactionScreenState extends ConsumerState<AddTransactionScreen>
note: note,
currency: state.overrideCurrency,
currencyCode: state.overrideCurrencyCode,
accountId: accountId,
);
if (state.isEditing) {
@@ -226,7 +239,10 @@ class _AddTransactionScreenState extends ConsumerState<AddTransactionScreen>
final categories = ref.watch(availableCategoriesProvider(widget.initial));
final overrideCurrency = state.overrideCurrency;
final isDark = Theme.of(context).brightness == Brightness.dark;
final accountNameAsync = ref.watch(mainAccountNameProvider);
// Get active account or fallback to main
final activeAccount = ref.watch(activeAccountProvider);
final accountRepository = ref.watch(accountRepositoryProvider);
return Scaffold(
backgroundColor: Theme.of(context).scaffoldBackgroundColor,
@@ -278,18 +294,24 @@ class _AddTransactionScreenState extends ConsumerState<AddTransactionScreen>
child: ListView(
padding: const EdgeInsets.all(20),
children: [
accountNameAsync.when(
data: (accountName) => Padding(
padding: const EdgeInsets.only(bottom: 16),
child: Text(
'Account: $accountName',
style: Theme.of(context).textTheme.bodySmall?.copyWith(
color: Theme.of(context).colorScheme.onSurface.withOpacity(0.5),
),
),
),
loading: () => const SizedBox.shrink(),
error: (_, __) => const SizedBox.shrink(),
FutureBuilder<String>(
future: activeAccount != null
? Future.value(activeAccount.name)
: accountRepository.getMain().then((acc) => acc.name),
builder: (context, snapshot) {
if (snapshot.hasData) {
return Padding(
padding: const EdgeInsets.only(bottom: 16),
child: Text(
'Account: ${snapshot.data}',
style: Theme.of(context).textTheme.bodySmall?.copyWith(
color: Theme.of(context).colorScheme.onSurface.withOpacity(0.5),
),
),
);
}
return const SizedBox.shrink();
},
),
_TypeToggle(
selected: state.type,
+38 -9
View File
@@ -117,9 +117,23 @@ final timeFilterProvider = StateProvider<TimeFilter>(
(ref) => TimeFilter.lastMonth,
);
final totalBalanceProvider = Provider<double>((ref) {
// 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();
});
final totalBalanceProvider = Provider<double>((ref) {
final txs = ref.watch(accountFilteredTransactionsProvider);
final exchangeService = ref.watch(exchangeRateServiceProvider);
final targetCurrency = ref.watch(currencyProvider).code;
@@ -134,8 +148,7 @@ final totalBalanceProvider = Provider<double>((ref) {
});
final totalIncomeProvider = Provider<double>((ref) {
final txsAsync = ref.watch(transactionsProvider);
final txs = txsAsync.valueOrNull ?? [];
final txs = ref.watch(accountFilteredTransactionsProvider);
final filtered = txs.where((t) => t.type == TransactionType.income);
final exchangeService = ref.watch(exchangeRateServiceProvider);
final targetCurrency = ref.watch(currencyProvider).code;
@@ -147,8 +160,7 @@ final totalIncomeProvider = Provider<double>((ref) {
});
final totalExpenseProvider = Provider<double>((ref) {
final txsAsync = ref.watch(transactionsProvider);
final txs = txsAsync.valueOrNull ?? [];
final txs = ref.watch(accountFilteredTransactionsProvider);
final filtered = txs.where((t) => t.type == TransactionType.expense);
final exchangeService = ref.watch(exchangeRateServiceProvider);
final targetCurrency = ref.watch(currencyProvider).code;
@@ -161,8 +173,7 @@ final totalExpenseProvider = Provider<double>((ref) {
final currentMonthExpenseProvider = Provider<double>((ref) {
final now = DateTime.now();
final txsAsync = ref.watch(transactionsProvider);
final txs = txsAsync.valueOrNull ?? [];
final txs = ref.watch(accountFilteredTransactionsProvider);
final filtered = txs.where(
(t) =>
t.type == TransactionType.expense &&
@@ -179,8 +190,7 @@ final currentMonthExpenseProvider = Provider<double>((ref) {
});
final filteredTransactionsProvider = Provider<List<Transaction>>((ref) {
final txsAsync = ref.watch(transactionsProvider);
final txs = txsAsync.valueOrNull ?? [];
final txs = ref.watch(accountFilteredTransactionsProvider);
final query = ref.watch(searchQueryProvider).toLowerCase();
final typeFilter = ref.watch(transactionFilterProvider);
final timeFilter = ref.watch(timeFilterProvider);
@@ -236,6 +246,25 @@ 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"
return accountsAsync.when(
data: (accounts) {
if (index > 0 && index <= accounts.length) {
return accounts[index - 1];
}
return null;
},
loading: () => null,
error: (_, __) => null,
);
});
class CardColors {
final Color primary;
final Color secondary;
@@ -34,6 +34,7 @@ class BalanceCard extends ConsumerStatefulWidget {
final Color? previewPrimary;
final Color? previewSecondary;
final GradientType? previewGradientType;
final String? accountName;
const BalanceCard({
super.key,
@@ -43,6 +44,7 @@ class BalanceCard extends ConsumerStatefulWidget {
this.previewPrimary,
this.previewSecondary,
this.previewGradientType,
this.accountName,
});
@override
@@ -181,6 +183,25 @@ class BalanceCardState extends ConsumerState<BalanceCard>
borderRadius: BorderRadius.circular(20),
child: Stack(
children: [
if (widget.accountName != null)
Positioned(
top: 20,
left: 24,
child: Row(
mainAxisAlignment: MainAxisAlignment.start,
children: [
Text(
widget.accountName!,
style: TextStyle(
color: Colors.white.withOpacity(0.7),
fontSize: 12,
fontWeight: FontWeight.w500,
letterSpacing: 0.3,
),
),
],
),
),
Padding(
padding: const EdgeInsets.symmetric(
horizontal: 24,
@@ -196,15 +217,17 @@ class BalanceCardState extends ConsumerState<BalanceCard>
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Text(
s.totalBalance,
style: TextStyle(
fontSize: 11,
letterSpacing: 1.5,
color: Colors.white.withOpacity(0.6),
if (widget.accountName == null)
Text(
s.totalBalance,
style: TextStyle(
fontSize: 11,
letterSpacing: 1.5,
color: Colors.white.withOpacity(0.6),
),
),
),
const SizedBox(height: 6),
if (widget.accountName == null)
const SizedBox(height: 6),
FittedBox(
fit: BoxFit.scaleDown,
alignment: Alignment.center,
@@ -1,6 +1,8 @@
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import '../../../core/services/card_color_service.dart';
import '../../../core/services/haptic_service.dart';
import '../../../shared/models/transaction.dart';
import '../../settings/provider.dart';
import '../provider.dart';
import 'balance_card.dart';
@@ -34,7 +36,8 @@ class _BalanceCardCarouselState extends ConsumerState<BalanceCardCarousel> {
@override
void initState() {
super.initState();
_pageController = PageController(viewportFraction: 1.0);
// 0.92 позволяет видеть края предыдущей/следующей карточки
_pageController = PageController(viewportFraction: 0.92);
}
@override
@@ -50,63 +53,59 @@ class _BalanceCardCarouselState extends ConsumerState<BalanceCardCarousel> {
return accountsAsync.when(
data: (accounts) {
// Debug logging
debugPrint('BalanceCardCarousel: accounts.length = ${accounts.length}');
if (accounts.isNotEmpty) {
debugPrint('BalanceCardCarousel: first account = ${accounts[0].name}');
}
// Page 0: Total balance
// Pages 1..N: Account cards
// Page N+1: AddAccountCard (if < 5 accounts)
final totalPages = 1 + accounts.length + (accounts.length < 5 ? 1 : 0);
return Column(
children: [
SizedBox(
height: 220,
child: PageView.builder(
clipBehavior: Clip.none,
controller: _pageController,
itemCount: totalPages,
onPageChanged: (index) {
ref.read(activeAccountIndexProvider.notifier).state = index;
},
itemBuilder: (context, index) {
Widget pageContent;
height: 230,
// OverflowBox позволяет PageView игнорировать паддинги родителя (DashboardScreen)
// и растянуться на всю ширину экрана
child: OverflowBox(
maxWidth: MediaQuery.of(context).size.width,
child: PageView.builder(
controller: _pageController,
clipBehavior: Clip.none, // Не обрезает карточку при 3D-наклоне
itemCount: totalPages,
onPageChanged: (index) {
ref.read(activeAccountIndexProvider.notifier).state = index;
if (ref.read(hapticEnabledProvider)) {
HapticService.light();
}
},
itemBuilder: (context, index) {
Widget cardWidget;
if (index == 0) {
// Page 0: Total balance card
pageContent = BalanceCard(
balance: widget.balance,
currencyInfo: widget.currencyInfo,
onLongPress: widget.onLongPress,
previewPrimary: widget.previewPrimary,
previewSecondary: widget.previewSecondary,
previewGradientType: widget.previewGradientType,
);
} else if (index <= accounts.length) {
// Pages 1..N: Account cards
final account = accounts[index - 1];
debugPrint('BalanceCardCarousel: building account card at index $index for ${account.name}');
pageContent = _AccountBalanceCard(
account: account,
balance: widget.balance, // TODO: Calculate per-account balance
currencyInfo: widget.currencyInfo,
);
} else {
// Page N+1: AddAccountCard
pageContent = AddAccountCard(
onTap: () {},
);
}
if (index == 0) {
cardWidget = BalanceCard(
balance: widget.balance,
currencyInfo: widget.currencyInfo,
onLongPress: widget.onLongPress,
previewPrimary: widget.previewPrimary,
previewSecondary: widget.previewSecondary,
previewGradientType: widget.previewGradientType,
);
} else if (index <= accounts.length) {
final account = accounts[index - 1];
cardWidget = BalanceCard(
balance: widget.balance, // TODO: Calculate per-account balance
currencyInfo: widget.currencyInfo,
onLongPress: null,
accountName: account.name,
);
} else {
cardWidget = AddAccountCard(
onTap: () {},
);
}
// Add horizontal padding to create gap between cards
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 6),
child: pageContent,
);
},
// Отступ между карточками во время свайпа
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 8),
child: cardWidget,
);
},
),
),
),
const SizedBox(height: 12),
@@ -122,27 +121,21 @@ class _BalanceCardCarouselState extends ConsumerState<BalanceCardCarousel> {
child: Center(child: CircularProgressIndicator()),
),
error: (error, stack) {
debugPrint('BalanceCardCarousel error: $error');
debugPrint('Stack: $stack');
// Show total balance card on error
return Column(
children: [
SizedBox(
height: 220,
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 6),
child: BalanceCard(
balance: widget.balance,
currencyInfo: widget.currencyInfo,
onLongPress: widget.onLongPress,
previewPrimary: widget.previewPrimary,
previewSecondary: widget.previewSecondary,
previewGradientType: widget.previewGradientType,
),
child: BalanceCard(
balance: widget.balance,
currencyInfo: widget.currencyInfo,
onLongPress: widget.onLongPress,
previewPrimary: widget.previewPrimary,
previewSecondary: widget.previewSecondary,
previewGradientType: widget.previewGradientType,
),
),
const SizedBox(height: 12),
_DotIndicators(
const _DotIndicators(
count: 1,
activeIndex: 0,
),
@@ -153,48 +146,6 @@ class _BalanceCardCarouselState extends ConsumerState<BalanceCardCarousel> {
}
}
class _AccountBalanceCard extends ConsumerWidget {
final dynamic account;
final double balance;
final CurrencyInfo currencyInfo;
const _AccountBalanceCard({
required this.account,
required this.balance,
required this.currencyInfo,
});
@override
Widget build(BuildContext context, WidgetRef ref) {
return Stack(
clipBehavior: Clip.none,
children: [
BalanceCard(
balance: balance,
currencyInfo: currencyInfo,
onLongPress: null, // No long press for account cards
previewPrimary: null,
previewSecondary: null,
previewGradientType: null,
),
Positioned(
top: 16,
left: 24,
child: Text(
account.name,
style: TextStyle(
fontSize: 11,
letterSpacing: 1.5,
color: Colors.white.withOpacity(0.6),
fontWeight: FontWeight.w600,
),
),
),
],
);
}
}
class AddAccountCard extends StatelessWidget {
final VoidCallback? onTap;
+6
View File
@@ -16,6 +16,7 @@ class Transaction {
final DateTime? lastOccurrence;
final String currency;
final String currencyCode;
final int accountId;
const Transaction({
required this.id,
@@ -28,6 +29,7 @@ class Transaction {
this.lastOccurrence,
this.currency = '\$',
this.currencyCode = 'USD',
required this.accountId,
});
factory Transaction.fromJson(Map<String, dynamic> json) => Transaction(
@@ -50,6 +52,7 @@ class Transaction {
: null,
currency: json['currency'] as String? ?? '\$',
currencyCode: json['currencyCode'] as String? ?? 'USD',
accountId: json['accountId'] as int,
);
Map<String, dynamic> toJson() => {
@@ -63,6 +66,7 @@ class Transaction {
'lastOccurrence': lastOccurrence?.toIso8601String(),
'currency': currency,
'currencyCode': currencyCode,
'accountId': accountId,
};
Transaction copyWith({
@@ -76,6 +80,7 @@ class Transaction {
DateTime? lastOccurrence,
String? currency,
String? currencyCode,
int? accountId,
}) =>
Transaction(
id: id ?? this.id,
@@ -88,5 +93,6 @@ class Transaction {
lastOccurrence: lastOccurrence ?? this.lastOccurrence,
currency: currency ?? this.currency,
currencyCode: currencyCode ?? this.currencyCode,
accountId: accountId ?? this.accountId,
);
}
+2
View File
@@ -178,6 +178,7 @@ class StorageService {
note: tx.note,
recurrence: tx.recurrence,
lastOccurrence: today,
accountId: tx.accountId,
);
transactions.add(newTx);
@@ -248,6 +249,7 @@ class StorageService {
lastOccurrence: today,
currency: tx.currency,
currencyCode: tx.currencyCode,
accountId: tx.accountId,
);
transactions.add(newTx);