Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
a92897b
feat: add automatic weight sync from Apple Health and Google Health C…
JohnWeidner Mar 27, 2026
7bab9b6
feat: add unit label to weight form and convert health sync values
JohnWeidner Mar 27, 2026
bed1416
Merge branch 'master' into feat/health-platform-weight-sync
JohnWeidner Mar 27, 2026
627b8b9
fix: unit conversion on enable, dashboard refresh, and permission check
JohnWeidner Mar 27, 2026
2f10ee9
refactor: simplify health sync code after review
JohnWeidner Mar 27, 2026
86246ac
refactor: use l10n for health sync user-facing strings
JohnWeidner Mar 27, 2026
a4d03bc
feat: import all health history instead of last 30 days
JohnWeidner Mar 27, 2026
e1968d0
fix: request historical data access from Health Connect
JohnWeidner Mar 27, 2026
7001bca
feat(measurements): implement measurement groups, dynamic formula eva…
pankaj-basnet May 16, 2026
38c0a16
adjust code files as per backend measurement entryurl endpoint
pankaj-basnet May 19, 2026
ee084f1
Merge branch 'pr-1156' into feature/health-sync
rolandgeider Jul 2, 2026
18b745f
Start reworking the sync logic
rolandgeider Jul 2, 2026
01c13b1
Add health repository
rolandgeider Jul 2, 2026
84d0de7
feat(measurements): introduce MetricType enum and category properties
pankaj-basnet Jul 2, 2026
5d1f037
feat(measurements): add metric type selection and detail view support
pankaj-basnet Jul 3, 2026
6c3373f
feat(measurements): add metricType to category schema
pankaj-basnet Jul 4, 2026
5ff9d25
feat(charts): add smoothed trendline to measurement charts
pankaj-basnet Jul 4, 2026
bad4993
Merge branch 'feature/health-sync' into feat/healthsync
rolandgeider Jul 9, 2026
4250784
Cleanup, translations and small tests
rolandgeider Jul 9, 2026
a7a31ba
Merge pull request #1251 from pankaj-basnet/feat/healthsync
rolandgeider Jul 9, 2026
23a792f
Merge branch 'feature/health-sync' into feature/dynamic-measurements-…
rolandgeider Jul 9, 2026
8aac3da
Add workaround for swift package manager for the health package
rolandgeider Jul 10, 2026
066d643
Refactor the data model for multi-value categories
rolandgeider Jul 10, 2026
29032de
feat(measurements): add order field to MeasurementCategory model and …
pankaj-basnet Jul 10, 2026
8ae054d
Merge branch 'feature/health-sync' into feat/healthsync--sort-measure…
rolandgeider Jul 10, 2026
cbb960b
Filter out sub-categories, fix off-by-one index
rolandgeider Jul 10, 2026
3fc33e1
Add multi-value measurements to the test data
rolandgeider Jul 10, 2026
55cdd64
Fix trigger for CI
rolandgeider Jul 10, 2026
c088af2
Merge pull request #1261 from pankaj-basnet/feat/healthsync--sort-mea…
rolandgeider Jul 10, 2026
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
5 changes: 4 additions & 1 deletion .github/workflows/analyze.yml
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
name: Analyze
on:
# Direct pushes are only validated on master (post-merge); everything else
# is covered by the pull_request trigger, avoiding duplicate runs for
# same-repo PR branches.
push:
branches: [ master, ]
paths:
- '**.dart'
- 'pubspec.yaml'
Expand All @@ -9,7 +13,6 @@ on:
- 'ios/Runner/Info.plist'
- 'android/app/src/main/res/xml/locales_config.xml'
pull_request:
branches: [ master, ]
paths:
- '**/*.dart'
- 'pubspec.yaml'
Expand Down
7 changes: 5 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
name: Continous Integration
name: Continuous Integration
on:
# Direct pushes are only validated on master (post-merge); everything else
# is covered by the pull_request trigger, avoiding duplicate runs for
# same-repo PR branches.
push:
branches: [ master, ]
paths:
- '**.dart'
- 'pubspec.yaml'
pull_request:
branches: [ master, ]
paths:
- '**/*.dart'
- 'pubspec.yaml'
Expand Down
2 changes: 1 addition & 1 deletion android/app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ android {
defaultConfig {
// Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
applicationId "de.wger.flutter"
minSdkVersion flutter.minSdkVersion
minSdkVersion 26
targetSdk = flutter.targetSdkVersion
versionCode = flutter.versionCode
versionName = flutter.versionName
Expand Down
26 changes: 26 additions & 0 deletions android/app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,21 @@
<uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.INTERNET" />

<!-- Health Connect permissions for the imported measurement types -->
<uses-permission android:name="android.permission.health.READ_BODY_FAT"/>
<uses-permission android:name="android.permission.health.READ_HEIGHT"/>
<uses-permission android:name="android.permission.health.READ_HEALTH_DATA_HISTORY"/>

<queries>
<intent>
<action android:name="android.intent.action.VIEW" />
<data android:scheme="https" />
</intent>
<!-- Health Connect -->
<package android:name="com.google.android.apps.healthdata" />
<intent>
<action android:name="androidx.health.ACTION_SHOW_PERMISSIONS_RATIONALE" />
</intent>
</queries>

<supports-screens
Expand Down Expand Up @@ -66,8 +76,24 @@
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="wger" android:host="app-auth" />
</intent-filter>

<!-- Health Connect permissions rationale -->
<intent-filter>
<action android:name="androidx.health.ACTION_SHOW_PERMISSIONS_RATIONALE" />
</intent-filter>
</activity>

<!-- Required for Health Connect on Google Play -->
<activity-alias
android:name="ViewPermissionUsageActivity"
android:exported="true"
android:targetActivity=".MainActivity"
android:permission="android.permission.START_VIEW_PERMISSION_USAGE">
<intent-filter>
<action android:name="android.intent.action.VIEW_PERMISSION_USAGE" />
<category android:name="android.intent.category.HEALTH_PERMISSIONS" />
</intent-filter>
</activity-alias>

<!-- Don't delete the meta-data below.
This is used by the Flutter tool to generate GeneratedPluginRegistrant.java -->
Expand Down
4 changes: 2 additions & 2 deletions android/app/src/main/kotlin/de/wger/flutter/MainActivity.kt
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package de.wger.flutter

import io.flutter.embedding.android.FlutterActivity
import io.flutter.embedding.android.FlutterFragmentActivity

class MainActivity: FlutterActivity() {
class MainActivity: FlutterFragmentActivity() {
}
14 changes: 3 additions & 11 deletions ios/Runner.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,6 @@
9740EEB11CF90186004384FC /* Flutter */,
97C146F01CF9000F007C117D /* Runner */,
97C146EF1CF9000F007C117D /* Products */,
BB0286322FD60C00014F981C /* Pods */,
);
sourceTree = "<group>";
};
Expand Down Expand Up @@ -105,13 +104,6 @@
path = Runner;
sourceTree = "<group>";
};
BB0286322FD60C00014F981C /* Pods */ = {
isa = PBXGroup;
children = (
);
path = Pods;
sourceTree = "<group>";
};
/* End PBXGroup section */

/* Begin PBXNativeTarget section */
Expand Down Expand Up @@ -295,7 +287,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
IPHONEOS_DEPLOYMENT_TARGET = 14.0;
MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = iphoneos;
SUPPORTED_PLATFORMS = iphoneos;
Expand Down Expand Up @@ -383,7 +375,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
IPHONEOS_DEPLOYMENT_TARGET = 14.0;
MTL_ENABLE_DEBUG_INFO = YES;
ONLY_ACTIVE_ARCH = YES;
SDKROOT = iphoneos;
Expand Down Expand Up @@ -432,7 +424,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
IPHONEOS_DEPLOYMENT_TARGET = 14.0;
MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = iphoneos;
SUPPORTED_PLATFORMS = iphoneos;
Expand Down
2 changes: 2 additions & 0 deletions ios/Runner/Info.plist
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,8 @@
<true/>
<key>NSCameraUsageDescription</key>
<string>Workout photos</string>
<key>NSHealthShareUsageDescription</key>
<string>wger uses your health data to automatically sync weight measurements from your smart scale</string>
<key>UIApplicationSceneManifest</key>
<dict>
<key>UIApplicationSupportsMultipleScenes</key>
Expand Down
2 changes: 2 additions & 0 deletions ios/Runner/Runner.entitlements
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,7 @@
<dict>
<key>aps-environment</key>
<string>development</string>
<key>com.apple.developer.healthkit</key>
<true/>
</dict>
</plist>
15 changes: 15 additions & 0 deletions lib/core/home_tabs_screen.dart
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import 'package:font_awesome_flutter/font_awesome_flutter.dart';
import 'package:wger/core/dashboard.dart';
import 'package:wger/core/material.dart';
import 'package:wger/features/gallery/screens/gallery_screen.dart';
import 'package:wger/features/health/providers/health_sync.dart';
import 'package:wger/features/nutrition/screens/nutritional_plans_screen.dart';
import 'package:wger/features/routines/screens/routine_list_screen.dart';
import 'package:wger/features/weight/screens/weight_screen.dart';
Expand All @@ -41,6 +42,20 @@ class _HomeTabsScreenState extends ConsumerState<HomeTabsScreen>
int _selectedIndex = 0;
bool _isWideScreen = false;

@override
void initState() {
super.initState();

// Pull any new readings from Apple Health / Health Connect once the app is
// open. The sync is a no-op unless the user enabled it in the settings.
WidgetsBinding.instance.addPostFrameCallback((_) {
if (!mounted) {
return;
}
ref.read(healthSyncProvider.notifier).syncOnAppOpen();
});
}

@override
void didChangeDependencies() {
super.didChangeDependencies();
Expand Down
27 changes: 27 additions & 0 deletions lib/core/shared_preferences.dart
Original file line number Diff line number Diff line change
Expand Up @@ -132,4 +132,31 @@ class PreferenceHelper {
orElse: () => fallback.searchMode,
);
}

// --- Health sync ---

static const _healthSyncEnabledKey = 'healthSyncEnabled';
static const _lastHealthSyncTimestampKey = 'lastHealthSyncTimestamp';

Future<void> setHealthSyncEnabled(bool value) async {
await PreferenceHelper.asyncPref.setBool(_healthSyncEnabledKey, value);
}

Future<bool> getHealthSyncEnabled() async {
final value = await PreferenceHelper.asyncPref.getBool(_healthSyncEnabledKey);
return value ?? false;
}

Future<void> setLastHealthSyncTimestamp(String value) async {
await PreferenceHelper.asyncPref.setString(_lastHealthSyncTimestampKey, value);
}

Future<String?> getLastHealthSyncTimestamp() async {
return PreferenceHelper.asyncPref.getString(_lastHealthSyncTimestampKey);
}

Future<void> clearHealthSyncPreferences() async {
await PreferenceHelper.asyncPref.remove(_healthSyncEnabledKey);
await PreferenceHelper.asyncPref.remove(_lastHealthSyncTimestampKey);
}
}
5 changes: 4 additions & 1 deletion lib/core/widgets/dashboard/widgets/measurements.dart
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,10 @@ class _DashboardMeasurementWidgetState extends ConsumerState<DashboardMeasuremen
return AsyncValueWidget<List<MeasurementCategory>>(
value: ref.watch(measurementProvider),
loggerName: 'DashboardMeasurementWidget',
data: (categoriesList) {
data: (allCategories) {
// Children of multi-value groups are shown inside their parent's card
final categoriesList = allCategories.where((c) => c.parentId == null).toList();

if (categoriesList.isEmpty) {
return NothingFound(
AppLocalizations.of(context).moreMeasurementEntries,
Expand Down
31 changes: 31 additions & 0 deletions lib/database/converters/measurement_metric_type_converter.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
/*
* This file is part of wger Workout Manager <https://github.com/wger-project>.
* Copyright (c) 2020 - 2026 wger Team
*
* wger Workout Manager is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/

import 'package:drift/drift.dart';
import 'package:wger/features/measurements/models/measurement_category.dart';

/// Maps a [MetricType] to and from the SQLite string format.
class MeasurementMetricTypeConverter extends TypeConverter<MetricType, String> {
const MeasurementMetricTypeConverter();

@override
MetricType fromSql(String fromDb) => MetricType.fromWire(fromDb);

@override
String toSql(MetricType value) => value.wireValue;
}
1 change: 1 addition & 0 deletions lib/database/powersync/database.dart
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import 'package:wger/core/language.dart';
import 'package:wger/core/license.dart';
import 'package:wger/database/converters/date_only_text_converter.dart';
import 'package:wger/database/converters/exercise_image_style_converter.dart';
import 'package:wger/database/converters/measurement_metric_type_converter.dart';
import 'package:wger/database/converters/time_of_day_converter.dart';
import 'package:wger/database/converters/utc_datetime_converter.dart';
import 'package:wger/database/converters/workout_impression_converter.dart';
Expand Down
Loading