Files
dailyou/lib/core/database/daos/mood_dao.dart
T
stefwill 78aa032c1b feat: implement settings screen with reminders, biometrics, theme, and PDF export
- Daily reminder scheduling via flutter_local_notifications (inexact, repeating at chosen time)
- Biometric lock gate on app launch with auto-prompt and manual unlock button
- Theme mode selector (System/Light/Dark) persisted to settings DB
- PDF export of full mood history shared via share_plus
- Android: POST_NOTIFICATIONS, USE_BIOMETRIC permissions; switched to FlutterFragmentActivity
2026-04-26 15:49:52 +10:00

124 lines
4.4 KiB
Dart

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 ───────────────────────────────────────────────────────────────────
Future<List<MoodEntry>> getAllEntries() =>
(select(moodEntries)
..orderBy([(t) => OrderingTerm.asc(t.timestamp)]))
.get();
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();
Future<bool> hasEntryToday() async {
final today = DateTime.now();
final start = DateTime(today.year, today.month, today.day);
final end = start.add(const Duration(days: 1));
return (await (select(moodEntries)
..where((t) => t.timestamp.isBetweenValues(start, end))
..limit(1))
.getSingleOrNull()) != null;
}
// Entries with their linked activities — used by LLM prompt builder
Future<List<MoodEntryWithActivities>> getEntriesWithActivitiesInRange(
DateTime start, DateTime end) async {
final entries = await getEntriesInRange(start, end);
return Future.wait(entries.map((e) async {
final links = await (select(entryActivities)
..where((t) => t.entryId.equals(e.id)))
.get();
final actIds = links.map((l) => l.activityId).toList();
final acts = actIds.isEmpty
? <Activity>[]
: await (select(activities)..where((t) => t.id.isIn(actIds))).get();
return MoodEntryWithActivities(entry: e, activities: acts, tags: []);
}));
}
// 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),
);
}
}