• 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

72.0
/lib/features/exercises/models/exercise.dart
1
/*
2
 * This file is part of wger Workout Manager <https://github.com/wger-project>.
3
 * Copyright (C) 2020, 2021 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
import 'package:collection/collection.dart';
19
import 'package:equatable/equatable.dart';
20
import 'package:logging/logging.dart';
21
import 'package:wger/core/consts.dart';
22
import 'package:wger/core/language.dart';
23
import 'package:wger/features/exercises/models/category.dart';
24
import 'package:wger/features/exercises/models/equipment.dart';
25
import 'package:wger/features/exercises/models/image.dart';
26
import 'package:wger/features/exercises/models/muscle.dart';
27
import 'package:wger/features/exercises/models/translation.dart';
28
import 'package:wger/features/exercises/models/video.dart';
29

30
class Exercise extends Equatable {
31
  static final _logger = Logger('ExerciseModel');
×
32

33
  final int id;
34
  final String uuid;
35
  final String? variationGroup;
36
  final DateTime? created;
37
  final DateTime? lastUpdate;
38
  final ExerciseCategory category;
39
  final List<Muscle> muscles;
40
  final List<Muscle> musclesSecondary;
41
  final List<Equipment> equipment;
42
  final List<ExerciseImage> images;
43
  final List<Translation> translations;
44
  final List<Video> videos;
45
  final List<String> authors;
46
  final List<String> authorsGlobal;
47

48
  int get categoryId => category.id;
×
49
  List<int> get musclesIds => muscles.map((e) => e.id).toList();
×
50
  List<int> get musclesSecondaryIds => musclesSecondary.map((e) => e.id).toList();
×
51
  List<int> get equipmentIds => equipment.map((e) => e.id).toList();
×
52

53
  const Exercise({
17✔
54
    required this.id,
55
    required this.uuid,
56
    this.created,
57
    this.lastUpdate,
58
    this.variationGroup,
59
    required this.category,
60
    this.muscles = const [],
61
    this.musclesSecondary = const [],
62
    this.equipment = const [],
63
    this.images = const [],
64
    this.translations = const [],
65
    this.videos = const [],
66
    this.authors = const [],
67
    this.authorsGlobal = const [],
68
  });
69

70
  Exercise copyWith({
1✔
71
    int? id,
72
    String? uuid,
73
    DateTime? created,
74
    DateTime? lastUpdate,
75
    String? variationGroup,
76
    ExerciseCategory? category,
77
    List<Muscle>? muscles,
78
    List<Muscle>? musclesSecondary,
79
    List<Equipment>? equipment,
80
    List<ExerciseImage>? images,
81
    List<Translation>? translations,
82
    List<Video>? videos,
83
    List<String>? authors,
84
    List<String>? authorsGlobal,
85
  }) {
86
    return Exercise(
1✔
87
      id: id ?? this.id,
1✔
88
      uuid: uuid ?? this.uuid,
1✔
89
      created: created ?? this.created,
1✔
90
      lastUpdate: lastUpdate ?? this.lastUpdate,
1✔
91
      variationGroup: variationGroup ?? this.variationGroup,
1✔
92
      category: category ?? this.category,
1✔
93
      muscles: muscles ?? this.muscles,
1✔
94
      musclesSecondary: musclesSecondary ?? this.musclesSecondary,
1✔
95
      equipment: equipment ?? this.equipment,
1✔
96
      images: images ?? this.images,
1✔
97
      translations: translations ?? this.translations,
×
98
      videos: videos ?? this.videos,
1✔
99
      authors: authors ?? this.authors,
1✔
100
      authorsGlobal: authorsGlobal ?? this.authorsGlobal,
1✔
101
    );
102
  }
103

104
  bool get showPlateCalculator => equipment.map((e) => e.id).contains(ID_EQUIPMENT_BARBELL);
12✔
105

106
  /// Returns the translation for the given language.
107
  ///
108
  /// Falls back to English, then to any available translation. If the exercise
109
  /// has no translations at all, an empty placeholder in the requested language
110
  /// is returned so callers never have to handle a missing translation. This
111
  /// happens transiently while translations are still syncing, or for exercises
112
  /// that genuinely ship without any translation.
113
  Translation getTranslation(String language) {
10✔
114
    // If the language is in the form en-US, take the language code only
115
    final languageCode = language.split('-')[0];
20✔
116

117
    if (translations.isEmpty) {
20✔
118
      _logger.info('Exercise-ID $id has no translations, returning an empty placeholder.');
×
119
      return Translation(
×
120
        name: '',
121
        description: '',
122
        language: Language(id: -1, shortName: languageCode, fullName: ''),
×
123
      );
124
    }
125

126
    return translations.firstWhere(
20✔
127
      (e) => e.language.shortName == languageCode,
40✔
128
      orElse: () => translations.firstWhere(
3✔
129
        (e) => e.language.shortName == LANGUAGE_SHORT_ENGLISH,
4✔
130
        orElse: () {
×
131
          _logger.info(
×
132
            'Could not find fallback english translation for exercise-ID $id, returning '
×
133
            'first language (${translations.first.language.shortName}) instead.',
×
134
          );
135
          return translations.first;
×
136
        },
137
      ),
138
    );
139
  }
140

141
  ExerciseImage? get getMainImage {
2✔
142
    return images.firstWhereOrNull((image) => image.isMain);
4✔
143
  }
144

145
  @override
4✔
146
  List<Object?> get props => [
4✔
147
    id,
4✔
148
    uuid,
4✔
149
    created,
4✔
150
    lastUpdate,
4✔
151
    category,
4✔
152
    equipment,
4✔
153
    muscles,
4✔
154
    musclesSecondary,
4✔
155
  ];
156
}
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