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