Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/GymTron.App/Pages/BodyWeightsHistoryPage.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@
BackgroundColor="{StaticResource GymTronSurface}"
Padding="12, 8">
<VerticalStackLayout Spacing="2">
<Label Text="% Grasa"
<Label Text="{ext:Translate BodyWeights_BodyFatLabel}"
FontSize="11"
TextColor="{StaticResource GymTronTextMuted}" />
<Label Text="{Binding BodyFatPercentage}"
Expand Down
2 changes: 1 addition & 1 deletion src/GymTron.App/Pages/Modals/CompleteExerciseModal.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -149,7 +149,7 @@
<Editor Text="{Binding Observations}"
HeightRequest="90"
TextColor="{StaticResource GymTronDark}"
Placeholder="Escriu aquí les teves observacions..." />
Placeholder="{ext:Translate CompleteModal_Observations_Placeholder}" />
</Border>
</VerticalStackLayout>

Expand Down
21 changes: 21 additions & 0 deletions src/GymTron.App/Resources/Strings/AppResources.en.resx
Original file line number Diff line number Diff line change
Expand Up @@ -247,6 +247,21 @@
<data name="CompleteModal_Observations" xml:space="preserve">
<value>Observations:</value>
</data>
<data name="CompleteModal_Observations_Placeholder" xml:space="preserve">
<value>Write your observations here...</value>
</data>
<data name="CompleteModal_ObsHard" xml:space="preserve">
<value>It was hard</value>
</data>
<data name="CompleteModal_ObsReallyHard" xml:space="preserve">
<value>It was very hard</value>
</data>
<data name="CompleteModal_ObsIncreaseWeight" xml:space="preserve">
<value>Increase weight</value>
</data>
<data name="CompleteModal_ObsIncreaseReps" xml:space="preserve">
<value>Increase reps</value>
</data>
<data name="CompleteModal_Complete" xml:space="preserve">
<value>Complete</value>
</data>
Expand Down Expand Up @@ -278,6 +293,12 @@
<data name="BodyWeights_Add" xml:space="preserve">
<value>Add new measure</value>
</data>
<data name="BodyWeights_NewMeasurementToast" xml:space="preserve">
<value>Congratulations, new measurement recorded successfully!</value>
</data>
<data name="BodyWeights_BodyFatLabel" xml:space="preserve">
<value>% Body Fat</value>
</data>

<!-- TrainingsHistoryPage -->
<data name="TrainingsHistory_Title" xml:space="preserve">
Expand Down
21 changes: 21 additions & 0 deletions src/GymTron.App/Resources/Strings/AppResources.es.resx
Original file line number Diff line number Diff line change
Expand Up @@ -247,6 +247,21 @@
<data name="CompleteModal_Observations" xml:space="preserve">
<value>Observaciones:</value>
</data>
<data name="CompleteModal_Observations_Placeholder" xml:space="preserve">
<value>Escribe aquí tus observaciones...</value>
</data>
<data name="CompleteModal_ObsHard" xml:space="preserve">
<value>Ha sido duro</value>
</data>
<data name="CompleteModal_ObsReallyHard" xml:space="preserve">
<value>Ha sido muy duro</value>
</data>
<data name="CompleteModal_ObsIncreaseWeight" xml:space="preserve">
<value>Subir peso</value>
</data>
<data name="CompleteModal_ObsIncreaseReps" xml:space="preserve">
<value>Subir repeticiones</value>
</data>
<data name="CompleteModal_Complete" xml:space="preserve">
<value>Completar</value>
</data>
Expand Down Expand Up @@ -278,6 +293,12 @@
<data name="BodyWeights_Add" xml:space="preserve">
<value>Añadir nueva medida</value>
</data>
<data name="BodyWeights_NewMeasurementToast" xml:space="preserve">
<value>¡Enhorabuena, nueva medida registrada correctamente!</value>
</data>
<data name="BodyWeights_BodyFatLabel" xml:space="preserve">
<value>% Grasa</value>
</data>

<!-- TrainingsHistoryPage -->
<data name="TrainingsHistory_Title" xml:space="preserve">
Expand Down
21 changes: 21 additions & 0 deletions src/GymTron.App/Resources/Strings/AppResources.resx
Original file line number Diff line number Diff line change
Expand Up @@ -247,6 +247,21 @@
<data name="CompleteModal_Observations" xml:space="preserve">
<value>Observacions:</value>
</data>
<data name="CompleteModal_Observations_Placeholder" xml:space="preserve">
<value>Escriu aquí les teves observacions...</value>
</data>
<data name="CompleteModal_ObsHard" xml:space="preserve">
<value>Ha estat dur</value>
</data>
<data name="CompleteModal_ObsReallyHard" xml:space="preserve">
<value>Ha estat molt dur</value>
</data>
<data name="CompleteModal_ObsIncreaseWeight" xml:space="preserve">
<value>Pujar pes</value>
</data>
<data name="CompleteModal_ObsIncreaseReps" xml:space="preserve">
<value>Pujar repeticions</value>
</data>
<data name="CompleteModal_Complete" xml:space="preserve">
<value>Completar</value>
</data>
Expand Down Expand Up @@ -278,6 +293,12 @@
<data name="BodyWeights_Add" xml:space="preserve">
<value>Afegir nova mesura</value>
</data>
<data name="BodyWeights_NewMeasurementToast" xml:space="preserve">
<value>Enhorabona, nova mesura registrada correctament!</value>
</data>
<data name="BodyWeights_BodyFatLabel" xml:space="preserve">
<value>% Greix</value>
</data>

<!-- TrainingsHistoryPage -->
<data name="TrainingsHistory_Title" xml:space="preserve">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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();
}

Expand Down
15 changes: 13 additions & 2 deletions src/GymTron.App/ViewModels/Pages/CurrentTrainingPageViewModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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}";
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -162,11 +157,11 @@ private List<string> 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))
Expand All @@ -176,12 +171,12 @@ private List<string> 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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down
4 changes: 2 additions & 2 deletions src/GymTron.Domain/Aggregates/Training.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
6 changes: 3 additions & 3 deletions src/GymTron.Domain/Entities/Exercise.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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.");
}
}

Expand Down
4 changes: 3 additions & 1 deletion tests/GymTron.UnitTests/Application/ValidatorTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
2 changes: 1 addition & 1 deletion tests/GymTron.UnitTests/Domain/EntityTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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)]
Expand All @@ -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, []);
Expand Down
44 changes: 44 additions & 0 deletions tests/GymTron.UnitTests/Domain/TrainingTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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()
{
Expand All @@ -53,6 +65,38 @@ public void CompleteExercise_WithInvalidExecutionValues_ThrowsInvalidDomainOpera
Assert.Throws<InvalidDomainOperationException>(() => 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<InvalidDomainOperationException>(() => 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<InvalidDomainOperationException>(() =>
Exercise.New(1, 11, "Squat", -1, 0, 10, []));
}

[Fact]
public void Exercise_New_WithZeroRepsAndZeroDuration_ThrowsInvalidDomainOperationException()
{
Assert.Throws<InvalidDomainOperationException>(() =>
Exercise.New(1, 11, "Squat", 0, 0, 0, []));
}

[Fact]
public void Complete_WhenActive_MarksTrainingCompletedAndRecordsCompletionTime()
{
Expand Down
Loading