maps/mobile/lib/core/database/app_database.dart
2026-03-30 09:22:16 +02:00

173 lines
5.3 KiB
Dart

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<List<SearchHistoryData>> getRecentSearches() {
return (select(searchHistory)
..orderBy([(t) => OrderingTerm.desc(t.timestamp)])
..limit(50))
.get();
}
/// Watches search history as a reactive stream.
Stream<List<SearchHistoryData>> 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<void> 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<void> deleteSearchEntry(int id) {
return (delete(searchHistory)..where((t) => t.id.equals(id))).go();
}
/// Deletes all search history.
Future<void> clearSearchHistory() {
return delete(searchHistory).go();
}
// ---------------------------------------------------------------------------
// Favorites DAO methods
// ---------------------------------------------------------------------------
/// Watches all favorites ordered by group then name.
Stream<List<Favorite>> watchAllFavorites() {
return (select(favorites)
..orderBy([
(t) => OrderingTerm.asc(t.groupName),
(t) => OrderingTerm.asc(t.name),
]))
.watch();
}
/// Returns all favorites.
Future<List<Favorite>> getAllFavorites() {
return (select(favorites)
..orderBy([
(t) => OrderingTerm.asc(t.groupName),
(t) => OrderingTerm.asc(t.name),
]))
.get();
}
/// Inserts a new favorite.
Future<int> 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<void> deleteFavorite(int id) {
return (delete(favorites)..where((t) => t.id.equals(id))).go();
}
/// Checks if a place is favorited by osmType and osmId.
Future<Favorite?> findFavoriteByOsm(String osmType, int osmId) {
return (select(favorites)
..where(
(t) => t.osmType.equals(osmType) & t.osmId.equals(osmId)))
.getSingleOrNull();
}
// ---------------------------------------------------------------------------
// Offline Regions DAO methods
// ---------------------------------------------------------------------------
Stream<List<OfflineRegion>> watchOfflineRegions() {
return (select(offlineRegions)
..orderBy([(t) => OrderingTerm.asc(t.name)]))
.watch();
}
Future<OfflineRegion?> getOfflineRegionById(String regionId) {
return (select(offlineRegions)..where((t) => t.id.equals(regionId)))
.getSingleOrNull();
}
Future<void> upsertOfflineRegion(OfflineRegionsCompanion entry) {
return into(offlineRegions).insertOnConflictUpdate(entry);
}
Future<void> deleteOfflineRegion(String regionId) {
return (delete(offlineRegions)..where((t) => t.id.equals(regionId))).go();
}
// ---------------------------------------------------------------------------
// Settings DAO methods
// ---------------------------------------------------------------------------
Future<String?> getSetting(String key) async {
final row = await (select(settings)..where((t) => t.key.equals(key)))
.getSingleOrNull();
return row?.value;
}
Future<void> setSetting(String key, String value) {
return into(settings).insertOnConflictUpdate(
SettingsCompanion.insert(key: key, value: value),
);
}
Stream<String?> 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<AppDatabase>((ref) {
final db = AppDatabase();
ref.onDispose(() => db.close());
return db;
});