feat: set up Flutter project with drift database, Riverpod providers, and LLM service layer
- Configure dependencies: drift 2.32.x, flutter_riverpod 3.3.1, fl_chart 1.2.x, flutter_local_notifications 21.x, local_auth 3.x, and supporting packages - Drop riverpod_generator (incompatible analyzer version with drift_dev 2.32.x); convert all providers to manual Riverpod 3.x API using Notifier<T> and typed families - Define drift schema: MoodEntries, Activities, EntryActivities, Tags, EntryTags, LlmInsights, UserSettings with four DAOs (mood, activity, insight, settings) - Add LLM service abstraction (LlmService) and MediaPipe implementation - Fix SettingsDao.delete → deleteByKey to avoid shadowing drift's inherited delete method
This commit is contained in:
@@ -0,0 +1,42 @@
|
||||
import 'package:drift/drift.dart';
|
||||
import '../app_database.dart';
|
||||
import '../tables.dart';
|
||||
|
||||
part 'activity_dao.g.dart';
|
||||
|
||||
@DriftAccessor(tables: [Activities, EntryActivities, MoodEntries])
|
||||
class ActivityDao extends DatabaseAccessor<AppDatabase>
|
||||
with _$ActivityDaoMixin {
|
||||
ActivityDao(super.db);
|
||||
|
||||
Stream<List<Activity>> watchAllActivities() =>
|
||||
(select(activities)..orderBy([(t) => OrderingTerm.asc(t.sortOrder)]))
|
||||
.watch();
|
||||
|
||||
Future<int> insertActivity(ActivitiesCompanion activity) =>
|
||||
into(activities).insert(activity);
|
||||
|
||||
Future<void> deleteActivity(int id) =>
|
||||
(delete(activities)..where((t) => t.id.equals(id))).go();
|
||||
|
||||
// How many times each activity was logged — used by charts
|
||||
Future<Map<int, int>> getActivityFrequency(
|
||||
DateTime start, DateTime end) async {
|
||||
final entries = await (select(moodEntries)
|
||||
..where((t) => t.timestamp.isBetweenValues(start, end)))
|
||||
.get();
|
||||
final entryIds = entries.map((e) => e.id).toList();
|
||||
|
||||
if (entryIds.isEmpty) return {};
|
||||
|
||||
final rows = await (select(entryActivities)
|
||||
..where((t) => t.entryId.isIn(entryIds)))
|
||||
.get();
|
||||
|
||||
final Map<int, int> freq = {};
|
||||
for (final r in rows) {
|
||||
freq[r.activityId] = (freq[r.activityId] ?? 0) + 1;
|
||||
}
|
||||
return freq;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'activity_dao.dart';
|
||||
|
||||
// ignore_for_file: type=lint
|
||||
mixin _$ActivityDaoMixin on DatabaseAccessor<AppDatabase> {
|
||||
$ActivitiesTable get activities => attachedDatabase.activities;
|
||||
$MoodEntriesTable get moodEntries => attachedDatabase.moodEntries;
|
||||
$EntryActivitiesTable get entryActivities => attachedDatabase.entryActivities;
|
||||
ActivityDaoManager get managers => ActivityDaoManager(this);
|
||||
}
|
||||
|
||||
class ActivityDaoManager {
|
||||
final _$ActivityDaoMixin _db;
|
||||
ActivityDaoManager(this._db);
|
||||
$$ActivitiesTableTableManager get activities =>
|
||||
$$ActivitiesTableTableManager(_db.attachedDatabase, _db.activities);
|
||||
$$MoodEntriesTableTableManager get moodEntries =>
|
||||
$$MoodEntriesTableTableManager(_db.attachedDatabase, _db.moodEntries);
|
||||
$$EntryActivitiesTableTableManager get entryActivities =>
|
||||
$$EntryActivitiesTableTableManager(
|
||||
_db.attachedDatabase, _db.entryActivities);
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
import 'package:drift/drift.dart';
|
||||
import '../app_database.dart';
|
||||
import '../tables.dart';
|
||||
|
||||
part 'insight_dao.g.dart';
|
||||
|
||||
@DriftAccessor(tables: [LlmInsights])
|
||||
class InsightDao extends DatabaseAccessor<AppDatabase> with _$InsightDaoMixin {
|
||||
InsightDao(super.db);
|
||||
|
||||
Future<LlmInsight?> getCached(String promptHash) => (select(llmInsights)
|
||||
..where((t) => t.promptHash.equals(promptHash))
|
||||
..limit(1))
|
||||
.getSingleOrNull();
|
||||
|
||||
Future<void> upsertInsight(LlmInsightsCompanion insight) =>
|
||||
into(llmInsights).insertOnConflictUpdate(insight);
|
||||
|
||||
// Purge cached insights older than 7 days
|
||||
Future<void> purgeStale() {
|
||||
final cutoff = DateTime.now().subtract(const Duration(days: 7));
|
||||
return (delete(llmInsights)
|
||||
..where((t) => t.generatedAt.isSmallerThanValue(cutoff)))
|
||||
.go();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'insight_dao.dart';
|
||||
|
||||
// ignore_for_file: type=lint
|
||||
mixin _$InsightDaoMixin on DatabaseAccessor<AppDatabase> {
|
||||
$LlmInsightsTable get llmInsights => attachedDatabase.llmInsights;
|
||||
InsightDaoManager get managers => InsightDaoManager(this);
|
||||
}
|
||||
|
||||
class InsightDaoManager {
|
||||
final _$InsightDaoMixin _db;
|
||||
InsightDaoManager(this._db);
|
||||
$$LlmInsightsTableTableManager get llmInsights =>
|
||||
$$LlmInsightsTableTableManager(_db.attachedDatabase, _db.llmInsights);
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
import 'package:drift/drift.dart';
|
||||
import '../app_database.dart';
|
||||
import '../tables.dart';
|
||||
|
||||
part 'mood_dao.g.dart';
|
||||
|
||||
// Joined result — entry + its activities
|
||||
class MoodEntryWithActivities {
|
||||
final MoodEntry entry;
|
||||
final List<Activity> activities;
|
||||
final List<Tag> tags;
|
||||
MoodEntryWithActivities({
|
||||
required this.entry,
|
||||
required this.activities,
|
||||
required this.tags,
|
||||
});
|
||||
}
|
||||
|
||||
@DriftAccessor(
|
||||
tables: [MoodEntries, EntryActivities, Activities, EntryTags, Tags])
|
||||
class MoodDao extends DatabaseAccessor<AppDatabase> with _$MoodDaoMixin {
|
||||
MoodDao(super.db);
|
||||
|
||||
// ── Writes ──────────────────────────────────────────────────────────────────
|
||||
|
||||
Future<int> insertEntry(MoodEntriesCompanion entry) =>
|
||||
into(moodEntries).insert(entry);
|
||||
|
||||
Future<void> insertEntryWithActivities({
|
||||
required MoodEntriesCompanion entry,
|
||||
required List<int> activityIds,
|
||||
required List<int> tagIds,
|
||||
}) async {
|
||||
await transaction(() async {
|
||||
final entryId = await into(moodEntries).insert(entry);
|
||||
for (final actId in activityIds) {
|
||||
await into(entryActivities).insert(
|
||||
EntryActivitiesCompanion.insert(entryId: entryId, activityId: actId),
|
||||
);
|
||||
}
|
||||
for (final tagId in tagIds) {
|
||||
await into(entryTags).insert(
|
||||
EntryTagsCompanion.insert(entryId: entryId, tagId: tagId),
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
Future<void> deleteEntry(int id) =>
|
||||
(delete(moodEntries)..where((t) => t.id.equals(id))).go();
|
||||
|
||||
Future<void> updateEntry(MoodEntriesCompanion entry) =>
|
||||
(update(moodEntries)..where((t) => t.id.equals(entry.id.value)))
|
||||
.write(entry);
|
||||
|
||||
// ── Reads ───────────────────────────────────────────────────────────────────
|
||||
|
||||
Stream<List<MoodEntry>> watchAllEntries() =>
|
||||
(select(moodEntries)..orderBy([(t) => OrderingTerm.desc(t.timestamp)]))
|
||||
.watch();
|
||||
|
||||
Stream<List<MoodEntry>> watchEntriesForDay(DateTime day) {
|
||||
final start = DateTime(day.year, day.month, day.day);
|
||||
final end = start.add(const Duration(days: 1));
|
||||
return (select(moodEntries)
|
||||
..where((t) => t.timestamp.isBetweenValues(start, end))
|
||||
..orderBy([(t) => OrderingTerm.desc(t.timestamp)]))
|
||||
.watch();
|
||||
}
|
||||
|
||||
Future<List<MoodEntry>> getEntriesInRange(DateTime start, DateTime end) =>
|
||||
(select(moodEntries)
|
||||
..where((t) => t.timestamp.isBetweenValues(start, end))
|
||||
..orderBy([(t) => OrderingTerm.asc(t.timestamp)]))
|
||||
.get();
|
||||
|
||||
// Average mood per day — used by calendar heatmap
|
||||
Future<Map<DateTime, double>> getDailyAverages(
|
||||
DateTime start, DateTime end) async {
|
||||
final entries = await getEntriesInRange(start, end);
|
||||
final Map<DateTime, List<int>> grouped = {};
|
||||
for (final e in entries) {
|
||||
final day =
|
||||
DateTime(e.timestamp.year, e.timestamp.month, e.timestamp.day);
|
||||
grouped.putIfAbsent(day, () => []).add(e.moodLevel);
|
||||
}
|
||||
return grouped.map(
|
||||
(day, levels) =>
|
||||
MapEntry(day, levels.reduce((a, b) => a + b) / levels.length),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'mood_dao.dart';
|
||||
|
||||
// ignore_for_file: type=lint
|
||||
mixin _$MoodDaoMixin on DatabaseAccessor<AppDatabase> {
|
||||
$MoodEntriesTable get moodEntries => attachedDatabase.moodEntries;
|
||||
$ActivitiesTable get activities => attachedDatabase.activities;
|
||||
$EntryActivitiesTable get entryActivities => attachedDatabase.entryActivities;
|
||||
$TagsTable get tags => attachedDatabase.tags;
|
||||
$EntryTagsTable get entryTags => attachedDatabase.entryTags;
|
||||
MoodDaoManager get managers => MoodDaoManager(this);
|
||||
}
|
||||
|
||||
class MoodDaoManager {
|
||||
final _$MoodDaoMixin _db;
|
||||
MoodDaoManager(this._db);
|
||||
$$MoodEntriesTableTableManager get moodEntries =>
|
||||
$$MoodEntriesTableTableManager(_db.attachedDatabase, _db.moodEntries);
|
||||
$$ActivitiesTableTableManager get activities =>
|
||||
$$ActivitiesTableTableManager(_db.attachedDatabase, _db.activities);
|
||||
$$EntryActivitiesTableTableManager get entryActivities =>
|
||||
$$EntryActivitiesTableTableManager(
|
||||
_db.attachedDatabase, _db.entryActivities);
|
||||
$$TagsTableTableManager get tags =>
|
||||
$$TagsTableTableManager(_db.attachedDatabase, _db.tags);
|
||||
$$EntryTagsTableTableManager get entryTags =>
|
||||
$$EntryTagsTableTableManager(_db.attachedDatabase, _db.entryTags);
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
import 'package:drift/drift.dart';
|
||||
import '../app_database.dart';
|
||||
import '../tables.dart';
|
||||
|
||||
part 'settings_dao.g.dart';
|
||||
|
||||
@DriftAccessor(tables: [UserSettings])
|
||||
class SettingsDao extends DatabaseAccessor<AppDatabase>
|
||||
with _$SettingsDaoMixin {
|
||||
SettingsDao(super.db);
|
||||
|
||||
Future<String?> get(String key) async {
|
||||
final row = await (select(userSettings)
|
||||
..where((t) => t.key.equals(key))
|
||||
..limit(1))
|
||||
.getSingleOrNull();
|
||||
return row?.value;
|
||||
}
|
||||
|
||||
Future<void> set(String key, String value) =>
|
||||
into(userSettings).insertOnConflictUpdate(
|
||||
UserSettingsCompanion.insert(key: key, value: value),
|
||||
);
|
||||
|
||||
Future<void> deleteByKey(String key) =>
|
||||
(delete(userSettings)..where((t) => t.key.equals(key))).go();
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'settings_dao.dart';
|
||||
|
||||
// ignore_for_file: type=lint
|
||||
mixin _$SettingsDaoMixin on DatabaseAccessor<AppDatabase> {
|
||||
$UserSettingsTable get userSettings => attachedDatabase.userSettings;
|
||||
SettingsDaoManager get managers => SettingsDaoManager(this);
|
||||
}
|
||||
|
||||
class SettingsDaoManager {
|
||||
final _$SettingsDaoMixin _db;
|
||||
SettingsDaoManager(this._db);
|
||||
$$UserSettingsTableTableManager get userSettings =>
|
||||
$$UserSettingsTableTableManager(_db.attachedDatabase, _db.userSettings);
|
||||
}
|
||||
Reference in New Issue
Block a user