witness-org / witness

Your fitness witness!
1 stars 0 forks source link

40: Copy Previously Logged Workout - [merged] #92

Closed raffaelfoidl closed 2 years ago

raffaelfoidl commented 2 years ago

In GitLab by @11712616 on Feb 4, 2022, 19:15

Merges 40-copy-previously-logged-workout -> develop

Closes #40

raffaelfoidl commented 2 years ago

In GitLab by @11712616 on Feb 4, 2022, 19:15

requested review from @11775820

raffaelfoidl commented 2 years ago

In GitLab by @11712616 on Feb 4, 2022, 19:26

Commented on server/src/test/java/com/witness/server/integration/web/WorkoutLogControllerTest.java line 115

my nemesis

raffaelfoidl commented 2 years ago

In GitLab by @11712616 on Feb 5, 2022, 21:13

added 1 commit

Compare with previous version

raffaelfoidl commented 2 years ago

In GitLab by @11712616 on Feb 5, 2022, 21:27

added 7 commits

Compare with previous version

raffaelfoidl commented 2 years ago

In GitLab by @11712616 on Feb 6, 2022, 14:27

added 7 commits

Compare with previous version

raffaelfoidl commented 2 years ago

In GitLab by @11712616 on Feb 7, 2022, 17:56

added 1 commit

Compare with previous version

raffaelfoidl commented 2 years ago

In GitLab by @11775820 on Feb 7, 2022, 23:01

Commented on client/lib/widgets/exercises/exercises_by_muscle_group_screen.dart line 35

Why can't we just use widget._muscleGroup and _widget._selectExerciseAction. Aren't such variables (in the whole client codebase) very redundant?

I know that I, absentminded, did something like this more than once. But now that my thoughts are clear again, I think it's just useless to introduce such variables? Or do I miss some benefits or caveats for doing one thing over the other (apart from redundant lines of code)?

Of course, there are cases where we cannot do this (where you ingeniously introduced didUpdateWidget, i.e. the variables are not final).

raffaelfoidl commented 2 years ago

In GitLab by @11775820 on Feb 7, 2022, 23:01

Commented on client/lib/extensions/date_time_extensions.dart line 47

How about moving this to a new extension DateTimeExtensions in the same class and renaming the existing one to TZDateTimeExtensions (would be more fitting, anyway)? Then we could convert it to a regular extension method (not a static one) and significantly shorten the invocations (DateTimeExtensions.onlyDateFromDateTime(date) become date.onlyDateFromDateTime()). Optionally, we could also choose a better name, e.g. onlyTZDate or something like that - but that's up to you.

I have attached a patch including the changes introduced by my suggestion. Note that is also restructures the tests accordingly (an own group per extension), that gives nicer CI and IDE outputs (proper nesting etc.). I have also done that for number_extensions where we failed to do so (and I have also renamed it to its correct name number_extensions_test.dart.

Patch for C&P ```patch Index: client/test/unit/extensions/date_time_extensions_test.dart IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== diff --git a/client/test/unit/extensions/date_time_extensions_test.dart b/client/test/unit/extensions/date_time_extensions_test.dart --- a/client/test/unit/extensions/date_time_extensions_test.dart (revision 203a6e264192796c74712ea0d9bd831f9d9e6e2c) +++ b/client/test/unit/extensions/date_time_extensions_test.dart (date 1644245507121) @@ -18,93 +18,97 @@ initializeDateFormatting('en', null); tz.setLocalLocation(tz.getLocation('America/Detroit')); - group(getPrefixedGroupName(_sutName, 'onlyDate'), () { - test('should return a date of the same day, with time set to midnight', () { - final inputDate = TZDateTime(_localTimezone, 2021, 12, 3, 14, 46, 23, 123, 7772); - final expectedDate = TZDateTime(_localTimezone, 2021, 12, 3, 0, 0, 0, 0); - final outputDate = inputDate.onlyDate(); - expect(outputDate, expectedDate); - }); - }); + group(getPrefixedGroupName(_sutName, 'TZDateTimeExtensions'), () { + group('onlyDate', () { + test('should return a date of the same day, with time set to midnight', () { + final inputDate = TZDateTime(_localTimezone, 2021, 12, 3, 14, 46, 23, 123, 7772); + final expectedDate = TZDateTime(_localTimezone, 2021, 12, 3, 0, 0, 0, 0); + final outputDate = inputDate.onlyDate(); + expect(outputDate, expectedDate); + }); + }); - group(getPrefixedGroupName(_sutName, 'addYears'), () { - test('should return the same date if 0 years are added', () { - final inputDate = TZDateTime(_localTimezone, 2021, 12, 3, 14, 46, 23, 123, 7772); - final expectedDate = TZDateTime(_localTimezone, 2021, 12, 3, 14, 46, 23, 123, 7772); - final outputDate = inputDate.addYears(0); - expect(outputDate, expectedDate); - }); + group('addYears', () { + test('should return the same date if 0 years are added', () { + final inputDate = TZDateTime(_localTimezone, 2021, 12, 3, 14, 46, 23, 123, 7772); + final expectedDate = TZDateTime(_localTimezone, 2021, 12, 3, 14, 46, 23, 123, 7772); + final outputDate = inputDate.addYears(0); + expect(outputDate, expectedDate); + }); - test('should return a date with equal components, but increased year', () { - final inputDate = TZDateTime(_localTimezone, 2021, 12, 3, 14, 46, 23, 123, 7772); - final expectedDate = TZDateTime(_localTimezone, 2023, 12, 3, 14, 46, 23, 123, 7772); - final outputDate = inputDate.addYears(2); - expect(outputDate, expectedDate); - }); - }); + test('should return a date with equal components, but increased year', () { + final inputDate = TZDateTime(_localTimezone, 2021, 12, 3, 14, 46, 23, 123, 7772); + final expectedDate = TZDateTime(_localTimezone, 2023, 12, 3, 14, 46, 23, 123, 7772); + final outputDate = inputDate.addYears(2); + expect(outputDate, expectedDate); + }); + }); - group(getPrefixedGroupName(_sutName, 'subtractYears'), () { - test('should return the same date if 0 years are subtracted', () { - final inputDate = TZDateTime(_localTimezone, 2021, 12, 3, 14, 46, 23, 123, 7772); - final expectedDate = TZDateTime(_localTimezone, 2021, 12, 3, 14, 46, 23, 123, 7772); - final outputDate = inputDate.subtractYears(0); - expect(outputDate, expectedDate); - }); + group('subtractYears', () { + test('should return the same date if 0 years are subtracted', () { + final inputDate = TZDateTime(_localTimezone, 2021, 12, 3, 14, 46, 23, 123, 7772); + final expectedDate = TZDateTime(_localTimezone, 2021, 12, 3, 14, 46, 23, 123, 7772); + final outputDate = inputDate.subtractYears(0); + expect(outputDate, expectedDate); + }); - test('should return a date with equal components, but increased year', () { - final inputDate = TZDateTime(_localTimezone, 2021, 12, 3, 14, 46, 23, 123, 7772); - final expectedDate = TZDateTime(_localTimezone, 2019, 12, 3, 14, 46, 23, 123, 7772); - final outputDate = inputDate.subtractYears(2); - expect(outputDate, expectedDate); - }); - }); + test('should return a date with equal components, but increased year', () { + final inputDate = TZDateTime(_localTimezone, 2021, 12, 3, 14, 46, 23, 123, 7772); + final expectedDate = TZDateTime(_localTimezone, 2019, 12, 3, 14, 46, 23, 123, 7772); + final outputDate = inputDate.subtractYears(2); + expect(outputDate, expectedDate); + }); + }); - group(getPrefixedGroupName(_sutName, 'getStringRepresentation'), () { - test('should return date string without year if no text for today', () { - final inputDate = TZDateTime.now(_localTimezone); - final outputDate = inputDate.getStringRepresentation(); - expect(outputDate, _dateFormatWithoutYear.format(inputDate)); - }); + group('getStringRepresentation', () { + test('should return date string without year if no text for today', () { + final inputDate = TZDateTime.now(_localTimezone); + final outputDate = inputDate.getStringRepresentation(); + expect(outputDate, _dateFormatWithoutYear.format(inputDate)); + }); - test('should return date string without year if no text for yesterday', () { - final inputDate = TZDateTime.now(_localTimezone).subtract(const Duration(days: 1)); - final outputDate = inputDate.getStringRepresentation(); - expect(outputDate, _dateFormatWithoutYear.format(inputDate)); - }); + test('should return date string without year if no text for yesterday', () { + final inputDate = TZDateTime.now(_localTimezone).subtract(const Duration(days: 1)); + final outputDate = inputDate.getStringRepresentation(); + expect(outputDate, _dateFormatWithoutYear.format(inputDate)); + }); - test('should return specified string if text for today', () { - final inputDate = TZDateTime.now(_localTimezone); - const todayText = 'today'; - final outputDate = inputDate.getStringRepresentation(todayText: todayText); - expect(outputDate, todayText); - }); + test('should return specified string if text for today', () { + final inputDate = TZDateTime.now(_localTimezone); + const todayText = 'today'; + final outputDate = inputDate.getStringRepresentation(todayText: todayText); + expect(outputDate, todayText); + }); - test('should return specified string if text for yesterday', () { - final inputDate = TZDateTime.now(_localTimezone).subtract(const Duration(days: 1)); - const yesterdayText = 'yesterday'; - final outputDate = inputDate.getStringRepresentation(yesterdayText: yesterdayText); - expect(outputDate, yesterdayText); - }); + test('should return specified string if text for yesterday', () { + final inputDate = TZDateTime.now(_localTimezone).subtract(const Duration(days: 1)); + const yesterdayText = 'yesterday'; + final outputDate = inputDate.getStringRepresentation(yesterdayText: yesterdayText); + expect(outputDate, yesterdayText); + }); - test('should return date string with year if date a year ago', () { - final inputDate = TZDateTime.now(_localTimezone).subtract(const Duration(days: 365)); - final outputDate = inputDate.getStringRepresentation(); - expect(outputDate, _dateFormatWithYear.format(inputDate)); - }); + test('should return date string with year if date a year ago', () { + final inputDate = TZDateTime.now(_localTimezone).subtract(const Duration(days: 365)); + final outputDate = inputDate.getStringRepresentation(); + expect(outputDate, _dateFormatWithYear.format(inputDate)); + }); - test('should return date string with year if date more than a year ago', () { - final inputDate = TZDateTime.now(_localTimezone).subtract(const Duration(days: 366)); - final outputDate = inputDate.getStringRepresentation(); - expect(outputDate, _dateFormatWithYear.format(inputDate)); + test('should return date string with year if date more than a year ago', () { + final inputDate = TZDateTime.now(_localTimezone).subtract(const Duration(days: 366)); + final outputDate = inputDate.getStringRepresentation(); + expect(outputDate, _dateFormatWithYear.format(inputDate)); + }); }); }); - group(getPrefixedGroupName(_sutName, 'onlyDateFromDateTime'), () { - test('should return a date of the same day from DateTime, with time set to midnight', () { - final inputDate = DateTime(2021, 12, 3, 14, 46, 23, 123, 7772); - final expectedDate = TZDateTime(_localTimezone, 2021, 12, 3, 0, 0, 0, 0); - final outputDate = DateTimeExtensions.onlyDateFromDateTime(inputDate); - expect(outputDate, expectedDate); + group(getPrefixedGroupName(_sutName, 'DateTimeExtensions'), () { + group('onlyDateFromDateTime', () { + test('should return a date of the same day from DateTime, with time set to midnight', () { + final inputDate = DateTime(2021, 12, 3, 14, 46, 23, 123, 7772); + final expectedDate = TZDateTime(_localTimezone, 2021, 12, 3, 0, 0, 0, 0); + final outputDate = inputDate.onlyDateFromDateTime(); + expect(outputDate, expectedDate); + }); }); }); } Index: client/lib/widgets/workouts/workout_log_screen.dart IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== diff --git a/client/lib/widgets/workouts/workout_log_screen.dart b/client/lib/widgets/workouts/workout_log_screen.dart --- a/client/lib/widgets/workouts/workout_log_screen.dart (revision 203a6e264192796c74712ea0d9bd831f9d9e6e2c) +++ b/client/lib/widgets/workouts/workout_log_screen.dart (date 1644245349397) @@ -95,7 +95,7 @@ void _copyExerciseLogsFromDay( final StringLocalizations uiStrings, final WorkoutLogProvider provider, final DateTime selectedDate, final TZDateTime referenceDate) { - final zonedDate = DateTimeExtensions.onlyDateFromDateTime(selectedDate); + final zonedDate = selectedDate.onlyDateFromDateTime(); if (!zonedDate.isAtSameMomentAs(referenceDate.onlyDate()) && provider.hasWorkoutLogsWithExerciseLogs(zonedDate)) { _showCopyWorkoutLogsDialog(uiStrings, provider, zonedDate); @@ -199,8 +199,8 @@ () => _createNewWorkoutLog(uiStrings, WorkoutLogCreate.empty(widget._date.onlyDate())), date, (final provider, final selectedDate) => _copyExerciseLogsFromDay(uiStrings, provider, selectedDate, date), - (final _date) async => _fetchLoggingDaysInMonth(context, DateTimeExtensions.onlyDateFromDateTime(_date)), - (final provider, final _date) => provider.hasWorkoutLogsWithExerciseLogs(DateTimeExtensions.onlyDateFromDateTime(_date)), + (final _date) async => _fetchLoggingDaysInMonth(context, _date.onlyDateFromDateTime()), + (final provider, final _date) => provider.hasWorkoutLogsWithExerciseLogs(_date.onlyDateFromDateTime()), _fetchLoggingDaysInMonth, ); } Index: client/lib/extensions/date_time_extensions.dart IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== diff --git a/client/lib/extensions/date_time_extensions.dart b/client/lib/extensions/date_time_extensions.dart --- a/client/lib/extensions/date_time_extensions.dart (revision 203a6e264192796c74712ea0d9bd831f9d9e6e2c) +++ b/client/lib/extensions/date_time_extensions.dart (date 1644245713280) @@ -3,7 +3,7 @@ import 'package:timezone/timezone.dart' as tz; /// Encapsulates commonly used operations on [TZDateTime] objects. -extension DateTimeExtensions on TZDateTime { +extension TZDateTimeExtensions on TZDateTime { /// Returns a [TZDateTime] object containing only information about date TZDateTime onlyDate() { return TZDateTime(tz.local, year, month, day); @@ -37,9 +37,12 @@ final format = difference > -365 ? DateFormat.MMMMd('en') : DateFormat.yMMMMd('en'); // TODO(leabrugger-raffaelfoidl): localize date format return format.format(this); } +} - /// Constructs a [TZDateTime] object from a [DateTime] instance containing only information about date - static TZDateTime onlyDateFromDateTime(final DateTime date) { - return TZDateTime.local(date.year, date.month, date.day); +/// Encapsulates commonly used operations on [DateTime] objects. +extension DateTimeExtensions on DateTime { + /// Constructs a [TZDateTime] object from a [DateTime] instance containing only information about date. + TZDateTime onlyDateFromDateTime() { + return TZDateTime.local(year, month, day); } } Index: client/test/unit/extensions/model_extensions_test.dart IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== diff --git a/client/test/unit/extensions/model_extensions_test.dart b/client/test/unit/extensions/model_extensions_test.dart --- a/client/test/unit/extensions/model_extensions_test.dart (revision 203a6e264192796c74712ea0d9bd831f9d9e6e2c) +++ b/client/test/unit/extensions/model_extensions_test.dart (date 1644246529767) @@ -12,7 +12,7 @@ import '../../common/test_helpers.dart'; -const _sutName = 'model_extensions_test'; +const _sutName = 'model_extensions'; void main() { group(getPrefixedGroupName(_sutName, 'WorkoutLogExtensions'), () { Index: client/lib/widgets/common/dialog_helper.dart IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== diff --git a/client/lib/widgets/common/dialog_helper.dart b/client/lib/widgets/common/dialog_helper.dart --- a/client/lib/widgets/common/dialog_helper.dart (revision 203a6e264192796c74712ea0d9bd831f9d9e6e2c) +++ b/client/lib/widgets/common/dialog_helper.dart (date 1644245351219) @@ -1,8 +1,9 @@ -import 'package:flutter/material.dart'; -import 'package:client/extensions/date_time_extensions.dart'; +import 'dart:math' as math; + import 'package:client/extensions/context_extensions.dart'; +import 'package:client/extensions/date_time_extensions.dart'; +import 'package:flutter/material.dart'; import 'package:timezone/timezone.dart'; -import 'dart:math' as math; abstract class DialogHelper { DialogHelper._(); @@ -79,7 +80,7 @@ ? (final _date) => // predicate must be fulfilled for [initialDate] -> either provided function returns `true` or [_date] is equivalent to // [initialDate] (which is [referenceDate]), not taking time components into consideration - selectableDayPredicate(_date) || DateTimeExtensions.onlyDateFromDateTime(_date).isAtSameMomentAs(referenceDate.onlyDate()) + selectableDayPredicate(_date) || _date.onlyDateFromDateTime().isAtSameMomentAs(referenceDate.onlyDate()) : null, ), ), Index: client/test/unit/extensions/number_extensions.dart IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== diff --git a/client/test/unit/extensions/number_extensions.dart b/client/test/unit/extensions/number_extensions_test.dart rename from client/test/unit/extensions/number_extensions.dart rename to client/test/unit/extensions/number_extensions_test.dart --- a/client/test/unit/extensions/number_extensions.dart (revision 203a6e264192796c74712ea0d9bd831f9d9e6e2c) +++ b/client/test/unit/extensions/number_extensions_test.dart (date 1644246287337) @@ -6,57 +6,61 @@ const _sutName = 'number_extensions'; void main() { - group(getPrefixedGroupName(_sutName, 'toNumberString'), () { - test("should append 's' if int is 0", () { - expect(0.toNumberString('house'), '0 houses'); - }); + group(getPrefixedGroupName(_sutName, 'IntExtensions'), () { + group('toNumberString', () { + test("should append 's' if int is 0", () { + expect(0.toNumberString('house'), '0 houses'); + }); - test(getPrefixedGroupName(_sutName, 'should append "s" if int is -0'), () { - expect((-0).toNumberString('house'), '0 houses'); - }); + test('should append "s" if int is -0', () { + expect((-0).toNumberString('house'), '0 houses'); + }); - test(getPrefixedGroupName(_sutName, 'should not append "s" if int is 1'), () { - expect(1.toNumberString('house'), '1 house'); - }); + test('should not append "s" if int is 1', () { + expect(1.toNumberString('house'), '1 house'); + }); - test(getPrefixedGroupName(_sutName, 'should append "s" if int is 3'), () { - expect(3.toNumberString('house'), '3 houses'); - }); - }); + test('should append "s" if int is 3', () { + expect(3.toNumberString('house'), '3 houses'); + }); + }); - group(getPrefixedGroupName(_sutName, 'gInKg'), () { - test("1000g should be 1kg", () { - expect(1000.gInKg, 1.0); - }); + group('gInKg', () { + test("1000g should be 1kg", () { + expect(1000.gInKg, 1.0); + }); - test("5270g should be 5.27kg", () { - expect(5270.gInKg, 5.27); - }); + test("5270g should be 5.27kg", () { + expect(5270.gInKg, 5.27); + }); - test("-40000g should be -40kg", () { - expect(-40000.gInKg, -40.0); + test("-40000g should be -40kg", () { + expect(-40000.gInKg, -40.0); + }); }); }); - group(getPrefixedGroupName(_sutName, 'kgInG'), () { - test("1kg should be 1000g", () { - expect(1.0.kgInG, 1000); - }); + group(getPrefixedGroupName(_sutName, 'DoubleExtensions'), () { + group('kgInG', () { + test("1kg should be 1000g", () { + expect(1.0.kgInG, 1000); + }); - test("5.27kg should be 5270g", () { - expect(5.27.kgInG, 5270); - }); + test("5.27kg should be 5270g", () { + expect(5.27.kgInG, 5270); + }); - test("-40kg should be -40000g", () { - expect(-40.0.kgInG, -40000); - }); + test("-40kg should be -40000g", () { + expect(-40.0.kgInG, -40000); + }); - test("1.2345kg should be 1235g", () { - expect(1.2345.kgInG, 1235); - }); + test("1.2345kg should be 1235g", () { + expect(1.2345.kgInG, 1235); + }); - test("1.2342kg should be 1234g", () { - expect(1.2342.kgInG, 1234); + test("1.2342kg should be 1234g", () { + expect(1.2342.kgInG, 1234); + }); }); }); } Index: client/lib/widgets/main_app_bar.dart IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== diff --git a/client/lib/widgets/main_app_bar.dart b/client/lib/widgets/main_app_bar.dart --- a/client/lib/widgets/main_app_bar.dart (revision 203a6e264192796c74712ea0d9bd831f9d9e6e2c) +++ b/client/lib/widgets/main_app_bar.dart (date 1644245351110) @@ -2,6 +2,7 @@ import 'package:client/logging/log_message_preparer.dart'; import 'package:client/logging/logger_factory.dart'; import 'package:client/providers/auth_provider.dart'; +import 'package:client/widgets/common/dialog_helper.dart'; import 'package:client/widgets/common/string_localizer.dart'; import 'package:client/widgets/settings/settings_screen.dart'; import 'package:client/widgets/workouts/workout_log_screen.dart'; @@ -9,7 +10,6 @@ import 'package:flutter/services.dart'; import 'package:provider/provider.dart'; import 'package:timezone/timezone.dart'; -import 'package:client/widgets/common/dialog_helper.dart'; final _logger = getLogger('main_app_bar'); @@ -57,7 +57,7 @@ dialogTitle, currentlyViewedDate, (final pickedDate) { - final date = DateTimeExtensions.onlyDateFromDateTime(pickedDate); + final date = pickedDate.onlyDateFromDateTime(); Navigator.of(context).pushReplacementNamed(WorkoutLogScreen.routeName, arguments: date); }, ); ```
raffaelfoidl commented 2 years ago

In GitLab by @11775820 on Feb 7, 2022, 23:01

Commented on client/lib/localization/app_en.arb line 945

"The start date must not ... end date."?

raffaelfoidl commented 2 years ago

In GitLab by @11775820 on Feb 7, 2022, 23:01

Commented on client/lib/models/workouts/set_log_create.dart line 19

Since we are in the SetLogCreate class and we are returning a SetLogCreate instance (i.e. this is a simple static factory method), it would imo be a bit more apt to call this method fromSetLog.

I would go even further and turn this static factory method into a factory constructor:

factory SetLogCreate.fromSetLog(final SetLog setLog) {
  if (setLog is RepsSetLog) {
    return RepsSetLogCreate(rpe: setLog.rpe, weightG: setLog.weightG, resistanceBands: setLog.resistanceBands, reps: setLog.reps);
  } else if (setLog is TimeSetLog) {
    return TimeSetLogCreate(rpe: setLog.rpe, weightG: setLog.weightG, resistanceBands: setLog.resistanceBands, seconds: setLog.seconds);
  }
  throw Exception('Could not identify type of set log!');
}

Then, we can make the invocation of this constructor more succinct by employing a method reference (in exercise_log_create):

ExerciseLogCreate.fromExerciseLog(final ExerciseLog exerciseLog)
    : this(
        exerciseId: exerciseLog.exercise.id,
        comment: exerciseLog.comment,
        setLogs: exerciseLog.setLogs.map(SetLogCreate.fromSetLog).toList(), // here
      );

(technically speaking, that should have already been possible before, but the factory constructor is just nice syntactic sugar we should use here)

The spoiler below contains yet again a patch with the changes described above and other occurrences where we could have used a method reference, but I had missed it.

Patch ```patch Index: client/lib/models/workouts/set_log_create.dart IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== diff --git a/client/lib/models/workouts/set_log_create.dart b/client/lib/models/workouts/set_log_create.dart --- a/client/lib/models/workouts/set_log_create.dart (revision 9b6b1a0372a199edd86051461e421885a1b15d65) +++ b/client/lib/models/workouts/set_log_create.dart (date 1644247356486) @@ -8,11 +8,7 @@ abstract class SetLogCreate { const SetLogCreate({final this.rpe, required final this.weightG, required final this.resistanceBands}); - final int? rpe; - final int weightG; - final List resistanceBands; - - static SetLogCreate constructSetLogCreateFromSetLog(final SetLog setLog) { + factory SetLogCreate.fromSetLog(final SetLog setLog) { if (setLog is RepsSetLog) { return RepsSetLogCreate(rpe: setLog.rpe, weightG: setLog.weightG, resistanceBands: setLog.resistanceBands, reps: setLog.reps); } else if (setLog is TimeSetLog) { @@ -21,4 +17,9 @@ throw Exception('Could not identify type of set log!'); } + + final int? rpe; + final int weightG; + + final List resistanceBands; } Index: client/lib/models/workouts/exercise_log_create.dart IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== diff --git a/client/lib/models/workouts/exercise_log_create.dart b/client/lib/models/workouts/exercise_log_create.dart --- a/client/lib/models/workouts/exercise_log_create.dart (revision 9b6b1a0372a199edd86051461e421885a1b15d65) +++ b/client/lib/models/workouts/exercise_log_create.dart (date 1644247494825) @@ -16,7 +16,7 @@ : this( exerciseId: exerciseLog.exercise.id, comment: exerciseLog.comment, - setLogs: exerciseLog.setLogs.map((final setLog) => SetLogCreate.constructSetLogCreateFromSetLog(setLog)).toList(), + setLogs: exerciseLog.setLogs.map(SetLogCreate.fromSetLog).toList(), ); factory ExerciseLogCreate.fromJson(final Map json) => _$ExerciseLogCreateFromJson(json); Index: client/lib/widgets/workouts/workout_log_screen.dart IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== diff --git a/client/lib/widgets/workouts/workout_log_screen.dart b/client/lib/widgets/workouts/workout_log_screen.dart --- a/client/lib/widgets/workouts/workout_log_screen.dart (revision 9b6b1a0372a199edd86051461e421885a1b15d65) +++ b/client/lib/widgets/workouts/workout_log_screen.dart (date 1644247867373) @@ -59,7 +59,7 @@ Future _createNewExerciseLogsForWorkoutLog( final StringLocalizations uiStrings, final WorkoutLog workoutLog, final List exerciseLogs) async { final response = await Provider.of(context, listen: false) - .postNewExerciseLogs(workoutLog, exerciseLogs.map((final exerciseLog) => ExerciseLogCreate.fromExerciseLog(exerciseLog)).toList()); + .postNewExerciseLogs(workoutLog, exerciseLogs.map(ExerciseLogCreate.fromExerciseLog).toList()); if (response.isError) { setState(() => _error = translate(uiStrings, response.error!)); @@ -81,7 +81,7 @@ (final exerciseLogs) { final workoutLog = WorkoutLogCreate( loggedOn: widget._date.onlyDate(), - exerciseLogs: exerciseLogs.map((final exerciseLog) => ExerciseLogCreate.fromExerciseLog(exerciseLog)).toList(), + exerciseLogs: exerciseLogs.map(ExerciseLogCreate.fromExerciseLog).toList(), ); _createNewWorkoutLog(uiStrings, workoutLog); Index: client/lib/extensions/map_extensions.dart IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== diff --git a/client/lib/extensions/map_extensions.dart b/client/lib/extensions/map_extensions.dart --- a/client/lib/extensions/map_extensions.dart (revision 9b6b1a0372a199edd86051461e421885a1b15d65) +++ b/client/lib/extensions/map_extensions.dart (date 1644247695695) @@ -7,11 +7,11 @@ /// Filters the map entries by the given predicate and returns a list of the keys of the filtered map entries. Iterable whereKeys(final bool Function(MapEntry entry) predicate) { - return entries.where((final element) => predicate(element)).map((final e) => e.key); + return entries.where(predicate).map((final e) => e.key); } /// Filters the map entries by the given predicate and returns a list of the values of the filtered map entries. Iterable whereValues(final bool Function(MapEntry entry) predicate) { - return entries.where((final element) => predicate(element)).map((final e) => e.value); + return entries.where(predicate).map((final e) => e.value); } } ```
raffaelfoidl commented 2 years ago

In GitLab by @11775820 on Feb 7, 2022, 23:01

Commented on client/lib/models/workouts/set_log_create_converter.dart line 29

  1. I think it would be better to also add the @JsonSerializable() annotation to the example (as well as to the examples of other converters, where applicable), missed that the last time.
  2. I think I'd prefer renaming the models/common package to models/converter(s) and move set_log_create_converter.dart as well as set_log_converter.dart to that package. What do you think?
raffaelfoidl commented 2 years ago

In GitLab by @11775820 on Feb 7, 2022, 23:01

Commented on client/lib/providers/workout_log_provider.dart line 32

I'd maybe call this variable _workoutLogsCount or workoutLogsCountByDay? For me, the fact that it only contains non-empty workout logs is a server-side implementation detail and not that relevant to the client, let alone this specific provider. It does not make any difference for the implementation of the provider and its consumers, therefore I'd simply drop it. If you really want an indication of this fact, then I suggest amending the log message in getLoggingDaysInPeriod accordingly.

Do you agree?

raffaelfoidl commented 2 years ago

In GitLab by @11775820 on Feb 7, 2022, 23:01

Commented on client/lib/providers/workout_log_provider.dart line 26

I think it's time to fix something that has been bothering me for quite some time now. Those ....?... ?? <...., .....>{} constructs are a bit annoying. Thanks to Dart's great generics support, I'd like to introduce extension methods on nullable maps and lists so that we can simply call .orEmtpy() on those nullable maps and lists.

See patch below for required changes, tests included. You may choose a better name if you want.

Patch ```patch Index: client/lib/providers/workout_log_provider.dart IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== diff --git a/client/lib/providers/workout_log_provider.dart b/client/lib/providers/workout_log_provider.dart --- a/client/lib/providers/workout_log_provider.dart (revision a91c66f7ca25a9f711dc28cde1e3c1ffa86a7e54) +++ b/client/lib/providers/workout_log_provider.dart (date 1644251536444) @@ -1,4 +1,6 @@ import 'package:client/extensions/date_time_extensions.dart'; +import 'package:client/extensions/list_extensions.dart'; +import 'package:client/extensions/map_extensions.dart'; import 'package:client/extensions/model_extensions.dart'; import 'package:client/logging/logger_factory.dart'; import 'package:client/models/workouts/exercise_log.dart'; @@ -23,7 +25,7 @@ WorkoutLogProvider.empty() : this._(null, >{}, {}); WorkoutLogProvider.fromProviders(final AuthProvider auth, final WorkoutLogProvider? instance) - : this._(auth, instance?._workoutLogs ?? >{}, instance?._nonEmptyWorkoutLogsCount ?? {}); + : this._(auth, (instance?._workoutLogs).orEmpty(), (instance?._nonEmptyWorkoutLogsCount).orEmpty()); static final Injector _injector = Injector.appInstance; late final WorkoutLogService _workoutLogService = _injector.get(); @@ -33,7 +35,7 @@ final AuthProvider? _auth; List getWorkoutLogsByDay(final TZDateTime date) { - return collection.UnmodifiableListView(_workoutLogs[date.onlyDate()] ?? []); + return collection.UnmodifiableListView(_workoutLogs[date.onlyDate()].orEmpty()); } bool hasWorkoutLogsWithExerciseLogs(final TZDateTime date) { Index: client/lib/providers/training_program_provider.dart IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== diff --git a/client/lib/providers/training_program_provider.dart b/client/lib/providers/training_program_provider.dart --- a/client/lib/providers/training_program_provider.dart (revision a91c66f7ca25a9f711dc28cde1e3c1ffa86a7e54) +++ b/client/lib/providers/training_program_provider.dart (date 1644251283690) @@ -1,3 +1,5 @@ +import 'package:client/extensions/list_extensions.dart'; +import 'package:client/extensions/map_extensions.dart'; import 'package:client/logging/logger_factory.dart'; import 'package:client/models/training_programs/overview/training_day_overview.dart'; import 'package:client/models/training_programs/overview/training_program_overview.dart'; @@ -26,10 +28,10 @@ TrainingProgramProvider.fromProviders(final AuthProvider auth, final TrainingProgramProvider? instance) : this._( auth, - instance?._trainingPrograms ?? [], - instance?._trainingWeeks ?? >{}, - instance?._trainingDays ?? >{}, - instance?._workouts ?? >{}, + (instance?._trainingPrograms).orEmpty(), + (instance?._trainingWeeks).orEmpty(), + (instance?._trainingDays).orEmpty(), + (instance?._workouts).orEmpty(), ); static final Injector _injector = Injector.appInstance; Index: client/lib/providers/exercise_provider.dart IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== diff --git a/client/lib/providers/exercise_provider.dart b/client/lib/providers/exercise_provider.dart --- a/client/lib/providers/exercise_provider.dart (revision a91c66f7ca25a9f711dc28cde1e3c1ffa86a7e54) +++ b/client/lib/providers/exercise_provider.dart (date 1644251536476) @@ -1,4 +1,6 @@ import 'package:client/extensions/enum_extensions.dart'; +import 'package:client/extensions/list_extensions.dart'; +import 'package:client/extensions/map_extensions.dart'; import 'package:client/logging/logger_factory.dart'; import 'package:client/models/exercises/exercise.dart'; import 'package:client/models/exercises/exercise_create.dart'; @@ -17,8 +19,7 @@ ExerciseProvider.empty() : this._(null, >{}); - ExerciseProvider.fromProviders(final AuthProvider auth, final ExerciseProvider? instance) - : this._(auth, instance?._exercises ?? >{}); + ExerciseProvider.fromProviders(final AuthProvider auth, final ExerciseProvider? instance) : this._(auth, (instance?._exercises).orEmpty()); static final Injector _injector = Injector.appInstance; late final ExerciseService _exerciseService = _injector.get(); @@ -31,7 +32,7 @@ } List getExercisesByMuscleGroup(final MuscleGroup group) { - return collection.UnmodifiableListView(_exercises[group] ?? []); + return collection.UnmodifiableListView(_exercises[group].orEmpty()); } Future fetchExercisesByMuscleGroup(final MuscleGroup group) async { Index: client/test/unit/extensions/list_extensions_test.dart IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== diff --git a/client/test/unit/extensions/list_extensions_test.dart b/client/test/unit/extensions/list_extensions_test.dart new file mode 100644 --- /dev/null (date 1644251311154) +++ b/client/test/unit/extensions/list_extensions_test.dart (date 1644251311154) @@ -0,0 +1,30 @@ +import 'package:client/extensions/list_extensions.dart'; +import 'package:flutter_test/flutter_test.dart'; + +import '../../common/test_helpers.dart'; + +const _sutName = 'list_extensions'; + +void main() { + group(getPrefixedGroupName(_sutName, 'NullableListExtensions'), () { + group('orEmpty', () { + test('should return input list if it is empty', () { + final List emptyList = []; + final returnedList = emptyList.orEmpty(); + expect(returnedList, emptyList); + }); + + test('should return input list if it is non-empty', () { + final List nonEmptyList = ['item1', 'item2', 'item3']; + final returnedMap = nonEmptyList.orEmpty(); + expect(returnedMap, nonEmptyList); + }); + + test('should return empty list if input is null', () { + const List? nullList = null; + final returnedList = nullList.orEmpty(); + expect(returnedList, []); + }); + }); + }); +} Index: client/lib/extensions/map_extensions.dart IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== diff --git a/client/lib/extensions/map_extensions.dart b/client/lib/extensions/map_extensions.dart --- a/client/lib/extensions/map_extensions.dart (revision a91c66f7ca25a9f711dc28cde1e3c1ffa86a7e54) +++ b/client/lib/extensions/map_extensions.dart (date 1644250543137) @@ -15,3 +15,11 @@ return entries.where(predicate).map((final e) => e.value); } } + +/// Provides methods that facilitate handling of nullable [Map]s. +extension NullableMapExtensions on Map? { + /// Returns the current instance if it is not `null`, otherwise an empty [Map] with the same generic types. + Map orEmpty() { + return this ?? {}; + } +} Index: client/lib/extensions/list_extensions.dart IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== diff --git a/client/lib/extensions/list_extensions.dart b/client/lib/extensions/list_extensions.dart new file mode 100644 --- /dev/null (date 1644251111415) +++ b/client/lib/extensions/list_extensions.dart (date 1644251111415) @@ -0,0 +1,7 @@ +/// Provides methods that facilitate handling of nullable [List]s. +extension NullableList on List? { + /// Returns the current instance if it is not `null`, otherwise an empty [List] of the same type. + List orEmpty() { + return this ?? []; + } +} Index: client/test/unit/extensions/map_extensions_test.dart IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== diff --git a/client/test/unit/extensions/map_extensions_test.dart b/client/test/unit/extensions/map_extensions_test.dart --- a/client/test/unit/extensions/map_extensions_test.dart (revision a91c66f7ca25a9f711dc28cde1e3c1ffa86a7e54) +++ b/client/test/unit/extensions/map_extensions_test.dart (date 1644251282874) @@ -10,89 +10,113 @@ final _unorderedIterableEqualsString = const UnorderedIterableEquality().equals; void main() { - // Since MapEntry objects cannot be compared (see https://github.com/dart-lang/sdk/issues/32559), a list of the returned map entry keys - // and values, respectively, is collected and checked for equality. - group(getPrefixedGroupName(_sutName, 'where'), () { - test('with TRUE predicate should return all map entries', () { - const map = {1: 'test1', 2: 'test2', 3: 'test3'}; - predicate(final MapEntry entry) => true; + group(getPrefixedGroupName(_sutName, 'MapExtensions'), () { + // Since MapEntry objects cannot be compared (see https://github.com/dart-lang/sdk/issues/32559), a list of the returned map entry keys + // and values, respectively, is collected and checked for equality. + group('where', () { + test('with TRUE predicate should return all map entries', () { + const map = {1: 'test1', 2: 'test2', 3: 'test3'}; + predicate(final MapEntry entry) => true; - final result = map.where(predicate); - final keyList = result.map((final entry) => entry.key).toList(); - final valueList = result.map((final entry) => entry.value).toList(); + final result = map.where(predicate); + final keyList = result.map((final entry) => entry.key).toList(); + final valueList = result.map((final entry) => entry.value).toList(); - expect(_unorderedIterableEqualsInt(keyList, const [1, 2, 3]), true); - expect(_unorderedIterableEqualsString(valueList, const ['test1', 'test2', 'test3']), true); - }); + expect(_unorderedIterableEqualsInt(keyList, const [1, 2, 3]), true); + expect(_unorderedIterableEqualsString(valueList, const ['test1', 'test2', 'test3']), true); + }); - test('with FALSE predicate should return no map entries', () { - const map = {1: 'test1', 2: 'test2', 3: 'test3'}; - predicate(final MapEntry entry) => false; + test('with FALSE predicate should return no map entries', () { + const map = {1: 'test1', 2: 'test2', 3: 'test3'}; + predicate(final MapEntry entry) => false; - final result = map.where(predicate); - final keyList = result.map((final entry) => entry.key).toList(); - final valueList = result.map((final entry) => entry.value).toList(); + final result = map.where(predicate); + final keyList = result.map((final entry) => entry.key).toList(); + final valueList = result.map((final entry) => entry.value).toList(); - expect(_unorderedIterableEqualsInt(keyList, []), true); - expect(_unorderedIterableEqualsString(valueList, []), true); - }); + expect(_unorderedIterableEqualsInt(keyList, []), true); + expect(_unorderedIterableEqualsString(valueList, []), true); + }); - test('with <= predicate should return list of filtered map entries', () { - const map = {1: 'test1', 2: 'test2', 3: 'test3'}; - predicate(final MapEntry entry) => entry.key <= 2; + test('with <= predicate should return list of filtered map entries', () { + const map = {1: 'test1', 2: 'test2', 3: 'test3'}; + predicate(final MapEntry entry) => entry.key <= 2; - final result = map.where(predicate); - final keyList = result.map((final entry) => entry.key).toList(); - final valueList = result.map((final entry) => entry.value).toList(); + final result = map.where(predicate); + final keyList = result.map((final entry) => entry.key).toList(); + final valueList = result.map((final entry) => entry.value).toList(); - expect(_unorderedIterableEqualsInt(keyList, const [1, 2]), true); - expect(_unorderedIterableEqualsString(valueList, const ['test1', 'test2']), true); - }); - }); + expect(_unorderedIterableEqualsInt(keyList, const [1, 2]), true); + expect(_unorderedIterableEqualsString(valueList, const ['test1', 'test2']), true); + }); + }); - group(getPrefixedGroupName(_sutName, 'whereKeys'), () { - test('with TRUE predicate should return all map keys', () { - const map = {1: 'test1', 2: 'test2', 3: 'test3'}; - predicate(final MapEntry entry) => true; + group('whereKeys', () { + test('with TRUE predicate should return all map keys', () { + const map = {1: 'test1', 2: 'test2', 3: 'test3'}; + predicate(final MapEntry entry) => true; - expect(_unorderedIterableEqualsInt(map.whereKeys(predicate), const [1, 2, 3]), true); - }); + expect(_unorderedIterableEqualsInt(map.whereKeys(predicate), const [1, 2, 3]), true); + }); - test('with FALSE predicate should return no map keys', () { - const map = {1: 'test1', 2: 'test2', 3: 'test3'}; - predicate(final MapEntry entry) => false; + test('with FALSE predicate should return no map keys', () { + const map = {1: 'test1', 2: 'test2', 3: 'test3'}; + predicate(final MapEntry entry) => false; - expect(_unorderedIterableEqualsInt(map.whereKeys(predicate), []), true); - }); + expect(_unorderedIterableEqualsInt(map.whereKeys(predicate), []), true); + }); - test('with <= predicate should return list of filtered map keys', () { - const map = {1: 'test1', 2: 'test2', 3: 'test3'}; - predicate(final MapEntry entry) => entry.key <= 2; + test('with <= predicate should return list of filtered map keys', () { + const map = {1: 'test1', 2: 'test2', 3: 'test3'}; + predicate(final MapEntry entry) => entry.key <= 2; - expect(_unorderedIterableEqualsInt(map.whereKeys(predicate), const [1, 2]), true); - }); - }); + expect(_unorderedIterableEqualsInt(map.whereKeys(predicate), const [1, 2]), true); + }); + }); - group(getPrefixedGroupName(_sutName, 'whereValues'), () { - test('with TRUE predicate should return all map values', () { - const map = {1: 'test1', 2: 'test2', 3: 'test3'}; - predicate(final MapEntry entry) => true; + group('whereValues', () { + test('with TRUE predicate should return all map values', () { + const map = {1: 'test1', 2: 'test2', 3: 'test3'}; + predicate(final MapEntry entry) => true; - expect(_unorderedIterableEqualsString(map.whereValues(predicate), const ['test1', 'test2', 'test3']), true); - }); + expect(_unorderedIterableEqualsString(map.whereValues(predicate), const ['test1', 'test2', 'test3']), true); + }); - test('with FALSE predicate should return no map values', () { - const map = {1: 'test1', 2: 'test2', 3: 'test3'}; - predicate(final MapEntry entry) => false; + test('with FALSE predicate should return no map values', () { + const map = {1: 'test1', 2: 'test2', 3: 'test3'}; + predicate(final MapEntry entry) => false; - expect(_unorderedIterableEqualsString(map.whereValues(predicate), []), true); - }); + expect(_unorderedIterableEqualsString(map.whereValues(predicate), []), true); + }); - test('with <= predicate should return list of filtered map values', () { - const map = {1: 'test1', 2: 'test2', 3: 'test3'}; - predicate(final MapEntry entry) => entry.key <= 2; + test('with <= predicate should return list of filtered map values', () { + const map = {1: 'test1', 2: 'test2', 3: 'test3'}; + predicate(final MapEntry entry) => entry.key <= 2; - expect(_unorderedIterableEqualsString(map.whereValues(predicate), const ['test1', 'test2']), true); + expect(_unorderedIterableEqualsString(map.whereValues(predicate), const ['test1', 'test2']), true); + }); }); }); + + group(getPrefixedGroupName(_sutName, 'NullableMapExtensions'), () { + group('orEmpty', () { + test('should return input map if it is empty', () { + final Map emptyMap = {}; + final returnedMap = emptyMap.orEmpty(); + expect(returnedMap, emptyMap); + }); + + test('should return input map if it is non-empty', () { + final Map nonEmptyMap = {"key1": 23, "key2": 272, "key3": -273}; + final returnedMap = nonEmptyMap.orEmpty(); + expect(returnedMap, nonEmptyMap); + }); + + test('should return empty map if input is null', () { + const Map? nullMap = null; + final returnedMap = nullMap.orEmpty(); + expect(returnedMap, {}); + }); + }); + }); } ```
raffaelfoidl commented 2 years ago

In GitLab by @11775820 on Feb 7, 2022, 23:01

Commented on client/lib/providers/workout_log_provider.dart line 75

How about using our whereKeys extension method (as well as expression body)?

final datesNotInResponse = _nonEmptyWorkoutLogsCount.whereKeys(
  (final date) => date.key.compareTo(startDate) >= 0 && date.key.compareTo(endDate) <= 0 && !resultMap!.keys.contains(date.key),
);
raffaelfoidl commented 2 years ago

In GitLab by @11775820 on Feb 7, 2022, 23:01

Commented on client/lib/providers/workout_log_provider.dart line 85

date, entry (no underscore for local variables)

raffaelfoidl commented 2 years ago

In GitLab by @11775820 on Feb 7, 2022, 23:01

Commented on client/lib/providers/workout_log_provider.dart line 170

Wouldn't logCreates be better instead of logsCreate?

raffaelfoidl commented 2 years ago

In GitLab by @11775820 on Feb 7, 2022, 23:01

Commented on client/lib/providers/workout_log_provider.dart line 142

new exercise logs (both in success and error case)

raffaelfoidl commented 2 years ago

In GitLab by @11775820 on Feb 7, 2022, 23:01

Commented on client/lib/widgets/common/dialog_helper.dart line 115

  1. Please call MediaQuery.of(context) only once at the beginning of the method and reuse it, also for "nested" method calls.
  2. Please change the return Column() lambda to an expression body => Column()
  3. Please change to .copyWith(textScaleFactor: textScaleFactor) (no trailing comma) and reformat)
  4. There's a missing comma , after the closing parenthesis of Column(), pls reformat.
  5. Rename the BuildContext context parameter of the Builder's builder property to something like innerContext or the like. Currently, we are shadowing the getDatePicker method's parameter name context, which should always be avoided because it may lead to confusing behaviour.
  6. Can we also have a Today button which jumps to the current date? Or is this not part of the standard Material Design Flutter DatePicker?

Apart from that, why the sudden interest in adaptability and responsiveness? It's really nice that you've done it here, but we've never put much attention to those aspects yet. Is there a specific reason?

raffaelfoidl commented 2 years ago

In GitLab by @11775820 on Feb 7, 2022, 23:01

Commented on client/lib/widgets/common/dialog_helper.dart line 144

Get MediaQuery from other method.

raffaelfoidl commented 2 years ago

In GitLab by @11775820 on Feb 7, 2022, 23:01

Commented on client/lib/widgets/workouts/workout_log_screen.dart line 38

_logger.v(prepare('_createNewWorkout()'));

raffaelfoidl commented 2 years ago

In GitLab by @11775820 on Feb 7, 2022, 23:01

Commented on client/lib/widgets/workouts/workout_log_screen.dart line 57

Please comma after exerciseLogs parameter and reformat (also applies to some other methods in this file (and possibly also provider).

raffaelfoidl commented 2 years ago

In GitLab by @11775820 on Feb 7, 2022, 23:01

Commented on client/lib/widgets/workouts/workout_log_screen.dart line 68

What happens if the fetchWorkoutLogsByDay call produces an error in the backend? Is this handled gracefully in the client with a respective error message? It does not necessarily seem so, I can't see any error handling.

Can you give me a short rundown of your considerations in regards to client-side error handling in this MR? Did I just stumble upon the only two mishaps or are there potentially more instances where server-side errors are not handled gracefully in the client?

raffaelfoidl commented 2 years ago

In GitLab by @11775820 on Feb 7, 2022, 23:01

Commented on client/lib/widgets/workouts/workout_log_screen.dart line 125

Please no comma after child and reformat.

This method should get a doc comment or so explaining what it actually does. buildRefreshableWidget alone is not really descriptive without context.

Apart from that, wouldn't the more "natural" or "correct" extraction be that you have child: child and the parameter is the Text wrapped in Center, just as before?

raffaelfoidl commented 2 years ago

In GitLab by @11775820 on Feb 7, 2022, 23:01

Commented on client/lib/widgets/workouts/workout_log_screen.dart line 209

Why copy all of those properties from the Widget to the State? Can't you just access them with widget.------ where needed? This also applies to some other occurrences in the codebase. It's not always possible, though. For example, of course, for non-final variables where the widget.--- variable is just an initial value. For those cases where we utilize didUpdateWidget, I'm not sure if we are allowed to tinker there (due to the if we have there) without breaking anything.

raffaelfoidl commented 2 years ago

In GitLab by @11775820 on Feb 7, 2022, 23:01

Commented on client/lib/widgets/workouts/workout_log_screen.dart line 29

_WorkoutFloatingActionButton is still within the allowed range of lines of code. However, with about 220 LOC, WorkoutLogScreen exceeds the maximum number of lines of code per widget (see Wiki) by around 100.

You are more familiar with the code, please see if you are able to extract one or another widget to make this screen more compact. This would not only increase readability and maintainability, but also performance because re-renderings don't need to re-create the whole huge widget tree of WorkoutLogScreen every time.

raffaelfoidl commented 2 years ago

In GitLab by @11775820 on Feb 7, 2022, 23:01

Commented on client/lib/widgets/workouts/copy_exercise_logs_dialog.dart line 35

So many final literal copies. Why not use widget.---- verbatim?

raffaelfoidl commented 2 years ago

In GitLab by @11775820 on Feb 7, 2022, 23:01

Commented on client/lib/widgets/workouts/copy_exercise_logs_dialog.dart line 191

Please remove map's generic parameter List<ExerciseLog, it's inferred by the machine and obvious to humans owing to the return type.

Maybe also add comment like // get list of list of exercise logs from all workout logs and flatten them into one single list for people like me?

raffaelfoidl commented 2 years ago

In GitLab by @11775820 on Feb 7, 2022, 23:01

Commented on client/lib/widgets/workouts/copy_exercise_logs_dialog.dart line 103

Pleas remove <Widget>, everywhere if possible (my global search tells me 6 occurrences - one of them is, I think, invalid withtoutt the type arg).

raffaelfoidl commented 2 years ago

In GitLab by @11775820 on Feb 7, 2022, 23:01

Commented on client/lib/widgets/workouts/copy_exercise_logs_dialog.dart line 116

One thing I have missed while playing around with big and numerous workout logs:

Would it be possible to include a button (toggle button or so) next to the Workout X that selects all/no exercise logs of the specific workout log? Doesn't need to be sophisticated, i.e. no matter how many exercise logs are selected, first tap is select all, second is select none etc.

I don't know how much effort that is and how nicely it plays with the UI (size constraints) here. If you are able to do this in 15-20 min at max, I would greatly appreciate it. If it takes more than that, we'll leave it as is.

raffaelfoidl commented 2 years ago

In GitLab by @11775820 on Feb 7, 2022, 23:01

Commented on client/lib/widgets/workouts/select_workout_log_dialog.dart line 15

Easily fits on one line. Also applies to some other new Widget constructors in this MR.

raffaelfoidl commented 2 years ago

In GitLab by @11775820 on Feb 7, 2022, 23:01

Commented on client/lib/widgets/workouts/select_workout_log_dialog.dart line 43

Please no trailing , for Text argument and reformat

raffaelfoidl commented 2 years ago

In GitLab by @11775820 on Feb 7, 2022, 23:01

Commented on client/lib/widgets/workouts/select_workout_log_dialog.dart line 41

I found this screen quite confusing, to be honest. The fact that we have ListTiles here (and, from a user perspective, "regular text with icons"), suggests that we might be able to select multiple workouts in this screen. One knows that from other apps where the + turns into a checkmark after clicking the list item.

Maybe we could use something different than a ListTile, for example a simple ElevatedButton with a more decent color? Just anything different from regular text so that users have a clear indication that they can tap one and only one of the options displayed (and that doesn't suggest that they can select multiple items).

raffaelfoidl commented 2 years ago

In GitLab by @11775820 on Feb 7, 2022, 23:01

Commented on client/test/unit/extensions/date_time_extensions_test.dart line 100

See comment in date_time_extensions.dart

raffaelfoidl commented 2 years ago

In GitLab by @11775820 on Feb 7, 2022, 23:01

Commented on client/lib/widgets/workouts/workout_log_screen.dart line 35

There is no error handling. All the calendar days stay disabled, which is UI-wise already the wisest option. However, we should strive not to have something like this (CONFIGURATION_NOT_FOUND is the ServerError I returned temporarily to produce the error):

image

Normally we have the provider calls that return Future<void> in COnsumers, FutureBUilders and so on - so we have an AsyncSnapshot the Future.error is propagated to. In this case, we don't have that and, without looking it up, I don't know how to gracefully handle it in the _fetchLoggiingDaysInMonth method. I hope there is an easy way you can find to do so. In any way, we should display the translated error to the user...I can't imagine that getting the error object of a failed Future is too difficult (maybe we have to use then instead of await, I don't know - you'll hopefully find out).

raffaelfoidl commented 2 years ago

In GitLab by @11775820 on Feb 7, 2022, 23:01

Commented on server/src/main/java/com/witness/server/enumeration/ServerError.java line 211

More context please, both for the enum member name as well as its documentation.

The name-change is semi-mandatory, but the documentation must be changed. Name + docs have to be descriptive enough to unambiguously determine the use case for the error key. This is not the case here.

raffaelfoidl commented 2 years ago

In GitLab by @11775820 on Feb 7, 2022, 23:01

Commented on server/src/main/java/com/witness/server/repository/WorkoutLogRepository.java line 39

I would do it the other way round: Remove the NonEmtpy from the method name (to make it more readable) and mention the documentation (in any case, it should be mentioned in the documentation) that only workout logs with at least one exercise log are returned.

Regarding the query itself, I'd rather hide the join and instead make use of HQL:

SELECT
  w
FROM
  WorkoutLog w
WHERE
  w.exerciseLogs.size > 0 AND w.loggedOn >= :loggedOnStart AND w.loggedOn <= :loggedOnEnd AND w.user.firebaseId = :firebaseId

That way, in my opinion, the intention of the query becomes much clearer and more explicit. What do you think?

raffaelfoidl commented 2 years ago

In GitLab by @11775820 on Feb 7, 2022, 23:01

Commented on server/src/test/resources/data/unit/repository/workout-log-repository-test/findNonEmptyByLoggedOnBetweenAndUserFirebaseIdEquals.json line 1

Maybe I'm mistaken, but I don't see a test case where the criterion of non-emptiness is exercised. Would you mind duplicating the first test case, removing the exercise logs (setting it to []) with an expectation of no returned IDs. Would that do the trick?

raffaelfoidl commented 2 years ago

In GitLab by @11775820 on Feb 7, 2022, 23:01

Commented on server/src/main/java/com/witness/server/repository/WorkoutLogRepository.java line 11

Please add docs to my method:

  /**
   * Queries all workout logs that logged between a given start date and a given end date by the user with the provided Firebase ID.
   *
   * @param loggedOnStart start date of the period that the resulting workout logs should be logged in
   * @param loggedOnEnd   end date of the period that the resulting workout logs should be logged in
   * @param firebaseId    Firebase ID of the user that logged the resulting workout logs
   * @return list of {@link WorkoutLog} instances logged between {@code loggedOnStart} and {@code loggedOnEnd} by {@code firebaseId}
   */
List<WorkoutLog> findByLoggedOnBetweenAndUserFirebaseIdEquals(ZonedDateTime loggedOnStart, ZonedDateTime loggedOnEnd, String firebaseId);
raffaelfoidl commented 2 years ago

In GitLab by @11775820 on Feb 7, 2022, 23:01

Commented on server/src/main/java/com/witness/server/service/impl/WorkoutLogServiceImpl.java line 281

Please reverse the order of parameters to make invocations a little bit clearer: startDate, endDate, queryExecution

Furthermore, rename queryExecution to workoutLogGetter or something better - queryExecution is somehow weird :thinking:

Also, rename startDateWithoutTime to startOfStartDay and endDateWithoutTime to endOfEndDayor so. The with calls do not truncate the time part (which your naming suggests), but set them to the earliest and latest value possible, respectively. The variable naming should indicate that.

raffaelfoidl commented 2 years ago

In GitLab by @11775820 on Feb 7, 2022, 23:01

Commented on server/src/main/java/com/witness/server/service/impl/WorkoutLogServiceImpl.java line 77

..must lie before the end date

raffaelfoidl commented 2 years ago

In GitLab by @11775820 on Feb 7, 2022, 23:01

Commented on server/src/main/java/com/witness/server/service/impl/WorkoutLogServiceImpl.java line 82

Please use Collectors.groupingBy (no static imports in production code unless justified, e.g. for readability - cf. OpenApiConfig.java).

raffaelfoidl commented 2 years ago

In GitLab by @11775820 on Feb 7, 2022, 23:01

Commented on server/src/test/java/com/witness/server/integration/web/WorkoutLogControllerTest.java line 109

missing new line after region

raffaelfoidl commented 2 years ago

In GitLab by @11775820 on Feb 7, 2022, 23:01

Commented on server/src/test/java/com/witness/server/integration/web/WorkoutLogControllerTest.java line 115

Suggestion (this also fixes two deprecation warnings from AssertJ). Obiously I didn't push it to test it in the CI, but I'm fairly confident that it works under any circumstance:

Click this to collapse/fold. ```patch Index: server/src/test/java/com/witness/server/integration/web/ExerciseControllerTest.java IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== diff --git a/server/src/test/java/com/witness/server/integration/web/ExerciseControllerTest.java b/server/src/test/java/com/witness/server/integration/web/ExerciseControllerTest.java --- a/server/src/test/java/com/witness/server/integration/web/ExerciseControllerTest.java (revision d3a714060ba92edcbed626e2dc59c8c26278bb4b) +++ b/server/src/test/java/com/witness/server/integration/web/ExerciseControllerTest.java (date 1644268548403) @@ -500,7 +500,7 @@ assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK); assertThat(response.getBody()) - .usingElementComparatorIgnoringFields("id") + .usingRecursiveFieldByFieldElementComparatorIgnoringFields("id") .containsExactlyInAnyOrder(expected); } @@ -538,7 +538,7 @@ assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK); assertThat(response.getBody()) - .usingElementComparatorIgnoringFields("id") + .usingRecursiveFieldByFieldElementComparatorIgnoringFields("id") .containsExactlyInAnyOrder(expected); } Index: server/src/test/resources/data/integration/web/workout-log-controller-test/GetLoggingDaysInputStartDateAfterEndDate.json IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== diff --git a/server/src/test/resources/data/integration/web/workout-log-controller-test/GetLoggingDaysInputStartDateAfterEndDate.json b/server/src/test/resources/data/integration/web/workout-log-controller-test/GetLoggingDaysInputStartDateAfterEndDate.json --- a/server/src/test/resources/data/integration/web/workout-log-controller-test/GetLoggingDaysInputStartDateAfterEndDate.json (revision d3a714060ba92edcbed626e2dc59c8c26278bb4b) +++ b/server/src/test/resources/data/integration/web/workout-log-controller-test/GetLoggingDaysInputStartDateAfterEndDate.json (date 1644267460394) @@ -15,5 +15,5 @@ "startDate": "2021-10-07T14:15:56.3007597+02:00", "endDate": "2021-10-07T14:15:55.3007597+02:00", "persistedWorkoutLogs": [], - "expectedMappingNoTimezone": {} + "expectedMapping": {} } \ No newline at end of file Index: server/src/test/java/com/witness/server/util/JsonFileArgumentsProvider.java IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== diff --git a/server/src/test/java/com/witness/server/util/JsonFileArgumentsProvider.java b/server/src/test/java/com/witness/server/util/JsonFileArgumentsProvider.java --- a/server/src/test/java/com/witness/server/util/JsonFileArgumentsProvider.java (revision d3a714060ba92edcbed626e2dc59c8c26278bb4b) +++ b/server/src/test/java/com/witness/server/util/JsonFileArgumentsProvider.java (date 1644266932198) @@ -8,9 +8,11 @@ import com.witness.server.util.converter.ArgumentConverter; import com.witness.server.util.converter.NoOpArgumentConverter; import com.witness.server.util.deserializer.SetLogDeserializer; +import com.witness.server.util.deserializer.ZonedDateTimeKeyDeserializer; import java.io.IOException; import java.io.InputStream; import java.lang.reflect.InvocationTargetException; +import java.time.ZonedDateTime; import java.util.Arrays; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -30,7 +32,8 @@ private static final ObjectMapper objectMapper = new ObjectMapper() .disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS) .registerModule(new SimpleModule("CUSTOM_DESERIALIZERS") - .addDeserializer(SetLog.class, new SetLogDeserializer())) + .addDeserializer(SetLog.class, new SetLogDeserializer()) + .addKeyDeserializer(ZonedDateTime.class, new ZonedDateTimeKeyDeserializer())) .registerModule(new JavaTimeModule()); private JsonFileSource[] files; @@ -92,7 +95,7 @@ if (!targetType.equals(file.type())) { log.error("The specified test argument converter is not NoOpArgumentConverter, but its target class differs from the class specified by the " - + "\"type\" argument of the @JsonFileSource annotation. Supplying arguments to the test will likely fail due to incompatible types."); + + "\"type\" argument of the @JsonFileSource annotation. Supplying arguments to the test will likely fail due to incompatible types."); } var intermediateObject = objectMapper.readValue(jsonStream, sourceType); @@ -109,7 +112,7 @@ var testMethod = context.getTestMethod().isPresent() ? context.getTestMethod().get().getName() : ""; var errorMessage = "Test \"" + testMethod + "\" is annotated with @JsonFileSources and \"unwrapArrays=true\", but {}. Will not unwrap, test " - + "will fail. Please examine your data sources and @JsonFileSource annotations."; + + "will fail. Please examine your data sources and @JsonFileSource annotations."; // 2) all arguments must be arrays var allArrays = Arrays.stream(parameters).allMatch(param -> param.getClass().isArray()); Index: server/src/test/java/com/witness/server/integration/web/WorkoutLogControllerTest.java IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== diff --git a/server/src/test/java/com/witness/server/integration/web/WorkoutLogControllerTest.java b/server/src/test/java/com/witness/server/integration/web/WorkoutLogControllerTest.java --- a/server/src/test/java/com/witness/server/integration/web/WorkoutLogControllerTest.java (revision d3a714060ba92edcbed626e2dc59c8c26278bb4b) +++ b/server/src/test/java/com/witness/server/integration/web/WorkoutLogControllerTest.java (date 1644269019666) @@ -1,6 +1,5 @@ package com.witness.server.integration.web; -import static com.witness.server.util.Comparators.ZONED_DATE_TIME_COMPARATOR; import static org.assertj.core.api.Assertions.assertThat; import com.witness.server.dto.workout.ExerciseLogCreateDto; @@ -20,18 +19,20 @@ import com.witness.server.repository.ExerciseRepository; import com.witness.server.repository.SetLogRepository; import com.witness.server.repository.WorkoutLogRepository; +import com.witness.server.util.Comparators; import com.witness.server.util.JsonFileSource; import com.witness.server.util.JsonFileSources; import java.time.ZonedDateTime; import java.util.ArrayList; +import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.regex.Pattern; -import java.util.stream.Collectors; import lombok.Data; import lombok.NoArgsConstructor; import org.junit.jupiter.params.ParameterizedTest; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.core.ParameterizedTypeReference; import org.springframework.http.HttpMethod; import org.springframework.http.HttpStatus; @@ -84,7 +85,7 @@ assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK); assertThat(response.getBody()) .usingRecursiveComparison() - .withComparatorForType(ZONED_DATE_TIME_COMPARATOR, ZonedDateTime.class) + .withComparatorForType(Comparators.ZONED_DATE_TIME_COMPARATOR, ZonedDateTime.class) .isEqualTo(specification.expectedWorkoutLogs); } @@ -108,11 +109,11 @@ //endregion //region all logging days in period + @ParameterizedTest @JsonFileSources(parameters = { @JsonFileSource(value = DATA_ROOT + "GetLoggingDaysInput1.json", type = GetByPeriodTestSpecification.class) }) - @SuppressWarnings("unchecked") void getLoggingDays_matchingNonEmptyPersistedWorkoutLogs_return200AndMap(GetByPeriodTestSpecification specification) { persistUserAndMockLoggedIn(specification.currentUser); persistUsers(specification.persistedUsers); @@ -120,34 +121,20 @@ persistEntities(workoutLogRepository, specification.persistedWorkoutLogs); var queryParams = toMultiValueMap(Map.of("startDate", specification.startDate, "endDate", specification.endDate)); - var response = get(TestAuthentication.REGULAR, requestUrl(GET_LOGGING_DAYS_URL), queryParams, Map.class); - var responseBody = (Map) response.getBody(); + var response = get(TestAuthentication.REGULAR, requestUrl(GET_LOGGING_DAYS_URL), queryParams, + new ParameterizedTypeReference>() { + }); + var responseMap = response.getBody(); assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK); - assertThat(responseBody).isNotNull(); - - var mapWithNoTimezones = responseBody.entrySet().stream().collect(Collectors.toMap( - entry -> { - var matcher = DATE_TIME_WITHOUT_TIMEZONE_REGEX.matcher(entry.getKey()); - if (matcher.find()) { - return matcher.group(1); - } - - return entry.getKey(); - }, - Map.Entry::getValue)); - var expectedResponse = specification.expectedMappingNoTimezone; - - assertThat(mapWithNoTimezones.size()).isEqualTo(expectedResponse.size()); - assertThat(mapWithNoTimezones.entrySet()).allSatisfy( - entry -> assertThat(expectedResponse.get(entry.getKey())).isEqualTo(entry.getValue())); + assertThat(responseMap).hasSameSizeAs(specification.expectedMapping); + assertThat(responseMap).isEqualTo(specification.expectedMapping); } @ParameterizedTest @JsonFileSources(parameters = { @JsonFileSource(value = DATA_ROOT + "GetLoggingDaysInput2.json", type = GetByPeriodTestSpecification.class) }) - @SuppressWarnings("unchecked") void getLoggingDays_noNonEmptyPersistedWorkoutLogs_return200AndEmptyMap(GetByPeriodTestSpecification specification) { persistUserAndMockLoggedIn(specification.currentUser); persistUsers(specification.persistedUsers); @@ -155,13 +142,15 @@ persistEntities(workoutLogRepository, specification.persistedWorkoutLogs); var queryParams = toMultiValueMap(Map.of("startDate", specification.startDate, "endDate", specification.endDate)); - var response = get(TestAuthentication.REGULAR, requestUrl(GET_LOGGING_DAYS_URL), queryParams, Map.class); + var response = get(TestAuthentication.REGULAR, requestUrl(GET_LOGGING_DAYS_URL), queryParams, + new ParameterizedTypeReference>() { + }); - var responseBody = (Map) response.getBody(); + var responseMap = response.getBody(); assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK); - assertThat(responseBody).isNotNull(); - assertThat(responseBody).isEmpty(); + assertThat(responseMap).isNotNull(); + assertThat(responseMap).isEmpty(); } @ParameterizedTest @@ -199,7 +188,7 @@ assertThat(response.getBody()).isNotNull(); assertThat(response.getBody()) .usingRecursiveComparison() - .withComparatorForType(ZONED_DATE_TIME_COMPARATOR, ZonedDateTime.class) + .withComparatorForType(Comparators.ZONED_DATE_TIME_COMPARATOR, ZonedDateTime.class) .isEqualTo(createdDto); } @@ -1017,7 +1006,7 @@ private String endDate; private Exercise[] persistedExercises; private WorkoutLog[] persistedWorkoutLogs; - private Map expectedMappingNoTimezone; + private Map expectedMapping; } @Data Index: server/src/test/java/com/witness/server/integration/web/BaseControllerIntegrationTest.java IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== diff --git a/server/src/test/java/com/witness/server/integration/web/BaseControllerIntegrationTest.java b/server/src/test/java/com/witness/server/integration/web/BaseControllerIntegrationTest.java --- a/server/src/test/java/com/witness/server/integration/web/BaseControllerIntegrationTest.java (revision d3a714060ba92edcbed626e2dc59c8c26278bb4b) +++ b/server/src/test/java/com/witness/server/integration/web/BaseControllerIntegrationTest.java (date 1644270977795) @@ -24,6 +24,7 @@ import com.witness.server.service.UserService; import com.witness.server.util.isolation.DatabaseResetService; import java.io.IOException; +import java.net.URI; import java.nio.charset.StandardCharsets; import java.time.Instant; import java.time.ZoneId; @@ -36,6 +37,7 @@ import java.util.stream.Collectors; import javax.servlet.http.HttpServletResponse; import lombok.SneakyThrows; +import org.assertj.core.util.TriFunction; import org.junit.jupiter.api.BeforeEach; import org.mockito.Answers; import org.mockito.invocation.InvocationOnMock; @@ -45,6 +47,7 @@ import org.springframework.boot.test.mock.mockito.MockBean; import org.springframework.boot.test.web.client.TestRestTemplate; import org.springframework.boot.web.server.LocalServerPort; +import org.springframework.core.ParameterizedTypeReference; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.http.HttpEntity; import org.springframework.http.HttpHeaders; @@ -117,6 +120,11 @@ return exchange(authMode, url, HttpMethod.GET, queryParams, clazz); } + protected ResponseEntity get(TestAuthentication authMode, String url, MultiValueMap queryParams, + ParameterizedTypeReference responseType) { + return exchange(authMode, url, HttpMethod.GET, queryParams, null, responseType); + } + protected ResponseEntity exchange(TestAuthentication authMode, String url, HttpMethod method, Class responseType) { return exchange(authMode, url, method, null, null, responseType); } @@ -131,9 +139,22 @@ return exchange(authMode, url, method, null, requestBody, responseType); } + protected ResponseEntity exchange(TestAuthentication authMode, String url, HttpMethod method, MultiValueMap queryParams, + U requestBody, Class responseType) { + return exchange(authMode, url, method, queryParams, requestBody, + (requestUri, httpMethod, requestEntity) -> restTemplate.exchange(requestUri, httpMethod, requestEntity, responseType)); + } + + protected ResponseEntity exchange(TestAuthentication authMode, String url, HttpMethod method, MultiValueMap queryParams, + U requestBody, ParameterizedTypeReference responseType) { + return exchange(authMode, url, method, queryParams, requestBody, + (requestUri, httpMethod, requestEntity) -> restTemplate.exchange(requestUri, httpMethod, requestEntity, responseType)); + } + @SneakyThrows({AuthenticationException.class, IOException.class}) - protected ResponseEntity exchange(TestAuthentication authMode, String url, HttpMethod method, - MultiValueMap queryParams, U requestBody, Class responseType) { + private ResponseEntity exchange(TestAuthentication authMode, String url, HttpMethod method, + MultiValueMap queryParams, U requestBody, + TriFunction, ResponseEntity> exchangeFunction) { doAnswer(this::stubAuthenticationError) .when(securityService) .replyAuthenticationError(any(HttpServletResponse.class), any(AuthenticationException.class)); @@ -165,7 +186,7 @@ .toUri(); var requestEntity = new HttpEntity<>(requestBody, headers); - return restTemplate.exchange(requestUri, method, requestEntity, responseType); + return exchangeFunction.apply(requestUri, method, requestEntity); } protected static MultiValueMap toMultiValueMap(Map map) { Index: server/src/test/resources/data/integration/web/workout-log-controller-test/GetLoggingDaysInput2.json IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== diff --git a/server/src/test/resources/data/integration/web/workout-log-controller-test/GetLoggingDaysInput2.json b/server/src/test/resources/data/integration/web/workout-log-controller-test/GetLoggingDaysInput2.json --- a/server/src/test/resources/data/integration/web/workout-log-controller-test/GetLoggingDaysInput2.json (revision d3a714060ba92edcbed626e2dc59c8c26278bb4b) +++ b/server/src/test/resources/data/integration/web/workout-log-controller-test/GetLoggingDaysInput2.json (date 1644267460434) @@ -75,5 +75,5 @@ "exerciseLogs": [] } ], - "expectedMappingNoTimezone": {} + "expectedMapping": {} } \ No newline at end of file Index: server/src/test/resources/data/integration/web/workout-log-controller-test/GetLoggingDaysInput1.json IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== diff --git a/server/src/test/resources/data/integration/web/workout-log-controller-test/GetLoggingDaysInput1.json b/server/src/test/resources/data/integration/web/workout-log-controller-test/GetLoggingDaysInput1.json --- a/server/src/test/resources/data/integration/web/workout-log-controller-test/GetLoggingDaysInput1.json (revision d3a714060ba92edcbed626e2dc59c8c26278bb4b) +++ b/server/src/test/resources/data/integration/web/workout-log-controller-test/GetLoggingDaysInput1.json (date 1644267565270) @@ -111,7 +111,7 @@ "exerciseLogs": [] } ], - "expectedMappingNoTimezone": { - "2021-10-08T00:00:00": 1 + "expectedMapping": { + "2021-10-08T00:00:00+02:00": 1 } } \ No newline at end of file ```
raffaelfoidl commented 2 years ago

In GitLab by @11775820 on Feb 7, 2022, 23:01

Looks very nice. Well done :)

raffaelfoidl commented 2 years ago

In GitLab by @11712616 on Feb 12, 2022, 12:43

added 1 commit

Compare with previous version

raffaelfoidl commented 2 years ago

In GitLab by @11775820 on Feb 12, 2022, 13:09

added 1 commit

Compare with previous version

raffaelfoidl commented 2 years ago

In GitLab by @11712616 on Feb 12, 2022, 13:24

Commented on client/lib/localization/app_en.arb line 945

changed this line in version 9 of the diff

raffaelfoidl commented 2 years ago

In GitLab by @11712616 on Feb 12, 2022, 13:24

Commented on client/lib/widgets/workouts/copy_exercise_logs_dialog.dart line 35

changed this line in version 9 of the diff

raffaelfoidl commented 2 years ago

In GitLab by @11712616 on Feb 12, 2022, 13:24

added 3 commits

Compare with previous version

raffaelfoidl commented 2 years ago

In GitLab by @11712616 on Feb 12, 2022, 13:40

Commented on client/lib/models/workouts/set_log_create_converter.dart line 29

changed this line in version 10 of the diff