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">
-
+ Placeholder="{ext:Translate CompleteModal_Observations_Placeholder}" />
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()
{