Init
This commit is contained in:
commit
295625c70f
151 files changed
+10677
No files matched your search
@@ -0,0 +1,432 @@
|
||||
// ignore_for_file: unused_import // Часто генерируется при условном импорте
|
||||
|
||||
import 'package:drift/drift.dart';
|
||||
import 'package:async/async.dart';
|
||||
import 'package:flutter/material.dart' as c; // Use alias 'c' for flutter material
|
||||
// Условный импорт бэкенда базы данных
|
||||
// Выбирает реализацию connect() в зависимости от платформы
|
||||
import 'database_connection/connection.dart' // Базовый импорт
|
||||
if (dart.library.html) 'database_connection/connection_web.dart' // Для Веб
|
||||
if (dart.library.io) 'database_connection/connection_native.dart'; // Для Нативных платформ (Android, iOS, Desktop)
|
||||
|
||||
// Импортируем модель категории для возвращаемого типа и утилиты
|
||||
import '../models/category.dart';
|
||||
import '../utils/category_utils.dart'; // Helper for category details
|
||||
|
||||
// Эта строка указывает Drift сгенерировать файл database.g.dart
|
||||
part 'database.g.dart';
|
||||
|
||||
// Enum for transaction type (used internally and potentially externally)
|
||||
enum TransactionType { income, expense }
|
||||
|
||||
// Определение таблицы Categories
|
||||
// ИСПРАВЛЕНО: Имя класса таблицы изменено на Categories для ясности
|
||||
@DataClassName('CategoryDb')
|
||||
class Categories extends Table { // Changed class name to plural 'Categories'
|
||||
IntColumn get id => integer().autoIncrement()();
|
||||
TextColumn get name => text().unique()();
|
||||
TextColumn get icon => text()(); // Store icon name (e.g., 'shopping_cart_outlined')
|
||||
IntColumn get color => integer()(); // Store color value (e.g., Colors.green.value)
|
||||
}
|
||||
|
||||
// Определение таблицы 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')(); // Имя категории (или 'Income')
|
||||
RealColumn get amount => real()(); // Сумма транзакции
|
||||
DateTimeColumn get date => dateTime()(); // Дата транзакции
|
||||
TextColumn get merchant => text().named('merchant')(); // Название продавца/источника
|
||||
// ИСПРАВЛЕНО: Добавлено .withDefault() для значения по умолчанию
|
||||
TextColumn get type => text().named('type').withDefault(const Constant('expense'))(); // Тип транзакции ('income' или 'expense')
|
||||
}
|
||||
|
||||
// Класс базы данных
|
||||
// Аннотация @DriftDatabase указывает Drift сгенерировать код для этой базы данных
|
||||
@DriftDatabase(tables: [Categories, Transactions]) // Updated table name here
|
||||
class AppDatabase extends _$AppDatabase {
|
||||
// Используем функцию connect() из условного импорта для создания соединения
|
||||
// Во время компиляции будет выбрана правильная реализация connect()
|
||||
// из connection_web.dart или connection_native.dart.
|
||||
AppDatabase() : super(connect());
|
||||
|
||||
// Версия схемы. Увеличивайте при изменении структуры таблиц.
|
||||
@override
|
||||
int get schemaVersion => 4; // Increased version due to adding Categories table
|
||||
|
||||
@override
|
||||
MigrationStrategy get migration => MigrationStrategy(
|
||||
onCreate: (m) async {
|
||||
await m.createAll();
|
||||
// Вставляем начальные данные ТОЛЬКО при создании базы данных
|
||||
print("Database created. Inserting initial data...");
|
||||
// ИЗМЕНЕНО: Вызов insertInitialDataIfNeeded теперь только здесь
|
||||
await insertInitialDataIfNeeded();
|
||||
},
|
||||
onUpgrade: (m, from, to) async {
|
||||
// Drift автоматически обработает добавление новых таблиц (Categories)
|
||||
// при обновлении до версии 4.
|
||||
// Нам нужно только обработать специфичные изменения, как добавление колонки type.
|
||||
if (from < 4) { // Check if upgrading from a version before 4
|
||||
// Check if the 'type' column exists before trying to add it
|
||||
// This requires a more complex check, usually involving inspecting the schema.
|
||||
// For simplicity, we assume if version is < 4, the column might be missing.
|
||||
// A safer approach involves querying PRAGMA table_info(transactions);
|
||||
// However, Drift's default migration handles adding columns well.
|
||||
// Let's ensure the Categories table is created if upgrading from very old versions.
|
||||
await m.createTable(categories); // Ensure categories table exists
|
||||
// Add the 'type' column if it doesn't exist (Drift might handle this, but explicit is safer)
|
||||
// We'll rely on Drift's default behavior for adding the column here.
|
||||
// If specific default values or constraints were needed during upgrade,
|
||||
// more complex logic would be required.
|
||||
}
|
||||
if (from == 1) {
|
||||
// Example: If migrating specifically from 1, maybe add the type column
|
||||
// await m.addColumn(transactions, transactions.type);
|
||||
// But the check `from < 4` above is more general if relying on Drift's auto-migration.
|
||||
}
|
||||
// Add more migration steps for future versions here
|
||||
// if (from < 5) { ... }
|
||||
|
||||
// ИЗМЕНЕНО: Удален вызов insertInitialDataIfNeeded отсюда
|
||||
// Не нужно вставлять начальные данные при обновлении существующей БД.
|
||||
},
|
||||
beforeOpen: (details) async {
|
||||
// ИЗМЕНЕНО: Удален вызов insertInitialDataIfNeeded отсюда
|
||||
// Логика вставки начальных данных теперь полностью в onCreate.
|
||||
if (details.wasCreated) {
|
||||
print("Database was created. Initial data should have been inserted via onCreate.");
|
||||
} else {
|
||||
print("Opening existing database version ${details.versionNow}.");
|
||||
}
|
||||
// Можно добавить здесь другие проверки или настройки при открытии, если нужно.
|
||||
return Future.value(); // beforeOpen должен возвращать Future<void>
|
||||
},
|
||||
);
|
||||
|
||||
|
||||
// --- Методы для работы с транзакциями ---
|
||||
|
||||
// Получить все транзакции в виде потока, упорядоченные по дате (сначала новые)
|
||||
// Возвращает non-nullable Stream<List<Transaction>> (Transaction - сгенерированный Drift класс)
|
||||
Stream<List<Transaction>> watchAllTransactions() {
|
||||
return (select(transactions)
|
||||
..orderBy([(t) => OrderingTerm(expression: t.date, mode: OrderingMode.desc)]))
|
||||
.watch();
|
||||
}
|
||||
|
||||
// Получить транзакции, отфильтрованные по категории РАСХОДОВ, в виде потока
|
||||
// Если categoryName == 'All', возвращает ВСЕ транзакции (и доходы, и расходы)
|
||||
// Возвращает non-nullable Stream<List<Transaction>> (Transaction - сгенерированный Drift класс)
|
||||
Stream<List<Transaction>> watchFilteredTransactions(String categoryName) {
|
||||
if (categoryName == 'All') {
|
||||
// watchAllTransactions теперь возвращает non-nullable Stream
|
||||
return watchAllTransactions(); // Return all types if filter is 'All'
|
||||
}
|
||||
// Otherwise, filter by the provided category name AND ensure it's an expense
|
||||
return (select(transactions)
|
||||
..where((t) => t.categoryName.equals(categoryName) & t.type.equals('expense'))
|
||||
..orderBy([(t) => OrderingTerm(expression: t.date, mode: OrderingMode.desc)]))
|
||||
.watch();
|
||||
}
|
||||
|
||||
// Добавить новую транзакцию
|
||||
// Принимает TransactionsCompanion - сгенерированный Drift класс (должен включать type)
|
||||
Future<int> addTransaction(TransactionsCompanion entry) {
|
||||
// Убедимся, что тип указан
|
||||
assert(entry.type.present && (entry.type.value == 'income' || entry.type.value == 'expense'));
|
||||
// Убедимся, что для дохода используется специальная категория
|
||||
if (entry.type.value == 'income') {
|
||||
assert(entry.categoryName.present && entry.categoryName.value == 'Income');
|
||||
}
|
||||
return into(transactions).insert(entry);
|
||||
}
|
||||
|
||||
// Обновить существующую транзакцию
|
||||
// Принимает TransactionsCompanion, который должен содержать ID
|
||||
// Возвращает true, если обновление прошло успешно
|
||||
Future<bool> updateTransaction(TransactionsCompanion entry) {
|
||||
// Проверяем, что ID предоставлен для обновления
|
||||
assert(entry.id.present);
|
||||
// Убедимся, что тип указан
|
||||
assert(entry.type.present && (entry.type.value == 'income' || entry.type.value == 'expense'));
|
||||
// Убедимся, что для дохода используется специальная категория
|
||||
if (entry.type.value == 'income') {
|
||||
assert(entry.categoryName.present && entry.categoryName.value == 'Income');
|
||||
}
|
||||
|
||||
// Используем .replace() для обновления записи по ID
|
||||
// replace возвращает true, если запись была обновлена (найдена по ID)
|
||||
return update(transactions).replace(entry);
|
||||
}
|
||||
|
||||
// Удалить транзакцию по ID
|
||||
// Возвращает количество удаленных строк (0 или 1)
|
||||
Future<int> deleteTransaction(int id) {
|
||||
// Используем .delete() с условием where
|
||||
return (delete(transactions)..where((t) => t.id.equals(id))).go();
|
||||
}
|
||||
|
||||
|
||||
// --- Методы для работы с категориями ---
|
||||
|
||||
// Получить все категории в виде потока, упорядоченные по имени
|
||||
// Возвращает Stream<List<CategoryDb>> (CategoryDb - сгенерированный Drift класс)
|
||||
Stream<List<CategoryDb>> watchAllCategoriesDb() {
|
||||
return (select(categories)..orderBy([(c) => OrderingTerm(expression: c.name)])).watch();
|
||||
}
|
||||
|
||||
// Добавить новую категорию
|
||||
// Принимает CategoriesCompanion (сгенерированный Drift)
|
||||
// Возвращает ID вставленной категории
|
||||
Future<int> addCategory(CategoriesCompanion entry) {
|
||||
// Проверяем, что имя, иконка и цвет предоставлены
|
||||
assert(entry.name.present && entry.name.value.isNotEmpty);
|
||||
assert(entry.icon.present); // Icon can be empty string if needed
|
||||
assert(entry.color.present);
|
||||
// Не позволяем добавить категорию с именем 'Income' (case-insensitive)
|
||||
assert(entry.name.value.toLowerCase() != 'income');
|
||||
return into(categories).insert(entry);
|
||||
}
|
||||
|
||||
// Обновить существующую категорию
|
||||
// Принимает CategoriesCompanion, который должен содержать ID
|
||||
// Возвращает true, если обновление прошло успешно
|
||||
Future<bool> updateCategory(CategoriesCompanion entry) {
|
||||
// Проверяем, что ID предоставлен для обновления
|
||||
assert(entry.id.present);
|
||||
// Проверяем, что имя не 'Income' (case-insensitive)
|
||||
if (entry.name.present && entry.name.value.toLowerCase() == 'income') {
|
||||
print("Error: Cannot rename category to 'Income'.");
|
||||
return Future.value(false); // Запрещаем переименование в 'Income'
|
||||
}
|
||||
|
||||
// Используем транзакцию для проверки и обновления
|
||||
return transaction(() async {
|
||||
// Находим категорию по ID перед обновлением
|
||||
final existingCategory = await (select(categories)..where((c) => c.id.equals(entry.id.value))).getSingleOrNull();
|
||||
|
||||
// Проверяем, существует ли категория и не является ли она 'Income'
|
||||
if (existingCategory == null) {
|
||||
print("Error: Category with ID ${entry.id.value} not found for update.");
|
||||
return false; // Категория не найдена
|
||||
}
|
||||
if (existingCategory.name == 'Income') {
|
||||
print("Error: Cannot update the 'Income' category.");
|
||||
return false; // Запрещаем обновление категории 'Income'
|
||||
}
|
||||
|
||||
// Выполняем обновление
|
||||
// Метод replace возвращает true, если запись была обновлена
|
||||
final updated = await update(categories).replace(entry);
|
||||
|
||||
// Если имя категории было изменено, нужно обновить categoryName во всех связанных транзакциях
|
||||
if (updated && entry.name.present && entry.name.value != existingCategory.name) {
|
||||
print("Category name changed from '${existingCategory.name}' to '${entry.name.value}'. Updating transactions...");
|
||||
final updatedTransactions = await (update(transactions)
|
||||
..where((t) => t.categoryName.equals(existingCategory.name)))
|
||||
.write(TransactionsCompanion(
|
||||
categoryName: Value(entry.name.value),
|
||||
));
|
||||
print("Updated $updatedTransactions transactions with the new category name.");
|
||||
}
|
||||
|
||||
return updated; // Возвращаем результат replace
|
||||
});
|
||||
}
|
||||
|
||||
// Удалить категорию по ID
|
||||
// Возвращает количество удаленных строк (0 или 1)
|
||||
Future<int> deleteCategory(int id) {
|
||||
// Используем транзакцию для проверок и удаления
|
||||
return transaction(() async {
|
||||
// Находим категорию по ID перед удалением
|
||||
final categoryToDelete = await (select(categories)..where((c) => c.id.equals(id))).getSingleOrNull();
|
||||
|
||||
// Проверяем, существует ли категория
|
||||
if (categoryToDelete == null) {
|
||||
print("Error: Category with ID $id not found for deletion.");
|
||||
return 0; // Категория не найдена
|
||||
}
|
||||
|
||||
// Запрещаем удаление категории 'Income'
|
||||
if (categoryToDelete.name == 'Income') {
|
||||
print("Error: Cannot delete the 'Income' category.");
|
||||
return 0; // Возвращаем 0, т.к. ничего не удалено
|
||||
}
|
||||
|
||||
// Проверяем, есть ли транзакции с этой категорией
|
||||
// Используем имя категории для связи (т.к. нет внешнего ключа)
|
||||
final query = selectOnly(transactions)
|
||||
..addColumns([transactions.id.count()])
|
||||
..where(transactions.categoryName.equals(categoryToDelete.name)); // Используем имя для связи
|
||||
|
||||
final result = await query.getSingleOrNull();
|
||||
final transactionCount = result?.read(transactions.id.count()) ?? 0;
|
||||
|
||||
if (transactionCount > 0) {
|
||||
print("Error: Cannot delete category '${categoryToDelete.name}' because it has $transactionCount associated transaction(s).");
|
||||
// Можно выбросить исключение или вернуть 0, чтобы показать неудачу
|
||||
// throw Exception("Cannot delete category with transactions.");
|
||||
return 0; // Не удаляем, возвращаем 0
|
||||
}
|
||||
|
||||
// Если транзакций нет, удаляем категорию
|
||||
print("Deleting category '${categoryToDelete.name}' (ID: $id)...");
|
||||
final deletedRows = await (delete(categories)..where((c) => c.id.equals(id))).go();
|
||||
print("Deleted $deletedRows category row(s).");
|
||||
return deletedRows; // Возвращаем количество удаленных строк
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
// Получить категорию по ID (если нужно)
|
||||
Future<CategoryDb?> getCategoryById(int id) {
|
||||
return (select(categories)..where((c) => c.id.equals(id))).getSingleOrNull();
|
||||
}
|
||||
|
||||
// --- Методы для агрегации и отчетов ---
|
||||
|
||||
// Вычислить и наблюдать за общими суммами по категориям РАСХОДОВ
|
||||
// Возвращает Stream<List<Category>> где Category - это класс модели из '../models/category.dart'
|
||||
Stream<List<Category>> calculateCategoryTotals() {
|
||||
// 1. Создаем поток, который объединяет транзакции и категории
|
||||
final transactionsStream = watchAllTransactions();
|
||||
final categoriesStream = watchAllCategoriesDb();
|
||||
|
||||
// Используем StreamZip для объединения последних данных из обоих потоков
|
||||
return StreamZip([transactionsStream, categoriesStream]).map((data) {
|
||||
final transactionList = data[0] as List<Transaction>;
|
||||
final categoryList = data[1] as List<CategoryDb>;
|
||||
|
||||
// Создаем Map для быстрого доступа к деталям категории по имени
|
||||
final categoryDetailsMap = {
|
||||
for (var cat in categoryList) cat.name: cat
|
||||
};
|
||||
|
||||
// 2. Фильтруем только расходы и группируем по categoryName, суммируя amount
|
||||
final categoryTotals = <String, double>{};
|
||||
for (var transaction in transactionList) {
|
||||
// Суммируем только расходы (исключая 'Income')
|
||||
if (transaction.type == 'expense') {
|
||||
categoryTotals.update(
|
||||
transaction.categoryName,
|
||||
(value) => value + transaction.amount,
|
||||
ifAbsent: () => transaction.amount,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// 3. Преобразуем сгруппированные данные в список объектов Category (модель UI)
|
||||
return categoryTotals.entries.map((entry) {
|
||||
final categoryName = entry.key;
|
||||
final totalAmount = entry.value;
|
||||
// Получаем детали категории из Map (или используем дефолтные, если категория была удалена)
|
||||
final categoryDb = categoryDetailsMap[categoryName];
|
||||
final iconData = CategoryUtils.getIconFromString(categoryDb?.icon); // Используем утилиту
|
||||
final colorData = categoryDb != null ? c.Color(categoryDb.color) : c.Colors.grey.shade500; // Цвет из БД или дефолтный
|
||||
|
||||
return Category( // Это Category из models/category.dart
|
||||
categoryName,
|
||||
totalAmount,
|
||||
colorData,
|
||||
iconData,
|
||||
);
|
||||
}).toList()
|
||||
// Сортируем категории по сумме (от большей к меньшей)
|
||||
..sort((a, b) => b.amount.compareTo(a.amount));
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
// Вычислить и наблюдать за общей суммой ДОХОДОВ
|
||||
Stream<double> watchTotalIncome() {
|
||||
// Создаем выражение для суммы amount
|
||||
final amountSum = transactions.amount.sum();
|
||||
// Строим запрос: выбрать сумму amount из transactions, где type = 'income'
|
||||
final query = selectOnly(transactions)
|
||||
..addColumns([amountSum])
|
||||
..where(transactions.type.equals('income'));
|
||||
|
||||
// Выполняем запрос и наблюдаем за изменениями
|
||||
// map преобразует результат (единственную строку с суммой) в double
|
||||
// ?? 0.0 обрабатывает случай, когда доходов нет (сумма будет null)
|
||||
return query.watchSingleOrNull().map((result) => result?.read(amountSum) ?? 0.0);
|
||||
}
|
||||
|
||||
// Вычислить и наблюдать за общей суммой РАСХОДОВ (альтернатива суммированию категорий)
|
||||
Stream<double> watchTotalExpenses() {
|
||||
final amountSum = transactions.amount.sum();
|
||||
final query = selectOnly(transactions)
|
||||
..addColumns([amountSum])
|
||||
..where(transactions.type.equals('expense'));
|
||||
return query.watchSingleOrNull().map((result) => result?.read(amountSum) ?? 0.0);
|
||||
}
|
||||
|
||||
|
||||
// Добавление начальных данных (вызывается только из onCreate)
|
||||
// ИЗМЕНЕНО: Убран необязательный параметр isCreating, т.к. вызывается только при создании
|
||||
Future<void> insertInitialDataIfNeeded() async {
|
||||
// Проверяем, есть ли уже категории (на всякий случай, хотя в onCreate их быть не должно)
|
||||
final categoriesCountResult = await (selectOnly(categories)..addColumns([categories.id.count()])).getSingleOrNull();
|
||||
final categoriesCount = categoriesCountResult?.read(categories.id.count()) ?? 0;
|
||||
|
||||
if (categoriesCount == 0) {
|
||||
print("Inserting initial categories...");
|
||||
// Получаем доступные цвета из CategoryUtils
|
||||
final availableColors = CategoryUtils.availableColors;
|
||||
|
||||
await batch((batch) {
|
||||
batch.insertAll(categories, [
|
||||
// Используем CategoriesCompanion (сгенерированный для таблицы Categories)
|
||||
// Используем реальные имена иконок Material Icons и конкретные значения цветов из списка
|
||||
CategoriesCompanion.insert(name: 'Groceries', icon: 'shopping_cart_outlined', color: availableColors[0].value), // Red
|
||||
CategoriesCompanion.insert(name: 'Subscriptions', icon: 'subscriptions_outlined', color: availableColors[4].value), // Indigo
|
||||
CategoriesCompanion.insert(name: 'Restaurant', icon: 'restaurant_menu_outlined', color: availableColors[14].value), // Orange
|
||||
CategoriesCompanion.insert(name: 'Shopping', icon: 'shopping_bag_outlined', color: availableColors[5].value), // Blue
|
||||
CategoriesCompanion.insert(name: 'Transport', icon: 'directions_bus_filled_outlined', color: availableColors[2].value), // Purple
|
||||
CategoriesCompanion.insert(name: 'Travel', icon: 'flight_takeoff_outlined', color: availableColors[7].value), // Cyan
|
||||
CategoriesCompanion.insert(name: 'Utilities', icon: 'home_outlined', color: availableColors[8].value), // Teal
|
||||
// 'Income' category - use a specific icon and color (можно тоже взять из списка или оставить уникальный)
|
||||
// Оставим уникальный цвет для Income для выделения
|
||||
CategoriesCompanion.insert(name: 'Income', icon: 'attach_money', color: c.Colors.lightGreenAccent.shade700.value),
|
||||
]);
|
||||
});
|
||||
print("Initial categories inserted.");
|
||||
} else {
|
||||
// Эта ветка не должна выполняться при вызове из onCreate, но оставим для отладки
|
||||
print("Categories table already contains data ($categoriesCount categories). Skipping initial category insertion.");
|
||||
}
|
||||
|
||||
// Проверяем, есть ли уже транзакции (аналогично, не должно быть в onCreate)
|
||||
final countResult = await (selectOnly(transactions)..addColumns([transactions.id.count()])).getSingleOrNull();
|
||||
final transactionCount = countResult?.read(transactions.id.count()) ?? 0;
|
||||
|
||||
// Вставляем данные только если таблица транзакций пуста
|
||||
if (transactionCount == 0) {
|
||||
print("Inserting initial transactions...");
|
||||
// Используем batch для эффективной вставки нескольких записей
|
||||
await batch((batch) {
|
||||
batch.insertAll(transactions, [
|
||||
// Используем TransactionsCompanion для создания записей для вставки
|
||||
// Поле type будет 'expense' по умолчанию из-за .withDefault() в определении колонки
|
||||
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'),
|
||||
TransactionsCompanion.insert(categoryName: 'Utilities', amount: 85.20, date: DateTime.now().subtract(const Duration(days: 6, hours: 10)), merchant: 'Electricity Bill'), // Example: Added Utilities transaction
|
||||
// Пример дохода - здесь нужно явно указать type: 'income'
|
||||
TransactionsCompanion.insert(categoryName: 'Income', amount: 1200.00, date: DateTime.now().subtract(const Duration(days: 7, hours: 9)), merchant: 'Salary', type: Value('income')),
|
||||
]);
|
||||
});
|
||||
print("Initial transactions inserted successfully.");
|
||||
} else {
|
||||
// Эта ветка не должна выполняться при вызове из onCreate
|
||||
print("Transactions table already contains data ($transactionCount transactions). Skipping initial transaction insertion.");
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,12 @@
|
||||
// lib/database/database_connection/connection.dart
|
||||
import 'package:drift/drift.dart';
|
||||
|
||||
// Этот файл служит базой для условного импорта.
|
||||
// Реализации находятся в connection_web.dart и connection_native.dart.
|
||||
|
||||
// Определим функцию-заглушку, чтобы основной файл database.dart
|
||||
// мог ее импортировать без ошибок анализатора.
|
||||
// Во время выполнения будет вызвана реализация из соответствующего
|
||||
// файла (web или native) благодаря условному импорту.
|
||||
QueryExecutor connect() => throw UnsupportedError(
|
||||
'Stub connect function should not be called. Ensure conditional imports are set up correctly.');
|
||||
@@ -0,0 +1,33 @@
|
||||
// lib/database/database_connection/connection_native.dart
|
||||
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;
|
||||
// sqlite3_flutter_libs и sqlite3 импортируются для возможной тонкой настройки,
|
||||
// но часто NativeDatabase справляется автоматически.
|
||||
// import 'package:sqlite3_flutter_libs/sqlite3_flutter_libs.dart';
|
||||
// import 'package:sqlite3/sqlite3.dart';
|
||||
|
||||
QueryExecutor connect() {
|
||||
print("Connecting to database using NativeDatabase (Lazy)");
|
||||
// Используем LazyDatabase для отложенной инициализации на нативных платформах
|
||||
return LazyDatabase(() async {
|
||||
// Получаем папку для хранения документов приложения
|
||||
final dbFolder = await getApplicationDocumentsDirectory();
|
||||
// Создаем путь к файлу 'db.sqlite' в этой папке
|
||||
final file = File(p.join(dbFolder.path, 'db.sqlite'));
|
||||
print("Database file path (Native): ${file.path}"); // Логируем путь для отладки
|
||||
|
||||
// Настройка библиотеки sqlite3 (обычно не требуется для Android/iOS/macOS с sqlite3_flutter_libs)
|
||||
// if (Platform.isWindows || Platform.isLinux) {
|
||||
// // Может потребоваться дополнительная настройка для Desktop
|
||||
// // await applyWorkaroundToOpenSqlite3Library();
|
||||
// }
|
||||
|
||||
// Используем NativeDatabase для открытия соединения
|
||||
// logStatements: true полезен для отладки SQL-запросов
|
||||
return NativeDatabase(file, logStatements: false);
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
// lib/database/database_connection/connection_web.dart
|
||||
import 'package:drift/drift.dart';
|
||||
import 'package:drift/web.dart';
|
||||
// import 'package:drift/wasm.dart'; // <-- Удалить этот импорт, он больше не нужен здесь
|
||||
|
||||
QueryExecutor connect() {
|
||||
print("Connecting to database using WebDatabase (default configuration)");
|
||||
|
||||
// Используем стандартный конструктор WebDatabase.
|
||||
// Drift попытается автоматически найти и загрузить sqlite3.wasm по пути /sqlite3.wasm
|
||||
return WebDatabase(
|
||||
'db', // Имя базы данных в IndexedDB
|
||||
logStatements: false,
|
||||
);
|
||||
}
|
||||
Reference in new issue
Block a user