173 lines
5.3 KiB
Dart
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;
|
|
});
|