Skip to content
Open
Changes from all commits
Commits
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
66 changes: 41 additions & 25 deletions lib/widgets/routines/forms/routine.dart
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ import 'package:wger/providers/routines.dart';
import 'package:wger/screens/routine_edit_screen.dart';
import 'package:wger/widgets/core/progress_indicator.dart';

/// A form widget for creating and editing workout routines.
/// Handles validation and user input for routine details, including start and end dates.
class RoutineForm extends StatefulWidget {
final Routine _routine;
final bool useListView;
Expand All @@ -27,9 +29,11 @@ class _RoutineFormState extends State<RoutineForm> {
bool isSaving = false;
late bool fitInWeek;
late DateTime startDate;
late DateTime endDate;
DateTime? endDate;
final workoutNameController = TextEditingController();
final workoutDescriptionController = TextEditingController();
final startDateController = TextEditingController();
final endDateController = TextEditingController();

@override
void initState() {
Expand All @@ -38,13 +42,20 @@ class _RoutineFormState extends State<RoutineForm> {
workoutNameController.text = widget._routine.name;
workoutDescriptionController.text = widget._routine.description;
startDate = widget._routine.start;
endDate = widget._routine.end;
endDate = widget._routine.end == widget._routine.start ? null : widget._routine.end;
WidgetsBinding.instance.addPostFrameCallback((_) {
final locale = Localizations.localeOf(context).languageCode;
startDateController.text = DateFormat.yMd(locale).format(startDate);
endDateController.text = endDate != null ? DateFormat.yMd(locale).format(endDate!) : '';
});
}

@override
void dispose() {
workoutNameController.dispose();
workoutDescriptionController.dispose();
startDateController.dispose();
endDateController.dispose();
super.dispose();
}

Expand Down Expand Up @@ -94,17 +105,18 @@ class _RoutineFormState extends State<RoutineForm> {
),
TextFormField(
key: const Key('field-start-date'),
// Stop keyboard from appearing
readOnly: true,
validator: (value) {
if (endDate.isBefore(startDate)) {
return 'End date must be after start date';
}
if (endDate.difference(startDate).inDays < Routine.MIN_DURATION * 7) {
return 'Duration of the routine must be more than ${Routine.MIN_DURATION} weeks';
}
if (endDate.difference(startDate).inDays > Routine.MAX_DURATION * 7) {
return 'Duration of the routine must be less than ${Routine.MAX_DURATION} weeks';
if (endDate != null) {
if (endDate!.isBefore(startDate)) {
return 'End date must be after start date';
}
if (endDate!.difference(startDate).inDays < Routine.MIN_DURATION * 7) {
return 'Duration of the routine must be more than {Routine.MIN_DURATION} weeks';
}
if (endDate!.difference(startDate).inDays > Routine.MAX_DURATION * 7) {
return 'Duration of the routine must be less than {Routine.MAX_DURATION} weeks';
}
}
return null;
},
Expand All @@ -116,11 +128,7 @@ class _RoutineFormState extends State<RoutineForm> {
),
),
enableInteractiveSelection: false,
controller: TextEditingController(
text: DateFormat.yMd(
Localizations.localeOf(context).languageCode,
).format(startDate),
),
controller: startDateController,
onTap: () async {
final picked = await showDatePicker(
context: context,
Expand All @@ -137,14 +145,18 @@ class _RoutineFormState extends State<RoutineForm> {
if (mounted) {
setState(() {
startDate = picked;
final locale = Localizations.localeOf(context).languageCode;
startDateController.text = DateFormat.yMd(locale).format(startDate);
});
}
},
),
TextFormField(
key: const Key('field-end-date'),
// Stop keyboard from appearing
readOnly: true,
/// Opens a date picker for selecting the routine's end date.
/// Ensures the end date is always after the start date and allows selection up to 10 years in the future.
/// The initial date shown is the current end date if valid, otherwise the minimum allowed end date.
decoration: const InputDecoration(
labelText: 'End date',
suffixIcon: Icon(
Expand All @@ -153,17 +165,19 @@ class _RoutineFormState extends State<RoutineForm> {
),
),
enableInteractiveSelection: false,
controller: TextEditingController(
text: DateFormat.yMd(
Localizations.localeOf(context).languageCode,
).format(endDate),
),
controller: endDateController,
onTap: () async {
final now = DateTime.now();
final firstEndDate = startDate.add(const Duration(days: 1));
DateTime initial = firstEndDate;
if (endDate != null && endDate!.isAfter(firstEndDate)) {
initial = endDate!;
}
final picked = await showDatePicker(
context: context,
initialDate: endDate,
firstDate: DateTime(DateTime.now().year - 10),
lastDate: DateTime.now(),
initialDate: initial,
firstDate: firstEndDate,
lastDate: now.add(const Duration(days: 365 * 10)),
);

if (picked == null) {
Expand All @@ -174,6 +188,8 @@ class _RoutineFormState extends State<RoutineForm> {
if (mounted) {
setState(() {
endDate = picked;
final locale = Localizations.localeOf(context).languageCode;
endDateController.text = DateFormat.yMd(locale).format(endDate!);
});
}
},
Expand Down