From 2fe390b068b27f9a563180a404405052cb23f4ae Mon Sep 17 00:00:00 2001 From: kolo Date: Sun, 22 Mar 2026 18:00:44 +0300 Subject: [PATCH] stableee --- build.yaml | 6 + lib/data/database/app_database.dart | 256 ++ lib/data/database/app_database.g.dart | 2840 +++++++++++++++++ lib/data/database/tables.dart | 49 + .../repositories/transaction_repository.dart | 195 ++ lib/features/categories/provider.dart | 21 +- lib/features/dashboard/provider.dart | 129 +- lib/features/settings/provider.dart | 37 +- lib/main.dart | 11 +- linux/flutter/generated_plugin_registrant.cc | 4 + linux/flutter/generated_plugins.cmake | 1 + macos/Flutter/GeneratedPluginRegistrant.swift | 2 + pubspec.lock | 258 +- pubspec.yaml | 5 + .../flutter/generated_plugin_registrant.cc | 3 + windows/flutter/generated_plugins.cmake | 1 + 16 files changed, 3744 insertions(+), 74 deletions(-) create mode 100644 build.yaml create mode 100644 lib/data/database/app_database.dart create mode 100644 lib/data/database/app_database.g.dart create mode 100644 lib/data/database/tables.dart create mode 100644 lib/data/repositories/transaction_repository.dart diff --git a/build.yaml b/build.yaml new file mode 100644 index 0000000..4ea5a80 --- /dev/null +++ b/build.yaml @@ -0,0 +1,6 @@ +targets: + $default: + builders: + drift_dev: + options: + store_date_time_values_as_text: true diff --git a/lib/data/database/app_database.dart b/lib/data/database/app_database.dart new file mode 100644 index 0000000..c40fc9b --- /dev/null +++ b/lib/data/database/app_database.dart @@ -0,0 +1,256 @@ +import 'dart:io'; +import 'package:drift/drift.dart'; +import 'package:drift/native.dart'; +import 'package:path_provider/path_provider.dart'; +import 'package:path/path.dart' as p; +import '../../core/utils/result.dart'; +import 'tables.dart'; + +part 'app_database.g.dart'; + +@DriftDatabase(tables: [Transactions, Categories, Budgets, ExchangeRates]) +class AppDatabase extends _$AppDatabase { + AppDatabase() : super(_openConnection()); + + @override + int get schemaVersion => 1; + + // ============================================================================ + // TRANSACTIONS + // ============================================================================ + + /// Get all transactions ordered by date descending + Future> getAllTransactions() { + return (select( + transactions, + )..orderBy([(t) => OrderingTerm.desc(t.date)])).get(); + } + + /// Get transactions by date range + Future> getTransactionsByDateRange( + DateTime start, + DateTime end, + ) { + return (select(transactions) + ..where((t) => t.date.isBiggerOrEqualValue(start)) + ..where((t) => t.date.isSmallerOrEqualValue(end)) + ..orderBy([(t) => OrderingTerm.desc(t.date)])) + .get(); + } + + /// Get transactions by type + Future> getTransactionsByType(String type) { + return (select(transactions) + ..where((t) => t.type.equals(type)) + ..orderBy([(t) => OrderingTerm.desc(t.date)])) + .get(); + } + + /// Get transactions by category + Future> getTransactionsByCategory(String category) { + return (select(transactions) + ..where((t) => t.category.equals(category)) + ..orderBy([(t) => OrderingTerm.desc(t.date)])) + .get(); + } + + /// Search transactions by note or category + Future> searchTransactions(String query) { + final lowerQuery = query.toLowerCase(); + return (select(transactions) + ..where( + (t) => + t.category.lower().like('%$lowerQuery%') | + t.note.lower().like('%$lowerQuery%'), + ) + ..orderBy([(t) => OrderingTerm.desc(t.date)])) + .get(); + } + + /// Get transaction by ID + Future getTransactionById(String id) { + return (select( + transactions, + )..where((t) => t.id.equals(id))).getSingleOrNull(); + } + + /// Insert transaction + Future> insertTransaction( + TransactionsCompanion transaction, + ) async { + return asyncResultOf(() async { + await into(transactions).insert(transaction); + }); + } + + /// Update transaction + Future> updateTransaction(dynamic transaction) async { + return asyncResultOf(() async { + final companion = transaction as TransactionsCompanion; + final updated = await (update( + transactions, + )..where((t) => t.id.equals(companion.id.value))).write(companion); + + if (updated == 0) { + throw Exception('Transaction not found'); + } + }); + } + + /// Delete transaction + Future> deleteTransaction(String id) async { + return asyncResultOf(() async { + final deleted = await (delete( + transactions, + )..where((t) => t.id.equals(id))).go(); + + if (deleted == 0) { + throw Exception('Transaction not found'); + } + }); + } + + /// Delete all transactions + Future deleteAllTransactions() { + return delete(transactions).go(); + } + + /// Get recurring transactions that need processing + Future> getRecurringTransactions() { + return (select( + transactions, + )..where((t) => t.recurrence.equals('none').not())).get(); + } + + // ============================================================================ + // CATEGORIES + // ============================================================================ + + /// Get all categories + Future> getAllCategories() { + return select(categories).get(); + } + + /// Get categories by type + Future> getCategoriesByType(String type) { + return (select(categories)..where((c) => c.type.equals(type))).get(); + } + + /// Insert category + Future insertCategory(CategoriesCompanion category) { + return into(categories).insert(category); + } + + /// Update category + Future updateCategory(Category category) { + return update(categories).replace(category); + } + + /// Delete category + Future deleteCategory(int id) { + return (delete(categories)..where((c) => c.id.equals(id))).go(); + } + + // ============================================================================ + // BUDGETS + // ============================================================================ + + /// Get budget for month/year + Future getBudget(int month, int year) { + return (select(budgets) + ..where((b) => b.month.equals(month) & b.year.equals(year))) + .getSingleOrNull(); + } + + /// Insert or update budget + Future upsertBudget(BudgetsCompanion budget) { + return into(budgets).insertOnConflictUpdate(budget); + } + + /// Delete budget + Future deleteBudget(int id) { + return (delete(budgets)..where((b) => b.id.equals(id))).go(); + } + + // ============================================================================ + // EXCHANGE RATES + // ============================================================================ + + /// Get exchange rate + Future getExchangeRate(String from, String to) { + return (select(exchangeRates) + ..where((r) => r.fromCurrency.equals(from) & r.toCurrency.equals(to))) + .getSingleOrNull(); + } + + /// Insert or update exchange rate + Future upsertExchangeRate(ExchangeRatesCompanion rate) { + return into(exchangeRates).insertOnConflictUpdate(rate); + } + + /// Get all exchange rates + Future> getAllExchangeRates() { + return select(exchangeRates).get(); + } + + /// Delete old exchange rates (older than 24 hours) + Future deleteOldExchangeRates() { + final yesterday = DateTime.now().subtract(const Duration(hours: 24)); + return (delete( + exchangeRates, + )..where((r) => r.updatedAt.isSmallerThanValue(yesterday))).go(); + } + + // ============================================================================ + // STATISTICS & AGGREGATIONS + // ============================================================================ + + /// Get total balance + Future getTotalBalance() async { + final txs = await getAllTransactions(); + return txs.fold(0.0, (sum, tx) { + return tx.type == 'income' ? sum + tx.amount : sum - tx.amount; + }); + } + + /// Get total income for date range + Future getTotalIncome(DateTime start, DateTime end) async { + final txs = await getTransactionsByDateRange(start, end); + return txs + .where((tx) => tx.type == 'income') + .fold(0.0, (sum, tx) => sum + tx.amount); + } + + /// Get total expense for date range + Future getTotalExpense(DateTime start, DateTime end) async { + final txs = await getTransactionsByDateRange(start, end); + return txs + .where((tx) => tx.type == 'expense') + .fold(0.0, (sum, tx) => sum + tx.amount); + } + + /// Get category totals for date range + Future> getCategoryTotals( + DateTime start, + DateTime end, + String type, + ) async { + final txs = await getTransactionsByDateRange(start, end); + final filtered = txs.where((tx) => tx.type == type); + + final Map totals = {}; + for (final tx in filtered) { + totals[tx.category] = (totals[tx.category] ?? 0) + tx.amount; + } + + return totals; + } +} + +LazyDatabase _openConnection() { + return LazyDatabase(() async { + final dbFolder = await getApplicationDocumentsDirectory(); + final file = File(p.join(dbFolder.path, 'casha.db')); + return NativeDatabase(file); + }); +} diff --git a/lib/data/database/app_database.g.dart b/lib/data/database/app_database.g.dart new file mode 100644 index 0000000..a3c824a --- /dev/null +++ b/lib/data/database/app_database.g.dart @@ -0,0 +1,2840 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'app_database.dart'; + +// ignore_for_file: type=lint +class $TransactionsTable extends Transactions + with TableInfo<$TransactionsTable, Transaction> { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + $TransactionsTable(this.attachedDatabase, [this._alias]); + static const VerificationMeta _idMeta = const VerificationMeta('id'); + @override + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + static const VerificationMeta _amountMeta = const VerificationMeta('amount'); + @override + late final GeneratedColumn amount = GeneratedColumn( + 'amount', + aliasedName, + false, + type: DriftSqlType.double, + requiredDuringInsert: true, + ); + static const VerificationMeta _categoryMeta = const VerificationMeta( + 'category', + ); + @override + late final GeneratedColumn category = GeneratedColumn( + 'category', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + static const VerificationMeta _typeMeta = const VerificationMeta('type'); + @override + late final GeneratedColumn type = GeneratedColumn( + 'type', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + static const VerificationMeta _dateMeta = const VerificationMeta('date'); + @override + late final GeneratedColumn date = GeneratedColumn( + 'date', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: true, + ); + static const VerificationMeta _noteMeta = const VerificationMeta('note'); + @override + late final GeneratedColumn note = GeneratedColumn( + 'note', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + static const VerificationMeta _recurrenceMeta = const VerificationMeta( + 'recurrence', + ); + @override + late final GeneratedColumn recurrence = GeneratedColumn( + 'recurrence', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: false, + defaultValue: const Constant('none'), + ); + static const VerificationMeta _lastOccurrenceMeta = const VerificationMeta( + 'lastOccurrence', + ); + @override + late final GeneratedColumn lastOccurrence = + GeneratedColumn( + 'last_occurrence', + aliasedName, + true, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + ); + static const VerificationMeta _currencyMeta = const VerificationMeta( + 'currency', + ); + @override + late final GeneratedColumn currency = GeneratedColumn( + 'currency', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: false, + defaultValue: const Constant('\$'), + ); + static const VerificationMeta _currencyCodeMeta = const VerificationMeta( + 'currencyCode', + ); + @override + late final GeneratedColumn currencyCode = GeneratedColumn( + 'currency_code', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: false, + defaultValue: const Constant('USD'), + ); + static const VerificationMeta _createdAtMeta = const VerificationMeta( + 'createdAt', + ); + @override + late final GeneratedColumn createdAt = GeneratedColumn( + 'created_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: currentDateAndTime, + ); + @override + List get $columns => [ + id, + amount, + category, + type, + date, + note, + recurrence, + lastOccurrence, + currency, + currencyCode, + createdAt, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'transactions'; + @override + VerificationContext validateIntegrity( + Insertable instance, { + bool isInserting = false, + }) { + final context = VerificationContext(); + final data = instance.toColumns(true); + if (data.containsKey('id')) { + context.handle(_idMeta, id.isAcceptableOrUnknown(data['id']!, _idMeta)); + } else if (isInserting) { + context.missing(_idMeta); + } + if (data.containsKey('amount')) { + context.handle( + _amountMeta, + amount.isAcceptableOrUnknown(data['amount']!, _amountMeta), + ); + } else if (isInserting) { + context.missing(_amountMeta); + } + if (data.containsKey('category')) { + context.handle( + _categoryMeta, + category.isAcceptableOrUnknown(data['category']!, _categoryMeta), + ); + } else if (isInserting) { + context.missing(_categoryMeta); + } + if (data.containsKey('type')) { + context.handle( + _typeMeta, + type.isAcceptableOrUnknown(data['type']!, _typeMeta), + ); + } else if (isInserting) { + context.missing(_typeMeta); + } + if (data.containsKey('date')) { + context.handle( + _dateMeta, + date.isAcceptableOrUnknown(data['date']!, _dateMeta), + ); + } else if (isInserting) { + context.missing(_dateMeta); + } + if (data.containsKey('note')) { + context.handle( + _noteMeta, + note.isAcceptableOrUnknown(data['note']!, _noteMeta), + ); + } + if (data.containsKey('recurrence')) { + context.handle( + _recurrenceMeta, + recurrence.isAcceptableOrUnknown(data['recurrence']!, _recurrenceMeta), + ); + } + if (data.containsKey('last_occurrence')) { + context.handle( + _lastOccurrenceMeta, + lastOccurrence.isAcceptableOrUnknown( + data['last_occurrence']!, + _lastOccurrenceMeta, + ), + ); + } + if (data.containsKey('currency')) { + context.handle( + _currencyMeta, + currency.isAcceptableOrUnknown(data['currency']!, _currencyMeta), + ); + } + if (data.containsKey('currency_code')) { + context.handle( + _currencyCodeMeta, + currencyCode.isAcceptableOrUnknown( + data['currency_code']!, + _currencyCodeMeta, + ), + ); + } + if (data.containsKey('created_at')) { + context.handle( + _createdAtMeta, + createdAt.isAcceptableOrUnknown(data['created_at']!, _createdAtMeta), + ); + } + return context; + } + + @override + Set get $primaryKey => {id}; + @override + Transaction map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return Transaction( + id: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}id'], + )!, + amount: attachedDatabase.typeMapping.read( + DriftSqlType.double, + data['${effectivePrefix}amount'], + )!, + category: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}category'], + )!, + type: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}type'], + )!, + date: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}date'], + )!, + note: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}note'], + ), + recurrence: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}recurrence'], + )!, + lastOccurrence: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}last_occurrence'], + ), + currency: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}currency'], + )!, + currencyCode: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}currency_code'], + )!, + createdAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}created_at'], + )!, + ); + } + + @override + $TransactionsTable createAlias(String alias) { + return $TransactionsTable(attachedDatabase, alias); + } +} + +class Transaction extends DataClass implements Insertable { + final String id; + final double amount; + final String category; + final String type; + final DateTime date; + final String? note; + final String recurrence; + final DateTime? lastOccurrence; + final String currency; + final String currencyCode; + final DateTime createdAt; + const Transaction({ + required this.id, + required this.amount, + required this.category, + required this.type, + required this.date, + this.note, + required this.recurrence, + this.lastOccurrence, + required this.currency, + required this.currencyCode, + required this.createdAt, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + map['amount'] = Variable(amount); + map['category'] = Variable(category); + map['type'] = Variable(type); + map['date'] = Variable(date); + if (!nullToAbsent || note != null) { + map['note'] = Variable(note); + } + map['recurrence'] = Variable(recurrence); + if (!nullToAbsent || lastOccurrence != null) { + map['last_occurrence'] = Variable(lastOccurrence); + } + map['currency'] = Variable(currency); + map['currency_code'] = Variable(currencyCode); + map['created_at'] = Variable(createdAt); + return map; + } + + TransactionsCompanion toCompanion(bool nullToAbsent) { + return TransactionsCompanion( + id: Value(id), + amount: Value(amount), + category: Value(category), + type: Value(type), + date: Value(date), + note: note == null && nullToAbsent ? const Value.absent() : Value(note), + recurrence: Value(recurrence), + lastOccurrence: lastOccurrence == null && nullToAbsent + ? const Value.absent() + : Value(lastOccurrence), + currency: Value(currency), + currencyCode: Value(currencyCode), + createdAt: Value(createdAt), + ); + } + + factory Transaction.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return Transaction( + id: serializer.fromJson(json['id']), + amount: serializer.fromJson(json['amount']), + category: serializer.fromJson(json['category']), + type: serializer.fromJson(json['type']), + date: serializer.fromJson(json['date']), + note: serializer.fromJson(json['note']), + recurrence: serializer.fromJson(json['recurrence']), + lastOccurrence: serializer.fromJson(json['lastOccurrence']), + currency: serializer.fromJson(json['currency']), + currencyCode: serializer.fromJson(json['currencyCode']), + createdAt: serializer.fromJson(json['createdAt']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'amount': serializer.toJson(amount), + 'category': serializer.toJson(category), + 'type': serializer.toJson(type), + 'date': serializer.toJson(date), + 'note': serializer.toJson(note), + 'recurrence': serializer.toJson(recurrence), + 'lastOccurrence': serializer.toJson(lastOccurrence), + 'currency': serializer.toJson(currency), + 'currencyCode': serializer.toJson(currencyCode), + 'createdAt': serializer.toJson(createdAt), + }; + } + + Transaction copyWith({ + String? id, + double? amount, + String? category, + String? type, + DateTime? date, + Value note = const Value.absent(), + String? recurrence, + Value lastOccurrence = const Value.absent(), + String? currency, + String? currencyCode, + DateTime? createdAt, + }) => Transaction( + id: id ?? this.id, + amount: amount ?? this.amount, + category: category ?? this.category, + type: type ?? this.type, + date: date ?? this.date, + note: note.present ? note.value : this.note, + recurrence: recurrence ?? this.recurrence, + lastOccurrence: lastOccurrence.present + ? lastOccurrence.value + : this.lastOccurrence, + currency: currency ?? this.currency, + currencyCode: currencyCode ?? this.currencyCode, + createdAt: createdAt ?? this.createdAt, + ); + Transaction copyWithCompanion(TransactionsCompanion data) { + return Transaction( + id: data.id.present ? data.id.value : this.id, + amount: data.amount.present ? data.amount.value : this.amount, + category: data.category.present ? data.category.value : this.category, + type: data.type.present ? data.type.value : this.type, + date: data.date.present ? data.date.value : this.date, + note: data.note.present ? data.note.value : this.note, + recurrence: data.recurrence.present + ? data.recurrence.value + : this.recurrence, + lastOccurrence: data.lastOccurrence.present + ? data.lastOccurrence.value + : this.lastOccurrence, + currency: data.currency.present ? data.currency.value : this.currency, + currencyCode: data.currencyCode.present + ? data.currencyCode.value + : this.currencyCode, + createdAt: data.createdAt.present ? data.createdAt.value : this.createdAt, + ); + } + + @override + String toString() { + return (StringBuffer('Transaction(') + ..write('id: $id, ') + ..write('amount: $amount, ') + ..write('category: $category, ') + ..write('type: $type, ') + ..write('date: $date, ') + ..write('note: $note, ') + ..write('recurrence: $recurrence, ') + ..write('lastOccurrence: $lastOccurrence, ') + ..write('currency: $currency, ') + ..write('currencyCode: $currencyCode, ') + ..write('createdAt: $createdAt') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash( + id, + amount, + category, + type, + date, + note, + recurrence, + lastOccurrence, + currency, + currencyCode, + createdAt, + ); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is Transaction && + other.id == this.id && + other.amount == this.amount && + other.category == this.category && + other.type == this.type && + other.date == this.date && + other.note == this.note && + other.recurrence == this.recurrence && + other.lastOccurrence == this.lastOccurrence && + other.currency == this.currency && + other.currencyCode == this.currencyCode && + other.createdAt == this.createdAt); +} + +class TransactionsCompanion extends UpdateCompanion { + final Value id; + final Value amount; + final Value category; + final Value type; + final Value date; + final Value note; + final Value recurrence; + final Value lastOccurrence; + final Value currency; + final Value currencyCode; + final Value createdAt; + final Value rowid; + const TransactionsCompanion({ + this.id = const Value.absent(), + this.amount = const Value.absent(), + this.category = const Value.absent(), + this.type = const Value.absent(), + this.date = const Value.absent(), + this.note = const Value.absent(), + this.recurrence = const Value.absent(), + this.lastOccurrence = const Value.absent(), + this.currency = const Value.absent(), + this.currencyCode = const Value.absent(), + this.createdAt = const Value.absent(), + this.rowid = const Value.absent(), + }); + TransactionsCompanion.insert({ + required String id, + required double amount, + required String category, + required String type, + required DateTime date, + this.note = const Value.absent(), + this.recurrence = const Value.absent(), + this.lastOccurrence = const Value.absent(), + this.currency = const Value.absent(), + this.currencyCode = const Value.absent(), + 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); + static Insertable custom({ + Expression? id, + Expression? amount, + Expression? category, + Expression? type, + Expression? date, + Expression? note, + Expression? recurrence, + Expression? lastOccurrence, + Expression? currency, + Expression? currencyCode, + Expression? createdAt, + Expression? rowid, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + if (amount != null) 'amount': amount, + if (category != null) 'category': category, + if (type != null) 'type': type, + if (date != null) 'date': date, + if (note != null) 'note': note, + if (recurrence != null) 'recurrence': recurrence, + if (lastOccurrence != null) 'last_occurrence': lastOccurrence, + if (currency != null) 'currency': currency, + if (currencyCode != null) 'currency_code': currencyCode, + if (createdAt != null) 'created_at': createdAt, + if (rowid != null) 'rowid': rowid, + }); + } + + TransactionsCompanion copyWith({ + Value? id, + Value? amount, + Value? category, + Value? type, + Value? date, + Value? note, + Value? recurrence, + Value? lastOccurrence, + Value? currency, + Value? currencyCode, + Value? createdAt, + Value? rowid, + }) { + return TransactionsCompanion( + id: id ?? this.id, + amount: amount ?? this.amount, + category: category ?? this.category, + type: type ?? this.type, + date: date ?? this.date, + note: note ?? this.note, + recurrence: recurrence ?? this.recurrence, + lastOccurrence: lastOccurrence ?? this.lastOccurrence, + currency: currency ?? this.currency, + currencyCode: currencyCode ?? this.currencyCode, + createdAt: createdAt ?? this.createdAt, + rowid: rowid ?? this.rowid, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (amount.present) { + map['amount'] = Variable(amount.value); + } + if (category.present) { + map['category'] = Variable(category.value); + } + if (type.present) { + map['type'] = Variable(type.value); + } + if (date.present) { + map['date'] = Variable(date.value); + } + if (note.present) { + map['note'] = Variable(note.value); + } + if (recurrence.present) { + map['recurrence'] = Variable(recurrence.value); + } + if (lastOccurrence.present) { + map['last_occurrence'] = Variable(lastOccurrence.value); + } + if (currency.present) { + map['currency'] = Variable(currency.value); + } + if (currencyCode.present) { + map['currency_code'] = Variable(currencyCode.value); + } + if (createdAt.present) { + map['created_at'] = Variable(createdAt.value); + } + if (rowid.present) { + map['rowid'] = Variable(rowid.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('TransactionsCompanion(') + ..write('id: $id, ') + ..write('amount: $amount, ') + ..write('category: $category, ') + ..write('type: $type, ') + ..write('date: $date, ') + ..write('note: $note, ') + ..write('recurrence: $recurrence, ') + ..write('lastOccurrence: $lastOccurrence, ') + ..write('currency: $currency, ') + ..write('currencyCode: $currencyCode, ') + ..write('createdAt: $createdAt, ') + ..write('rowid: $rowid') + ..write(')')) + .toString(); + } +} + +class $CategoriesTable extends Categories + with TableInfo<$CategoriesTable, Category> { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + $CategoriesTable(this.attachedDatabase, [this._alias]); + static const VerificationMeta _idMeta = const VerificationMeta('id'); + @override + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + hasAutoIncrement: true, + type: DriftSqlType.int, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'PRIMARY KEY AUTOINCREMENT', + ), + ); + static const VerificationMeta _nameMeta = const VerificationMeta('name'); + @override + late final GeneratedColumn name = GeneratedColumn( + 'name', + aliasedName, + false, + additionalChecks: GeneratedColumn.checkTextLength( + minTextLength: 1, + maxTextLength: 50, + ), + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + static const VerificationMeta _typeMeta = const VerificationMeta('type'); + @override + late final GeneratedColumn type = GeneratedColumn( + 'type', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + static const VerificationMeta _iconMeta = const VerificationMeta('icon'); + @override + late final GeneratedColumn icon = GeneratedColumn( + 'icon', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + static const VerificationMeta _colorMeta = const VerificationMeta('color'); + @override + late final GeneratedColumn color = GeneratedColumn( + 'color', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + static const VerificationMeta _isDefaultMeta = const VerificationMeta( + 'isDefault', + ); + @override + late final GeneratedColumn isDefault = GeneratedColumn( + 'is_default', + aliasedName, + false, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("is_default" IN (0, 1))', + ), + defaultValue: const Constant(false), + ); + static const VerificationMeta _createdAtMeta = const VerificationMeta( + 'createdAt', + ); + @override + late final GeneratedColumn createdAt = GeneratedColumn( + 'created_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: currentDateAndTime, + ); + @override + List get $columns => [ + id, + name, + type, + icon, + color, + isDefault, + createdAt, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'categories'; + @override + VerificationContext validateIntegrity( + Insertable instance, { + bool isInserting = false, + }) { + final context = VerificationContext(); + final data = instance.toColumns(true); + if (data.containsKey('id')) { + context.handle(_idMeta, id.isAcceptableOrUnknown(data['id']!, _idMeta)); + } + if (data.containsKey('name')) { + context.handle( + _nameMeta, + name.isAcceptableOrUnknown(data['name']!, _nameMeta), + ); + } else if (isInserting) { + context.missing(_nameMeta); + } + if (data.containsKey('type')) { + context.handle( + _typeMeta, + type.isAcceptableOrUnknown(data['type']!, _typeMeta), + ); + } else if (isInserting) { + context.missing(_typeMeta); + } + if (data.containsKey('icon')) { + context.handle( + _iconMeta, + icon.isAcceptableOrUnknown(data['icon']!, _iconMeta), + ); + } + if (data.containsKey('color')) { + context.handle( + _colorMeta, + color.isAcceptableOrUnknown(data['color']!, _colorMeta), + ); + } + if (data.containsKey('is_default')) { + context.handle( + _isDefaultMeta, + isDefault.isAcceptableOrUnknown(data['is_default']!, _isDefaultMeta), + ); + } + if (data.containsKey('created_at')) { + context.handle( + _createdAtMeta, + createdAt.isAcceptableOrUnknown(data['created_at']!, _createdAtMeta), + ); + } + return context; + } + + @override + Set get $primaryKey => {id}; + @override + Category map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return Category( + id: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}id'], + )!, + name: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}name'], + )!, + type: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}type'], + )!, + icon: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}icon'], + ), + color: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}color'], + ), + isDefault: attachedDatabase.typeMapping.read( + DriftSqlType.bool, + data['${effectivePrefix}is_default'], + )!, + createdAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}created_at'], + )!, + ); + } + + @override + $CategoriesTable createAlias(String alias) { + return $CategoriesTable(attachedDatabase, alias); + } +} + +class Category extends DataClass implements Insertable { + final int id; + final String name; + final String type; + final String? icon; + final String? color; + final bool isDefault; + final DateTime createdAt; + const Category({ + required this.id, + required this.name, + required this.type, + this.icon, + this.color, + required this.isDefault, + required this.createdAt, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + map['name'] = Variable(name); + map['type'] = Variable(type); + if (!nullToAbsent || icon != null) { + map['icon'] = Variable(icon); + } + if (!nullToAbsent || color != null) { + map['color'] = Variable(color); + } + map['is_default'] = Variable(isDefault); + map['created_at'] = Variable(createdAt); + return map; + } + + CategoriesCompanion toCompanion(bool nullToAbsent) { + return CategoriesCompanion( + id: Value(id), + name: Value(name), + type: Value(type), + icon: icon == null && nullToAbsent ? const Value.absent() : Value(icon), + color: color == null && nullToAbsent + ? const Value.absent() + : Value(color), + isDefault: Value(isDefault), + createdAt: Value(createdAt), + ); + } + + factory Category.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return Category( + id: serializer.fromJson(json['id']), + name: serializer.fromJson(json['name']), + type: serializer.fromJson(json['type']), + icon: serializer.fromJson(json['icon']), + color: serializer.fromJson(json['color']), + isDefault: serializer.fromJson(json['isDefault']), + createdAt: serializer.fromJson(json['createdAt']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'name': serializer.toJson(name), + 'type': serializer.toJson(type), + 'icon': serializer.toJson(icon), + 'color': serializer.toJson(color), + 'isDefault': serializer.toJson(isDefault), + 'createdAt': serializer.toJson(createdAt), + }; + } + + Category copyWith({ + int? id, + String? name, + String? type, + Value icon = const Value.absent(), + Value color = const Value.absent(), + bool? isDefault, + DateTime? createdAt, + }) => Category( + id: id ?? this.id, + name: name ?? this.name, + type: type ?? this.type, + icon: icon.present ? icon.value : this.icon, + color: color.present ? color.value : this.color, + isDefault: isDefault ?? this.isDefault, + createdAt: createdAt ?? this.createdAt, + ); + Category copyWithCompanion(CategoriesCompanion data) { + return Category( + id: data.id.present ? data.id.value : this.id, + name: data.name.present ? data.name.value : this.name, + type: data.type.present ? data.type.value : this.type, + icon: data.icon.present ? data.icon.value : this.icon, + color: data.color.present ? data.color.value : this.color, + isDefault: data.isDefault.present ? data.isDefault.value : this.isDefault, + createdAt: data.createdAt.present ? data.createdAt.value : this.createdAt, + ); + } + + @override + String toString() { + return (StringBuffer('Category(') + ..write('id: $id, ') + ..write('name: $name, ') + ..write('type: $type, ') + ..write('icon: $icon, ') + ..write('color: $color, ') + ..write('isDefault: $isDefault, ') + ..write('createdAt: $createdAt') + ..write(')')) + .toString(); + } + + @override + int get hashCode => + Object.hash(id, name, type, icon, color, isDefault, createdAt); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is Category && + other.id == this.id && + other.name == this.name && + other.type == this.type && + other.icon == this.icon && + other.color == this.color && + other.isDefault == this.isDefault && + other.createdAt == this.createdAt); +} + +class CategoriesCompanion extends UpdateCompanion { + final Value id; + final Value name; + final Value type; + final Value icon; + final Value color; + final Value isDefault; + final Value createdAt; + const CategoriesCompanion({ + this.id = const Value.absent(), + this.name = const Value.absent(), + this.type = const Value.absent(), + this.icon = const Value.absent(), + this.color = const Value.absent(), + this.isDefault = const Value.absent(), + this.createdAt = const Value.absent(), + }); + CategoriesCompanion.insert({ + this.id = const Value.absent(), + required String name, + required String type, + this.icon = const Value.absent(), + this.color = const Value.absent(), + this.isDefault = const Value.absent(), + this.createdAt = const Value.absent(), + }) : name = Value(name), + type = Value(type); + static Insertable custom({ + Expression? id, + Expression? name, + Expression? type, + Expression? icon, + Expression? color, + Expression? isDefault, + Expression? createdAt, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + if (name != null) 'name': name, + if (type != null) 'type': type, + if (icon != null) 'icon': icon, + if (color != null) 'color': color, + if (isDefault != null) 'is_default': isDefault, + if (createdAt != null) 'created_at': createdAt, + }); + } + + CategoriesCompanion copyWith({ + Value? id, + Value? name, + Value? type, + Value? icon, + Value? color, + Value? isDefault, + Value? createdAt, + }) { + return CategoriesCompanion( + id: id ?? this.id, + name: name ?? this.name, + type: type ?? this.type, + icon: icon ?? this.icon, + color: color ?? this.color, + isDefault: isDefault ?? this.isDefault, + createdAt: createdAt ?? this.createdAt, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (name.present) { + map['name'] = Variable(name.value); + } + if (type.present) { + map['type'] = Variable(type.value); + } + if (icon.present) { + map['icon'] = Variable(icon.value); + } + if (color.present) { + map['color'] = Variable(color.value); + } + if (isDefault.present) { + map['is_default'] = Variable(isDefault.value); + } + if (createdAt.present) { + map['created_at'] = Variable(createdAt.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('CategoriesCompanion(') + ..write('id: $id, ') + ..write('name: $name, ') + ..write('type: $type, ') + ..write('icon: $icon, ') + ..write('color: $color, ') + ..write('isDefault: $isDefault, ') + ..write('createdAt: $createdAt') + ..write(')')) + .toString(); + } +} + +class $BudgetsTable extends Budgets with TableInfo<$BudgetsTable, Budget> { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + $BudgetsTable(this.attachedDatabase, [this._alias]); + static const VerificationMeta _idMeta = const VerificationMeta('id'); + @override + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + hasAutoIncrement: true, + type: DriftSqlType.int, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'PRIMARY KEY AUTOINCREMENT', + ), + ); + static const VerificationMeta _amountMeta = const VerificationMeta('amount'); + @override + late final GeneratedColumn amount = GeneratedColumn( + 'amount', + aliasedName, + false, + type: DriftSqlType.double, + requiredDuringInsert: true, + ); + static const VerificationMeta _categoryIdMeta = const VerificationMeta( + 'categoryId', + ); + @override + late final GeneratedColumn categoryId = GeneratedColumn( + 'category_id', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + static const VerificationMeta _monthMeta = const VerificationMeta('month'); + @override + late final GeneratedColumn month = GeneratedColumn( + 'month', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + static const VerificationMeta _yearMeta = const VerificationMeta('year'); + @override + late final GeneratedColumn year = GeneratedColumn( + 'year', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + static const VerificationMeta _createdAtMeta = const VerificationMeta( + 'createdAt', + ); + @override + late final GeneratedColumn createdAt = GeneratedColumn( + 'created_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: currentDateAndTime, + ); + @override + List get $columns => [ + id, + amount, + categoryId, + month, + year, + createdAt, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'budgets'; + @override + VerificationContext validateIntegrity( + Insertable instance, { + bool isInserting = false, + }) { + final context = VerificationContext(); + final data = instance.toColumns(true); + if (data.containsKey('id')) { + context.handle(_idMeta, id.isAcceptableOrUnknown(data['id']!, _idMeta)); + } + if (data.containsKey('amount')) { + context.handle( + _amountMeta, + amount.isAcceptableOrUnknown(data['amount']!, _amountMeta), + ); + } else if (isInserting) { + context.missing(_amountMeta); + } + if (data.containsKey('category_id')) { + context.handle( + _categoryIdMeta, + categoryId.isAcceptableOrUnknown(data['category_id']!, _categoryIdMeta), + ); + } + if (data.containsKey('month')) { + context.handle( + _monthMeta, + month.isAcceptableOrUnknown(data['month']!, _monthMeta), + ); + } else if (isInserting) { + context.missing(_monthMeta); + } + if (data.containsKey('year')) { + context.handle( + _yearMeta, + year.isAcceptableOrUnknown(data['year']!, _yearMeta), + ); + } else if (isInserting) { + context.missing(_yearMeta); + } + if (data.containsKey('created_at')) { + context.handle( + _createdAtMeta, + createdAt.isAcceptableOrUnknown(data['created_at']!, _createdAtMeta), + ); + } + return context; + } + + @override + Set get $primaryKey => {id}; + @override + Budget map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return Budget( + id: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}id'], + )!, + amount: attachedDatabase.typeMapping.read( + DriftSqlType.double, + data['${effectivePrefix}amount'], + )!, + categoryId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}category_id'], + ), + month: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}month'], + )!, + year: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}year'], + )!, + createdAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}created_at'], + )!, + ); + } + + @override + $BudgetsTable createAlias(String alias) { + return $BudgetsTable(attachedDatabase, alias); + } +} + +class Budget extends DataClass implements Insertable { + final int id; + final double amount; + final String? categoryId; + final int month; + final int year; + final DateTime createdAt; + const Budget({ + required this.id, + required this.amount, + this.categoryId, + required this.month, + required this.year, + required this.createdAt, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + map['amount'] = Variable(amount); + if (!nullToAbsent || categoryId != null) { + map['category_id'] = Variable(categoryId); + } + map['month'] = Variable(month); + map['year'] = Variable(year); + map['created_at'] = Variable(createdAt); + return map; + } + + BudgetsCompanion toCompanion(bool nullToAbsent) { + return BudgetsCompanion( + id: Value(id), + amount: Value(amount), + categoryId: categoryId == null && nullToAbsent + ? const Value.absent() + : Value(categoryId), + month: Value(month), + year: Value(year), + createdAt: Value(createdAt), + ); + } + + factory Budget.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return Budget( + id: serializer.fromJson(json['id']), + amount: serializer.fromJson(json['amount']), + categoryId: serializer.fromJson(json['categoryId']), + month: serializer.fromJson(json['month']), + year: serializer.fromJson(json['year']), + createdAt: serializer.fromJson(json['createdAt']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'amount': serializer.toJson(amount), + 'categoryId': serializer.toJson(categoryId), + 'month': serializer.toJson(month), + 'year': serializer.toJson(year), + 'createdAt': serializer.toJson(createdAt), + }; + } + + Budget copyWith({ + int? id, + double? amount, + Value categoryId = const Value.absent(), + int? month, + int? year, + DateTime? createdAt, + }) => Budget( + id: id ?? this.id, + amount: amount ?? this.amount, + categoryId: categoryId.present ? categoryId.value : this.categoryId, + month: month ?? this.month, + year: year ?? this.year, + createdAt: createdAt ?? this.createdAt, + ); + Budget copyWithCompanion(BudgetsCompanion data) { + return Budget( + id: data.id.present ? data.id.value : this.id, + amount: data.amount.present ? data.amount.value : this.amount, + categoryId: data.categoryId.present + ? data.categoryId.value + : this.categoryId, + month: data.month.present ? data.month.value : this.month, + year: data.year.present ? data.year.value : this.year, + createdAt: data.createdAt.present ? data.createdAt.value : this.createdAt, + ); + } + + @override + String toString() { + return (StringBuffer('Budget(') + ..write('id: $id, ') + ..write('amount: $amount, ') + ..write('categoryId: $categoryId, ') + ..write('month: $month, ') + ..write('year: $year, ') + ..write('createdAt: $createdAt') + ..write(')')) + .toString(); + } + + @override + int get hashCode => + Object.hash(id, amount, categoryId, month, year, createdAt); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is Budget && + other.id == this.id && + other.amount == this.amount && + other.categoryId == this.categoryId && + other.month == this.month && + other.year == this.year && + other.createdAt == this.createdAt); +} + +class BudgetsCompanion extends UpdateCompanion { + final Value id; + final Value amount; + final Value categoryId; + final Value month; + final Value year; + final Value createdAt; + const BudgetsCompanion({ + this.id = const Value.absent(), + this.amount = const Value.absent(), + this.categoryId = const Value.absent(), + this.month = const Value.absent(), + this.year = const Value.absent(), + this.createdAt = const Value.absent(), + }); + BudgetsCompanion.insert({ + this.id = const Value.absent(), + required double amount, + this.categoryId = const Value.absent(), + required int month, + required int year, + this.createdAt = const Value.absent(), + }) : amount = Value(amount), + month = Value(month), + year = Value(year); + static Insertable custom({ + Expression? id, + Expression? amount, + Expression? categoryId, + Expression? month, + Expression? year, + Expression? createdAt, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + if (amount != null) 'amount': amount, + if (categoryId != null) 'category_id': categoryId, + if (month != null) 'month': month, + if (year != null) 'year': year, + if (createdAt != null) 'created_at': createdAt, + }); + } + + BudgetsCompanion copyWith({ + Value? id, + Value? amount, + Value? categoryId, + Value? month, + Value? year, + Value? createdAt, + }) { + return BudgetsCompanion( + id: id ?? this.id, + amount: amount ?? this.amount, + categoryId: categoryId ?? this.categoryId, + month: month ?? this.month, + year: year ?? this.year, + createdAt: createdAt ?? this.createdAt, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (amount.present) { + map['amount'] = Variable(amount.value); + } + if (categoryId.present) { + map['category_id'] = Variable(categoryId.value); + } + if (month.present) { + map['month'] = Variable(month.value); + } + if (year.present) { + map['year'] = Variable(year.value); + } + if (createdAt.present) { + map['created_at'] = Variable(createdAt.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('BudgetsCompanion(') + ..write('id: $id, ') + ..write('amount: $amount, ') + ..write('categoryId: $categoryId, ') + ..write('month: $month, ') + ..write('year: $year, ') + ..write('createdAt: $createdAt') + ..write(')')) + .toString(); + } +} + +class $ExchangeRatesTable extends ExchangeRates + with TableInfo<$ExchangeRatesTable, ExchangeRate> { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + $ExchangeRatesTable(this.attachedDatabase, [this._alias]); + static const VerificationMeta _idMeta = const VerificationMeta('id'); + @override + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + hasAutoIncrement: true, + type: DriftSqlType.int, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'PRIMARY KEY AUTOINCREMENT', + ), + ); + static const VerificationMeta _fromCurrencyMeta = const VerificationMeta( + 'fromCurrency', + ); + @override + late final GeneratedColumn fromCurrency = GeneratedColumn( + 'from_currency', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + static const VerificationMeta _toCurrencyMeta = const VerificationMeta( + 'toCurrency', + ); + @override + late final GeneratedColumn toCurrency = GeneratedColumn( + 'to_currency', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + static const VerificationMeta _rateMeta = const VerificationMeta('rate'); + @override + late final GeneratedColumn rate = GeneratedColumn( + 'rate', + aliasedName, + false, + type: DriftSqlType.double, + requiredDuringInsert: true, + ); + static const VerificationMeta _updatedAtMeta = const VerificationMeta( + 'updatedAt', + ); + @override + late final GeneratedColumn updatedAt = GeneratedColumn( + 'updated_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: currentDateAndTime, + ); + @override + List get $columns => [ + id, + fromCurrency, + toCurrency, + rate, + updatedAt, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'exchange_rates'; + @override + VerificationContext validateIntegrity( + Insertable instance, { + bool isInserting = false, + }) { + final context = VerificationContext(); + final data = instance.toColumns(true); + if (data.containsKey('id')) { + context.handle(_idMeta, id.isAcceptableOrUnknown(data['id']!, _idMeta)); + } + if (data.containsKey('from_currency')) { + context.handle( + _fromCurrencyMeta, + fromCurrency.isAcceptableOrUnknown( + data['from_currency']!, + _fromCurrencyMeta, + ), + ); + } else if (isInserting) { + context.missing(_fromCurrencyMeta); + } + if (data.containsKey('to_currency')) { + context.handle( + _toCurrencyMeta, + toCurrency.isAcceptableOrUnknown(data['to_currency']!, _toCurrencyMeta), + ); + } else if (isInserting) { + context.missing(_toCurrencyMeta); + } + if (data.containsKey('rate')) { + context.handle( + _rateMeta, + rate.isAcceptableOrUnknown(data['rate']!, _rateMeta), + ); + } else if (isInserting) { + context.missing(_rateMeta); + } + if (data.containsKey('updated_at')) { + context.handle( + _updatedAtMeta, + updatedAt.isAcceptableOrUnknown(data['updated_at']!, _updatedAtMeta), + ); + } + return context; + } + + @override + Set get $primaryKey => {id}; + @override + ExchangeRate map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return ExchangeRate( + id: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}id'], + )!, + fromCurrency: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}from_currency'], + )!, + toCurrency: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}to_currency'], + )!, + rate: attachedDatabase.typeMapping.read( + DriftSqlType.double, + data['${effectivePrefix}rate'], + )!, + updatedAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}updated_at'], + )!, + ); + } + + @override + $ExchangeRatesTable createAlias(String alias) { + return $ExchangeRatesTable(attachedDatabase, alias); + } +} + +class ExchangeRate extends DataClass implements Insertable { + final int id; + final String fromCurrency; + final String toCurrency; + final double rate; + final DateTime updatedAt; + const ExchangeRate({ + required this.id, + required this.fromCurrency, + required this.toCurrency, + required this.rate, + required this.updatedAt, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + map['from_currency'] = Variable(fromCurrency); + map['to_currency'] = Variable(toCurrency); + map['rate'] = Variable(rate); + map['updated_at'] = Variable(updatedAt); + return map; + } + + ExchangeRatesCompanion toCompanion(bool nullToAbsent) { + return ExchangeRatesCompanion( + id: Value(id), + fromCurrency: Value(fromCurrency), + toCurrency: Value(toCurrency), + rate: Value(rate), + updatedAt: Value(updatedAt), + ); + } + + factory ExchangeRate.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return ExchangeRate( + id: serializer.fromJson(json['id']), + fromCurrency: serializer.fromJson(json['fromCurrency']), + toCurrency: serializer.fromJson(json['toCurrency']), + rate: serializer.fromJson(json['rate']), + updatedAt: serializer.fromJson(json['updatedAt']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'fromCurrency': serializer.toJson(fromCurrency), + 'toCurrency': serializer.toJson(toCurrency), + 'rate': serializer.toJson(rate), + 'updatedAt': serializer.toJson(updatedAt), + }; + } + + ExchangeRate copyWith({ + int? id, + String? fromCurrency, + String? toCurrency, + double? rate, + DateTime? updatedAt, + }) => ExchangeRate( + id: id ?? this.id, + fromCurrency: fromCurrency ?? this.fromCurrency, + toCurrency: toCurrency ?? this.toCurrency, + rate: rate ?? this.rate, + updatedAt: updatedAt ?? this.updatedAt, + ); + ExchangeRate copyWithCompanion(ExchangeRatesCompanion data) { + return ExchangeRate( + id: data.id.present ? data.id.value : this.id, + fromCurrency: data.fromCurrency.present + ? data.fromCurrency.value + : this.fromCurrency, + toCurrency: data.toCurrency.present + ? data.toCurrency.value + : this.toCurrency, + rate: data.rate.present ? data.rate.value : this.rate, + updatedAt: data.updatedAt.present ? data.updatedAt.value : this.updatedAt, + ); + } + + @override + String toString() { + return (StringBuffer('ExchangeRate(') + ..write('id: $id, ') + ..write('fromCurrency: $fromCurrency, ') + ..write('toCurrency: $toCurrency, ') + ..write('rate: $rate, ') + ..write('updatedAt: $updatedAt') + ..write(')')) + .toString(); + } + + @override + int get hashCode => + Object.hash(id, fromCurrency, toCurrency, rate, updatedAt); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is ExchangeRate && + other.id == this.id && + other.fromCurrency == this.fromCurrency && + other.toCurrency == this.toCurrency && + other.rate == this.rate && + other.updatedAt == this.updatedAt); +} + +class ExchangeRatesCompanion extends UpdateCompanion { + final Value id; + final Value fromCurrency; + final Value toCurrency; + final Value rate; + final Value updatedAt; + const ExchangeRatesCompanion({ + this.id = const Value.absent(), + this.fromCurrency = const Value.absent(), + this.toCurrency = const Value.absent(), + this.rate = const Value.absent(), + this.updatedAt = const Value.absent(), + }); + ExchangeRatesCompanion.insert({ + this.id = const Value.absent(), + required String fromCurrency, + required String toCurrency, + required double rate, + this.updatedAt = const Value.absent(), + }) : fromCurrency = Value(fromCurrency), + toCurrency = Value(toCurrency), + rate = Value(rate); + static Insertable custom({ + Expression? id, + Expression? fromCurrency, + Expression? toCurrency, + Expression? rate, + Expression? updatedAt, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + if (fromCurrency != null) 'from_currency': fromCurrency, + if (toCurrency != null) 'to_currency': toCurrency, + if (rate != null) 'rate': rate, + if (updatedAt != null) 'updated_at': updatedAt, + }); + } + + ExchangeRatesCompanion copyWith({ + Value? id, + Value? fromCurrency, + Value? toCurrency, + Value? rate, + Value? updatedAt, + }) { + return ExchangeRatesCompanion( + id: id ?? this.id, + fromCurrency: fromCurrency ?? this.fromCurrency, + toCurrency: toCurrency ?? this.toCurrency, + rate: rate ?? this.rate, + updatedAt: updatedAt ?? this.updatedAt, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (fromCurrency.present) { + map['from_currency'] = Variable(fromCurrency.value); + } + if (toCurrency.present) { + map['to_currency'] = Variable(toCurrency.value); + } + if (rate.present) { + map['rate'] = Variable(rate.value); + } + if (updatedAt.present) { + map['updated_at'] = Variable(updatedAt.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('ExchangeRatesCompanion(') + ..write('id: $id, ') + ..write('fromCurrency: $fromCurrency, ') + ..write('toCurrency: $toCurrency, ') + ..write('rate: $rate, ') + ..write('updatedAt: $updatedAt') + ..write(')')) + .toString(); + } +} + +abstract class _$AppDatabase extends GeneratedDatabase { + _$AppDatabase(QueryExecutor e) : super(e); + $AppDatabaseManager get managers => $AppDatabaseManager(this); + late final $TransactionsTable transactions = $TransactionsTable(this); + late final $CategoriesTable categories = $CategoriesTable(this); + late final $BudgetsTable budgets = $BudgetsTable(this); + late final $ExchangeRatesTable exchangeRates = $ExchangeRatesTable(this); + @override + Iterable> get allTables => + allSchemaEntities.whereType>(); + @override + List get allSchemaEntities => [ + transactions, + categories, + budgets, + exchangeRates, + ]; + @override + DriftDatabaseOptions get options => + const DriftDatabaseOptions(storeDateTimeAsText: true); +} + +typedef $$TransactionsTableCreateCompanionBuilder = + TransactionsCompanion Function({ + required String id, + required double amount, + required String category, + required String type, + required DateTime date, + Value note, + Value recurrence, + Value lastOccurrence, + Value currency, + Value currencyCode, + Value createdAt, + Value rowid, + }); +typedef $$TransactionsTableUpdateCompanionBuilder = + TransactionsCompanion Function({ + Value id, + Value amount, + Value category, + Value type, + Value date, + Value note, + Value recurrence, + Value lastOccurrence, + Value currency, + Value currencyCode, + Value createdAt, + Value rowid, + }); + +class $$TransactionsTableFilterComposer + extends Composer<_$AppDatabase, $TransactionsTable> { + $$TransactionsTableFilterComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + ColumnFilters get id => $composableBuilder( + column: $table.id, + builder: (column) => ColumnFilters(column), + ); + + ColumnFilters get amount => $composableBuilder( + column: $table.amount, + builder: (column) => ColumnFilters(column), + ); + + ColumnFilters get category => $composableBuilder( + column: $table.category, + builder: (column) => ColumnFilters(column), + ); + + ColumnFilters get type => $composableBuilder( + column: $table.type, + builder: (column) => ColumnFilters(column), + ); + + ColumnFilters get date => $composableBuilder( + column: $table.date, + builder: (column) => ColumnFilters(column), + ); + + ColumnFilters get note => $composableBuilder( + column: $table.note, + builder: (column) => ColumnFilters(column), + ); + + ColumnFilters get recurrence => $composableBuilder( + column: $table.recurrence, + builder: (column) => ColumnFilters(column), + ); + + ColumnFilters get lastOccurrence => $composableBuilder( + column: $table.lastOccurrence, + builder: (column) => ColumnFilters(column), + ); + + ColumnFilters get currency => $composableBuilder( + column: $table.currency, + builder: (column) => ColumnFilters(column), + ); + + ColumnFilters get currencyCode => $composableBuilder( + column: $table.currencyCode, + builder: (column) => ColumnFilters(column), + ); + + ColumnFilters get createdAt => $composableBuilder( + column: $table.createdAt, + builder: (column) => ColumnFilters(column), + ); +} + +class $$TransactionsTableOrderingComposer + extends Composer<_$AppDatabase, $TransactionsTable> { + $$TransactionsTableOrderingComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + ColumnOrderings get id => $composableBuilder( + column: $table.id, + builder: (column) => ColumnOrderings(column), + ); + + ColumnOrderings get amount => $composableBuilder( + column: $table.amount, + builder: (column) => ColumnOrderings(column), + ); + + ColumnOrderings get category => $composableBuilder( + column: $table.category, + builder: (column) => ColumnOrderings(column), + ); + + ColumnOrderings get type => $composableBuilder( + column: $table.type, + builder: (column) => ColumnOrderings(column), + ); + + ColumnOrderings get date => $composableBuilder( + column: $table.date, + builder: (column) => ColumnOrderings(column), + ); + + ColumnOrderings get note => $composableBuilder( + column: $table.note, + builder: (column) => ColumnOrderings(column), + ); + + ColumnOrderings get recurrence => $composableBuilder( + column: $table.recurrence, + builder: (column) => ColumnOrderings(column), + ); + + ColumnOrderings get lastOccurrence => $composableBuilder( + column: $table.lastOccurrence, + builder: (column) => ColumnOrderings(column), + ); + + ColumnOrderings get currency => $composableBuilder( + column: $table.currency, + builder: (column) => ColumnOrderings(column), + ); + + ColumnOrderings get currencyCode => $composableBuilder( + column: $table.currencyCode, + builder: (column) => ColumnOrderings(column), + ); + + ColumnOrderings get createdAt => $composableBuilder( + column: $table.createdAt, + builder: (column) => ColumnOrderings(column), + ); +} + +class $$TransactionsTableAnnotationComposer + extends Composer<_$AppDatabase, $TransactionsTable> { + $$TransactionsTableAnnotationComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + GeneratedColumn get id => + $composableBuilder(column: $table.id, builder: (column) => column); + + GeneratedColumn get amount => + $composableBuilder(column: $table.amount, builder: (column) => column); + + GeneratedColumn get category => + $composableBuilder(column: $table.category, builder: (column) => column); + + GeneratedColumn get type => + $composableBuilder(column: $table.type, builder: (column) => column); + + GeneratedColumn get date => + $composableBuilder(column: $table.date, builder: (column) => column); + + GeneratedColumn get note => + $composableBuilder(column: $table.note, builder: (column) => column); + + GeneratedColumn get recurrence => $composableBuilder( + column: $table.recurrence, + builder: (column) => column, + ); + + GeneratedColumn get lastOccurrence => $composableBuilder( + column: $table.lastOccurrence, + builder: (column) => column, + ); + + GeneratedColumn get currency => + $composableBuilder(column: $table.currency, builder: (column) => column); + + GeneratedColumn get currencyCode => $composableBuilder( + column: $table.currencyCode, + builder: (column) => column, + ); + + GeneratedColumn get createdAt => + $composableBuilder(column: $table.createdAt, builder: (column) => column); +} + +class $$TransactionsTableTableManager + extends + RootTableManager< + _$AppDatabase, + $TransactionsTable, + Transaction, + $$TransactionsTableFilterComposer, + $$TransactionsTableOrderingComposer, + $$TransactionsTableAnnotationComposer, + $$TransactionsTableCreateCompanionBuilder, + $$TransactionsTableUpdateCompanionBuilder, + ( + Transaction, + BaseReferences<_$AppDatabase, $TransactionsTable, Transaction>, + ), + Transaction, + PrefetchHooks Function() + > { + $$TransactionsTableTableManager(_$AppDatabase db, $TransactionsTable table) + : super( + TableManagerState( + db: db, + table: table, + createFilteringComposer: () => + $$TransactionsTableFilterComposer($db: db, $table: table), + createOrderingComposer: () => + $$TransactionsTableOrderingComposer($db: db, $table: table), + createComputedFieldComposer: () => + $$TransactionsTableAnnotationComposer($db: db, $table: table), + updateCompanionCallback: + ({ + Value id = const Value.absent(), + Value amount = const Value.absent(), + Value category = const Value.absent(), + Value type = const Value.absent(), + Value date = const Value.absent(), + Value note = const Value.absent(), + Value recurrence = const Value.absent(), + Value lastOccurrence = const Value.absent(), + Value currency = const Value.absent(), + Value currencyCode = const Value.absent(), + Value createdAt = const Value.absent(), + Value rowid = const Value.absent(), + }) => TransactionsCompanion( + id: id, + amount: amount, + category: category, + type: type, + date: date, + note: note, + recurrence: recurrence, + lastOccurrence: lastOccurrence, + currency: currency, + currencyCode: currencyCode, + createdAt: createdAt, + rowid: rowid, + ), + createCompanionCallback: + ({ + required String id, + required double amount, + required String category, + required String type, + required DateTime date, + Value note = const Value.absent(), + Value recurrence = const Value.absent(), + Value lastOccurrence = const Value.absent(), + Value currency = const Value.absent(), + Value currencyCode = const Value.absent(), + Value createdAt = const Value.absent(), + Value rowid = const Value.absent(), + }) => TransactionsCompanion.insert( + id: id, + amount: amount, + category: category, + type: type, + date: date, + note: note, + recurrence: recurrence, + lastOccurrence: lastOccurrence, + currency: currency, + currencyCode: currencyCode, + createdAt: createdAt, + rowid: rowid, + ), + withReferenceMapper: (p0) => p0 + .map((e) => (e.readTable(table), BaseReferences(db, table, e))) + .toList(), + prefetchHooksCallback: null, + ), + ); +} + +typedef $$TransactionsTableProcessedTableManager = + ProcessedTableManager< + _$AppDatabase, + $TransactionsTable, + Transaction, + $$TransactionsTableFilterComposer, + $$TransactionsTableOrderingComposer, + $$TransactionsTableAnnotationComposer, + $$TransactionsTableCreateCompanionBuilder, + $$TransactionsTableUpdateCompanionBuilder, + ( + Transaction, + BaseReferences<_$AppDatabase, $TransactionsTable, Transaction>, + ), + Transaction, + PrefetchHooks Function() + >; +typedef $$CategoriesTableCreateCompanionBuilder = + CategoriesCompanion Function({ + Value id, + required String name, + required String type, + Value icon, + Value color, + Value isDefault, + Value createdAt, + }); +typedef $$CategoriesTableUpdateCompanionBuilder = + CategoriesCompanion Function({ + Value id, + Value name, + Value type, + Value icon, + Value color, + Value isDefault, + Value createdAt, + }); + +class $$CategoriesTableFilterComposer + extends Composer<_$AppDatabase, $CategoriesTable> { + $$CategoriesTableFilterComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + ColumnFilters get id => $composableBuilder( + column: $table.id, + builder: (column) => ColumnFilters(column), + ); + + ColumnFilters get name => $composableBuilder( + column: $table.name, + builder: (column) => ColumnFilters(column), + ); + + ColumnFilters get type => $composableBuilder( + column: $table.type, + builder: (column) => ColumnFilters(column), + ); + + ColumnFilters get icon => $composableBuilder( + column: $table.icon, + builder: (column) => ColumnFilters(column), + ); + + ColumnFilters get color => $composableBuilder( + column: $table.color, + builder: (column) => ColumnFilters(column), + ); + + ColumnFilters get isDefault => $composableBuilder( + column: $table.isDefault, + builder: (column) => ColumnFilters(column), + ); + + ColumnFilters get createdAt => $composableBuilder( + column: $table.createdAt, + builder: (column) => ColumnFilters(column), + ); +} + +class $$CategoriesTableOrderingComposer + extends Composer<_$AppDatabase, $CategoriesTable> { + $$CategoriesTableOrderingComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + ColumnOrderings get id => $composableBuilder( + column: $table.id, + builder: (column) => ColumnOrderings(column), + ); + + ColumnOrderings get name => $composableBuilder( + column: $table.name, + builder: (column) => ColumnOrderings(column), + ); + + ColumnOrderings get type => $composableBuilder( + column: $table.type, + builder: (column) => ColumnOrderings(column), + ); + + ColumnOrderings get icon => $composableBuilder( + column: $table.icon, + builder: (column) => ColumnOrderings(column), + ); + + ColumnOrderings get color => $composableBuilder( + column: $table.color, + builder: (column) => ColumnOrderings(column), + ); + + ColumnOrderings get isDefault => $composableBuilder( + column: $table.isDefault, + builder: (column) => ColumnOrderings(column), + ); + + ColumnOrderings get createdAt => $composableBuilder( + column: $table.createdAt, + builder: (column) => ColumnOrderings(column), + ); +} + +class $$CategoriesTableAnnotationComposer + extends Composer<_$AppDatabase, $CategoriesTable> { + $$CategoriesTableAnnotationComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + GeneratedColumn get id => + $composableBuilder(column: $table.id, builder: (column) => column); + + GeneratedColumn get name => + $composableBuilder(column: $table.name, builder: (column) => column); + + GeneratedColumn get type => + $composableBuilder(column: $table.type, builder: (column) => column); + + GeneratedColumn get icon => + $composableBuilder(column: $table.icon, builder: (column) => column); + + GeneratedColumn get color => + $composableBuilder(column: $table.color, builder: (column) => column); + + GeneratedColumn get isDefault => + $composableBuilder(column: $table.isDefault, builder: (column) => column); + + GeneratedColumn get createdAt => + $composableBuilder(column: $table.createdAt, builder: (column) => column); +} + +class $$CategoriesTableTableManager + extends + RootTableManager< + _$AppDatabase, + $CategoriesTable, + Category, + $$CategoriesTableFilterComposer, + $$CategoriesTableOrderingComposer, + $$CategoriesTableAnnotationComposer, + $$CategoriesTableCreateCompanionBuilder, + $$CategoriesTableUpdateCompanionBuilder, + (Category, BaseReferences<_$AppDatabase, $CategoriesTable, Category>), + Category, + PrefetchHooks Function() + > { + $$CategoriesTableTableManager(_$AppDatabase db, $CategoriesTable table) + : super( + TableManagerState( + db: db, + table: table, + createFilteringComposer: () => + $$CategoriesTableFilterComposer($db: db, $table: table), + createOrderingComposer: () => + $$CategoriesTableOrderingComposer($db: db, $table: table), + createComputedFieldComposer: () => + $$CategoriesTableAnnotationComposer($db: db, $table: table), + updateCompanionCallback: + ({ + Value id = const Value.absent(), + Value name = const Value.absent(), + Value type = const Value.absent(), + Value icon = const Value.absent(), + Value color = const Value.absent(), + Value isDefault = const Value.absent(), + Value createdAt = const Value.absent(), + }) => CategoriesCompanion( + id: id, + name: name, + type: type, + icon: icon, + color: color, + isDefault: isDefault, + createdAt: createdAt, + ), + createCompanionCallback: + ({ + Value id = const Value.absent(), + required String name, + required String type, + Value icon = const Value.absent(), + Value color = const Value.absent(), + Value isDefault = const Value.absent(), + Value createdAt = const Value.absent(), + }) => CategoriesCompanion.insert( + id: id, + name: name, + type: type, + icon: icon, + color: color, + isDefault: isDefault, + createdAt: createdAt, + ), + withReferenceMapper: (p0) => p0 + .map((e) => (e.readTable(table), BaseReferences(db, table, e))) + .toList(), + prefetchHooksCallback: null, + ), + ); +} + +typedef $$CategoriesTableProcessedTableManager = + ProcessedTableManager< + _$AppDatabase, + $CategoriesTable, + Category, + $$CategoriesTableFilterComposer, + $$CategoriesTableOrderingComposer, + $$CategoriesTableAnnotationComposer, + $$CategoriesTableCreateCompanionBuilder, + $$CategoriesTableUpdateCompanionBuilder, + (Category, BaseReferences<_$AppDatabase, $CategoriesTable, Category>), + Category, + PrefetchHooks Function() + >; +typedef $$BudgetsTableCreateCompanionBuilder = + BudgetsCompanion Function({ + Value id, + required double amount, + Value categoryId, + required int month, + required int year, + Value createdAt, + }); +typedef $$BudgetsTableUpdateCompanionBuilder = + BudgetsCompanion Function({ + Value id, + Value amount, + Value categoryId, + Value month, + Value year, + Value createdAt, + }); + +class $$BudgetsTableFilterComposer + extends Composer<_$AppDatabase, $BudgetsTable> { + $$BudgetsTableFilterComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + ColumnFilters get id => $composableBuilder( + column: $table.id, + builder: (column) => ColumnFilters(column), + ); + + ColumnFilters get amount => $composableBuilder( + column: $table.amount, + builder: (column) => ColumnFilters(column), + ); + + ColumnFilters get categoryId => $composableBuilder( + column: $table.categoryId, + builder: (column) => ColumnFilters(column), + ); + + ColumnFilters get month => $composableBuilder( + column: $table.month, + builder: (column) => ColumnFilters(column), + ); + + ColumnFilters get year => $composableBuilder( + column: $table.year, + builder: (column) => ColumnFilters(column), + ); + + ColumnFilters get createdAt => $composableBuilder( + column: $table.createdAt, + builder: (column) => ColumnFilters(column), + ); +} + +class $$BudgetsTableOrderingComposer + extends Composer<_$AppDatabase, $BudgetsTable> { + $$BudgetsTableOrderingComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + ColumnOrderings get id => $composableBuilder( + column: $table.id, + builder: (column) => ColumnOrderings(column), + ); + + ColumnOrderings get amount => $composableBuilder( + column: $table.amount, + builder: (column) => ColumnOrderings(column), + ); + + ColumnOrderings get categoryId => $composableBuilder( + column: $table.categoryId, + builder: (column) => ColumnOrderings(column), + ); + + ColumnOrderings get month => $composableBuilder( + column: $table.month, + builder: (column) => ColumnOrderings(column), + ); + + ColumnOrderings get year => $composableBuilder( + column: $table.year, + builder: (column) => ColumnOrderings(column), + ); + + ColumnOrderings get createdAt => $composableBuilder( + column: $table.createdAt, + builder: (column) => ColumnOrderings(column), + ); +} + +class $$BudgetsTableAnnotationComposer + extends Composer<_$AppDatabase, $BudgetsTable> { + $$BudgetsTableAnnotationComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + GeneratedColumn get id => + $composableBuilder(column: $table.id, builder: (column) => column); + + GeneratedColumn get amount => + $composableBuilder(column: $table.amount, builder: (column) => column); + + GeneratedColumn get categoryId => $composableBuilder( + column: $table.categoryId, + builder: (column) => column, + ); + + GeneratedColumn get month => + $composableBuilder(column: $table.month, builder: (column) => column); + + GeneratedColumn get year => + $composableBuilder(column: $table.year, builder: (column) => column); + + GeneratedColumn get createdAt => + $composableBuilder(column: $table.createdAt, builder: (column) => column); +} + +class $$BudgetsTableTableManager + extends + RootTableManager< + _$AppDatabase, + $BudgetsTable, + Budget, + $$BudgetsTableFilterComposer, + $$BudgetsTableOrderingComposer, + $$BudgetsTableAnnotationComposer, + $$BudgetsTableCreateCompanionBuilder, + $$BudgetsTableUpdateCompanionBuilder, + (Budget, BaseReferences<_$AppDatabase, $BudgetsTable, Budget>), + Budget, + PrefetchHooks Function() + > { + $$BudgetsTableTableManager(_$AppDatabase db, $BudgetsTable table) + : super( + TableManagerState( + db: db, + table: table, + createFilteringComposer: () => + $$BudgetsTableFilterComposer($db: db, $table: table), + createOrderingComposer: () => + $$BudgetsTableOrderingComposer($db: db, $table: table), + createComputedFieldComposer: () => + $$BudgetsTableAnnotationComposer($db: db, $table: table), + updateCompanionCallback: + ({ + Value id = const Value.absent(), + Value amount = const Value.absent(), + Value categoryId = const Value.absent(), + Value month = const Value.absent(), + Value year = const Value.absent(), + Value createdAt = const Value.absent(), + }) => BudgetsCompanion( + id: id, + amount: amount, + categoryId: categoryId, + month: month, + year: year, + createdAt: createdAt, + ), + createCompanionCallback: + ({ + Value id = const Value.absent(), + required double amount, + Value categoryId = const Value.absent(), + required int month, + required int year, + Value createdAt = const Value.absent(), + }) => BudgetsCompanion.insert( + id: id, + amount: amount, + categoryId: categoryId, + month: month, + year: year, + createdAt: createdAt, + ), + withReferenceMapper: (p0) => p0 + .map((e) => (e.readTable(table), BaseReferences(db, table, e))) + .toList(), + prefetchHooksCallback: null, + ), + ); +} + +typedef $$BudgetsTableProcessedTableManager = + ProcessedTableManager< + _$AppDatabase, + $BudgetsTable, + Budget, + $$BudgetsTableFilterComposer, + $$BudgetsTableOrderingComposer, + $$BudgetsTableAnnotationComposer, + $$BudgetsTableCreateCompanionBuilder, + $$BudgetsTableUpdateCompanionBuilder, + (Budget, BaseReferences<_$AppDatabase, $BudgetsTable, Budget>), + Budget, + PrefetchHooks Function() + >; +typedef $$ExchangeRatesTableCreateCompanionBuilder = + ExchangeRatesCompanion Function({ + Value id, + required String fromCurrency, + required String toCurrency, + required double rate, + Value updatedAt, + }); +typedef $$ExchangeRatesTableUpdateCompanionBuilder = + ExchangeRatesCompanion Function({ + Value id, + Value fromCurrency, + Value toCurrency, + Value rate, + Value updatedAt, + }); + +class $$ExchangeRatesTableFilterComposer + extends Composer<_$AppDatabase, $ExchangeRatesTable> { + $$ExchangeRatesTableFilterComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + ColumnFilters get id => $composableBuilder( + column: $table.id, + builder: (column) => ColumnFilters(column), + ); + + ColumnFilters get fromCurrency => $composableBuilder( + column: $table.fromCurrency, + builder: (column) => ColumnFilters(column), + ); + + ColumnFilters get toCurrency => $composableBuilder( + column: $table.toCurrency, + builder: (column) => ColumnFilters(column), + ); + + ColumnFilters get rate => $composableBuilder( + column: $table.rate, + builder: (column) => ColumnFilters(column), + ); + + ColumnFilters get updatedAt => $composableBuilder( + column: $table.updatedAt, + builder: (column) => ColumnFilters(column), + ); +} + +class $$ExchangeRatesTableOrderingComposer + extends Composer<_$AppDatabase, $ExchangeRatesTable> { + $$ExchangeRatesTableOrderingComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + ColumnOrderings get id => $composableBuilder( + column: $table.id, + builder: (column) => ColumnOrderings(column), + ); + + ColumnOrderings get fromCurrency => $composableBuilder( + column: $table.fromCurrency, + builder: (column) => ColumnOrderings(column), + ); + + ColumnOrderings get toCurrency => $composableBuilder( + column: $table.toCurrency, + builder: (column) => ColumnOrderings(column), + ); + + ColumnOrderings get rate => $composableBuilder( + column: $table.rate, + builder: (column) => ColumnOrderings(column), + ); + + ColumnOrderings get updatedAt => $composableBuilder( + column: $table.updatedAt, + builder: (column) => ColumnOrderings(column), + ); +} + +class $$ExchangeRatesTableAnnotationComposer + extends Composer<_$AppDatabase, $ExchangeRatesTable> { + $$ExchangeRatesTableAnnotationComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + GeneratedColumn get id => + $composableBuilder(column: $table.id, builder: (column) => column); + + GeneratedColumn get fromCurrency => $composableBuilder( + column: $table.fromCurrency, + builder: (column) => column, + ); + + GeneratedColumn get toCurrency => $composableBuilder( + column: $table.toCurrency, + builder: (column) => column, + ); + + GeneratedColumn get rate => + $composableBuilder(column: $table.rate, builder: (column) => column); + + GeneratedColumn get updatedAt => + $composableBuilder(column: $table.updatedAt, builder: (column) => column); +} + +class $$ExchangeRatesTableTableManager + extends + RootTableManager< + _$AppDatabase, + $ExchangeRatesTable, + ExchangeRate, + $$ExchangeRatesTableFilterComposer, + $$ExchangeRatesTableOrderingComposer, + $$ExchangeRatesTableAnnotationComposer, + $$ExchangeRatesTableCreateCompanionBuilder, + $$ExchangeRatesTableUpdateCompanionBuilder, + ( + ExchangeRate, + BaseReferences<_$AppDatabase, $ExchangeRatesTable, ExchangeRate>, + ), + ExchangeRate, + PrefetchHooks Function() + > { + $$ExchangeRatesTableTableManager(_$AppDatabase db, $ExchangeRatesTable table) + : super( + TableManagerState( + db: db, + table: table, + createFilteringComposer: () => + $$ExchangeRatesTableFilterComposer($db: db, $table: table), + createOrderingComposer: () => + $$ExchangeRatesTableOrderingComposer($db: db, $table: table), + createComputedFieldComposer: () => + $$ExchangeRatesTableAnnotationComposer($db: db, $table: table), + updateCompanionCallback: + ({ + Value id = const Value.absent(), + Value fromCurrency = const Value.absent(), + Value toCurrency = const Value.absent(), + Value rate = const Value.absent(), + Value updatedAt = const Value.absent(), + }) => ExchangeRatesCompanion( + id: id, + fromCurrency: fromCurrency, + toCurrency: toCurrency, + rate: rate, + updatedAt: updatedAt, + ), + createCompanionCallback: + ({ + Value id = const Value.absent(), + required String fromCurrency, + required String toCurrency, + required double rate, + Value updatedAt = const Value.absent(), + }) => ExchangeRatesCompanion.insert( + id: id, + fromCurrency: fromCurrency, + toCurrency: toCurrency, + rate: rate, + updatedAt: updatedAt, + ), + withReferenceMapper: (p0) => p0 + .map((e) => (e.readTable(table), BaseReferences(db, table, e))) + .toList(), + prefetchHooksCallback: null, + ), + ); +} + +typedef $$ExchangeRatesTableProcessedTableManager = + ProcessedTableManager< + _$AppDatabase, + $ExchangeRatesTable, + ExchangeRate, + $$ExchangeRatesTableFilterComposer, + $$ExchangeRatesTableOrderingComposer, + $$ExchangeRatesTableAnnotationComposer, + $$ExchangeRatesTableCreateCompanionBuilder, + $$ExchangeRatesTableUpdateCompanionBuilder, + ( + ExchangeRate, + BaseReferences<_$AppDatabase, $ExchangeRatesTable, ExchangeRate>, + ), + ExchangeRate, + PrefetchHooks Function() + >; + +class $AppDatabaseManager { + final _$AppDatabase _db; + $AppDatabaseManager(this._db); + $$TransactionsTableTableManager get transactions => + $$TransactionsTableTableManager(_db, _db.transactions); + $$CategoriesTableTableManager get categories => + $$CategoriesTableTableManager(_db, _db.categories); + $$BudgetsTableTableManager get budgets => + $$BudgetsTableTableManager(_db, _db.budgets); + $$ExchangeRatesTableTableManager get exchangeRates => + $$ExchangeRatesTableTableManager(_db, _db.exchangeRates); +} diff --git a/lib/data/database/tables.dart b/lib/data/database/tables.dart new file mode 100644 index 0000000..82ebd05 --- /dev/null +++ b/lib/data/database/tables.dart @@ -0,0 +1,49 @@ +import 'package:drift/drift.dart'; + +/// Transactions table +class Transactions extends Table { + TextColumn get id => text()(); + RealColumn get amount => real()(); + TextColumn get category => text()(); + TextColumn get type => text()(); // 'income' or 'expense' + DateTimeColumn get date => dateTime()(); + TextColumn get note => text().nullable()(); + TextColumn get recurrence => text().withDefault(const Constant('none'))(); + DateTimeColumn get lastOccurrence => dateTime().nullable()(); + TextColumn get currency => text().withDefault(const Constant('\$'))(); + TextColumn get currencyCode => text().withDefault(const Constant('USD'))(); + DateTimeColumn get createdAt => dateTime().withDefault(currentDateAndTime)(); + + @override + Set get primaryKey => {id}; +} + +/// Categories table for custom categories +class Categories extends Table { + IntColumn get id => integer().autoIncrement()(); + TextColumn get name => text().withLength(min: 1, max: 50)(); + TextColumn get type => text()(); // 'income' or 'expense' + TextColumn get icon => text().nullable()(); + TextColumn get color => text().nullable()(); + BoolColumn get isDefault => boolean().withDefault(const Constant(false))(); + DateTimeColumn get createdAt => dateTime().withDefault(currentDateAndTime)(); +} + +/// Budgets table for monthly budgets +class Budgets extends Table { + IntColumn get id => integer().autoIncrement()(); + RealColumn get amount => real()(); + TextColumn get categoryId => text().nullable()(); + IntColumn get month => integer()(); + IntColumn get year => integer()(); + DateTimeColumn get createdAt => dateTime().withDefault(currentDateAndTime)(); +} + +/// Exchange rates cache +class ExchangeRates extends Table { + IntColumn get id => integer().autoIncrement()(); + TextColumn get fromCurrency => text()(); + TextColumn get toCurrency => text()(); + RealColumn get rate => real()(); + DateTimeColumn get updatedAt => dateTime().withDefault(currentDateAndTime)(); +} diff --git a/lib/data/repositories/transaction_repository.dart b/lib/data/repositories/transaction_repository.dart new file mode 100644 index 0000000..b2ca1b6 --- /dev/null +++ b/lib/data/repositories/transaction_repository.dart @@ -0,0 +1,195 @@ +import 'package:drift/drift.dart'; +import '../../core/utils/result.dart'; +import '../../shared/models/transaction.dart' as model; +import '../database/app_database.dart'; + +class TransactionRepository { + final AppDatabase _db; + + TransactionRepository(this._db); + + /// Get all transactions + Future>> getAll() async { + return asyncResultOf(() async { + final transactions = await _db.getAllTransactions(); + return transactions.map(_toModel).toList(); + }); + } + + /// Get transactions by date range + Future>> getByDateRange( + DateTime start, + DateTime end, + ) async { + return asyncResultOf(() async { + final transactions = await _db.getTransactionsByDateRange(start, end); + return transactions.map(_toModel).toList(); + }); + } + + /// Get transactions by type + Future>> getByType( + model.TransactionType type, + ) async { + return asyncResultOf(() async { + final transactions = await _db.getTransactionsByType(type.name); + return transactions.map(_toModel).toList(); + }); + } + + /// Search transactions + Future>> search(String query) async { + return asyncResultOf(() async { + final transactions = await _db.searchTransactions(query); + return transactions.map(_toModel).toList(); + }); + } + + /// Get transaction by ID + Future> getById(String id) async { + return asyncResultOf(() async { + final transaction = await _db.getTransactionById(id); + return transaction != null ? _toModel(transaction) : null; + }); + } + + /// Add transaction + Future> add(model.Transaction transaction) async { + return asyncResultOf(() async { + final companion = _toCompanion(transaction); + final result = await _db.insertTransaction(companion); + + if (result.isFailure) { + throw Exception(result.errorOrNull); + } + }); + } + + /// Update transaction + Future> update(model.Transaction transaction) async { + return asyncResultOf(() async { + final dbTransaction = _toDbModel(transaction); + final result = await _db.updateTransaction(dbTransaction); + + if (result.isFailure) { + throw Exception(result.errorOrNull); + } + }); + } + + /// Delete transaction + Future> delete(String id) async { + return _db.deleteTransaction(id); + } + + /// Delete all transactions + Future> deleteAll() async { + return asyncResultOf(() async { + await _db.deleteAllTransactions(); + }); + } + + /// Get recurring transactions + Future>> getRecurring() async { + return asyncResultOf(() async { + final transactions = await _db.getRecurringTransactions(); + return transactions.map(_toModel).toList(); + }); + } + + /// Get total balance + Future> getTotalBalance() async { + return asyncResultOf(() async { + return await _db.getTotalBalance(); + }); + } + + /// Get total income for date range + Future> getTotalIncome(DateTime start, DateTime end) async { + return asyncResultOf(() async { + return await _db.getTotalIncome(start, end); + }); + } + + /// Get total expense for date range + Future> getTotalExpense(DateTime start, DateTime end) async { + return asyncResultOf(() async { + return await _db.getTotalExpense(start, end); + }); + } + + /// Get category totals + Future>> getCategoryTotals( + DateTime start, + DateTime end, + model.TransactionType type, + ) async { + return asyncResultOf(() async { + return await _db.getCategoryTotals(start, end, type.name); + }); + } + + // ============================================================================ + // CONVERTERS + // ============================================================================ + + /// Convert database model to app model + model.Transaction _toModel(dynamic dbTransaction) { + return model.Transaction( + id: dbTransaction.id as String, + amount: dbTransaction.amount as double, + category: dbTransaction.category as String, + type: (dbTransaction.type as String) == 'income' + ? model.TransactionType.income + : model.TransactionType.expense, + date: dbTransaction.date as DateTime, + note: dbTransaction.note as String?, + recurrence: _parseRecurrence(dbTransaction.recurrence as String), + lastOccurrence: dbTransaction.lastOccurrence as DateTime?, + currency: dbTransaction.currency as String, + currencyCode: dbTransaction.currencyCode as String, + ); + } + + /// Convert app model to database model + dynamic _toDbModel(model.Transaction transaction) { + // This will be replaced with proper TransactionData after code generation + return TransactionsCompanion( + id: Value(transaction.id), + amount: Value(transaction.amount), + category: Value(transaction.category), + type: Value(transaction.type.name), + date: Value(transaction.date), + note: Value(transaction.note), + recurrence: Value(transaction.recurrence.name), + lastOccurrence: Value(transaction.lastOccurrence), + currency: Value(transaction.currency), + currencyCode: Value(transaction.currencyCode), + createdAt: Value(DateTime.now()), + ); + } + + /// Convert app model to companion for insert + TransactionsCompanion _toCompanion(model.Transaction transaction) { + return TransactionsCompanion( + id: Value(transaction.id), + amount: Value(transaction.amount), + category: Value(transaction.category), + type: Value(transaction.type.name), + date: Value(transaction.date), + note: Value(transaction.note), + recurrence: Value(transaction.recurrence.name), + lastOccurrence: Value(transaction.lastOccurrence), + currency: Value(transaction.currency), + currencyCode: Value(transaction.currencyCode), + ); + } + + /// Parse recurrence type + model.RecurrenceType _parseRecurrence(String recurrence) { + return model.RecurrenceType.values.firstWhere( + (e) => e.name == recurrence, + orElse: () => model.RecurrenceType.none, + ); + } +} diff --git a/lib/features/categories/provider.dart b/lib/features/categories/provider.dart index 176cc86..d91ef2e 100644 --- a/lib/features/categories/provider.dart +++ b/lib/features/categories/provider.dart @@ -3,37 +3,40 @@ import '../../shared/models/transaction.dart'; import '../dashboard/provider.dart'; final categoryExpenseProvider = Provider>((ref) { - final txs = ref.watch(transactionsProvider) - .where((t) => t.type == TransactionType.expense); + final txsAsync = ref.watch(transactionsProvider); + final txs = txsAsync.valueOrNull ?? []; + final filtered = txs.where((t) => t.type == TransactionType.expense); final map = {}; - for (final t in txs) { + for (final t in filtered) { map[t.category] = (map[t.category] ?? 0) + t.amount; } return map; }); final categoryIncomeProvider = Provider>((ref) { - final txs = ref.watch(transactionsProvider) - .where((t) => t.type == TransactionType.income); + final txsAsync = ref.watch(transactionsProvider); + final txs = txsAsync.valueOrNull ?? []; + final filtered = txs.where((t) => t.type == TransactionType.income); final map = {}; - for (final t in txs) { + for (final t in filtered) { map[t.category] = (map[t.category] ?? 0) + t.amount; } return map; }); final monthlyBreakdownProvider = Provider>((ref) { - final txs = ref.watch(transactionsProvider) - .where((t) => t.type == TransactionType.expense); + final txsAsync = ref.watch(transactionsProvider); + final txs = txsAsync.valueOrNull ?? []; + final filtered = txs.where((t) => t.type == TransactionType.expense); final now = DateTime.now(); final months = []; for (var i = 5; i >= 0; i--) { final month = DateTime(now.year, now.month - i, 1); - final total = txs + final total = filtered .where((t) => t.date.year == month.year && t.date.month == month.month) .fold(0.0, (sum, t) => sum + t.amount); months.add(MonthlyData(month: month, amount: total)); diff --git a/lib/features/dashboard/provider.dart b/lib/features/dashboard/provider.dart index b9878c6..041af3b 100644 --- a/lib/features/dashboard/provider.dart +++ b/lib/features/dashboard/provider.dart @@ -3,6 +3,8 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:shared_preferences/shared_preferences.dart'; import '../../core/services/card_color_service.dart'; import '../../core/utils/result.dart'; +import '../../data/database/app_database.dart' as db; +import '../../data/repositories/transaction_repository.dart'; import '../../shared/models/transaction.dart'; import '../../shared/services/storage_service.dart'; import '../settings/provider.dart'; @@ -11,53 +13,86 @@ final sharedPreferencesProvider = Provider((ref) { throw UnimplementedError('Override in main'); }); +final appDatabaseProvider = Provider((ref) { + return db.AppDatabase(); +}); + +final transactionRepositoryProvider = Provider((ref) { + final db = ref.watch(appDatabaseProvider); + return TransactionRepository(db); +}); + final storageServiceProvider = Provider((ref) { return StorageService(ref.watch(sharedPreferencesProvider)); }); final transactionsProvider = - StateNotifierProvider>((ref) { - final storage = ref.watch(storageServiceProvider); - return TransactionsNotifier(storage); + StateNotifierProvider>>(( + ref, + ) { + final repository = ref.watch(transactionRepositoryProvider); + return TransactionsNotifier(repository); }); -class TransactionsNotifier extends StateNotifier> { - final StorageService _storage; +class TransactionsNotifier + extends StateNotifier>> { + final TransactionRepository _repository; - TransactionsNotifier(this._storage) - : super(_storage.loadTransactionsUnsafe()); + TransactionsNotifier(this._repository) : super(const AsyncValue.loading()) { + _load(); + } + + Future _load() async { + state = const AsyncValue.loading(); + final result = await _repository.getAll(); + + state = result.isSuccess + ? AsyncValue.data(result.dataOrNull!) + : AsyncValue.error(result.errorOrNull!, StackTrace.current); + } Future> add(Transaction transaction) async { - final result = await _storage.addTransaction(transaction); - return result.onSuccess((_) { - state = _storage.loadTransactionsUnsafe(); - }); + final result = await _repository.add(transaction); + + if (result.isSuccess) { + await _load(); + } + + return result; } Future> update(Transaction transaction) async { - final result = await _storage.updateTransaction(transaction); - return result.onSuccess((_) { - state = _storage.loadTransactionsUnsafe(); - }); + final result = await _repository.update(transaction); + + if (result.isSuccess) { + await _load(); + } + + return result; } Future> delete(String id) async { - final result = await _storage.deleteTransaction(id); - return result.onSuccess((_) { - state = _storage.loadTransactionsUnsafe(); - }); + final result = await _repository.delete(id); + + if (result.isSuccess) { + await _load(); + } + + return result; } - void restore(Transaction transaction) { - state = [...state, transaction]; - _storage.addTransaction(transaction); + Future restore(Transaction transaction) async { + await _repository.add(transaction); + await _load(); } - void clearAll() { - state = []; - SharedPreferences.getInstance().then( - (prefs) => prefs.remove('transactions'), - ); + Future clearAll() async { + await _repository.deleteAll(); + state = const AsyncValue.data([]); + } + + Future refresh() async { + await _load(); } } @@ -76,7 +111,8 @@ final timeFilterProvider = StateProvider( ); final totalBalanceProvider = Provider((ref) { - final txs = ref.watch(transactionsProvider); + final txsAsync = ref.watch(transactionsProvider); + final txs = txsAsync.valueOrNull ?? []; final exchangeService = ref.watch(exchangeRateServiceProvider); final targetCurrency = ref.watch(currencyProvider).code; @@ -91,26 +127,26 @@ final totalBalanceProvider = Provider((ref) { }); final totalIncomeProvider = Provider((ref) { - final txs = ref - .watch(transactionsProvider) - .where((t) => t.type == TransactionType.income); + final txsAsync = ref.watch(transactionsProvider); + final txs = txsAsync.valueOrNull ?? []; + final filtered = txs.where((t) => t.type == TransactionType.income); final exchangeService = ref.watch(exchangeRateServiceProvider); final targetCurrency = ref.watch(currencyProvider).code; - return txs.fold(0.0, (sum, t) { + return filtered.fold(0.0, (sum, t) { return sum + exchangeService.convert(t.amount, t.currencyCode, targetCurrency); }); }); final totalExpenseProvider = Provider((ref) { - final txs = ref - .watch(transactionsProvider) - .where((t) => t.type == TransactionType.expense); + final txsAsync = ref.watch(transactionsProvider); + final txs = txsAsync.valueOrNull ?? []; + final filtered = txs.where((t) => t.type == TransactionType.expense); final exchangeService = ref.watch(exchangeRateServiceProvider); final targetCurrency = ref.watch(currencyProvider).code; - return txs.fold(0.0, (sum, t) { + return filtered.fold(0.0, (sum, t) { return sum + exchangeService.convert(t.amount, t.currencyCode, targetCurrency); }); @@ -118,25 +154,26 @@ final totalExpenseProvider = Provider((ref) { final currentMonthExpenseProvider = Provider((ref) { final now = DateTime.now(); - final txs = ref - .watch(transactionsProvider) - .where( - (t) => - t.type == TransactionType.expense && - t.date.year == now.year && - t.date.month == now.month, - ); + final txsAsync = ref.watch(transactionsProvider); + final txs = txsAsync.valueOrNull ?? []; + final filtered = txs.where( + (t) => + t.type == TransactionType.expense && + t.date.year == now.year && + t.date.month == now.month, + ); final exchangeService = ref.watch(exchangeRateServiceProvider); final targetCurrency = ref.watch(currencyProvider).code; - return txs.fold(0.0, (sum, t) { + return filtered.fold(0.0, (sum, t) { return sum + exchangeService.convert(t.amount, t.currencyCode, targetCurrency); }); }); final filteredTransactionsProvider = Provider>((ref) { - final txs = ref.watch(transactionsProvider); + final txsAsync = ref.watch(transactionsProvider); + final txs = txsAsync.valueOrNull ?? []; final query = ref.watch(searchQueryProvider).toLowerCase(); final typeFilter = ref.watch(transactionFilterProvider); final timeFilter = ref.watch(timeFilterProvider); diff --git a/lib/features/settings/provider.dart b/lib/features/settings/provider.dart index 7644100..9adc0e8 100644 --- a/lib/features/settings/provider.dart +++ b/lib/features/settings/provider.dart @@ -26,7 +26,11 @@ class BudgetNotifier extends StateNotifier { state = budget; } - void onCurrencyChanged(String oldCode, String newCode, ExchangeRateService rates) { + void onCurrencyChanged( + String oldCode, + String newCode, + ExchangeRateService rates, + ) { if (state == null) return; final converted = rates.convert(state!, oldCode, newCode); setBudget(converted); @@ -64,12 +68,12 @@ class CurrencyNotifier extends StateNotifier { } } -final currencyProvider = StateNotifierProvider( - (ref) { - final prefs = ref.watch(sharedPreferencesProvider); - return CurrencyNotifier(prefs); - }, -); +final currencyProvider = StateNotifierProvider(( + ref, +) { + final prefs = ref.watch(sharedPreferencesProvider); + return CurrencyNotifier(prefs); +}); class ThemeModeNotifier extends StateNotifier { final SharedPreferences _prefs; @@ -95,12 +99,12 @@ class ThemeModeNotifier extends StateNotifier { } } -final themeProvider = StateNotifierProvider( - (ref) { - final prefs = ref.watch(sharedPreferencesProvider); - return ThemeModeNotifier(prefs); - }, -); +final themeProvider = StateNotifierProvider(( + ref, +) { + final prefs = ref.watch(sharedPreferencesProvider); + return ThemeModeNotifier(prefs); +}); final exchangeRateServiceProvider = Provider((ref) { final prefs = ref.watch(sharedPreferencesProvider); @@ -111,7 +115,9 @@ final ratesInitProvider = FutureProvider((ref) async { await ref.read(exchangeRateServiceProvider).fetchRates(); }); -final hapticEnabledProvider = StateNotifierProvider((ref) { +final hapticEnabledProvider = StateNotifierProvider(( + ref, +) { return HapticNotifier(); }); @@ -141,7 +147,8 @@ class ExportService { ExportService(this._ref); Future exportToCSV() async { - final transactions = _ref.read(transactionsProvider); + final transactionsAsync = _ref.read(transactionsProvider); + final transactions = transactionsAsync.valueOrNull ?? []; final currency = _ref.read(currencyProvider); final fmt = _ref.read(amountFormatProvider); diff --git a/lib/main.dart b/lib/main.dart index 4920336..a278d03 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,15 +1,15 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:google_fonts/google_fonts.dart'; import 'package:intl/date_symbol_data_local.dart'; import 'package:shared_preferences/shared_preferences.dart'; import 'app/app.dart'; import 'core/services/haptic_service.dart'; +import 'data/database/app_database.dart'; import 'features/dashboard/provider.dart'; void main() async { WidgetsFlutterBinding.ensureInitialized(); - + await initializeDateFormatting('en_US', null); await initializeDateFormatting('ru_RU', null); await initializeDateFormatting('en', null); @@ -18,9 +18,14 @@ void main() async { final prefs = await SharedPreferences.getInstance(); await HapticService.init(); + final database = AppDatabase(); + runApp( ProviderScope( - overrides: [sharedPreferencesProvider.overrideWithValue(prefs)], + overrides: [ + sharedPreferencesProvider.overrideWithValue(prefs), + appDatabaseProvider.overrideWithValue(database), + ], child: const App(), ), ); diff --git a/linux/flutter/generated_plugin_registrant.cc b/linux/flutter/generated_plugin_registrant.cc index e71a16d..2c1ec4f 100644 --- a/linux/flutter/generated_plugin_registrant.cc +++ b/linux/flutter/generated_plugin_registrant.cc @@ -6,6 +6,10 @@ #include "generated_plugin_registrant.h" +#include void fl_register_plugins(FlPluginRegistry* registry) { + g_autoptr(FlPluginRegistrar) sqlite3_flutter_libs_registrar = + fl_plugin_registry_get_registrar_for_plugin(registry, "Sqlite3FlutterLibsPlugin"); + sqlite3_flutter_libs_plugin_register_with_registrar(sqlite3_flutter_libs_registrar); } diff --git a/linux/flutter/generated_plugins.cmake b/linux/flutter/generated_plugins.cmake index 2e1de87..7ea2a80 100644 --- a/linux/flutter/generated_plugins.cmake +++ b/linux/flutter/generated_plugins.cmake @@ -3,6 +3,7 @@ # list(APPEND FLUTTER_PLUGIN_LIST + sqlite3_flutter_libs ) list(APPEND FLUTTER_FFI_PLUGIN_LIST diff --git a/macos/Flutter/GeneratedPluginRegistrant.swift b/macos/Flutter/GeneratedPluginRegistrant.swift index 80dc39e..89bcd1d 100644 --- a/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/macos/Flutter/GeneratedPluginRegistrant.swift @@ -7,8 +7,10 @@ import Foundation import local_auth_darwin import shared_preferences_foundation +import sqlite3_flutter_libs func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { LocalAuthPlugin.register(with: registry.registrar(forPlugin: "LocalAuthPlugin")) SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin")) + Sqlite3FlutterLibsPlugin.register(with: registry.registrar(forPlugin: "Sqlite3FlutterLibsPlugin")) } diff --git a/pubspec.lock b/pubspec.lock index b93fc83..b31a01d 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -1,6 +1,22 @@ # Generated by pub # See https://dart.dev/tools/pub/glossary#lockfile packages: + _fe_analyzer_shared: + dependency: transitive + description: + name: _fe_analyzer_shared + sha256: "8d7ff3948166b8ec5da0fbb5962000926b8e02f2ed9b3e51d1738905fbd4c98d" + url: "https://pub.dev" + source: hosted + version: "93.0.0" + analyzer: + dependency: transitive + description: + name: analyzer + sha256: de7148ed2fcec579b19f122c1800933dfa028f6d9fd38a152b04b1516cec120b + url: "https://pub.dev" + source: hosted + version: "10.0.1" ansicolor: dependency: transitive description: @@ -41,6 +57,54 @@ packages: url: "https://pub.dev" source: hosted version: "2.1.2" + build: + dependency: transitive + description: + name: build + sha256: aadd943f4f8cc946882c954c187e6115a84c98c81ad1d9c6cbf0895a8c85da9c + url: "https://pub.dev" + source: hosted + version: "4.0.5" + build_config: + dependency: transitive + description: + name: build_config + sha256: "4070d2a59f8eec34c97c86ceb44403834899075f66e8a9d59706f8e7834f6f71" + url: "https://pub.dev" + source: hosted + version: "1.3.0" + build_daemon: + dependency: transitive + description: + name: build_daemon + sha256: bf05f6e12cfea92d3c09308d7bcdab1906cd8a179b023269eed00c071004b957 + url: "https://pub.dev" + source: hosted + version: "4.1.1" + build_runner: + dependency: "direct dev" + description: + name: build_runner + sha256: "521daf8d189deb79ba474e43a696b41c49fb3987818dbacf3308f1e03673a75e" + url: "https://pub.dev" + source: hosted + version: "2.13.1" + built_collection: + dependency: transitive + description: + name: built_collection + sha256: "376e3dd27b51ea877c28d525560790aee2e6fbb5f20e2f85d5081027d94e2100" + url: "https://pub.dev" + source: hosted + version: "5.1.1" + built_value: + dependency: transitive + description: + name: built_value + sha256: "6ae8a6435a8c6520c7077b107e77f1fb4ba7009633259a4d49a8afd8e7efc5e9" + url: "https://pub.dev" + source: hosted + version: "8.12.4" characters: dependency: transitive description: @@ -49,6 +113,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.4.1" + charcode: + dependency: transitive + description: + name: charcode + sha256: fb0f1107cac15a5ea6ef0a6ef71a807b9e4267c713bb93e00e92d737cc8dbd8a + url: "https://pub.dev" + source: hosted + version: "1.4.0" checked_yaml: dependency: transitive description: @@ -81,6 +153,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.0.0" + code_builder: + dependency: transitive + description: + name: code_builder + sha256: "6a6cab2ba4680d6423f34a9b972a4c9a94ebe1b62ecec4e1a1f2cba91fd1319d" + url: "https://pub.dev" + source: hosted + version: "4.11.1" collection: dependency: transitive description: @@ -89,6 +169,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.19.1" + convert: + dependency: transitive + description: + name: convert + sha256: b30acd5944035672bc15c6b7a8b47d773e41e2f17de064350988c5d02adb1c68 + url: "https://pub.dev" + source: hosted + version: "3.1.2" crypto: dependency: transitive description: @@ -113,6 +201,30 @@ packages: url: "https://pub.dev" source: hosted version: "1.0.8" + dart_style: + dependency: transitive + description: + name: dart_style + sha256: "29f7ecc274a86d32920b1d9cfc7502fa87220da41ec60b55f329559d5732e2b2" + url: "https://pub.dev" + source: hosted + version: "3.1.7" + drift: + dependency: "direct main" + description: + name: drift + sha256: "61f876c0291b194980bafd203f48e85d5fb04e4a7334367d1a89f44004dbcb83" + url: "https://pub.dev" + source: hosted + version: "2.32.0" + drift_dev: + dependency: "direct dev" + description: + name: drift_dev + sha256: d687e955cc4b1706ad49b3860fcc1045c09bbf1d84c3c7383615f7f9c3320aa2 + url: "https://pub.dev" + source: hosted + version: "2.32.0" equatable: dependency: transitive description: @@ -248,6 +360,14 @@ packages: url: "https://pub.dev" source: hosted version: "6.3.3" + graphs: + dependency: transitive + description: + name: graphs + sha256: "741bbf84165310a68ff28fe9e727332eef1407342fca52759cb21ad8177bb8d0" + url: "https://pub.dev" + source: hosted + version: "2.3.2" hooks: dependency: transitive description: @@ -272,6 +392,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.6.0" + http_multi_server: + dependency: transitive + description: + name: http_multi_server + sha256: aa6199f908078bb1c5efb8d8638d4ae191aac11b311132c3ef48ce352fb52ef8 + url: "https://pub.dev" + source: hosted + version: "3.2.2" http_parser: dependency: transitive description: @@ -296,6 +424,14 @@ packages: url: "https://pub.dev" source: hosted version: "0.19.0" + io: + dependency: transitive + description: + name: io + sha256: dfd5a80599cf0165756e3181807ed3e77daf6dd4137caaad72d0b7931597650b + url: "https://pub.dev" + source: hosted + version: "1.0.5" json_annotation: dependency: transitive description: @@ -408,6 +544,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.17.0" + mime: + dependency: transitive + description: + name: mime + sha256: "41a20518f0cb1256669420fdba0cd90d21561e560ac240f26ef8322e45bb7ed6" + url: "https://pub.dev" + source: hosted + version: "2.0.0" native_toolchain_c: dependency: transitive description: @@ -424,8 +568,16 @@ packages: url: "https://pub.dev" source: hosted version: "9.3.0" - path: + package_config: dependency: transitive + description: + name: package_config + sha256: f096c55ebb7deb7e384101542bfba8c52696c1b56fca2eb62827989ef2353bbc + url: "https://pub.dev" + source: hosted + version: "2.2.0" + path: + dependency: "direct main" description: name: path sha256: "75cca69d1490965be98c73ceaea117e8a04dd21217b37b292c9ddbec0d955bc5" @@ -504,6 +656,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.1.8" + pool: + dependency: transitive + description: + name: pool + sha256: "978783255c543aa3586a1b3c21f6e9d720eb315376a915872c61ef8b5c20177d" + url: "https://pub.dev" + source: hosted + version: "1.5.2" posix: dependency: transitive description: @@ -520,6 +680,22 @@ packages: url: "https://pub.dev" source: hosted version: "2.2.0" + pubspec_parse: + dependency: transitive + description: + name: pubspec_parse + sha256: "0560ba233314abbed0a48a2956f7f022cce7c3e1e73df540277da7544cad4082" + url: "https://pub.dev" + source: hosted + version: "1.5.0" + recase: + dependency: transitive + description: + name: recase + sha256: e4eb4ec2dcdee52dcf99cb4ceabaffc631d7424ee55e56f280bc039737f89213 + url: "https://pub.dev" + source: hosted + version: "4.1.0" riverpod: dependency: transitive description: @@ -600,11 +776,35 @@ packages: url: "https://pub.dev" source: hosted version: "2.4.1" + shelf: + dependency: transitive + description: + name: shelf + sha256: e7dd780a7ffb623c57850b33f43309312fc863fb6aa3d276a754bb299839ef12 + url: "https://pub.dev" + source: hosted + version: "1.4.2" + shelf_web_socket: + dependency: transitive + description: + name: shelf_web_socket + sha256: "3632775c8e90d6c9712f883e633716432a27758216dfb61bd86a8321c0580925" + url: "https://pub.dev" + source: hosted + version: "3.0.0" sky_engine: dependency: transitive description: flutter source: sdk version: "0.0.0" + source_gen: + dependency: transitive + description: + name: source_gen + sha256: "732792cfd197d2161a65bb029606a46e0a18ff30ef9e141a7a82172b05ea8ecd" + url: "https://pub.dev" + source: hosted + version: "4.2.2" source_span: dependency: transitive description: @@ -613,6 +813,30 @@ packages: url: "https://pub.dev" source: hosted version: "1.10.2" + sqlite3: + dependency: transitive + description: + name: sqlite3 + sha256: caa693ad15a587a2b4fde093b728131a1827903872171089dedb16f7665d3a91 + url: "https://pub.dev" + source: hosted + version: "3.2.0" + sqlite3_flutter_libs: + dependency: "direct main" + description: + name: sqlite3_flutter_libs + sha256: eeb9e3a45207649076b808f8a5a74d68770d0b7f26ccef6d5f43106eee5375ad + url: "https://pub.dev" + source: hosted + version: "0.5.42" + sqlparser: + dependency: transitive + description: + name: sqlparser + sha256: ab2b467425f1d4f3acfa5fd11a08226f7d6c26ff102c06be1807e1dff34e050b + url: "https://pub.dev" + source: hosted + version: "0.44.3" stack_trace: dependency: transitive description: @@ -637,6 +861,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.1.4" + stream_transform: + dependency: transitive + description: + name: stream_transform + sha256: ad47125e588cfd37a9a7f86c7d6356dde8dfe89d071d293f80ca9e9273a33871 + url: "https://pub.dev" + source: hosted + version: "2.1.1" string_scanner: dependency: transitive description: @@ -701,6 +933,14 @@ packages: url: "https://pub.dev" source: hosted version: "15.0.2" + watcher: + dependency: transitive + description: + name: watcher + sha256: "1398c9f081a753f9226febe8900fce8f7d0a67163334e1c94a2438339d79d635" + url: "https://pub.dev" + source: hosted + version: "1.2.1" web: dependency: transitive description: @@ -709,6 +949,22 @@ packages: url: "https://pub.dev" source: hosted version: "1.1.1" + web_socket: + dependency: transitive + description: + name: web_socket + sha256: "34d64019aa8e36bf9842ac014bb5d2f5586ca73df5e4d9bf5c936975cae6982c" + url: "https://pub.dev" + source: hosted + version: "1.0.1" + web_socket_channel: + dependency: transitive + description: + name: web_socket_channel + sha256: d645757fb0f4773d602444000a8131ff5d48c9e47adfe9772652dd1a4f2d45c8 + url: "https://pub.dev" + source: hosted + version: "3.0.3" xdg_directories: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 3dfcd1a..9f9a48d 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -22,6 +22,9 @@ dependencies: sensors_plus: ^6.1.0 local_auth: ^2.3.0 flutter_colorpicker: ^1.1.0 + drift: ^2.14.1 + sqlite3_flutter_libs: ^0.5.20 + path: ^1.8.3 flutter_launcher_icons: android: true @@ -50,6 +53,8 @@ dev_dependencies: flutter_lints: ^6.0.0 flutter_launcher_icons: ^0.14.1 flutter_native_splash: ^2.4.3 + drift_dev: ^2.14.1 + build_runner: ^2.4.7 flutter: uses-material-design: true diff --git a/windows/flutter/generated_plugin_registrant.cc b/windows/flutter/generated_plugin_registrant.cc index 7407ddd..5888a93 100644 --- a/windows/flutter/generated_plugin_registrant.cc +++ b/windows/flutter/generated_plugin_registrant.cc @@ -7,8 +7,11 @@ #include "generated_plugin_registrant.h" #include +#include void RegisterPlugins(flutter::PluginRegistry* registry) { LocalAuthPluginRegisterWithRegistrar( registry->GetRegistrarForPlugin("LocalAuthPlugin")); + Sqlite3FlutterLibsPluginRegisterWithRegistrar( + registry->GetRegistrarForPlugin("Sqlite3FlutterLibsPlugin")); } diff --git a/windows/flutter/generated_plugins.cmake b/windows/flutter/generated_plugins.cmake index ef187dc..8881dfe 100644 --- a/windows/flutter/generated_plugins.cmake +++ b/windows/flutter/generated_plugins.cmake @@ -4,6 +4,7 @@ list(APPEND FLUTTER_PLUGIN_LIST local_auth_windows + sqlite3_flutter_libs ) list(APPEND FLUTTER_FFI_PLUGIN_LIST