Skip to content

Commit a7a31ba

Browse files
authored
Merge pull request #1251 from pankaj-basnet/feat/healthsync
feat(measurements): measurement charts and metric type selection #1247
2 parents 01c13b1 + 4250784 commit a7a31ba

25 files changed

Lines changed: 725 additions & 92 deletions
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
/*
2+
* This file is part of wger Workout Manager <https://github.com/wger-project>.
3+
* Copyright (c) 2020 - 2026 wger Team
4+
*
5+
* wger Workout Manager is free software: you can redistribute it and/or modify
6+
* it under the terms of the GNU Affero General Public License as published by
7+
* the Free Software Foundation, either version 3 of the License, or
8+
* (at your option) any later version.
9+
*
10+
* This program is distributed in the hope that it will be useful,
11+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
12+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13+
* GNU Affero General Public License for more details.
14+
*
15+
* You should have received a copy of the GNU Affero General Public License
16+
* along with this program. If not, see <http://www.gnu.org/licenses/>.
17+
*/
18+
19+
import 'package:drift/drift.dart';
20+
import 'package:wger/features/measurements/models/measurement_category.dart';
21+
22+
/// Maps a [MetricType] to and from the SQLite string format.
23+
class MeasurementMetricTypeConverter extends TypeConverter<MetricType, String> {
24+
const MeasurementMetricTypeConverter();
25+
26+
@override
27+
MetricType fromSql(String fromDb) => MetricType.fromWire(fromDb);
28+
29+
@override
30+
String toSql(MetricType value) => value.wireValue;
31+
}

lib/database/powersync/database.dart

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ import 'package:wger/core/language.dart';
2525
import 'package:wger/core/license.dart';
2626
import 'package:wger/database/converters/date_only_text_converter.dart';
2727
import 'package:wger/database/converters/exercise_image_style_converter.dart';
28+
import 'package:wger/database/converters/measurement_metric_type_converter.dart';
2829
import 'package:wger/database/converters/time_of_day_converter.dart';
2930
import 'package:wger/database/converters/utc_datetime_converter.dart';
3031
import 'package:wger/database/converters/workout_impression_converter.dart';

lib/database/powersync/database.g.dart

Lines changed: 38 additions & 33 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

lib/database/powersync/tables/measurements.dart

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818

1919
import 'package:drift/drift.dart';
2020
import 'package:powersync/powersync.dart' as ps;
21+
import 'package:wger/database/converters/measurement_metric_type_converter.dart';
2122
import 'package:wger/database/converters/utc_datetime_converter.dart';
2223
import 'package:wger/features/measurements/models/measurement_category.dart';
2324
import 'package:wger/features/measurements/models/measurement_entry.dart';
@@ -31,18 +32,13 @@ class MeasurementCategoryTable extends Table {
3132

3233
TextColumn get name => text()();
3334
TextColumn get unit => text()();
34-
35-
/// `null` for plain user-created ("custom") categories.
36-
TextColumn get metricType => text().named('metric_type').nullable()();
35+
TextColumn get metricType =>
36+
text().named('metric_type').map(const MeasurementMetricTypeConverter())();
3737
}
3838

3939
const PowersyncMeasurementCategoryTable = ps.Table(
4040
'measurements_category',
41-
[
42-
ps.Column.text('name'),
43-
ps.Column.text('unit'),
44-
ps.Column.text('metric_type'),
45-
],
41+
[ps.Column.text('name'), ps.Column.text('unit'), ps.Column.text('metric_type')],
4642
);
4743

4844
@UseRowClass(MeasurementEntry)

lib/features/health/models/health_metric.dart

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -17,13 +17,13 @@
1717
*/
1818

1919
import 'package:health/health.dart';
20+
import 'package:wger/features/measurements/models/measurement_category.dart';
2021

2122
/// A body metric that can be imported from Apple Health / Health Connect into a
2223
/// measurement category.
2324
///
24-
/// [metricType] mirrors the server's planned `metric_type` enum and is stored
25-
/// on the created category; it drives the mapping back to a category on the
26-
/// next import.
25+
/// [metricType] is stored on the created category; it drives the mapping back
26+
/// to a category on the next import.
2727
class HealthMetric {
2828
const HealthMetric({
2929
required this.metricType,
@@ -35,15 +35,15 @@ class HealthMetric {
3535
this.disabledReason,
3636
});
3737

38-
/// Value of the server `metric_type` enum, e.g. `body_fat`.
39-
final String metricType;
38+
/// Metric type stored on the category, e.g. [MetricType.bodyFat].
39+
final MetricType metricType;
4040

4141
/// Health platform type this metric reads.
4242
final HealthDataType dataType;
4343

4444
/// Stable, non-localized category name. Used to find-or-create the category,
45-
/// and as the fallback match after a sync round-trip nulls [metricType]
46-
/// (the server does not persist it yet).
45+
/// and as the fallback match for a category the user already created by hand
46+
/// (which carries [MetricType.custom], not this metric's type).
4747
final String canonicalName;
4848

4949
/// Unit the value is stored in on the category.
@@ -79,23 +79,23 @@ double _identity(double raw) => raw;
7979
/// its [HealthMetric.disabledReason].
8080
const List<HealthMetric> healthMetrics = [
8181
HealthMetric(
82-
metricType: 'body_fat',
82+
metricType: MetricType.bodyFat,
8383
dataType: HealthDataType.BODY_FAT_PERCENTAGE,
8484
canonicalName: 'Body fat',
8585
unit: '%',
8686
toCategoryValue: _bodyFatToPercent,
8787
enabled: true,
8888
),
8989
HealthMetric(
90-
metricType: 'height',
90+
metricType: MetricType.height,
9191
dataType: HealthDataType.HEIGHT,
9292
canonicalName: 'Height',
9393
unit: 'cm',
9494
toCategoryValue: _heightToCm,
9595
enabled: true,
9696
),
9797
HealthMetric(
98-
metricType: 'body_weight',
98+
metricType: MetricType.bodyWeight,
9999
dataType: HealthDataType.WEIGHT,
100100
canonicalName: 'Weight',
101101
unit: 'kg',
@@ -105,23 +105,23 @@ const List<HealthMetric> healthMetrics = [
105105
'weight/measurements merge (metric_type=body_weight).',
106106
),
107107
HealthMetric(
108-
metricType: 'blood_pressure',
108+
metricType: MetricType.bloodPressure,
109109
dataType: HealthDataType.BLOOD_PRESSURE_SYSTOLIC,
110110
canonicalName: 'Blood pressure',
111111
unit: 'mmHg',
112112
toCategoryValue: _identity,
113113
disabledReason: 'Needs measurement grouping to pair systolic/diastolic.',
114114
),
115115
HealthMetric(
116-
metricType: 'heart_rate',
116+
metricType: MetricType.heartRate,
117117
dataType: HealthDataType.HEART_RATE,
118118
canonicalName: 'Heart rate',
119119
unit: 'bpm',
120120
toCategoryValue: _identity,
121121
disabledReason: 'High-frequency; needs a per-day aggregation strategy.',
122122
),
123123
HealthMetric(
124-
metricType: 'steps',
124+
metricType: MetricType.steps,
125125
dataType: HealthDataType.STEPS,
126126
canonicalName: 'Steps',
127127
unit: 'count',

lib/features/health/providers/health_repository.dart

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,8 +35,9 @@ class HealthRepository {
3535
final Health _health;
3636
final _logger = Logger('HealthRepository');
3737

38-
/// Identifies which platform a reading came from.
39-
String get sourceName => Platform.isIOS ? 'apple_health' : 'health_connect';
38+
/// The measurement `source` value for readings from this platform: `apple`
39+
/// for Apple Health, `google` for Health Connect.
40+
String get sourceName => Platform.isIOS ? 'apple' : 'google';
4041

4142
/// Whether a health platform is usable on this device.
4243
Future<bool> isAvailable() async {

lib/features/health/providers/health_sync.dart

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
/*
22
* This file is part of wger Workout Manager <https://github.com/wger-project>.
3-
* Copyright (c) 2026 wger Team
3+
* Copyright (c) 2026 - 2026 wger Team
44
*
55
* wger Workout Manager is free software: you can redistribute it and/or modify
66
* it under the terms of the GNU Affero General Public License as published by
@@ -188,8 +188,8 @@ class HealthSyncNotifier extends _$HealthSyncNotifier {
188188
}
189189

190190
/// Finds the category for [metric] (by `metric_type`, falling back to the
191-
/// canonical name once the server has nulled `metric_type` on a round-trip)
192-
/// or creates it. The created category is appended to [categories] so a later
191+
/// canonical name to reuse a matching category the user created by hand) or
192+
/// creates it. The created category is appended to [categories] so a later
193193
/// metric in the same run reuses it.
194194
Future<MeasurementCategory> _findOrCreateCategory(
195195
HealthMetric metric,

0 commit comments

Comments
 (0)