mirror of
https://github.com/wger-project/flutter.git
synced 2026-02-18 00:17:48 +01:00
Simplify code by adding new log provider
This makes the logic for copying or modifying the logs much easier. Also, there were some user reports that the old logic sometimes behaved erratically and old values were sometimes reverted.
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* This file is part of wger Workout Manager <https://github.com/wger-project>.
|
||||
* Copyright (c) 2020 - 2025 wger Team
|
||||
* Copyright (c) 2020 - 2026 wger Team
|
||||
*
|
||||
* wger Workout Manager is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
@@ -101,13 +101,13 @@ class Log {
|
||||
this.repetitions,
|
||||
this.repetitionsTarget,
|
||||
this.repetitionsUnitId = REP_UNIT_REPETITIONS_ID,
|
||||
required this.rir,
|
||||
this.rir,
|
||||
this.rirTarget,
|
||||
this.weight,
|
||||
this.weightTarget,
|
||||
this.weightUnitId = WEIGHT_UNIT_KG,
|
||||
required this.date,
|
||||
});
|
||||
DateTime? date,
|
||||
}) : date = date ?? DateTime.now();
|
||||
|
||||
Log.empty();
|
||||
|
||||
@@ -130,6 +130,51 @@ class Log {
|
||||
rirTarget = setConfig.rir;
|
||||
}
|
||||
|
||||
Log copyWith({
|
||||
int? id,
|
||||
int? exerciseId,
|
||||
int? routineId,
|
||||
int? sessionId,
|
||||
int? iteration,
|
||||
int? slotEntryId,
|
||||
num? rir,
|
||||
num? rirTarget,
|
||||
num? repetitions,
|
||||
num? repetitionsTarget,
|
||||
int? repetitionsUnitId,
|
||||
num? weight,
|
||||
num? weightTarget,
|
||||
int? weightUnitId,
|
||||
DateTime? date,
|
||||
}) {
|
||||
final out = Log(
|
||||
id: id ?? this.id,
|
||||
exerciseId: exerciseId ?? this.exerciseId,
|
||||
iteration: iteration ?? this.iteration,
|
||||
slotEntryId: slotEntryId ?? this.slotEntryId,
|
||||
routineId: routineId ?? this.routineId,
|
||||
repetitions: repetitions ?? this.repetitions,
|
||||
repetitionsTarget: repetitionsTarget ?? this.repetitionsTarget,
|
||||
repetitionsUnitId: repetitionsUnitId ?? this.repetitionsUnitId,
|
||||
rir: rir ?? this.rir,
|
||||
rirTarget: rirTarget ?? this.rirTarget,
|
||||
weight: weight ?? this.weight,
|
||||
weightTarget: weightTarget ?? this.weightTarget,
|
||||
weightUnitId: weightUnitId ?? this.weightUnitId,
|
||||
date: date ?? this.date,
|
||||
);
|
||||
|
||||
if (sessionId != null) {
|
||||
out.sessionId = sessionId;
|
||||
}
|
||||
|
||||
out.exerciseBase = exercise;
|
||||
out.repetitionUnit = repetitionsUnitObj;
|
||||
out.weightUnitObj = weightUnitObj;
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
// Boilerplate
|
||||
factory Log.fromJson(Map<String, dynamic> json) => _$LogFromJson(json);
|
||||
|
||||
|
||||
@@ -23,7 +23,9 @@ WorkoutSession _$WorkoutSessionFromJson(Map<String, dynamic> json) {
|
||||
id: (json['id'] as num?)?.toInt(),
|
||||
dayId: (json['day'] as num?)?.toInt(),
|
||||
routineId: (json['routine'] as num?)?.toInt(),
|
||||
impression: json['impression'] == null ? 2 : int.parse(json['impression'] as String),
|
||||
impression: json['impression'] == null
|
||||
? DEFAULT_IMPRESSION
|
||||
: int.parse(json['impression'] as String),
|
||||
notes: json['notes'] as String? ?? '',
|
||||
timeStart: stringToTimeNull(json['time_start'] as String?),
|
||||
timeEnd: stringToTimeNull(json['time_end'] as String?),
|
||||
|
||||
@@ -1,21 +1,3 @@
|
||||
/*
|
||||
* This file is part of wger Workout Manager <https://github.com/wger-project>.
|
||||
* Copyright (c) 2025 wger Team
|
||||
*
|
||||
* wger Workout Manager is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'slot_data.dart';
|
||||
|
||||
46
lib/providers/gym_log_state.dart
Normal file
46
lib/providers/gym_log_state.dart
Normal file
@@ -0,0 +1,46 @@
|
||||
/*
|
||||
* This file is part of wger Workout Manager <https://github.com/wger-project>.
|
||||
* Copyright (c) 2026 wger Team
|
||||
*
|
||||
* wger Workout Manager is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import 'package:logging/logging.dart';
|
||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||
import 'package:wger/models/workouts/log.dart';
|
||||
|
||||
part 'gym_log_state.g.dart';
|
||||
|
||||
@Riverpod(keepAlive: true)
|
||||
class GymLogNotifier extends _$GymLogNotifier {
|
||||
final _logger = Logger('GymLogNotifier');
|
||||
|
||||
@override
|
||||
Log? build() {
|
||||
_logger.finer('Initializing GymLogNotifier');
|
||||
return null;
|
||||
}
|
||||
|
||||
void setLog(Log newLog) {
|
||||
state = newLog;
|
||||
}
|
||||
|
||||
void setWeight(num weight) {
|
||||
state = state?.copyWith(weight: weight);
|
||||
}
|
||||
|
||||
void setRepetitions(num repetitions) {
|
||||
state = state?.copyWith(repetitions: repetitions);
|
||||
}
|
||||
}
|
||||
56
lib/providers/gym_log_state.g.dart
Normal file
56
lib/providers/gym_log_state.g.dart
Normal file
@@ -0,0 +1,56 @@
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'gym_log_state.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// RiverpodGenerator
|
||||
// **************************************************************************
|
||||
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
// ignore_for_file: type=lint, type=warning
|
||||
|
||||
@ProviderFor(GymLogNotifier)
|
||||
const gymLogProvider = GymLogNotifierProvider._();
|
||||
|
||||
final class GymLogNotifierProvider extends $NotifierProvider<GymLogNotifier, Log?> {
|
||||
const GymLogNotifierProvider._()
|
||||
: super(
|
||||
from: null,
|
||||
argument: null,
|
||||
retry: null,
|
||||
name: r'gymLogProvider',
|
||||
isAutoDispose: false,
|
||||
dependencies: null,
|
||||
$allTransitiveDependencies: null,
|
||||
);
|
||||
|
||||
@override
|
||||
String debugGetCreateSourceHash() => _$gymLogNotifierHash();
|
||||
|
||||
@$internal
|
||||
@override
|
||||
GymLogNotifier create() => GymLogNotifier();
|
||||
|
||||
/// {@macro riverpod.override_with_value}
|
||||
Override overrideWithValue(Log? value) {
|
||||
return $ProviderOverride(
|
||||
origin: this,
|
||||
providerOverride: $SyncValueProvider<Log?>(value),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
String _$gymLogNotifierHash() => r'f7cdc8f72506e366ca028360b654da0bdd9bcae6';
|
||||
|
||||
abstract class _$GymLogNotifier extends $Notifier<Log?> {
|
||||
Log? build();
|
||||
@$mustCallSuper
|
||||
@override
|
||||
void runBuild() {
|
||||
final created = build();
|
||||
final ref = this.ref as $Ref<Log?, Log?>;
|
||||
final element =
|
||||
ref.element as $ClassProviderElement<AnyNotifier<Log?, Log?>, Log?, Object?, Object?>;
|
||||
element.handleValue(ref, created);
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* This file is part of wger Workout Manager <https://github.com/wger-project>.
|
||||
* Copyright (c) 2025 wger Team
|
||||
* Copyright (c) 2026 wger Team
|
||||
*
|
||||
* wger Workout Manager is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
@@ -25,8 +25,10 @@ import 'package:wger/helpers/shared_preferences.dart';
|
||||
import 'package:wger/helpers/uuid.dart';
|
||||
import 'package:wger/models/exercises/exercise.dart';
|
||||
import 'package:wger/models/workouts/day_data.dart';
|
||||
import 'package:wger/models/workouts/log.dart';
|
||||
import 'package:wger/models/workouts/routine.dart';
|
||||
import 'package:wger/models/workouts/set_config_data.dart';
|
||||
import 'package:wger/providers/gym_log_state.dart';
|
||||
|
||||
part 'gym_state.g.dart';
|
||||
|
||||
@@ -131,7 +133,11 @@ class SlotPageEntry {
|
||||
this.setConfigData,
|
||||
this.logDone = false,
|
||||
String? uuid,
|
||||
}) : uuid = uuid ?? uuidV4();
|
||||
}) : assert(
|
||||
type != SlotPageType.log || setConfigData != null,
|
||||
'You need to set setConfigData for SlotPageType.log',
|
||||
),
|
||||
uuid = uuid ?? uuidV4();
|
||||
|
||||
SlotPageEntry copyWith({
|
||||
String? uuid,
|
||||
@@ -481,7 +487,7 @@ class GymStateNotifier extends _$GymStateNotifier {
|
||||
pages.add(PageEntry(type: PageType.workoutSummary, pageIndex: pageIndex + 1));
|
||||
|
||||
state = state.copyWith(pages: pages);
|
||||
print(readPageStructure());
|
||||
// print(readPageStructure());
|
||||
_logger.finer('Initialized ${state.pages.length} pages');
|
||||
}
|
||||
|
||||
@@ -573,6 +579,17 @@ class GymStateNotifier extends _$GymStateNotifier {
|
||||
|
||||
void setCurrentPage(int page) {
|
||||
state = state.copyWith(currentPage: page);
|
||||
|
||||
// Ensure that there is a log entry for the current slot entry
|
||||
final slotEntryPage = state.getSlotEntryPageByIndex();
|
||||
if (slotEntryPage == null || slotEntryPage.setConfigData == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
final log = Log.fromSetConfigData(slotEntryPage.setConfigData!);
|
||||
log.routineId = state.routine.id!;
|
||||
log.iteration = state.iteration;
|
||||
ref.read(gymLogProvider.notifier).setLog(log);
|
||||
}
|
||||
|
||||
void setShowExercisePages(bool value) {
|
||||
|
||||
@@ -1,21 +1,3 @@
|
||||
/*
|
||||
* This file is part of wger Workout Manager <https://github.com/wger-project>.
|
||||
* Copyright (c) 2025 wger Team
|
||||
*
|
||||
* wger Workout Manager is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'gym_state.dart';
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* This file is part of wger Workout Manager <https://github.com/wger-project>.
|
||||
* Copyright (c) 2020 - 2025 wger Team
|
||||
* Copyright (c) 2020 - 2026 wger Team
|
||||
*
|
||||
* wger Workout Manager is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
@@ -27,6 +27,7 @@ import 'package:wger/l10n/generated/app_localizations.dart';
|
||||
import 'package:wger/models/workouts/log.dart';
|
||||
import 'package:wger/models/workouts/set_config_data.dart';
|
||||
import 'package:wger/models/workouts/slot_entry.dart';
|
||||
import 'package:wger/providers/gym_log_state.dart';
|
||||
import 'package:wger/providers/gym_state.dart';
|
||||
import 'package:wger/providers/plate_weights.dart';
|
||||
import 'package:wger/providers/routines.dart';
|
||||
@@ -39,75 +40,37 @@ import 'package:wger/widgets/routines/forms/weight_unit.dart';
|
||||
import 'package:wger/widgets/routines/gym_mode/navigation.dart';
|
||||
import 'package:wger/widgets/routines/plate_calculator.dart';
|
||||
|
||||
class LogPage extends ConsumerStatefulWidget {
|
||||
class LogPage extends ConsumerWidget {
|
||||
final _logger = Logger('LogPage');
|
||||
|
||||
final PageController _controller;
|
||||
|
||||
LogPage(this._controller);
|
||||
|
||||
@override
|
||||
_LogPageState createState() => _LogPageState();
|
||||
}
|
||||
|
||||
class _LogPageState extends ConsumerState<LogPage> {
|
||||
final GlobalKey<_LogFormWidgetState> _logFormKey = GlobalKey<_LogFormWidgetState>();
|
||||
|
||||
late FocusNode focusNode;
|
||||
// Persistent log and current slot-page id to avoid recreating the Log on rebuilds
|
||||
Log? _currentLog;
|
||||
String? _currentSlotPageUuid;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
focusNode = FocusNode();
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
focusNode.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final theme = Theme.of(context);
|
||||
final state = ref.watch(gymStateProvider);
|
||||
final gymState = ref.watch(gymStateProvider);
|
||||
final languageCode = Localizations.localeOf(context).languageCode;
|
||||
|
||||
final page = state.getPageByIndex();
|
||||
final page = gymState.getPageByIndex();
|
||||
if (page == null) {
|
||||
widget._logger.info(
|
||||
'getPageByIndex for ${state.currentPage} returned null, showing empty container.',
|
||||
_logger.info(
|
||||
'getPageByIndex for ${gymState.currentPage} returned null, showing empty container.',
|
||||
);
|
||||
return Container();
|
||||
}
|
||||
|
||||
final slotEntryPage = state.getSlotEntryPageByIndex();
|
||||
final slotEntryPage = gymState.getSlotEntryPageByIndex();
|
||||
if (slotEntryPage == null) {
|
||||
widget._logger.info(
|
||||
'getSlotPageByIndex for ${state.currentPage} returned null, showing empty container',
|
||||
_logger.info(
|
||||
'getSlotPageByIndex for ${gymState.currentPage} returned null, showing empty container',
|
||||
);
|
||||
return Container();
|
||||
}
|
||||
|
||||
final setConfigData = slotEntryPage.setConfigData!;
|
||||
|
||||
// Create a Log only when the slot page changed or none exists yet
|
||||
if (_currentLog == null || _currentSlotPageUuid != slotEntryPage.uuid) {
|
||||
_currentLog = Log.fromSetConfigData(setConfigData)
|
||||
..routineId = state.routine.id!
|
||||
..iteration = state.iteration;
|
||||
_currentSlotPageUuid = slotEntryPage.uuid;
|
||||
} else {
|
||||
// Update routine/iteration if needed without creating a new Log
|
||||
_currentLog!
|
||||
..routineId = state.routine.id!
|
||||
..iteration = state.iteration;
|
||||
}
|
||||
|
||||
final log = _currentLog!;
|
||||
final log = ref.watch(gymLogProvider);
|
||||
|
||||
// Mark done sets
|
||||
final decorationStyle = slotEntryPage.logDone
|
||||
@@ -117,8 +80,9 @@ class _LogPageState extends ConsumerState<LogPage> {
|
||||
return Column(
|
||||
children: [
|
||||
NavigationHeader(
|
||||
log.exercise.getTranslation(languageCode).name,
|
||||
widget._controller,
|
||||
log!.exercise.getTranslation(languageCode).name,
|
||||
_controller,
|
||||
key: const ValueKey('log-page-navigation-header'),
|
||||
),
|
||||
|
||||
Container(
|
||||
@@ -164,16 +128,9 @@ class _LogPageState extends ConsumerState<LogPage> {
|
||||
Text(slotEntryPage.setConfigData!.comment, textAlign: TextAlign.center),
|
||||
const SizedBox(height: 10),
|
||||
Expanded(
|
||||
child: (state.routine.filterLogsByExercise(log.exercise.id!).isNotEmpty)
|
||||
child: (gymState.routine.filterLogsByExercise(log.exerciseId).isNotEmpty)
|
||||
? LogsPastLogsWidget(
|
||||
log: log,
|
||||
pastLogs: state.routine.filterLogsByExercise(log.exercise.id!),
|
||||
onCopy: (pastLog) {
|
||||
_logFormKey.currentState?.copyFromPastLog(pastLog);
|
||||
},
|
||||
setStateCallback: (fn) {
|
||||
setState(fn);
|
||||
},
|
||||
pastLogs: gymState.routine.filterLogsByExercise(log.exerciseId),
|
||||
)
|
||||
: Container(),
|
||||
),
|
||||
@@ -186,16 +143,15 @@ class _LogPageState extends ConsumerState<LogPage> {
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 5),
|
||||
child: LogFormWidget(
|
||||
controller: widget._controller,
|
||||
controller: _controller,
|
||||
configData: setConfigData,
|
||||
log: log,
|
||||
focusNode: focusNode,
|
||||
// log: log!,
|
||||
key: _logFormKey,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
NavigationFooter(widget._controller),
|
||||
NavigationFooter(_controller),
|
||||
],
|
||||
);
|
||||
}
|
||||
@@ -255,68 +211,62 @@ class LogsPlatesWidget extends ConsumerWidget {
|
||||
}
|
||||
}
|
||||
|
||||
class LogsRepsWidget extends StatelessWidget {
|
||||
final TextEditingController controller;
|
||||
final SetConfigData configData;
|
||||
final FocusNode focusNode;
|
||||
final Log log;
|
||||
final void Function(VoidCallback fn) setStateCallback;
|
||||
|
||||
class LogsRepsWidget extends ConsumerWidget {
|
||||
final _logger = Logger('LogsRepsWidget');
|
||||
|
||||
final num valueChange;
|
||||
|
||||
LogsRepsWidget({
|
||||
super.key,
|
||||
required this.controller,
|
||||
required this.configData,
|
||||
required this.focusNode,
|
||||
required this.log,
|
||||
required this.setStateCallback,
|
||||
});
|
||||
num? valueChange,
|
||||
}) : valueChange = valueChange ?? 1;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final repsValueChange = configData.repetitionsRounding ?? 1;
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final numberFormat = NumberFormat.decimalPattern(Localizations.localeOf(context).toString());
|
||||
|
||||
final i18n = AppLocalizations.of(context);
|
||||
|
||||
final logNotifier = ref.read(gymLogProvider.notifier);
|
||||
final log = ref.watch(gymLogProvider);
|
||||
|
||||
final currentReps = log?.repetitions;
|
||||
final repText = currentReps != null ? numberFormat.format(currentReps) : '';
|
||||
|
||||
return Row(
|
||||
children: [
|
||||
// "Quick-remove" button
|
||||
IconButton(
|
||||
icon: const Icon(Icons.remove, color: Colors.black),
|
||||
onPressed: () {
|
||||
final currentValue = numberFormat.tryParse(controller.text) ?? 0;
|
||||
final newValue = currentValue - repsValueChange;
|
||||
if (newValue >= 0) {
|
||||
setStateCallback(() {
|
||||
log.repetitions = newValue;
|
||||
controller.text = numberFormat.format(newValue);
|
||||
});
|
||||
final base = currentReps ?? 0;
|
||||
final newValue = base - valueChange;
|
||||
if (newValue >= 0 && log != null) {
|
||||
logNotifier.setRepetitions(newValue);
|
||||
}
|
||||
},
|
||||
),
|
||||
|
||||
// Text field
|
||||
Expanded(
|
||||
child: TextFormField(
|
||||
decoration: InputDecoration(labelText: i18n.repetitions),
|
||||
enabled: true,
|
||||
controller: controller,
|
||||
key: ValueKey('reps-field-$repText'),
|
||||
initialValue: repText,
|
||||
keyboardType: textInputTypeDecimal,
|
||||
focusNode: focusNode,
|
||||
onChanged: (value) {
|
||||
try {
|
||||
final newValue = numberFormat.parse(value);
|
||||
setStateCallback(() {
|
||||
log.repetitions = newValue;
|
||||
});
|
||||
logNotifier.setRepetitions(newValue);
|
||||
} on FormatException catch (error) {
|
||||
_logger.fine('Error parsing repetitions: $error');
|
||||
_logger.finer('Error parsing repetitions: $error');
|
||||
}
|
||||
},
|
||||
onSaved: (newValue) {
|
||||
_logger.info('Saving new reps value: $newValue');
|
||||
setStateCallback(() {
|
||||
log.repetitions = numberFormat.parse(newValue!);
|
||||
});
|
||||
if (newValue == null || log == null) {
|
||||
return;
|
||||
}
|
||||
logNotifier.setRepetitions(numberFormat.parse(newValue));
|
||||
},
|
||||
validator: (value) {
|
||||
if (numberFormat.tryParse(value ?? '') == null) {
|
||||
@@ -326,19 +276,15 @@ class LogsRepsWidget extends StatelessWidget {
|
||||
},
|
||||
),
|
||||
),
|
||||
|
||||
// "Quick-add" button
|
||||
IconButton(
|
||||
icon: const Icon(Icons.add, color: Colors.black),
|
||||
onPressed: () {
|
||||
final value = controller.text.isNotEmpty ? controller.text : '0';
|
||||
|
||||
try {
|
||||
final newValue = numberFormat.parse(value) + repsValueChange;
|
||||
setStateCallback(() {
|
||||
log.repetitions = newValue;
|
||||
controller.text = numberFormat.format(newValue);
|
||||
});
|
||||
} on FormatException catch (error) {
|
||||
_logger.fine('Error parsing reps during quick-add: $error');
|
||||
final base = currentReps ?? 0;
|
||||
final newValue = base + valueChange;
|
||||
if (newValue >= 0 && log != null) {
|
||||
logNotifier.setRepetitions(newValue);
|
||||
}
|
||||
},
|
||||
),
|
||||
@@ -348,76 +294,62 @@ class LogsRepsWidget extends StatelessWidget {
|
||||
}
|
||||
|
||||
class LogsWeightWidget extends ConsumerWidget {
|
||||
final TextEditingController controller;
|
||||
final SetConfigData configData;
|
||||
final FocusNode focusNode;
|
||||
final Log log;
|
||||
final void Function(VoidCallback fn) setStateCallback;
|
||||
|
||||
final _logger = Logger('LogsWeightWidget');
|
||||
|
||||
final num valueChange;
|
||||
|
||||
LogsWeightWidget({
|
||||
super.key,
|
||||
required this.controller,
|
||||
required this.configData,
|
||||
required this.focusNode,
|
||||
required this.log,
|
||||
required this.setStateCallback,
|
||||
});
|
||||
num? valueChange,
|
||||
}) : valueChange = valueChange ?? 1.25;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final weightValueChange = configData.weightRounding ?? 1.25;
|
||||
final numberFormat = NumberFormat.decimalPattern(Localizations.localeOf(context).toString());
|
||||
final i18n = AppLocalizations.of(context);
|
||||
|
||||
final plateProvider = ref.read(plateCalculatorProvider.notifier);
|
||||
final logProvider = ref.read(gymLogProvider.notifier);
|
||||
final log = ref.watch(gymLogProvider);
|
||||
|
||||
final currentWeight = log?.weight;
|
||||
final weightText = currentWeight != null ? numberFormat.format(currentWeight) : '';
|
||||
|
||||
return Row(
|
||||
children: [
|
||||
IconButton(
|
||||
// "Quick-remove" button
|
||||
icon: const Icon(Icons.remove, color: Colors.black),
|
||||
onPressed: () {
|
||||
try {
|
||||
final newValue = numberFormat.parse(controller.text) - weightValueChange;
|
||||
if (newValue > 0) {
|
||||
setStateCallback(() {
|
||||
log.weight = newValue;
|
||||
controller.text = numberFormat.format(newValue);
|
||||
ref
|
||||
.read(plateCalculatorProvider.notifier)
|
||||
.setWeight(
|
||||
controller.text == '' ? 0 : newValue,
|
||||
);
|
||||
});
|
||||
}
|
||||
} on FormatException catch (error) {
|
||||
_logger.fine('Error parsing weight during quick-remove: $error');
|
||||
final base = currentWeight ?? 0;
|
||||
final newValue = base - valueChange;
|
||||
if (newValue >= 0 && log != null) {
|
||||
logProvider.setWeight(newValue);
|
||||
}
|
||||
},
|
||||
),
|
||||
|
||||
// Text field
|
||||
Expanded(
|
||||
child: TextFormField(
|
||||
key: ValueKey('weight-field-$weightText'),
|
||||
decoration: InputDecoration(labelText: i18n.weight),
|
||||
controller: controller,
|
||||
initialValue: weightText,
|
||||
keyboardType: textInputTypeDecimal,
|
||||
onChanged: (value) {
|
||||
try {
|
||||
final newValue = numberFormat.parse(value);
|
||||
setStateCallback(() {
|
||||
log.weight = newValue;
|
||||
ref
|
||||
.read(plateCalculatorProvider.notifier)
|
||||
.setWeight(
|
||||
controller.text == '' ? 0 : numberFormat.parse(controller.text),
|
||||
);
|
||||
});
|
||||
plateProvider.setWeight(newValue);
|
||||
logProvider.setWeight(newValue);
|
||||
} on FormatException catch (error) {
|
||||
_logger.fine('Error parsing weight: $error');
|
||||
_logger.finer('Error parsing weight: $error');
|
||||
}
|
||||
},
|
||||
onSaved: (newValue) {
|
||||
setStateCallback(() {
|
||||
log.weight = numberFormat.parse(newValue!);
|
||||
});
|
||||
if (newValue == null || log == null) {
|
||||
return;
|
||||
}
|
||||
logProvider.setWeight(numberFormat.parse(newValue));
|
||||
},
|
||||
validator: (value) {
|
||||
if (numberFormat.tryParse(value ?? '') == null) {
|
||||
@@ -427,24 +359,15 @@ class LogsWeightWidget extends ConsumerWidget {
|
||||
},
|
||||
),
|
||||
),
|
||||
|
||||
// "Quick-add" button
|
||||
IconButton(
|
||||
icon: const Icon(Icons.add, color: Colors.black),
|
||||
onPressed: () {
|
||||
final value = controller.text.isNotEmpty ? controller.text : '0';
|
||||
|
||||
try {
|
||||
final newValue = numberFormat.parse(value) + weightValueChange;
|
||||
setStateCallback(() {
|
||||
log.weight = newValue;
|
||||
controller.text = numberFormat.format(newValue);
|
||||
ref
|
||||
.read(plateCalculatorProvider.notifier)
|
||||
.setWeight(
|
||||
controller.text == '' ? 0 : newValue,
|
||||
);
|
||||
});
|
||||
} on FormatException catch (error) {
|
||||
_logger.fine('Error parsing weight during quick-add: $error');
|
||||
final base = currentWeight ?? 0;
|
||||
final newValue = base + valueChange;
|
||||
if (log != null) {
|
||||
logProvider.setWeight(newValue);
|
||||
}
|
||||
},
|
||||
),
|
||||
@@ -453,22 +376,19 @@ class LogsWeightWidget extends ConsumerWidget {
|
||||
}
|
||||
}
|
||||
|
||||
class LogsPastLogsWidget extends StatelessWidget {
|
||||
final Log log;
|
||||
class LogsPastLogsWidget extends ConsumerWidget {
|
||||
final List<Log> pastLogs;
|
||||
final void Function(Log pastLog) onCopy;
|
||||
final void Function(VoidCallback fn) setStateCallback;
|
||||
|
||||
const LogsPastLogsWidget({
|
||||
super.key,
|
||||
required this.log,
|
||||
required this.pastLogs,
|
||||
required this.onCopy,
|
||||
required this.setStateCallback,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final logProvider = ref.read(gymLogProvider.notifier);
|
||||
final dateFormat = DateFormat.yMd(Localizations.localeOf(context).languageCode);
|
||||
|
||||
return Container(
|
||||
padding: const EdgeInsets.symmetric(vertical: 8),
|
||||
child: ListView(
|
||||
@@ -482,25 +402,16 @@ class LogsPastLogsWidget extends StatelessWidget {
|
||||
return ListTile(
|
||||
key: ValueKey('past-log-${pastLog.id}'),
|
||||
title: Text(pastLog.repTextNoNl(context)),
|
||||
subtitle: Text(
|
||||
DateFormat.yMd(Localizations.localeOf(context).languageCode).format(pastLog.date),
|
||||
),
|
||||
subtitle: Text(dateFormat.format(pastLog.date)),
|
||||
trailing: const Icon(Icons.copy),
|
||||
onTap: () {
|
||||
setStateCallback(() {
|
||||
log.rir = pastLog.rir;
|
||||
log.repetitionUnit = pastLog.repetitionsUnitObj;
|
||||
log.weightUnit = pastLog.weightUnitObj;
|
||||
|
||||
onCopy(pastLog);
|
||||
|
||||
ScaffoldMessenger.of(context).hideCurrentSnackBar();
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: Text(AppLocalizations.of(context).dataCopied),
|
||||
),
|
||||
);
|
||||
});
|
||||
logProvider.setLog(pastLog);
|
||||
ScaffoldMessenger.of(context).hideCurrentSnackBar();
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: Text(AppLocalizations.of(context).dataCopied),
|
||||
),
|
||||
);
|
||||
},
|
||||
contentPadding: const EdgeInsets.symmetric(horizontal: 40),
|
||||
);
|
||||
@@ -516,15 +427,11 @@ class LogFormWidget extends ConsumerStatefulWidget {
|
||||
|
||||
final PageController controller;
|
||||
final SetConfigData configData;
|
||||
final Log log;
|
||||
final FocusNode focusNode;
|
||||
|
||||
LogFormWidget({
|
||||
super.key,
|
||||
required this.controller,
|
||||
required this.configData,
|
||||
required this.log,
|
||||
required this.focusNode,
|
||||
});
|
||||
|
||||
@override
|
||||
@@ -535,116 +442,11 @@ class _LogFormWidgetState extends ConsumerState<LogFormWidget> {
|
||||
final _form = GlobalKey<FormState>();
|
||||
var _detailed = false;
|
||||
bool _isSaving = false;
|
||||
late Log _log;
|
||||
|
||||
late final TextEditingController _repetitionsController;
|
||||
late final TextEditingController _weightController;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
|
||||
_log = widget.log;
|
||||
_repetitionsController = TextEditingController();
|
||||
_weightController = TextEditingController();
|
||||
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
_syncControllersWithWidget();
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
void didUpdateWidget(covariant LogFormWidget oldWidget) {
|
||||
super.didUpdateWidget(oldWidget);
|
||||
|
||||
// If the log or config changed, update internal _log and controllers
|
||||
if (oldWidget.log != widget.log || oldWidget.configData != widget.configData) {
|
||||
_log = widget.log;
|
||||
_syncControllersWithWidget();
|
||||
}
|
||||
}
|
||||
|
||||
void _syncControllersWithWidget() {
|
||||
final locale = Localizations.localeOf(context).toString();
|
||||
final numberFormat = NumberFormat.decimalPattern(locale);
|
||||
|
||||
// Priority: current log -> config defaults -> empty
|
||||
try {
|
||||
_repetitionsController.text = widget.log.repetitions != null
|
||||
? numberFormat.format(widget.log.repetitions)
|
||||
: (widget.configData.repetitions != null
|
||||
? numberFormat.format(widget.configData.repetitions)
|
||||
: '');
|
||||
|
||||
_weightController.text = widget.log.weight != null
|
||||
? numberFormat.format(widget.log.weight)
|
||||
: (widget.configData.weight != null ? numberFormat.format(widget.configData.weight) : '');
|
||||
} on Exception catch (e) {
|
||||
// Defensive fallback: set empty strings if formatting fails
|
||||
widget._logger.warning('Error syncing controllers: $e');
|
||||
_repetitionsController.text = '';
|
||||
_weightController.text = '';
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_repetitionsController.dispose();
|
||||
_weightController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
void copyFromPastLog(Log pastLog) {
|
||||
final locale = Localizations.localeOf(context).toString();
|
||||
final numberFormat = NumberFormat.decimalPattern(locale);
|
||||
|
||||
setState(() {
|
||||
_repetitionsController.text = pastLog.repetitions != null
|
||||
? numberFormat.format(pastLog.repetitions)
|
||||
: '';
|
||||
widget._logger.finer('Setting log repetitions to ${_repetitionsController.text}');
|
||||
|
||||
_weightController.text = pastLog.weight != null ? numberFormat.format(pastLog.weight) : '';
|
||||
widget._logger.finer('Setting log weight to ${_weightController.text}');
|
||||
|
||||
_log.repetitions = pastLog.repetitions;
|
||||
_log.weight = pastLog.weight;
|
||||
_log.rir = pastLog.rir;
|
||||
if (pastLog.repetitionsUnitObj != null) {
|
||||
_log.repetitionUnit = pastLog.repetitionsUnitObj;
|
||||
}
|
||||
if (pastLog.weightUnitObj != null) {
|
||||
_log.weightUnit = pastLog.weightUnitObj;
|
||||
}
|
||||
|
||||
widget._logger.finer(
|
||||
'Copied to _log: repetitions=${_log.repetitions}, weight=${_log.weight}, repetitionsUnitId=${_log.repetitionsUnitId}, weightUnitId=${_log.weightUnitId}, rir=${_log.rir}',
|
||||
);
|
||||
|
||||
// Update plate calculator using the value currently visible in the controllers
|
||||
try {
|
||||
final weightValue = _weightController.text.isEmpty
|
||||
? 0
|
||||
: numberFormat.parse(_weightController.text);
|
||||
ref.read(plateCalculatorProvider.notifier).setWeight(weightValue);
|
||||
} catch (e) {
|
||||
widget._logger.fine('Error updating plate calculator: $e');
|
||||
}
|
||||
});
|
||||
|
||||
// Ensure subsequent syncs (e.g., didUpdateWidget) don't overwrite these values
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
if (!mounted) {
|
||||
return;
|
||||
}
|
||||
|
||||
_syncControllersWithWidget();
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final i18n = AppLocalizations.of(context);
|
||||
final log = ref.watch(gymLogProvider);
|
||||
|
||||
return Form(
|
||||
key: _form,
|
||||
@@ -662,26 +464,14 @@ class _LogFormWidgetState extends ConsumerState<LogFormWidget> {
|
||||
Flexible(
|
||||
child: LogsRepsWidget(
|
||||
key: const ValueKey('logs-reps-widget'),
|
||||
controller: _repetitionsController,
|
||||
configData: widget.configData,
|
||||
focusNode: widget.focusNode,
|
||||
log: _log,
|
||||
setStateCallback: (fn) {
|
||||
setState(fn);
|
||||
},
|
||||
valueChange: widget.configData.repetitionsRounding,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
Flexible(
|
||||
child: LogsWeightWidget(
|
||||
key: const ValueKey('logs-weight-widget'),
|
||||
controller: _weightController,
|
||||
configData: widget.configData,
|
||||
focusNode: widget.focusNode,
|
||||
log: _log,
|
||||
setStateCallback: (fn) {
|
||||
setState(fn);
|
||||
},
|
||||
valueChange: widget.configData.weightRounding,
|
||||
),
|
||||
),
|
||||
],
|
||||
@@ -693,20 +483,14 @@ class _LogFormWidgetState extends ConsumerState<LogFormWidget> {
|
||||
Flexible(
|
||||
child: LogsRepsWidget(
|
||||
key: const ValueKey('logs-reps-widget'),
|
||||
controller: _repetitionsController,
|
||||
configData: widget.configData,
|
||||
focusNode: widget.focusNode,
|
||||
log: _log,
|
||||
setStateCallback: (fn) {
|
||||
setState(fn);
|
||||
},
|
||||
valueChange: widget.configData.repetitionsRounding,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
Flexible(
|
||||
child: RepetitionUnitInputWidget(
|
||||
key: const ValueKey('repetition-unit-input-widget'),
|
||||
_log.repetitionsUnitId,
|
||||
log!.repetitionsUnitId,
|
||||
onChanged: (v) => {},
|
||||
),
|
||||
),
|
||||
@@ -720,19 +504,13 @@ class _LogFormWidgetState extends ConsumerState<LogFormWidget> {
|
||||
Flexible(
|
||||
child: LogsWeightWidget(
|
||||
key: const ValueKey('logs-weight-widget'),
|
||||
controller: _weightController,
|
||||
configData: widget.configData,
|
||||
focusNode: widget.focusNode,
|
||||
log: _log,
|
||||
setStateCallback: (fn) {
|
||||
setState(fn);
|
||||
},
|
||||
valueChange: widget.configData.weightRounding,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
Flexible(
|
||||
child: WeightUnitInputWidget(
|
||||
_log.weightUnitId,
|
||||
log!.weightUnitId,
|
||||
onChanged: (v) => {},
|
||||
key: const ValueKey('weight-unit-input-widget'),
|
||||
),
|
||||
@@ -743,13 +521,9 @@ class _LogFormWidgetState extends ConsumerState<LogFormWidget> {
|
||||
if (_detailed)
|
||||
RiRInputWidget(
|
||||
key: const ValueKey('rir-input-widget'),
|
||||
_log.rir,
|
||||
log!.rir,
|
||||
onChanged: (value) {
|
||||
if (value == '') {
|
||||
_log.rir = null;
|
||||
} else {
|
||||
_log.rir = num.parse(value);
|
||||
}
|
||||
log.rir = value == '' ? null : num.parse(value);
|
||||
},
|
||||
),
|
||||
SwitchListTile(
|
||||
@@ -782,7 +556,7 @@ class _LogFormWidgetState extends ConsumerState<LogFormWidget> {
|
||||
await provider.Provider.of<RoutinesProvider>(
|
||||
context,
|
||||
listen: false,
|
||||
).addLog(_log);
|
||||
).addLog(log!);
|
||||
final page = gymState.getSlotEntryPageByIndex()!;
|
||||
gymProvider.markSlotPageAsDone(page.uuid, isDone: true);
|
||||
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
/*
|
||||
* This file is part of wger Workout Manager <https://github.com/wger-project>.
|
||||
* Copyright (C) 2020, 2021 wger Team
|
||||
* Copyright (c) 2026 wger Team
|
||||
*
|
||||
* wger Workout Manager is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* wger Workout Manager is distributed in the hope that it will be useful,
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
@@ -28,7 +28,12 @@ class NavigationHeader extends StatelessWidget {
|
||||
final String _title;
|
||||
final bool showEndWorkoutButton;
|
||||
|
||||
const NavigationHeader(this._title, this._controller, {this.showEndWorkoutButton = true});
|
||||
const NavigationHeader(
|
||||
this._title,
|
||||
this._controller, {
|
||||
this.showEndWorkoutButton = true,
|
||||
super.key,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
|
||||
@@ -1,21 +1,3 @@
|
||||
/*
|
||||
* This file is part of wger Workout Manager <https://github.com/wger-project>.
|
||||
* Copyright (c) 2025 wger Team
|
||||
*
|
||||
* wger Workout Manager is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
// Mocks generated by Mockito 5.4.6 from annotations
|
||||
// in wger/test/core/validators_test.dart.
|
||||
// Do not manually edit this file.
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* This file is part of wger Workout Manager <https://github.com/wger-project>.
|
||||
* Copyright (c) 2020, 2025 wger Team
|
||||
* Copyright (c) 2020 - 2026 wger Team
|
||||
*
|
||||
* wger Workout Manager is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
@@ -109,12 +109,10 @@ void main() {
|
||||
|
||||
await withClock(Clock.fixed(DateTime(2025, 3, 29, 14, 33)), () async {
|
||||
await tester.pumpWidget(renderGymMode());
|
||||
|
||||
await tester.pumpAndSettle();
|
||||
await tester.tap(find.byType(TextButton));
|
||||
|
||||
await tester.pumpAndSettle();
|
||||
//await tester.ensureVisible(find.byKey(Key(key as String)));
|
||||
|
||||
//
|
||||
// Start page
|
||||
//
|
||||
@@ -306,6 +304,7 @@ void main() {
|
||||
expect(find.byIcon(Icons.chevron_right), findsNothing);
|
||||
});
|
||||
},
|
||||
tags: ['golden'],
|
||||
semanticsEnabled: false,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,21 +1,3 @@
|
||||
/*
|
||||
* This file is part of wger Workout Manager <https://github.com/wger-project>.
|
||||
* Copyright (c) 2025 wger Team
|
||||
*
|
||||
* wger Workout Manager is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
// Mocks generated by Mockito 5.4.6 from annotations
|
||||
// in wger/test/user/provider_test.dart.
|
||||
// Do not manually edit this file.
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* This file is part of wger Workout Manager <https://github.com/wger-project>.
|
||||
* Copyright (c) 2025 - 2025 wger Team
|
||||
* Copyright (c) 2025 - 2026 wger Team
|
||||
*
|
||||
* wger Workout Manager is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
@@ -128,6 +128,7 @@ void main() {
|
||||
|
||||
// Act
|
||||
notifier.calculatePages();
|
||||
notifier.setCurrentPage(2);
|
||||
|
||||
// Assert
|
||||
expect(notifier.state.getSlotEntryPageByIndex()!.type, SlotPageType.log);
|
||||
@@ -159,6 +160,7 @@ void main() {
|
||||
iteration: 1,
|
||||
);
|
||||
notifier.calculatePages();
|
||||
notifier.setCurrentPage(2);
|
||||
|
||||
// Act
|
||||
// Log page is at index 2
|
||||
@@ -197,6 +199,7 @@ void main() {
|
||||
iteration: 1,
|
||||
);
|
||||
notifier.calculatePages();
|
||||
notifier.setCurrentPage(2);
|
||||
notifier.state = notifier.state.copyWith(currentPage: 2);
|
||||
final mockRoutines = MockRoutinesProvider();
|
||||
|
||||
@@ -206,8 +209,8 @@ void main() {
|
||||
final editableFields = find.byType(EditableText);
|
||||
expect(editableFields, findsWidgets);
|
||||
|
||||
await tester.enterText(editableFields.at(0), '7');
|
||||
await tester.enterText(editableFields.at(1), '77');
|
||||
await tester.enterText(editableFields.at(0), '12'); // Reps
|
||||
await tester.enterText(editableFields.at(1), '34'); // Weight
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
Log? capturedLog;
|
||||
@@ -226,8 +229,8 @@ void main() {
|
||||
// Assert
|
||||
verify(mockRoutines.addLog(any)).called(1);
|
||||
expect(capturedLog, isNotNull);
|
||||
expect(capturedLog!.repetitions, equals(7));
|
||||
expect(capturedLog!.weight, equals(77));
|
||||
expect(capturedLog!.repetitions, equals(12));
|
||||
expect(capturedLog!.weight, equals(34));
|
||||
|
||||
final currentSlotPage = notifier.state.getSlotEntryPageByIndex()!;
|
||||
expect(capturedLog!.slotEntryId, equals(currentSlotPage.setConfigData!.slotEntryId));
|
||||
|
||||
@@ -1,21 +1,3 @@
|
||||
/*
|
||||
* This file is part of wger Workout Manager <https://github.com/wger-project>.
|
||||
* Copyright (c) 2025 wger Team
|
||||
*
|
||||
* wger Workout Manager is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
// Mocks generated by Mockito 5.4.6 from annotations
|
||||
// in wger/test/widgets/routines/gym_mode/log_page_test.dart.
|
||||
// Do not manually edit this file.
|
||||
|
||||
Reference in New Issue
Block a user