Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -90,3 +90,11 @@ lib/firebase_options.dart
# Local agent worktrees and execution ledgers.
/.worktrees/
/.superpowers/

.codegraph/

# Rendered previews of docs/*.svg — regenerate from the svg rather than
# committing them. The svg IS the source; these are just what gets pasted
# into Discord/issues, and they're ~750kb a piece.
docs/*.gif
docs/*.mp4
Binary file added docs/calorie-heatmap-ios-dark.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/calorie-heatmap-ios-light.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
419 changes: 419 additions & 0 deletions docs/calorie-heatmap-preview.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
12 changes: 10 additions & 2 deletions lib/data/local_repository.dart
Original file line number Diff line number Diff line change
Expand Up @@ -54,8 +54,16 @@ abstract class LocalRepository {
throw UnimplementedError('re-layer: getSleep');
Future<List<Map<String, dynamic>>> getStrain({int? from, int? to}) =>
throw UnimplementedError('re-layer: getStrain');
Future<List<Map<String, dynamic>>> getSessions({int? from, int? to}) =>
throw UnimplementedError('re-layer: getSessions');
/// Saved sessions in the window, merged with unconfirmed auto-detected bouts.
///
/// Pass `includeDetected: false` when only saved sessions are wanted: the
/// detected half has to read every recent day bundle to find them, and those
/// rows carry the full hr_curve/hypnogram/HRV payload.
Future<List<Map<String, dynamic>>> getSessions({
int? from,
int? to,
bool includeDetected = true,
}) => throw UnimplementedError('re-layer: getSessions');
Future<Map<String, dynamic>> getHistory({String range = '30d'}) =>
throw UnimplementedError('re-layer: getHistory');

Expand Down
14 changes: 13 additions & 1 deletion lib/data/local_repository_impl.dart
Original file line number Diff line number Diff line change
Expand Up @@ -1237,7 +1237,11 @@ class LocalRepositoryImpl extends LocalRepository {
}

@override
Future<List<Map<String, dynamic>>> getSessions({int? from, int? to}) async {
Future<List<Map<String, dynamic>>> getSessions({
int? from,
int? to,
bool includeDetected = true,
}) async {
// Manual/live sessions (the sessions table) MERGED with auto-detected
// workouts from the per-day bundle. Manual/saved WINS on overlap: a detected
// bout overlapping a manual session is dropped here (and is already dropped
Expand All @@ -1253,6 +1257,14 @@ class LocalRepositoryImpl extends LocalRepository {
final manualRows = await LocalDb.sessionsInRange(fromSec, toSec);
final manual = [for (final r in manualRows) _workoutOf(r)];

// Finding the detected half means reading every recent day bundle, and
// recentDayResults does SELECT r.* — the whole hr_curve/hypnogram/HRV
// payload, tens of KB a day, across the isolate boundary and back through
// jsonDecode. A caller that only wants saved sessions must not pay that.
// Already newest-first — sessionsInRange orders by start_ts DESC, the same
// order the merged path sorts into below.
if (!includeDetected) return manual;

// Saved spans (manual) for overlap-dedup of detected bouts.
final savedSpans = <List<int>>[];
for (final w in manual) {
Expand Down
Loading
Loading