diff --git a/src/GymTron.App/Pages/BodyWeightsHistoryPage.xaml b/src/GymTron.App/Pages/BodyWeightsHistoryPage.xaml index 685ed47..695709b 100644 --- a/src/GymTron.App/Pages/BodyWeightsHistoryPage.xaml +++ b/src/GymTron.App/Pages/BodyWeightsHistoryPage.xaml @@ -95,7 +95,7 @@ BackgroundColor="{StaticResource GymTronSurface}" Padding="12, 8"> - diff --git a/src/GymTron.App/Resources/Strings/AppResources.en.resx b/src/GymTron.App/Resources/Strings/AppResources.en.resx index 0157a97..569bc70 100644 --- a/src/GymTron.App/Resources/Strings/AppResources.en.resx +++ b/src/GymTron.App/Resources/Strings/AppResources.en.resx @@ -247,6 +247,21 @@ Observations: + + Write your observations here... + + + It was hard + + + It was very hard + + + Increase weight + + + Increase reps + Complete @@ -278,6 +293,12 @@ Add new measure + + Congratulations, new measurement recorded successfully! + + + % Body Fat + diff --git a/src/GymTron.App/Resources/Strings/AppResources.es.resx b/src/GymTron.App/Resources/Strings/AppResources.es.resx index 886996f..d759dd8 100644 --- a/src/GymTron.App/Resources/Strings/AppResources.es.resx +++ b/src/GymTron.App/Resources/Strings/AppResources.es.resx @@ -247,6 +247,21 @@ Observaciones: + + Escribe aquí tus observaciones... + + + Ha sido duro + + + Ha sido muy duro + + + Subir peso + + + Subir repeticiones + Completar @@ -278,6 +293,12 @@ Añadir nueva medida + + ¡Enhorabuena, nueva medida registrada correctamente! + + + % Grasa + diff --git a/src/GymTron.App/Resources/Strings/AppResources.resx b/src/GymTron.App/Resources/Strings/AppResources.resx index d52512e..9e81c77 100644 --- a/src/GymTron.App/Resources/Strings/AppResources.resx +++ b/src/GymTron.App/Resources/Strings/AppResources.resx @@ -247,6 +247,21 @@ Observacions: + + Escriu aquí les teves observacions... + + + Ha estat dur + + + Ha estat molt dur + + + Pujar pes + + + Pujar repeticions + Completar @@ -278,6 +293,12 @@ Afegir nova mesura + + Enhorabona, nova mesura registrada correctament! + + + % Greix + diff --git a/src/GymTron.App/ViewModels/Pages/BodyWeightsHistoryPageViewModel.cs b/src/GymTron.App/ViewModels/Pages/BodyWeightsHistoryPageViewModel.cs index dee5500..fc4f14b 100644 --- a/src/GymTron.App/ViewModels/Pages/BodyWeightsHistoryPageViewModel.cs +++ b/src/GymTron.App/ViewModels/Pages/BodyWeightsHistoryPageViewModel.cs @@ -73,7 +73,7 @@ static async Task onAddBodyWeight() await Shell.Current.Navigation.PopModalAsync(); await Shell.Current.Navigation.PopAsync(); - IToast toast = Toast.Make("Enhorabona, nova mesura registrada correctament!"); + IToast toast = Toast.Make(LocalizationService.GetString("BodyWeights_NewMeasurementToast")); await toast.Show(); } diff --git a/src/GymTron.App/ViewModels/Pages/CurrentTrainingPageViewModel.cs b/src/GymTron.App/ViewModels/Pages/CurrentTrainingPageViewModel.cs index 3a6e7da..d708bf8 100644 --- a/src/GymTron.App/ViewModels/Pages/CurrentTrainingPageViewModel.cs +++ b/src/GymTron.App/ViewModels/Pages/CurrentTrainingPageViewModel.cs @@ -23,8 +23,19 @@ public string ElapsedTime { get { - TimeSpan elapsed = DateTime.Now - StartTime; - return $"{elapsed.Hours:D2}:{elapsed.Minutes:D2}:{elapsed.Seconds:D2}"; + if (_currentTraining == null || StartTime == DateTime.MinValue) + { + return "00:00:00"; + } + + TimeSpan elapsed = DateTime.UtcNow - StartTime; + if (elapsed < TimeSpan.Zero) + { + elapsed = TimeSpan.Zero; + } + + int totalHours = (int)elapsed.TotalHours; + return $"{totalHours:D2}:{elapsed.Minutes:D2}:{elapsed.Seconds:D2}"; } } diff --git a/src/GymTron.App/ViewModels/Pages/Modals/CompleteExerciseModalViewModel.cs b/src/GymTron.App/ViewModels/Pages/Modals/CompleteExerciseModalViewModel.cs index d83f2e2..67cb3fd 100644 --- a/src/GymTron.App/ViewModels/Pages/Modals/CompleteExerciseModalViewModel.cs +++ b/src/GymTron.App/ViewModels/Pages/Modals/CompleteExerciseModalViewModel.cs @@ -8,11 +8,6 @@ public class CompleteExerciseModalViewModel : PageBaseViewModel { - private const string HARD_OBSERVATION = "Ha estat dur"; - private const string REALLY_HARD_OBSERVATION = "Ha estat molt dur"; - private const string INCREASE_WEIGHT_OBSERVATION = "Pujar pes"; - private const string INCREASE_REPETITIONS_OBSERVATION = "Pujar repeticions"; - public bool IsDurationExercise { get; set; } public bool IsWeightExercise => !IsDurationExercise; @@ -122,7 +117,7 @@ await Shell.Current.DisplayAlert( } else { - bool hasValidWeight = TryParseDecimal(Weight, out decimal weight) && weight > 0; + bool hasValidWeight = TryParseDecimal(Weight, out decimal weight) && weight >= 0; bool hasValidReps = int.TryParse(Repetitions?.Trim(), out int repetitions) && repetitions > 0; if (!hasValidWeight || !hasValidReps) @@ -162,11 +157,11 @@ private List GetObservations() if (SelectedHardDifficulty) { - observations.Add(HARD_OBSERVATION); + observations.Add(LocalizationService.GetString("CompleteModal_ObsHard")); } else if (SelectedReallyHardDifficulty) { - observations.Add(REALLY_HARD_OBSERVATION); + observations.Add(LocalizationService.GetString("CompleteModal_ObsReallyHard")); } if (!string.IsNullOrWhiteSpace(Observations)) @@ -176,12 +171,12 @@ private List GetObservations() if (IncreaseWeight) { - observations.Add(INCREASE_WEIGHT_OBSERVATION); + observations.Add(LocalizationService.GetString("CompleteModal_ObsIncreaseWeight")); } if (IncreaseRepetitions) { - observations.Add(INCREASE_REPETITIONS_OBSERVATION); + observations.Add(LocalizationService.GetString("CompleteModal_ObsIncreaseReps")); } return observations; diff --git a/src/GymTron.Application/Trainings/Commands/AddExerciseToTrainingCommand.cs b/src/GymTron.Application/Trainings/Commands/AddExerciseToTrainingCommand.cs index d7fe166..f4ccf6c 100644 --- a/src/GymTron.Application/Trainings/Commands/AddExerciseToTrainingCommand.cs +++ b/src/GymTron.Application/Trainings/Commands/AddExerciseToTrainingCommand.cs @@ -80,8 +80,8 @@ public AddExerciseToTrainingCommandValidator() RuleFor(x => x.Weight) .NotNull() .WithMessage("Weight is required.") - .GreaterThan(0) - .WithMessage("Weight must be greater than zero."); + .GreaterThanOrEqualTo(0) + .WithMessage("Weight must be greater than or equal to zero."); RuleFor(x => x.Repetitions) .NotNull() diff --git a/src/GymTron.Domain/Aggregates/Training.cs b/src/GymTron.Domain/Aggregates/Training.cs index 6beb5ec..6a82d2d 100644 --- a/src/GymTron.Domain/Aggregates/Training.cs +++ b/src/GymTron.Domain/Aggregates/Training.cs @@ -97,9 +97,9 @@ public void CompleteExercise(Exercise exercise, IClock clock) { ArgumentNullException.ThrowIfNull(exercise); - if (exercise.DurationInSeconds <= 0 && (exercise.Weight <= 0 || exercise.CurrentRepetitions <= 0)) + if (exercise.DurationInSeconds <= 0 && (exercise.Weight < 0 || exercise.CurrentRepetitions <= 0)) { - throw new InvalidDomainOperationException("Exercise completion values must be greater than zero."); + throw new InvalidDomainOperationException("Exercise completion values must specify either duration greater than zero or repetitions greater than zero with non-negative weight."); } bool notExistsExerciseInTraining = !_completedWorkout.Any(x => x.ExerciseParametersId == exercise.ExerciseParametersId); diff --git a/src/GymTron.Domain/Entities/Exercise.cs b/src/GymTron.Domain/Entities/Exercise.cs index 7541a38..b7637d7 100644 --- a/src/GymTron.Domain/Entities/Exercise.cs +++ b/src/GymTron.Domain/Entities/Exercise.cs @@ -122,11 +122,11 @@ private static void ValidateExecutionValues(decimal weight, int duration, int re } bool isValidDuration = duration > 0; - bool isValidWeightAndReps = weight > 0 && repetitions > 0; + bool isValidRepetitions = repetitions > 0; - if (!isValidDuration && !isValidWeightAndReps) + if (!isValidDuration && !isValidRepetitions) { - throw new InvalidDomainOperationException("Exercise execution must specify either duration greater than zero or weight and repetitions greater than zero."); + throw new InvalidDomainOperationException("Exercise execution must specify either duration greater than zero or repetitions greater than zero with non-negative weight."); } } diff --git a/tests/GymTron.UnitTests/Application/ValidatorTests.cs b/tests/GymTron.UnitTests/Application/ValidatorTests.cs index 4a14fc3..7ef71ef 100644 --- a/tests/GymTron.UnitTests/Application/ValidatorTests.cs +++ b/tests/GymTron.UnitTests/Application/ValidatorTests.cs @@ -265,8 +265,10 @@ public void RegisterUserCommandValidator_PasswordRules_ValidatedCorrectly(string [Theory] [InlineData(50, 10, true)] - [InlineData(0, 10, false)] + [InlineData(0, 10, true)] + [InlineData(-1, 10, false)] [InlineData(-5, 10, false)] + [InlineData(0, 0, false)] [InlineData(50, 0, false)] [InlineData(50, -2, false)] public void AddExerciseToTrainingCommand_WeightAndRepsValidation(decimal weight, int reps, bool expectedValid) diff --git a/tests/GymTron.UnitTests/Domain/EntityTests.cs b/tests/GymTron.UnitTests/Domain/EntityTests.cs index 9d28fb4..ec4d69f 100644 --- a/tests/GymTron.UnitTests/Domain/EntityTests.cs +++ b/tests/GymTron.UnitTests/Domain/EntityTests.cs @@ -230,7 +230,6 @@ public void RefreshToken_New_FromDatabase_Revoke_And_IsActive_WorkCorrectly() [Theory] [InlineData(0, 0, 0)] - [InlineData(0, 0, 10)] [InlineData(10, 0, 0)] [InlineData(-5, 0, 10)] [InlineData(10, 0, -2)] @@ -244,6 +243,7 @@ public void Exercise_New_WithInvalidValues_ThrowsInvalidDomainOperationException [Theory] [InlineData(0, 45, 0)] [InlineData(80, 0, 10)] + [InlineData(0, 0, 10)] public void Exercise_New_WithValidValues_Succeeds(decimal weight, int duration, int repetitions) { Exercise exercise = Exercise.New(1, 1, "Bench Press", weight, duration, repetitions, []); diff --git a/tests/GymTron.UnitTests/Domain/TrainingTests.cs b/tests/GymTron.UnitTests/Domain/TrainingTests.cs index f8ef75d..d347aff 100644 --- a/tests/GymTron.UnitTests/Domain/TrainingTests.cs +++ b/tests/GymTron.UnitTests/Domain/TrainingTests.cs @@ -44,6 +44,18 @@ public void CompleteExercise_WithMatchingPendingItem_MovesExerciseOnlyOnce() Assert.Equal(22, pendingItem.ExerciseParameters.Id); } + [Fact] + public void CompleteExercise_WithZeroWeightAndPositiveReps_IsAccepted() + { + Training training = CreateActiveTraining(); + Exercise bodyweightExercise = Exercise.New(training.Id, 11, "Pushups", 0, 0, 10, []); + + training.CompleteExercise(bodyweightExercise, Clock); + + Exercise completedExercise = Assert.Single(training.CompletedWorkout); + Assert.Same(bodyweightExercise, completedExercise); + } + [Fact] public void CompleteExercise_WithInvalidExecutionValues_ThrowsInvalidDomainOperationException() { @@ -53,6 +65,38 @@ public void CompleteExercise_WithInvalidExecutionValues_ThrowsInvalidDomainOpera Assert.Throws(() => training.CompleteExercise(invalidExercise, Clock)); } + [Fact] + public void CompleteExercise_WithNegativeWeight_ThrowsInvalidDomainOperationException() + { + Training training = CreateActiveTraining(); + Exercise negativeWeightExercise = Exercise.FromDatabase(1, training.Id, 11, "Squat", -1, 0, 10, DateTime.UtcNow, []); + + Assert.Throws(() => training.CompleteExercise(negativeWeightExercise, Clock)); + } + + [Fact] + public void Exercise_New_WithZeroWeightAndPositiveReps_IsAccepted() + { + Exercise exercise = Exercise.New(1, 11, "Pushups", 0, 0, 10, []); + + Assert.Equal(0, exercise.Weight); + Assert.Equal(10, exercise.CurrentRepetitions); + } + + [Fact] + public void Exercise_New_WithNegativeWeight_ThrowsInvalidDomainOperationException() + { + Assert.Throws(() => + Exercise.New(1, 11, "Squat", -1, 0, 10, [])); + } + + [Fact] + public void Exercise_New_WithZeroRepsAndZeroDuration_ThrowsInvalidDomainOperationException() + { + Assert.Throws(() => + Exercise.New(1, 11, "Squat", 0, 0, 0, [])); + } + [Fact] public void Complete_WhenActive_MarksTrainingCompletedAndRecordsCompletionTime() {