138 lines
7.3 KiB
Dart
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()); // Возвращаем существующий экземпляр
|
|
}
|
|
}
|