Files
FinanceApp/finance_app/lib/database/database.dart
T
2025-05-04 12:27:09 +03:00

138 lines
7.3 KiB
Dart

import 'dart:async';
import 'package:flutter/material.dart'; // Для Color/IconData в Category
import 'package:isar/isar.dart';
import 'package:path_provider/path_provider.dart';
// Импортируем модели
import '../models/category.dart';
import '../models/isar_transaction.dart'; // Наша Isar модель
import '../utils/category_utils.dart'; // Помощник для деталей категории
// Этот файл больше не использует Drift, поэтому part 'database.g.dart'; удален.
// Класс-сервис для инкапсуляции логики работы с Isar
class IsarService {
final Isar isar;
IsarService(this.isar);
// --- Методы для работы с транзакциями ---
// Получить все транзакции в виде потока, упорядоченные по дате (сначала новые)
// Возвращает Stream<List<IsarTransaction>>
Stream<List<IsarTransaction>> watchAllTransactions() {
// isar.isarTransactions ссылается на коллекцию IsarTransaction
// .where() без условий выбирает все
// .sortByDateDesc() сортирует по полю date по убыванию
// .watch(fireImmediately: true) создает поток, который срабатывает сразу
return isar.isarTransactions
.where()
.sortByDateDesc()
.watch(fireImmediately: true);
}
// Получить транзакции, отфильтрованные по категории, в виде потока
// Возвращает 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);
}
// Добавить новую транзакцию
// Принимает объект 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. Получаем поток всех транзакций
return watchAllTransactions().map((transactionList) {
// 2. Группируем транзакции по categoryName и суммируем amount
final categoryTotals = <String, double>{};
for (var transaction in transactionList) {
categoryTotals.update(
transaction.categoryName,
(value) => value + transaction.amount,
ifAbsent: () => transaction.amount,
);
}
// 3. Преобразуем сгруппированные данные в список объектов Category
return categoryTotals.entries.map((entry) {
final categoryDetails = CategoryUtils.getCategoryDetails(entry.key);
return Category(
entry.key, // name
entry.value, // amount
categoryDetails.colorCode,
categoryDetails.iconCode,
);
}).toList()
// Сортируем категории по сумме (от большей к меньшей)
..sort((a, b) => b.amount.compareTo(a.amount));
});
}
// Пример добавления начальных данных (если база данных пуста)
Future<void> insertInitialDataIfNeeded() async {
// Проверяем, есть ли уже транзакции
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("Isar Database already contains data ($count transactions). Skipping initial data insertion.");
}
}
// Статический метод для открытия базы данных 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()); // Возвращаем существующий экземпляр
}
}