• 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

49.32
/lib/features/exercises/widgets/exercises.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
 * wger Workout Manager 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:carousel_slider/carousel_slider.dart';
20
import 'package:flutter/material.dart';
21
import 'package:flutter_svg/svg.dart';
22
import 'package:wger/core/consts.dart';
23
import 'package:wger/features/exercises/models/exercise.dart';
24
import 'package:wger/features/exercises/models/image.dart';
25
import 'package:wger/features/exercises/models/muscle.dart';
26
import 'package:wger/features/exercises/widgets/detail/aliases_section.dart';
27
import 'package:wger/features/exercises/widgets/detail/category_equipment_row.dart';
28
import 'package:wger/features/exercises/widgets/detail/description_section.dart';
29
import 'package:wger/features/exercises/widgets/detail/images_section.dart';
30
import 'package:wger/features/exercises/widgets/detail/muscles_section.dart';
31
import 'package:wger/features/exercises/widgets/detail/notes_section.dart';
32
import 'package:wger/features/exercises/widgets/detail/variations_section.dart';
33
import 'package:wger/features/exercises/widgets/detail/videos_section.dart';
34
import 'package:wger/features/exercises/widgets/images.dart';
35
import 'package:wger/l10n/generated/app_localizations.dart';
36

37
class ExerciseDetail extends StatelessWidget {
38
  final Exercise _exercise;
39

40
  const ExerciseDetail(this._exercise, {super.key});
×
41

42
  @override
×
43
  Widget build(BuildContext context) {
44
    final translation = _exercise.getTranslation(Localizations.localeOf(context).languageCode);
×
45

46
    return SingleChildScrollView(
×
47
      child: Column(
×
48
        mainAxisSize: MainAxisSize.min,
49
        crossAxisAlignment: CrossAxisAlignment.start,
50
        children: [
×
51
          CategoryEquipmentRow(exercise: _exercise),
×
52
          AliasesSection(translation: translation),
×
53
          VideosSection(exercise: _exercise),
×
54
          ImagesSection(exercise: _exercise),
×
55
          DescriptionSection(translation: translation),
×
56
          NotesSection(translation: translation),
×
57
          MusclesSection(exercise: _exercise),
×
58
          VariationsSection(exercise: _exercise),
×
59
        ],
60
      ),
61
    );
62
  }
63
}
64

65
class MuscleColorHelper extends StatelessWidget {
66
  final bool main;
67

68
  const MuscleColorHelper({this.main = true, super.key});
1✔
69

70
  @override
1✔
71
  Widget build(BuildContext context) {
72
    return Row(
1✔
73
      children: [
1✔
74
        Container(
1✔
75
          height: 16,
76
          width: 16,
77
          color: main ? COLOR_MAIN_MUSCLES : COLOR_SECONDARY_MUSCLES,
1✔
78
        ),
79
        const SizedBox(width: 2),
80
        Text(
1✔
81
          main
1✔
82
              ? AppLocalizations.of(context).muscles
2✔
83
              : AppLocalizations.of(context).musclesSecondary,
2✔
84
          style: const TextStyle(fontWeight: FontWeight.bold),
85
        ),
86
      ],
87
    );
88
  }
89
}
90

91
class MuscleRowWidget extends StatelessWidget {
92
  final List<Muscle> muscles;
93
  final List<Muscle> musclesSecondary;
94

95
  const MuscleRowWidget({
1✔
96
    required this.muscles,
97
    required this.musclesSecondary,
98
  });
99

100
  @override
1✔
101
  Widget build(BuildContext context) {
102
    return Row(
1✔
103
      //mainAxisAlignment: MainAxisAlignment.spaceAround,
104
      //crossAxisAlignment: CrossAxisAlignment.start,
105
      children: [
1✔
106
        Expanded(
1✔
107
          child: Padding(
1✔
108
            padding: const EdgeInsets.symmetric(horizontal: 4),
109
            child: MuscleWidget(
1✔
110
              muscles: muscles,
1✔
111
              musclesSecondary: musclesSecondary,
1✔
112
              isFront: true,
113
            ),
114
          ),
115
        ),
116
        Expanded(
1✔
117
          child: Padding(
1✔
118
            padding: const EdgeInsets.symmetric(horizontal: 4),
119
            child: MuscleWidget(
1✔
120
              muscles: muscles,
1✔
121
              musclesSecondary: musclesSecondary,
1✔
122
              isFront: false,
123
            ),
124
          ),
125
        ),
126
      ],
127
    );
128
  }
129
}
130

131
class MuscleWidget extends StatelessWidget {
132
  late final List<Muscle> muscles;
133
  late final List<Muscle> musclesSecondary;
134
  final bool isFront;
135

136
  MuscleWidget({
1✔
137
    List<Muscle> muscles = const [],
138
    List<Muscle> musclesSecondary = const [],
139
    this.isFront = true,
140
  }) {
141
    this.muscles = muscles.where((m) => m.isFront == isFront).toList();
3✔
142
    this.musclesSecondary = musclesSecondary.where((m) => m.isFront == isFront).toList();
3✔
143
  }
144

145
  @override
1✔
146
  Widget build(BuildContext context) {
147
    final background = isFront ? 'front' : 'back';
1✔
148

149
    return Stack(
1✔
150
      children: [
1✔
151
        SvgPicture.asset('assets/images/muscles/$background.svg'),
2✔
152
        ...muscles.map((m) => SvgPicture.asset('assets/images/muscles/main/muscle-${m.id}.svg')),
2✔
153
        ...musclesSecondary
1✔
154
            .where((m) => !muscles.contains(m))
1✔
155
            .map(
1✔
156
              (m) => SvgPicture.asset(
×
157
                'assets/images/muscles/secondary/muscle-${m.id}.svg',
×
158
              ),
159
            ),
160
      ],
161
    );
162
  }
163
}
164

165
class CarouselImages extends StatefulWidget {
166
  final List<ExerciseImage> images;
167

168
  const CarouselImages({super.key, required this.images});
×
169

170
  @override
×
171
  State<CarouselImages> createState() => _CarouselImagesState();
×
172
}
173

174
class _CarouselImagesState extends State<CarouselImages> {
175
  int pageIndex = 0;
176

177
  @override
×
178
  Widget build(BuildContext context) {
179
    return SizedBox(
×
180
      height: 250,
181
      child: Stack(
×
182
        children: [
×
183
          CarouselSlider(
×
184
            options: CarouselOptions(
×
185
              onPageChanged: (index, _) => setState(() => pageIndex = index),
×
186
            ),
187
            items: List.generate(
×
188
              widget.images.length,
×
189
              (index) => Padding(
×
190
                padding: const EdgeInsets.only(top: 15),
191
                child: ExerciseImageWidget(
×
192
                  image: widget.images[index],
×
193
                  height: 260,
194
                ),
195
              ),
196
            ),
197
          ),
198
          Row(
×
199
            crossAxisAlignment: CrossAxisAlignment.center,
200
            mainAxisAlignment: MainAxisAlignment.center,
201
            spacing: 5,
202
            children: List.generate(
×
203
              widget.images.length,
×
204
              (index) => AnimatedContainer(
×
205
                duration: const Duration(milliseconds: 500),
206
                height: 8,
207
                width: 8,
208
                decoration: BoxDecoration(
×
209
                  color: pageIndex == index ? Colors.black : Colors.black26,
×
210
                  shape: BoxShape.circle,
211
                ),
212
              ),
213
            ),
214
          ),
215
        ],
216
      ),
217
    );
218
  }
219
}
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