import 'package:app/database/database.steps.dart'; import 'package:drift/drift.dart'; import 'package:drift_flutter/drift_flutter.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart' show Colors; import 'package:path_provider/path_provider.dart'; import 'package:riverpod/legacy.dart'; import 'connection/connection.dart' as impl; // Manually generated by `drift_dev make-migrations` - this file makes writing // migrations easier. See this for details: // https://drift.simonbinder.eu/docs/advanced-features/migrations/#step-by-step import 'tables.dart'; // Generated by drift_dev when running `build_runner build` part 'database.g.dart'; @DriftDatabase(tables: [TodoEntries, Categories], include: {'sql.drift'}) class AppDatabase extends _$AppDatabase { AppDatabase([QueryExecutor? e]) : super( e ?? driftDatabase( name: 'todo-app', native: const DriftNativeOptions( databaseDirectory: getApplicationSupportDirectory, ), web: DriftWebOptions( sqlite3Wasm: Uri.parse('sqlite3.wasm'), driftWorker: Uri.parse('drift_worker.js'), onResult: (result) { if (result.missingFeatures.isNotEmpty) { debugPrint( 'Using ${result.chosenImplementation} due to unsupported ' 'browser features: ${result.missingFeatures}', ); } }, ), ), ); AppDatabase.forTesting(DatabaseConnection super.connection); @override int get schemaVersion => 3; @override MigrationStrategy get migration { return MigrationStrategy( onUpgrade: stepByStep( from1To2: (m, schema) async { // The todoEntries.dueDate column was added in version 2. await m.addColumn(schema.todoEntries, schema.todoEntries.dueDate); }, from2To3: (m, schema) async { // New triggers were added in version 3: await m.create(schema.todosDelete); await m.create(schema.todosUpdate); // Also, the `REFERENCES` constraint was added to // [TodoEntries.category]. Run a table migration to rebuild all // column constraints without loosing data. await m.alterTable(TableMigration(schema.todoEntries)); }, ), beforeOpen: (details) async { // Make sure that foreign keys are enabled await customStatement('PRAGMA foreign_keys = ON'); if (details.wasCreated) { // Create a bunch of default values so the app doesn't look too empty // on the first start. await batch((b) { b.insert( categories, CategoriesCompanion.insert(name: 'Important', color: Colors.red), ); b.insertAll(todoEntries, [ TodoEntriesCompanion.insert(description: 'Check out drift'), TodoEntriesCompanion.insert( description: 'Fix session invalidation bug', category: const Value(1)), TodoEntriesCompanion.insert( description: 'Add favorite movies to home page'), ]); }); } // This follows the recommendation to validate that the database schema // matches what drift expects (https://drift.simonbinder.eu/docs/advanced-features/migrations/#verifying-a-database-schema-at-runtime). // It allows catching bugs in the migration logic early. await impl.validateDatabaseSchema(this); }, ); } Future> search(String query) { return _search(query).map((row) { return TodoEntryWithCategory(entry: row.todos, category: row.cat); }).get(); } Stream> categoriesWithCount() { // the _categoriesWithCount method has been generated automatically based // on the query declared in the @DriftDatabase annotation return _categoriesWithCount().map((row) { final hasId = row.id != null; final category = hasId ? Category(id: row.id!, name: row.name!, color: row.color!) : null; return CategoryWithCount(category, row.amount); }).watch(); } /// Returns an auto-updating stream of all todo entries in a given category /// id. Stream> entriesInCategory(int? categoryId) { final query = select(todoEntries).join([ leftOuterJoin(categories, categories.id.equalsExp(todoEntries.category)) ]); if (categoryId != null) { query.where(categories.id.equals(categoryId)); } else { query.where(categories.id.isNull()); } return query.map((row) { return TodoEntryWithCategory( entry: row.readTable(todoEntries), category: row.readTableOrNull(categories), ); }).watch(); } Future deleteCategory(Category category) { return transaction(() async { // First, move todo entries that might remain into the default category await (todoEntries.update() ..where((todo) => todo.category.equals(category.id))) .write(const TodoEntriesCompanion(category: Value(null))); // Then, delete the category await categories.deleteOne(category); }); } static final StateProvider provider = StateProvider((ref) { final database = AppDatabase(); ref.onDispose(database.close); return database; }); } class TodoEntryWithCategory { final TodoEntry entry; final Category? category; TodoEntryWithCategory({required this.entry, this.category}); } class CategoryWithCount { // can be null, in which case we count how many entries don't have a category final Category? category; final int count; // amount of entries in this category CategoryWithCount(this.category, this.count); }