diff --git a/CHANGELOG.md b/CHANGELOG.md index e0504dcf8..b964995c1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] ### Added: - Count duration for entries spanning multiple days for each individual day +- Weekly aggregation in story time charts ## [0.8.112] - 2022-07-19 ### Changed: diff --git a/lib/logic/charts/story_data.dart b/lib/logic/charts/story_data.dart index f3785a509..1fd71fe49 100644 --- a/lib/logic/charts/story_data.dart +++ b/lib/logic/charts/story_data.dart @@ -1,10 +1,12 @@ import 'dart:core'; import 'dart:math'; +import 'package:equatable/equatable.dart'; import 'package:flutter/material.dart'; import 'package:jiffy/jiffy.dart'; import 'package:lotti/classes/journal_entities.dart'; import 'package:lotti/widgets/charts/utils.dart'; +import 'package:week_of_year/week_of_year.dart'; enum AggregationTimeframe { daily, @@ -112,24 +114,63 @@ List aggregateStoryDailyTimeSum( return aggregated; } +class WeeklyAggregate extends Equatable { + const WeeklyAggregate(this.isoWeek, this.value); + + final String isoWeek; + final num value; + + @override + String toString() { + return '$isoWeek $value'; + } + + @override + List get props => [isoWeek, value]; +} + +List aggregateStoryWeeklyTimeSum( + List entities, { + required DateTime rangeStart, + required DateTime rangeEnd, +}) { + final minutesByWeek = {}; + + aggregateStoryDailyTimeSum( + entities, + rangeStart: rangeStart, + rangeEnd: rangeEnd, + ).forEach((byDay) { + final year = byDay.dateTime.year; + final weekOfYear = byDay.dateTime.weekOfYear; + final isoWeek = '$year-W${padLeft(weekOfYear)}'; + final prev = minutesByWeek[isoWeek] ?? 0; + minutesByWeek[isoWeek] = prev + byDay.value; + }); + + final aggregated = []; + minutesByWeek.forEach((isoWeek, value) { + aggregated.add(WeeklyAggregate(isoWeek, value)); + }); + + return aggregated; +} + List aggregateStoryTimeSum( List entities, { required DateTime rangeStart, required DateTime rangeEnd, required AggregationTimeframe timeframe, }) { - switch (timeframe) { - case AggregationTimeframe.daily: - return aggregateStoryDailyTimeSum( - entities, - rangeStart: rangeStart, - rangeEnd: rangeEnd, - ); - case AggregationTimeframe.weekly: - return aggregateStoryDailyTimeSum( - entities, - rangeStart: rangeStart, - rangeEnd: rangeEnd, - ); - } + aggregateStoryWeeklyTimeSum( + entities, + rangeStart: rangeStart, + rangeEnd: rangeEnd, + ); + + return aggregateStoryDailyTimeSum( + entities, + rangeStart: rangeStart, + rangeEnd: rangeEnd, + ); } diff --git a/pubspec.lock b/pubspec.lock index fb76f1886..528197730 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -2202,6 +2202,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "7.3.2" + week_of_year: + dependency: "direct main" + description: + name: week_of_year + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.0" win32: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index d86644146..32b4b3fd1 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,7 +1,7 @@ name: lotti description: A Smart Journal. publish_to: 'none' -version: 0.8.113+1168 +version: 0.8.113+1169 msix_config: display_name: Lotti @@ -143,6 +143,7 @@ dependencies: url_launcher: ^6.1.2 uuid: ^3.0.4 wechat_assets_picker: ^7.0.1 + week_of_year: ^2.0.0 window_manager: ^0.2.0 yaml: ^3.1.0 diff --git a/test/journal_test_data/test_data.dart b/test/journal_test_data/test_data.dart index 2db4dfff9..9eeac6d14 100644 --- a/test/journal_test_data/test_data.dart +++ b/test/journal_test_data/test_data.dart @@ -375,3 +375,13 @@ final testDurationEntry5 = JournalEntry( dateTo: DateTime(2022, 7, 5, 1), ), ); + +final testDurationEntry6 = JournalEntry( + meta: Metadata( + id: 'id', + createdAt: DateTime.now(), + updatedAt: DateTime.now(), + dateFrom: DateTime(2022, 6, 1, 22), + dateTo: DateTime(2022, 6, 12, 1), + ), +); diff --git a/test/logic/charts/story_data_test.dart b/test/logic/charts/story_data_test.dart index fae1a3319..8df34e024 100644 --- a/test/logic/charts/story_data_test.dart +++ b/test/logic/charts/story_data_test.dart @@ -205,5 +205,51 @@ void main() { ); }, ); + + test( + 'weekly aggregates handle entries stretching multiple days', + () { + expect( + aggregateStoryWeeklyTimeSum( + [ + testDurationEntry4, + testDurationEntry5, + testDurationEntry6, + ], + rangeStart: DateTime(2022, 6, 1), + rangeEnd: DateTime(2022, 7, 15), + ), + const [ + WeeklyAggregate('2022-W22', 5880.0), + WeeklyAggregate('2022-W23', 8700.0), + WeeklyAggregate('2022-W24', 0), + WeeklyAggregate('2022-W25', 0), + WeeklyAggregate('2022-W26', 1740.0), + WeeklyAggregate('2022-W27', 1500.0), + WeeklyAggregate('2022-W28', 0) + ], + ); + }, + ); + + test( + 'weekly aggregates handle entries stretching multiple days', + () { + expect( + aggregateStoryWeeklyTimeSum( + [], + rangeStart: DateTime(2022, 6, 1), + rangeEnd: DateTime(2022, 6, 30), + ), + const [ + WeeklyAggregate('2022-W22', 0), + WeeklyAggregate('2022-W23', 0), + WeeklyAggregate('2022-W24', 0), + WeeklyAggregate('2022-W25', 0), + WeeklyAggregate('2022-W26', 0), + ], + ); + }, + ); }); }