• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

wger-project / flutter / 30854254088

03 Aug 2026 09:21PM UTC coverage: 40.895% (-15.0%) from 55.93%
30854254088

Pull #1260

github

web-flow
Merge d93794de5 into e3affdd4f
Pull Request #1260: WIP: health sync

9980 of 24404 relevant lines covered (40.89%)

1.72 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

83.1
/lib/core/shared_preferences.dart
1
import 'package:shared_preferences/shared_preferences.dart';
2
import 'package:shared_preferences/util/legacy_to_async_migration_util.dart';
3
import 'package:wger/core/search_options.dart';
4
import 'package:wger/features/exercises/models/exercise_filters.dart';
5
import 'package:wger/features/nutrition/models/ingredient.dart';
6
import 'package:wger/features/nutrition/models/ingredient_filters.dart';
7

8
/// A helper class that manages preferences using SharedPreferencesAsync
9
/// and handles migration from the legacy SharedPreferences to
10
/// SharedPreferencesAsync.
11
class PreferenceHelper {
12
  SharedPreferencesAsync _asyncPref = SharedPreferencesAsync();
13

14
  PreferenceHelper._instantiate();
23✔
15

16
  static final PreferenceHelper _instance = PreferenceHelper._instantiate();
69✔
17

18
  static SharedPreferencesAsync get asyncPref => _instance._asyncPref;
65✔
19

20
  static PreferenceHelper get instance => _instance;
10✔
21

22
  /// Migration function that ensures any legacy data stored in
23
  /// SharedPreferences is migrated to SharedPreferencesAsync. This migration
24
  /// only happens once, as checked by the migrationCompletedKey.
25
  ///
26
  /// `migrationCompletedKey` is used to track if the migration has been
27
  /// completed.
28
  Future<void> migrationSupportFunctionForSharedPreferences() async {
1✔
29
    const SharedPreferencesOptions sharedPreferencesOptions = SharedPreferencesOptions();
30
    final SharedPreferences prefs = await SharedPreferences.getInstance();
1✔
31
    await migrateLegacySharedPreferencesToSharedPreferencesAsyncIfNecessary(
1✔
32
      legacySharedPreferencesInstance: prefs,
33
      sharedPreferencesAsyncOptions: sharedPreferencesOptions,
34
      migrationCompletedKey: 'migrationCompleted',
35
    );
36
    _asyncPref = SharedPreferencesAsync();
2✔
37
  }
38

39
  //ingredients filters
40
  //1.vegan
41
  Future<void> saveIngredientVeganFilter(bool value) async {
×
42
    await PreferenceHelper.asyncPref.setBool('ingredientVeganFilter', value);
×
43
  }
44

45
  Future<bool> getIngredientVeganFilter() async {
2✔
46
    return await PreferenceHelper.asyncPref.getBool('ingredientVeganFilter') ?? false;
4✔
47
  }
48

49
  //2.vegetarian
50
  Future<void> saveIngredientVegetarianFilter(bool value) async {
×
51
    await PreferenceHelper.asyncPref.setBool('ingredientVegetarianFilter', value);
×
52
  }
53

54
  Future<bool> getIngredientVegetarianFilter() async {
2✔
55
    return await PreferenceHelper.asyncPref.getBool('ingredientVegetarianFilter') ?? false;
4✔
56
  }
57

58
  //3.language
59
  Future<void> saveIngredientSearchLanguage(SearchLanguage language) async {
2✔
60
    await PreferenceHelper.asyncPref.setString('search_language', language.name);
6✔
61
  }
62

63
  Future<SearchLanguage> getIngredientSearchLanguage() async {
2✔
64
    const fallback = IngredientFilters();
65
    final value = await PreferenceHelper.asyncPref.getString('search_language');
4✔
66
    if (value == null) {
67
      return fallback.searchLanguage;
×
68
    } else {
69
      return SearchLanguage.values.firstWhere(
2✔
70
        (e) => e.name == value,
6✔
71
        orElse: () => fallback.searchLanguage,
×
72
      );
73
    }
74
  }
75

76
  //4.nutri-score worst acceptable grade (null means the filter is off)
77
  Future<void> saveIngredientNutriscoreMax(NutriScore? value) async {
1✔
78
    if (value == null) {
79
      await PreferenceHelper.asyncPref.remove('ingredientNutriscoreMax');
×
80
    } else {
81
      await PreferenceHelper.asyncPref.setString('ingredientNutriscoreMax', value.name);
3✔
82
    }
83
  }
84

85
  Future<NutriScore?> getIngredientNutriscoreMax() async {
2✔
86
    final value = await PreferenceHelper.asyncPref.getString('ingredientNutriscoreMax');
4✔
87
    if (value == null) {
88
      return null;
89
    }
90
    return NutriScore.values.firstWhere(
×
91
      (e) => e.name == value,
×
92
      orElse: () => NutriScore.c,
×
93
    );
94
  }
95

96
  // --- Exercise search filters ---
97

98
  Future<void> saveExerciseSearchLanguage(SearchLanguage language) async {
2✔
99
    await PreferenceHelper.asyncPref.setString(
4✔
100
      'exercise_search_language',
101
      language.name,
2✔
102
    );
103
  }
104

105
  Future<SearchLanguage> getExerciseSearchLanguage() async {
2✔
106
    const fallback = ExerciseFilters();
107
    final value = await PreferenceHelper.asyncPref.getString('exercise_search_language');
4✔
108
    if (value == null) {
109
      return fallback.searchLanguage;
2✔
110
    }
111
    return SearchLanguage.values.firstWhere(
1✔
112
      (e) => e.name == value,
3✔
113
      orElse: () => fallback.searchLanguage,
×
114
    );
115
  }
116

117
  Future<void> saveExerciseSearchMode(ExerciseSearchMode mode) async {
1✔
118
    await PreferenceHelper.asyncPref.setString(
2✔
119
      'exercise_search_mode',
120
      mode.name,
1✔
121
    );
122
  }
123

124
  Future<ExerciseSearchMode> getExerciseSearchMode() async {
2✔
125
    const fallback = ExerciseFilters();
126
    final value = await PreferenceHelper.asyncPref.getString('exercise_search_mode');
4✔
127
    if (value == null) {
128
      return fallback.searchMode;
2✔
129
    }
130
    return ExerciseSearchMode.values.firstWhere(
1✔
131
      (e) => e.name == value,
3✔
132
      orElse: () => fallback.searchMode,
×
133
    );
134
  }
135

136
  // --- Health sync ---
137

138
  static const _healthSyncEnabledKey = 'healthSyncEnabled';
139
  static const _lastHealthSyncTimestampKey = 'lastHealthSyncTimestamp';
140
  static const _healthSyncReadableTypesKey = 'healthSyncReadableTypes';
141
  static const _healthSyncEmptyMetricsKey = 'healthSyncEmptyMetrics';
142

143
  Future<void> setHealthSyncEnabled(bool value) async {
1✔
144
    await PreferenceHelper.asyncPref.setBool(_healthSyncEnabledKey, value);
2✔
145
  }
146

147
  Future<bool> getHealthSyncEnabled() async {
1✔
148
    final value = await PreferenceHelper.asyncPref.getBool(_healthSyncEnabledKey);
2✔
149
    return value ?? false;
150
  }
151

152
  Future<void> setLastHealthSyncTimestamp(String value) async {
1✔
153
    await PreferenceHelper.asyncPref.setString(_lastHealthSyncTimestampKey, value);
2✔
154
  }
155

156
  Future<String?> getLastHealthSyncTimestamp() async {
1✔
157
    return PreferenceHelper.asyncPref.getString(_lastHealthSyncTimestampKey);
2✔
158
  }
159

160
  /// The health data types the platform let us read during the last sync.
161
  ///
162
  /// A type that was not readable then has no history in wger, so the sync
163
  /// reads the full window once it becomes readable, instead of starting at
164
  /// the watermark and leaving everything before it missing.
165
  Future<void> setHealthSyncReadableTypes(List<String> value) async {
1✔
166
    await PreferenceHelper.asyncPref.setStringList(_healthSyncReadableTypesKey, value);
2✔
167
  }
168

169
  Future<List<String>?> getHealthSyncReadableTypes() async {
1✔
170
    return PreferenceHelper.asyncPref.getStringList(_healthSyncReadableTypesKey);
2✔
171
  }
172

173
  /// The metrics the platform had nothing at all for when their full history
174
  /// was last read.
175
  ///
176
  /// Such a metric never gets a category, and a missing category is what sends
177
  /// the sync back to the full window; without this it would do so on every
178
  /// run, for every metric.
179
  Future<void> setHealthSyncEmptyMetrics(List<String> value) async {
1✔
180
    await PreferenceHelper.asyncPref.setStringList(_healthSyncEmptyMetricsKey, value);
2✔
181
  }
182

183
  Future<List<String>?> getHealthSyncEmptyMetrics() async {
1✔
184
    return PreferenceHelper.asyncPref.getStringList(_healthSyncEmptyMetricsKey);
2✔
185
  }
186

187
  Future<void> clearHealthSyncPreferences() async {
1✔
188
    await PreferenceHelper.asyncPref.remove(_healthSyncEnabledKey);
2✔
189
    await PreferenceHelper.asyncPref.remove(_lastHealthSyncTimestampKey);
2✔
190
    await PreferenceHelper.asyncPref.remove(_healthSyncReadableTypesKey);
2✔
191
    await PreferenceHelper.asyncPref.remove(_healthSyncEmptyMetricsKey);
2✔
192
  }
193
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE TRIAL · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc