import 'dart:io'; import 'package:drift/drift.dart'; import 'package:drift/native.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:path_provider/path_provider.dart'; import 'package:path/path.dart' as p; import 'tables.dart'; part 'app_database.g.dart'; @DriftDatabase(tables: [SearchHistory, Favorites, OfflineRegions, Settings]) class AppDatabase extends _$AppDatabase { AppDatabase() : super(_openConnection()); AppDatabase.forTesting(super.e); @override int get schemaVersion => 1; // --------------------------------------------------------------------------- // Search History DAO methods // --------------------------------------------------------------------------- /// Returns the most recent 50 search history items, newest first. Future> getRecentSearches() { return (select(searchHistory) ..orderBy([(t) => OrderingTerm.desc(t.timestamp)]) ..limit(50)) .get(); } /// Watches search history as a reactive stream. Stream> watchRecentSearches() { return (select(searchHistory) ..orderBy([(t) => OrderingTerm.desc(t.timestamp)]) ..limit(50)) .watch(); } /// Inserts a new search entry. Evicts entries beyond the 50-item limit. Future addSearch(String query, {double? lat, double? lon}) async { await into(searchHistory).insert(SearchHistoryCompanion.insert( query: query, latitude: Value(lat), longitude: Value(lon), timestamp: DateTime.now().millisecondsSinceEpoch ~/ 1000, )); await customStatement(''' DELETE FROM search_history WHERE id NOT IN ( SELECT id FROM search_history ORDER BY timestamp DESC LIMIT 50 ) '''); } /// Deletes a single history entry by ID. Future deleteSearchEntry(int id) { return (delete(searchHistory)..where((t) => t.id.equals(id))).go(); } /// Deletes all search history. Future clearSearchHistory() { return delete(searchHistory).go(); } // --------------------------------------------------------------------------- // Favorites DAO methods // --------------------------------------------------------------------------- /// Watches all favorites ordered by group then name. Stream> watchAllFavorites() { return (select(favorites) ..orderBy([ (t) => OrderingTerm.asc(t.groupName), (t) => OrderingTerm.asc(t.name), ])) .watch(); } /// Returns all favorites. Future> getAllFavorites() { return (select(favorites) ..orderBy([ (t) => OrderingTerm.asc(t.groupName), (t) => OrderingTerm.asc(t.name), ])) .get(); } /// Inserts a new favorite. Future addFavorite(FavoritesCompanion entry) { final now = DateTime.now().millisecondsSinceEpoch ~/ 1000; return into(favorites).insert(entry.copyWith( createdAt: Value(now), updatedAt: Value(now), )); } /// Deletes a favorite by ID. Future deleteFavorite(int id) { return (delete(favorites)..where((t) => t.id.equals(id))).go(); } /// Checks if a place is favorited by osmType and osmId. Future findFavoriteByOsm(String osmType, int osmId) { return (select(favorites) ..where( (t) => t.osmType.equals(osmType) & t.osmId.equals(osmId))) .getSingleOrNull(); } // --------------------------------------------------------------------------- // Offline Regions DAO methods // --------------------------------------------------------------------------- Stream> watchOfflineRegions() { return (select(offlineRegions) ..orderBy([(t) => OrderingTerm.asc(t.name)])) .watch(); } Future getOfflineRegionById(String regionId) { return (select(offlineRegions)..where((t) => t.id.equals(regionId))) .getSingleOrNull(); } Future upsertOfflineRegion(OfflineRegionsCompanion entry) { return into(offlineRegions).insertOnConflictUpdate(entry); } Future deleteOfflineRegion(String regionId) { return (delete(offlineRegions)..where((t) => t.id.equals(regionId))).go(); } // --------------------------------------------------------------------------- // Settings DAO methods // --------------------------------------------------------------------------- Future getSetting(String key) async { final row = await (select(settings)..where((t) => t.key.equals(key))) .getSingleOrNull(); return row?.value; } Future setSetting(String key, String value) { return into(settings).insertOnConflictUpdate( SettingsCompanion.insert(key: key, value: value), ); } Stream watchSetting(String key) { return (select(settings)..where((t) => t.key.equals(key))) .watchSingleOrNull() .map((row) => row?.value); } } LazyDatabase _openConnection() { return LazyDatabase(() async { final dbFolder = await getApplicationDocumentsDirectory(); final file = File(p.join(dbFolder.path, 'app.db')); return NativeDatabase.createInBackground(file); }); } /// Riverpod provider for the database. final appDatabaseProvider = Provider((ref) { final db = AppDatabase(); ref.onDispose(() => db.close()); return db; });