refactor: migrate database from Drift to Isar

This commit is contained in:
2025-05-04 11:13:42 +03:00
parent 44173f527e
commit 30b9e66a7a
6 changed files with 198 additions and 144 deletions
+5 -4
View File
@@ -1,12 +1,13 @@
import 'package:flutter/material.dart';
import 'package:isar/isar.dart'; // Импорт Isar
import 'screens/expenses_screen.dart';
import 'theme/app_theme.dart';
import 'database/database.dart'; // Import the database
// import 'database/database.dart'; // Больше не нужен прямой импорт AppDatabase или IsarService
class MyApp extends StatefulWidget {
final AppDatabase database; // Принимаем экземпляр базы данных
final Isar isar; // Принимаем экземпляр Isar
const MyApp({Key? key, required this.database}) : super(key: key);
const MyApp({Key? key, required this.isar}) : super(key: key); // Обновленный конструктор
@override
State<MyApp> createState() => _MyAppState();
@@ -32,7 +33,7 @@ class _MyAppState extends State<MyApp> {
theme: AppTheme.lightTheme, // Светлая тема
darkTheme: AppTheme.darkTheme, // Темная тема
home: ExpensesScreen(
database: widget.database, // Передаем базу данных в главный экран
isar: widget.isar, // Передаем Isar в главный экран
toggleTheme: toggleTheme, // Передаем функцию переключения темы
isDarkMode: _isDarkMode, // Передаем текущее состояние темы
),
+84 -91
View File
@@ -1,80 +1,75 @@
import 'dart:io';
import 'package:drift/drift.dart';
import 'package:drift/native.dart';
// import 'package:flutter/material.dart'; // Не требуется напрямую, т.к. Category импортируется
import 'dart:async';
import 'package:flutter/material.dart'; // Для Color/IconData в Category
import 'package:isar/isar.dart';
import 'package:path_provider/path_provider.dart';
import 'package:path/path.dart' as p;
// import 'package:rxdart/rxdart.dart'; // Не используется
import 'package:rxdart/rxdart.dart'; // Используем для map на потоке
// Импортируем модель категории для возвращаемого типа и утилиты
// Импортируем модели
import '../models/category.dart';
// Удален ненужный импорт: import '../models/transaction_record.dart';
import '../utils/category_utils.dart'; // Helper for category details
import '../models/isar_transaction.dart'; // Наша Isar модель
import '../utils/category_utils.dart'; // Помощник для деталей категории
// Эта строка указывает Drift сгенерировать файл database.g.dart
part 'database.g.dart';
// Этот файл больше не использует Drift, поэтому part 'database.g.dart'; удален.
// Определение таблицы Transactions
// Имя таблицы в SQL будет 'transactions' (snake_case от имени класса)
@DataClassName('Transaction') // Keep the generated class name as Transaction
class Transactions extends Table {
IntColumn get id => integer().autoIncrement()(); // Primary key
TextColumn get categoryName => text().named('category_name')(); // Имя категории
RealColumn get amount => real()(); // Сумма транзакции
DateTimeColumn get date => dateTime()(); // Дата транзакции
TextColumn get merchant => text()(); // Название продавца/магазина
}
// Класс-сервис для инкапсуляции логики работы с Isar
class IsarService {
final Isar isar;
// Класс базы данных
// Аннотация @DriftDatabase указывает Drift сгенерировать код для этой базы данных
@DriftDatabase(tables: [Transactions])
class AppDatabase extends _$AppDatabase {
AppDatabase() : super(_openConnection());
// Версия схемы. Увеличивайте при изменении структуры таблиц.
@override
int get schemaVersion => 1;
IsarService(this.isar);
// --- Методы для работы с транзакциями ---
// Получить все транзакции в виде потока, упорядоченные по дате (сначала новые)
// Возвращает non-nullable Stream<List<Transaction>> (Transaction - сгенерированный Drift класс)
Stream<List<Transaction>> watchAllTransactions() {
return (select(transactions)
..orderBy([(t) => OrderingTerm(expression: t.date, mode: OrderingMode.desc)]))
.watch();
// Возвращает Stream<List<IsarTransaction>>
Stream<List<IsarTransaction>> watchAllTransactions() {
// isar.isarTransactions ссылается на коллекцию IsarTransaction
// .where() без условий выбирает все
// .sortByDateDesc() сортирует по полю date по убыванию
// .watch(fireImmediately: true) создает поток, который срабатывает сразу
return isar.isarTransactions
.where()
.sortByDateDesc()
.watch(fireImmediately: true);
}
// Получить транзакции, отфильтрованные по категории, в виде потока
// Возвращает non-nullable Stream<List<Transaction>> (Transaction - сгенерированный Drift класс)
Stream<List<Transaction>> watchFilteredTransactions(String categoryName) {
if (categoryName == 'All') {
// watchAllTransactions теперь возвращает non-nullable Stream
return watchAllTransactions(); // Return all if filter is 'All'
}
// Otherwise, filter by the provided category name
return (select(transactions)
..where((t) => t.categoryName.equals(categoryName))
..orderBy([(t) => OrderingTerm(expression: t.date, mode: OrderingMode.desc)]))
.watch();
}
// Возвращает Stream<List<IsarTransaction>>
Stream<List<IsarTransaction>> watchFilteredTransactions(String categoryName) {
if (categoryName == 'All') {
return watchAllTransactions(); // Возвращаем все, если фильтр 'All'
}
// Иначе фильтруем по categoryName
return isar.isarTransactions
.filter()
.categoryNameEqualTo(categoryName) // Используем сгенерированный Isar фильтр
.sortByDateDesc()
.watch(fireImmediately: true);
}
// Добавить новую транзакцию
// Принимает TransactionsCompanion - сгенерированный Drift класс
Future<int> addTransaction(TransactionsCompanion entry) {
return into(transactions).insert(entry);
// Принимает объект IsarTransaction
Future<void> addTransaction(IsarTransaction transaction) async {
// Используем writeTxn для выполнения операции записи
await isar.writeTxn(() async {
await isar.isarTransactions.put(transaction); // put добавляет или обновляет запись
});
}
// Добавить несколько транзакций (для начальных данных)
Future<void> addTransactions(List<IsarTransaction> transactions) async {
await isar.writeTxn(() async {
await isar.isarTransactions.putAll(transactions);
});
}
// --- Методы для работы с категориями (вычисляются из транзакций) ---
// Вычислить и наблюдать за общими суммами по категориям
Stream<List<Category>> calculateCategoryTotals() {
// 1. Получаем поток всех транзакций (теперь non-nullable Stream<List<Transaction>>)
// 1. Получаем поток всех транзакций
return watchAllTransactions().map((transactionList) {
// 2. Группируем транзакции по categoryName и суммируем amount
final categoryTotals = <String, double>{};
// transactionList теперь содержит объекты Transaction, сгенерированные Drift
for (var transaction in transactionList) {
categoryTotals.update(
transaction.categoryName,
@@ -85,9 +80,6 @@ class AppDatabase extends _$AppDatabase {
// 3. Преобразуем сгруппированные данные в список объектов Category
return categoryTotals.entries.map((entry) {
// Используем CategoryUtils для получения деталей (иконка, цвет)
// Убедитесь, что CategoryUtils корректно обрабатывает все categoryName,
// включая 'Utilities' из начальных данных, или предоставляет дефолтные значения.
final categoryDetails = CategoryUtils.getCategoryDetails(entry.key);
return Category(
entry.key, // name
@@ -96,7 +88,7 @@ class AppDatabase extends _$AppDatabase {
categoryDetails.iconCode,
);
}).toList()
// Сортируем категории по сумме (от большей к меньшей) для диаграммы/легенды
// Сортируем категории по сумме (от большей к меньшей)
..sort((a, b) => b.amount.compareTo(a.amount));
});
}
@@ -104,42 +96,43 @@ class AppDatabase extends _$AppDatabase {
// Пример добавления начальных данных (если база данных пуста)
Future<void> insertInitialDataIfNeeded() async {
// Проверяем, есть ли уже транзакции
final existingTransactions = await select(transactions).get();
if (existingTransactions.isEmpty) {
print("Database is empty. Inserting initial data...");
// Используем batch для эффективной вставки нескольких записей
await batch((batch) {
batch.insertAll(transactions, [
// Используем TransactionsCompanion для создания записей для вставки
TransactionsCompanion.insert(categoryName: 'Groceries', amount: 45.99, date: DateTime.now().subtract(const Duration(days: 1, hours: 2)), merchant: 'Whole Foods Market'),
TransactionsCompanion.insert(categoryName: 'Subscriptions', amount: 39.99, date: DateTime.now().subtract(const Duration(days: 2, hours: 5)), merchant: 'Netflix Premium'),
TransactionsCompanion.insert(categoryName: 'Restaurant', amount: 78.50, date: DateTime.now().subtract(const Duration(days: 2, hours: 19)), merchant: 'Italian Corner'),
TransactionsCompanion.insert(categoryName: 'Shopping', amount: 132.75, date: DateTime.now().subtract(const Duration(days: 3, hours: 11)), merchant: 'Apple Store'),
TransactionsCompanion.insert(categoryName: 'Groceries', amount: 23.45, date: DateTime.now().subtract(const Duration(days: 4, hours: 9)), merchant: 'Local Market'),
TransactionsCompanion.insert(categoryName: 'Transport', amount: 15.00, date: DateTime.now().subtract(const Duration(days: 5, hours: 8)), merchant: 'City Bus'),
TransactionsCompanion.insert(categoryName: 'Restaurant', amount: 56.80, date: DateTime.now().subtract(const Duration(days: 5, hours: 20)), merchant: 'Sushi Express'),
// Убедитесь, что 'Utilities' определена в CategoryUtils или обрабатывается как неизвестная категория
TransactionsCompanion.insert(categoryName: 'Utilities', amount: 85.20, date: DateTime.now().subtract(const Duration(days: 6, hours: 10)), merchant: 'Electricity Bill'),
]);
});
print("Initial data inserted successfully.");
final count = await isar.isarTransactions.count();
if (count == 0) {
print("Isar Database is empty. Inserting initial data...");
final initialTransactions = [
IsarTransaction(categoryName: 'Groceries', amount: 45.99, date: DateTime.now().subtract(const Duration(days: 1, hours: 2)), merchant: 'Whole Foods Market'),
IsarTransaction(categoryName: 'Subscriptions', amount: 39.99, date: DateTime.now().subtract(const Duration(days: 2, hours: 5)), merchant: 'Netflix Premium'),
IsarTransaction(categoryName: 'Restaurant', amount: 78.50, date: DateTime.now().subtract(const Duration(days: 2, hours: 19)), merchant: 'Italian Corner'),
IsarTransaction(categoryName: 'Shopping', amount: 132.75, date: DateTime.now().subtract(const Duration(days: 3, hours: 11)), merchant: 'Apple Store'),
IsarTransaction(categoryName: 'Groceries', amount: 23.45, date: DateTime.now().subtract(const Duration(days: 4, hours: 9)), merchant: 'Local Market'),
IsarTransaction(categoryName: 'Transport', amount: 15.00, date: DateTime.now().subtract(const Duration(days: 5, hours: 8)), merchant: 'City Bus'),
IsarTransaction(categoryName: 'Restaurant', amount: 56.80, date: DateTime.now().subtract(const Duration(days: 5, hours: 20)), merchant: 'Sushi Express'),
// Убедитесь, что 'Utilities' определена в CategoryUtils или обрабатывается как неизвестная категория
IsarTransaction(categoryName: 'Utilities', amount: 85.20, date: DateTime.now().subtract(const Duration(days: 6, hours: 10)), merchant: 'Electricity Bill'),
];
await addTransactions(initialTransactions);
print("Initial data inserted successfully into Isar.");
} else {
print("Database already contains data (${existingTransactions.length} transactions). Skipping initial data insertion.");
print("Isar Database already contains data ($count transactions). Skipping initial data insertion.");
}
}
}
// Функция для открытия соединения с базой данных
LazyDatabase _openConnection() {
// Вычисление пути к файлу базы данных в папке документов приложения
return LazyDatabase(() async {
final dbFolder = await getApplicationDocumentsDirectory();
// Создаем файл 'db.sqlite' в этой папке
final file = File(p.join(dbFolder.path, 'db.sqlite'));
print("Database file path: ${file.path}"); // Логируем путь для отладки
// Используем NativeDatabase для открытия соединения
// logStatements: true полезен для отладки SQL-запросов
return NativeDatabase(file, logStatements: false);
});
// Статический метод для открытия базы данных Isar
static Future<Isar> openDB() async {
final dir = await getApplicationDocumentsDirectory();
// Проверяем, открыт ли уже экземпляр Isar с таким именем
// Это полезно для hot reload/restart
if (Isar.instanceNames.isEmpty) {
print("Opening Isar database at: ${dir.path}");
return await Isar.open(
[IsarTransactionSchema], // Передаем сгенерированную схему
directory: dir.path,
inspector: true, // Включаем инспектор для отладки (доступен в debug режиме)
);
}
// Возвращаем существующий экземпляр
print("Returning existing Isar instance.");
// ignore: RETHROW_IF_NOT_THROWING
return Future.value(Isar.getInstance()); // Возвращаем существующий экземпляр
}
}
+19 -8
View File
@@ -1,18 +1,29 @@
import 'package:flutter/material.dart';
import 'package:isar/isar.dart'; // Импорт Isar
// import 'package:path_provider/path_provider.dart'; // Больше не нужен здесь
import 'app.dart'; // Import the new app root widget
import 'database/database.dart'; // Import the database
import 'database/database.dart'; // Импортируем наш IsarService
import 'models/isar_transaction.dart'; // Импортируем схему Isar
Future<void> main() async {
// Необходимо для асинхронных операций перед runApp, например, инициализации БД
// Необходимо для асинхронных операций перед runApp
WidgetsFlutterBinding.ensureInitialized();
// Создаем единственный экземпляр базы данных для всего приложения
final database = AppDatabase();
// Инициализация Isar Core (нужно для нативных библиотек)
// download: true скачает нужную библиотеку, если ее нет
// await Isar.initializeIsarCore(download: true); // Больше не требуется с isar_flutter_libs
// Открываем базу данных Isar с помощью статического метода в IsarService
final isar = await IsarService.openDB();
// Создаем экземпляр нашего сервиса (хотя он может быть не нужен здесь, если логика в IsarService)
final isarService = IsarService(isar);
// Опционально: Вставляем начальные данные, если база данных пуста
// Это полезно для первого запуска или демонстрации
await database.insertInitialDataIfNeeded();
// Используем метод из IsarService
await isarService.insertInitialDataIfNeeded();
// Запускаем приложение, передавая экземпляр базы данных
runApp(MyApp(database: database));
// Запускаем приложение, передавая экземпляр Isar
runApp(MyApp(isar: isar)); // Передаем isar вместо database
}
@@ -0,0 +1,30 @@
import 'package:isar/isar.dart';
// Эта строка указывает Isar сгенерировать файл isar_transaction.g.dart
part 'isar_transaction.g.dart';
@collection // Аннотация для Isar коллекции
class IsarTransaction {
Id id = Isar.autoIncrement; // Автоинкрементный ID
@Index() // Индекс для быстрого поиска/фильтрации по имени категории
late String categoryName;
late double amount;
@Index() // Индекс для быстрой сортировки/фильтрации по дате
late DateTime date;
late String merchant;
// Конструктор (необязателен, но удобен)
IsarTransaction({
required this.categoryName,
required this.amount,
required this.date,
required this.merchant,
});
// Пустой конструктор для Isar (не используется напрямую)
// IsarTransaction();
}
+52 -37
View File
@@ -1,13 +1,16 @@
import 'package:drift/drift.dart' show Value; // Only import Value for optional fields
// import 'package:drift/drift.dart' show Value; // Больше не нужно
import 'package:flutter/material.dart';
import 'package:fl_chart/fl_chart.dart'; // Used indirectly by SpendingPieChart
import 'package:intl/intl.dart'; // For date formatting
import 'dart:async';
import 'dart:math'; // For random data generation in sample add
import 'package:isar/isar.dart'; // Импорт Isar
import '../database/database.dart' as db; // Import database with prefix 'db'
// import '../database/database.dart' as db; // Старый импорт Drift
import '../database/database.dart'; // Импортируем наш IsarService
import '../models/category.dart'; // Keep Category model for UI structure (SpendingPieChart)
// Import TransactionRecord with a different alias to avoid conflict with db.Transaction
import '../models/isar_transaction.dart'; // Импортируем Isar модель
// Import TransactionRecord with a different alias to avoid conflict
import '../models/transaction_record.dart' as model;
import '../widgets/summary_item.dart';
import '../widgets/expandable_section.dart';
@@ -20,13 +23,13 @@ import 'profile_screen.dart'; // Import profile screen
class ExpensesScreen extends StatefulWidget {
final Function toggleTheme;
final bool isDarkMode;
final db.AppDatabase database; // Accept database instance
final Isar isar; // Принимаем экземпляр Isar
const ExpensesScreen({
Key? key,
required this.toggleTheme,
required this.isDarkMode,
required this.database, // Require database instance
required this.isar, // Требуем экземпляр Isar
}) : super(key: key);
@override
@@ -44,13 +47,19 @@ class _ExpensesScreenState extends State<ExpensesScreen> with TickerProviderStat
int _selectedPieIndex = -1; // Index of the selected pie chart slice
int _selectedNavIndex = 0; // Index for bottom navigation bar
bool _isPieChartExpanded = true; // Controls visibility of the pie chart section
bool _isFilterVisible = false; // Controls visibility of the filter chips
bool _isFilterVisible = false; // Controls visibility of the filter chip row
String _selectedFilter = 'All'; // Currently selected transaction filter
// Экземпляр нашего сервиса для удобства
late IsarService _isarService;
@override
void initState() {
super.initState();
// Создаем экземпляр сервиса, передавая Isar из виджета
_isarService = IsarService(widget.isar);
// Initialize animation controller for pie chart fade/scale effect
_pieChartAnimationController = AnimationController(
vsync: this,
@@ -84,14 +93,16 @@ class _ExpensesScreenState extends State<ExpensesScreen> with TickerProviderStat
super.dispose();
}
// Stream that provides transactions based on the selected filter
Stream<List<db.Transaction>> _watchTransactions() {
return widget.database.watchFilteredTransactions(_selectedFilter);
// Stream that provides transactions based on the selected filter using IsarService
Stream<List<IsarTransaction>> _watchTransactions() {
// Используем метод из IsarService
return _isarService.watchFilteredTransactions(_selectedFilter);
}
// Stream that provides category totals calculated from transactions
// Stream that provides category totals calculated from transactions using IsarService
Stream<List<Category>> _watchCategoryTotals() {
return widget.database.calculateCategoryTotals();
// Используем метод из IsarService
return _isarService.calculateCategoryTotals();
}
// Toggles the visibility of the pie chart section with animation
@@ -133,7 +144,7 @@ class _ExpensesScreenState extends State<ExpensesScreen> with TickerProviderStat
});
}
// --- Function to add a sample transaction (for testing/demo) ---
// --- Function to add a sample transaction (using Isar) ---
void _addSampleTransaction() async {
final random = Random();
// Get available category names from our utility class
@@ -145,8 +156,8 @@ class _ExpensesScreenState extends State<ExpensesScreen> with TickerProviderStat
final randomHour = random.nextInt(24);
final randomMerchant = ['Amazon', 'Local Cafe', 'Gas Station', 'Online Store', 'Supermarket'][random.nextInt(5)];
// Create a companion object for inserting into the Drift table
final newTransaction = db.TransactionsCompanion.insert(
// Create an IsarTransaction object
final newTransaction = IsarTransaction(
categoryName: randomCategory,
amount: randomAmount,
date: DateTime.now().subtract(Duration(days: randomDay, hours: randomHour)),
@@ -154,9 +165,9 @@ class _ExpensesScreenState extends State<ExpensesScreen> with TickerProviderStat
);
try {
// Insert the transaction into the database
await widget.database.addTransaction(newTransaction);
print('Sample transaction added: $randomCategory - \$${randomAmount.toStringAsFixed(2)}');
// Insert the transaction into the database using IsarService
await _isarService.addTransaction(newTransaction);
print('Sample transaction added via Isar: $randomCategory - \$${randomAmount.toStringAsFixed(2)}');
// Show a confirmation message
if (mounted) { // Check if the widget is still in the tree
ScaffoldMessenger.of(context).showSnackBar(
@@ -168,7 +179,7 @@ class _ExpensesScreenState extends State<ExpensesScreen> with TickerProviderStat
);
}
} catch (e) {
print('Error adding transaction: $e');
print('Error adding Isar transaction: $e');
// Show an error message
if (mounted) {
ScaffoldMessenger.of(context).showSnackBar(
@@ -249,10 +260,11 @@ class _ExpensesScreenState extends State<ExpensesScreen> with TickerProviderStat
),
// Use StreamBuilder to get category totals for the summary and pie chart
body: StreamBuilder<List<Category>>(
stream: _watchCategoryTotals(),
stream: _watchCategoryTotals(), // Используем метод IsarService
builder: (context, categorySnapshot) {
// Handle loading state
if (categorySnapshot.connectionState == ConnectionState.waiting && !categorySnapshot.hasData) {
// Показываем индикатор загрузки, если данные еще не пришли, но соединение устанавливается
return const Center(child: CircularProgressIndicator());
}
// Handle error state
@@ -260,7 +272,8 @@ class _ExpensesScreenState extends State<ExpensesScreen> with TickerProviderStat
return Center(child: Text('Error loading categories: ${categorySnapshot.error}'));
}
// Get categories data (or empty list if null)
// Get categories data (or empty list if null/error)
// Если есть ошибка, categories будет пустым списком, totalExpenses будет 0
final categories = categorySnapshot.data ?? [];
// Calculate total expenses from the categories stream data
final totalExpenses = categories.fold(0.0, (sum, item) => sum + item.amount);
@@ -454,7 +467,7 @@ class _ExpensesScreenState extends State<ExpensesScreen> with TickerProviderStat
),
),
// --- Transactions List (using StreamBuilder) ---
// --- Transactions List (using StreamBuilder with Isar) ---
Padding(
padding: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 8.0),
child: Card( // Wrap list in a Card for background/border
@@ -465,11 +478,12 @@ class _ExpensesScreenState extends State<ExpensesScreen> with TickerProviderStat
side: BorderSide(color: theme.dividerColor.withOpacity(0.5), width: 1) // Subtle border
),
clipBehavior: Clip.antiAlias, // Clip list items to card shape
child: StreamBuilder<List<db.Transaction>>(
stream: _watchTransactions(), // Stream based on _selectedFilter
child: StreamBuilder<List<IsarTransaction>>( // Ожидаем List<IsarTransaction>
stream: _watchTransactions(), // Stream из IsarService
builder: (context, transactionSnapshot) {
// Handle loading state for transactions
if (transactionSnapshot.connectionState == ConnectionState.waiting) {
if (transactionSnapshot.connectionState == ConnectionState.waiting && !transactionSnapshot.hasData) {
// Показываем индикатор, если данные еще не пришли
return const SizedBox(
height: 150, // Placeholder height
child: Center(child: CircularProgressIndicator(strokeWidth: 2)),
@@ -482,11 +496,11 @@ class _ExpensesScreenState extends State<ExpensesScreen> with TickerProviderStat
child: Center(child: Text('Error: ${transactionSnapshot.error}')),
);
}
// Get transaction data (or empty list)
final transactions = transactionSnapshot.data ?? [];
// Get transaction data (or empty list if null/error)
final isarTransactions = transactionSnapshot.data ?? [];
// Display message if no transactions match the filter
if (transactions.isEmpty) {
if (isarTransactions.isEmpty) {
return SizedBox(
height: 150,
child: Center(
@@ -502,7 +516,7 @@ class _ExpensesScreenState extends State<ExpensesScreen> with TickerProviderStat
// Use ListView.builder to display transactions efficiently
return ListView.separated(
itemCount: transactions.length,
itemCount: isarTransactions.length,
physics: const NeverScrollableScrollPhysics(), // Disable inner scrolling
shrinkWrap: true, // Fit content height
padding: const EdgeInsets.symmetric(vertical: 8.0), // Padding inside the card
@@ -511,24 +525,25 @@ class _ExpensesScreenState extends State<ExpensesScreen> with TickerProviderStat
color: theme.dividerColor.withOpacity(0.3),
),
itemBuilder: (context, index) {
// Get the db.Transaction object from the stream
final dbTransaction = transactions[index];
// Get the IsarTransaction object from the stream
final isarTx = isarTransactions[index];
// Get the corresponding category details (icon, color)
final categoryDetails = CategoryUtils.getCategoryDetails(dbTransaction.categoryName);
final categoryDetails = CategoryUtils.getCategoryDetails(isarTx.categoryName);
// Create the model.TransactionRecord needed by TransactionListItem
// Преобразуем IsarTransaction -> model.TransactionRecord
final transactionRecord = model.TransactionRecord(
dbTransaction.categoryName,
dbTransaction.amount,
isarTx.categoryName,
isarTx.amount,
categoryDetails.iconCode, // Get icon from utils
categoryDetails.colorCode, // Get color from utils
dbTransaction.date,
dbTransaction.merchant,
isarTx.date,
isarTx.merchant,
);
// Use the TransactionListItem widget with the correct model type
return TransactionListItem(
transaction: transactionRecord, // Pass the model.TransactionRecord
animation: kAlwaysCompleteAnimation, // Required by FadeTransition
animation: kAlwaysCompleteAnimation, // Required by FadeTransition (or remove animation)
);
},
);
@@ -587,7 +602,7 @@ class _ExpensesScreenState extends State<ExpensesScreen> with TickerProviderStat
),
// Floating Action Button to add new transaction
floatingActionButton: FloatingActionButton.extended( // Use extended FAB
onPressed: _addSampleTransaction, // Add sample data on press
onPressed: _addSampleTransaction, // Add sample data on press (uses Isar now)
tooltip: 'Add Transaction',
icon: const Icon(Icons.add),
label: const Text('Add'),
+8 -4
View File
@@ -32,11 +32,13 @@ dependencies:
sdk: flutter
fl_chart: ^0.71.0
intl: ^0.19.0 # For date formatting
drift: ^2.26.0 # Added Drift
sqlite3_flutter_libs: ^0.5.22 # Recommended for Drift on Flutter
# --- Isar Dependencies ---
isar: ^3.1.0+1
isar_flutter_libs: ^3.1.0+1 # Contains Isar Core for Flutter
# --- End Isar Dependencies ---
path_provider: ^2.1.3 # To find database file location
path: ^1.9.0 # To construct database file path
rxdart: ^0.28.0 # Added rxdart for combining streams in database
rxdart: ^0.28.0 # Keep for combining streams if needed, or remove if not used elsewhere
# The following adds the Cupertino Icons font to your application.
# Use with the CupertinoIcons class for iOS style icons.
cupertino_icons: ^1.0.8
@@ -44,7 +46,9 @@ dependencies:
dev_dependencies:
flutter_test:
sdk: flutter
drift_dev: ^2.18.0 # Added Drift code generator
# --- Isar Generator ---
isar_generator: ^3.1.0+1
# --- End Isar Generator ---
build_runner: ^2.4.11 # Added build_runner
# The "flutter_lints" package below contains a set of recommended lints to