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

wger-project / flutter / 30901196754

04 Aug 2026 10:34AM UTC coverage: 51.289% (-1.8%) from 53.103%
30901196754

Pull #1301

github

web-flow
Merge 5c77a99bf into e3affdd4f
Pull Request #1301: feat : implemented dynamic colour support

98 of 138 new or added lines in 24 files covered. (71.01%)

1088 existing lines in 87 files now uncovered.

11659 of 22732 relevant lines covered (51.29%)

5.17 hits per line

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

69.35
/lib/core/widgets/datetime_input.dart
1
/*
2
 * This file is part of wger Workout Manager <https://github.com/wger-project>.
3
 * Copyright (c) 2026 - 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:flutter/material.dart';
20
import 'package:wger/core/formatting/formatting.dart';
21

22
/// Read-only field that opens a time picker on tap.
23
///
24
/// The selected time is reported through [onChanged]; the caller keeps the
25
/// source of truth and decides how to store it. The displayed text uses the
26
/// active locale's time format (12h with AM/PM where the device prefers it),
27
/// so it must never be parsed back into a [TimeOfDay].
28
///
29
/// When [onCleared] is provided, a clear button is shown while a value is set.
30
class TimeInputWidget extends StatefulWidget {
31
  const TimeInputWidget({
14✔
32
    required this.value,
33
    required this.onChanged,
34
    required this.labelText,
35
    this.onCleared,
36
    this.validator,
37
    super.key,
38
  });
39

40
  /// The currently selected time, or null when unset.
41
  final TimeOfDay? value;
42

43
  /// Called with the time picked from the dialog.
44
  final ValueChanged<TimeOfDay> onChanged;
45

46
  final String labelText;
47

48
  /// When provided, a clear button is shown while [value] is set; tapping it
49
  /// invokes this callback.
50
  final VoidCallback? onCleared;
51

52
  /// Optional form validator. The argument is the displayed text; cross-field
53
  /// rules should read the source of truth from the surrounding state instead.
54
  final FormFieldValidator<String>? validator;
55

56
  @override
14✔
57
  State<TimeInputWidget> createState() => _TimeInputWidgetState();
14✔
58
}
59

60
class _TimeInputWidgetState extends State<TimeInputWidget> {
61
  TimeOfDay? _value;
62

63
  @override
14✔
64
  void initState() {
65
    super.initState();
14✔
66
    _value = widget.value;
42✔
67
  }
68

69
  @override
1✔
70
  void didUpdateWidget(TimeInputWidget oldWidget) {
71
    super.didUpdateWidget(oldWidget);
1✔
72
    if (widget.value != oldWidget.value) {
4✔
73
      _value = widget.value;
3✔
74
    }
75
  }
76

77
  @override
14✔
78
  Widget build(BuildContext context) {
79
    // Keyed initialValue, not a controller: a controller notifies the enclosing
80
    // Form on assignment, which crashes if that happens during a build.
81
    return TextFormField(
14✔
82
      key: ValueKey(_value),
28✔
83
      readOnly: true,
84
      initialValue: _value?.format(context) ?? '',
26✔
85
      validator: widget.validator,
28✔
86
      decoration: InputDecoration(
14✔
87
        labelText: widget.labelText,
28✔
88
        errorMaxLines: 2,
89
        suffixIcon: widget.onCleared != null && _value != null
34✔
90
            ? IconButton(
4✔
91
                icon: const Icon(Icons.clear),
92
                onPressed: () {
1✔
93
                  setState(() => _value = null);
3✔
94
                  widget.onCleared!();
3✔
95
                },
96
              )
97
            : const Icon(Icons.access_time_outlined),
98
      ),
99
      onTap: () async {
×
100
        // Stop the keyboard from appearing for the read-only field.
101
        FocusScope.of(context).requestFocus(FocusNode());
×
102

103
        final picked = await showTimePicker(
×
104
          context: context,
105
          initialTime: _value ?? TimeOfDay.now(),
×
106
        );
107
        if (picked != null && context.mounted) {
×
108
          setState(() => _value = picked);
×
109
          widget.onChanged(picked);
×
110
        }
111
      },
112
    );
113
  }
114
}
115

116
/// Read-only field that opens a date picker on tap.
117
///
118
/// The selected date is reported through [onChanged]; the caller keeps the
119
/// source of truth and decides how to store it. The displayed text uses the
120
/// active locale's date format.
121
///
122
/// When [onCleared] is provided, a clear button is shown while a value is set.
123
class DateInputWidget extends StatefulWidget {
124
  const DateInputWidget({
19✔
125
    required this.value,
126
    required this.onChanged,
127
    required this.labelText,
128
    this.firstDate,
129
    this.lastDate,
130
    this.onCleared,
131
    this.validator,
132
    this.helperText,
133
    super.key,
134
  });
135

136
  /// The currently selected date, or null when unset.
137
  final DateTime? value;
138

139
  /// Called with the date picked from the dialog.
140
  final ValueChanged<DateTime> onChanged;
141

142
  final String labelText;
143

144
  /// Optional hint shown below the field.
145
  final String? helperText;
146

147
  /// Earliest selectable date. Defaults to the year 2000.
148
  final DateTime? firstDate;
149

150
  /// Latest selectable date. Defaults to the year 2100.
151
  final DateTime? lastDate;
152

153
  /// When provided, a clear button is shown while [value] is set; tapping it
154
  /// invokes this callback.
155
  final VoidCallback? onCleared;
156

157
  /// Optional form validator. The argument is the displayed text; cross-field
158
  /// rules should read the source of truth from the surrounding state instead.
159
  final FormFieldValidator<String>? validator;
160

161
  @override
19✔
162
  State<DateInputWidget> createState() => _DateInputWidgetState();
19✔
163
}
164

165
class _DateInputWidgetState extends State<DateInputWidget> {
166
  DateTime? _value;
167

168
  @override
19✔
169
  void initState() {
170
    super.initState();
19✔
171
    _value = widget.value;
57✔
172
  }
173

174
  @override
4✔
175
  void didUpdateWidget(DateInputWidget oldWidget) {
176
    super.didUpdateWidget(oldWidget);
4✔
177
    if (widget.value != oldWidget.value) {
16✔
178
      _value = widget.value;
3✔
179
    }
180
  }
181

182
  @override
19✔
183
  Widget build(BuildContext context) {
184
    final dateFormat = localizedDate(context);
19✔
185
    // Keyed initialValue, not a controller: a controller notifies the enclosing
186
    // Form on assignment, which crashes if that happens during a build.
187
    return TextFormField(
19✔
188
      key: ValueKey(_value),
38✔
189
      readOnly: true,
190
      initialValue: _value != null ? dateFormat.format(_value!) : '',
57✔
191
      validator: widget.validator,
38✔
192
      decoration: InputDecoration(
19✔
193
        labelText: widget.labelText,
38✔
194
        helperText: widget.helperText,
38✔
195
        errorMaxLines: 2,
196
        suffixIcon: widget.onCleared != null && _value != null
42✔
197
            ? IconButton(
1✔
198
                icon: const Icon(Icons.clear),
199
                onPressed: () {
×
200
                  setState(() => _value = null);
×
201
                  widget.onCleared!();
×
202
                },
203
              )
204
            : const Icon(Icons.calendar_today),
205
      ),
UNCOV
206
      onTap: () async {
×
207
        // Stop the keyboard from appearing for the read-only field.
UNCOV
208
        FocusScope.of(context).requestFocus(FocusNode());
×
209

UNCOV
210
        final picked = await showDatePicker(
×
211
          context: context,
UNCOV
212
          initialDate: _value ?? DateTime.now(),
×
UNCOV
213
          firstDate: widget.firstDate ?? DateTime(2000),
×
UNCOV
214
          lastDate: widget.lastDate ?? DateTime(2100),
×
215
        );
UNCOV
216
        if (picked != null && context.mounted) {
×
UNCOV
217
          setState(() => _value = picked);
×
UNCOV
218
          widget.onChanged(picked);
×
219
        }
220
      },
221
    );
222
  }
223
}
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