Skip to content

Commit b75cecb

Browse files
authored
Merge pull request #20 from AbdoWa7eed/features/trainee-measurements/target-body-fat
Add Target Body Fat Feature and Update UI Components
2 parents 3bdb315 + 0c24121 commit b75cecb

30 files changed

+588
-74
lines changed

lib/core/di/di.config.dart

Lines changed: 9 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

lib/core/res/strings_manager.dart

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,7 @@ abstract class AppStrings {
9999
static const String yourBodyFat = 'Your Body Fat In The';
100100
static const String bodyFatTitle = 'What Is Your Body Shape?';
101101
static const String targetBodyFatTitle = 'What Is Your Target Body Shape?';
102+
static const String yourTargetBodyFat = 'Your Target Body Fat Is';
102103

103104
// Body Fat Categories - Status
104105
static const String bodyFatStatusIdeal = 'Optimal Range';
@@ -134,4 +135,16 @@ abstract class AppStrings {
134135
'High range indicating potential health risks. Consider implementing structured exercise routine.';
135136
static const String bodyFatFemaleVeryHighDesc =
136137
'Very high range requiring immediate attention. Seek professional guidance for personalized plan.';
138+
139+
// Goal Status Messages
140+
static const String goalStatusSweet = "Sweaty Choice!";
141+
static const String goalStatusReasonable = "Reasonable Goal!";
142+
static const String goalStatusChallenging = "Challenging Goal!";
143+
144+
static const String goalStatusSweetMessage =
145+
"You've set an ambitious goal - embrace the challenge and let the sweat be your badge of honor!";
146+
static const String goalStatusReasonableMessage =
147+
"Perfect balance! Your goal is realistic and achievable. Keep pushing forward with confidence!";
148+
static const String goalStatusChallengingMessage =
149+
"This goal will test your limits, but that's where true transformation happens. Stay focused and determined!";
137150
}

lib/core/routes/app_routes.dart

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import 'package:graduation_project/features/profile_completion/presentation/view
1010
import 'package:graduation_project/features/trainee_measurements/presentation/views/body_fat_selection_view.dart';
1111
import 'package:graduation_project/features/trainee_measurements/presentation/views/goal_selection_view.dart';
1212
import 'package:graduation_project/features/trainee_measurements/presentation/views/height_selection_view.dart';
13+
import 'package:graduation_project/features/trainee_measurements/presentation/views/target_body_fat_selection_view.dart';
1314
import 'package:graduation_project/features/trainee_measurements/presentation/views/target_weight_selection_view.dart';
1415
import 'package:graduation_project/features/trainee_measurements/presentation/views/weight_selection_view.dart';
1516

lib/core/routes/app_routes.main.dart

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ abstract class Routes {
1313
static const String targetWeightSelectionRoute = "/target-weight-selection";
1414
static const String goalSelectionRoute = "/goal-selection";
1515
static const String bodyFatSelectionRoute = "/body-fat-selection";
16+
static const String targetBodyFatSelectionRoute = "/target-body-fat-selection";
1617
}
1718

1819
abstract class RouteGenerator {
@@ -98,5 +99,11 @@ abstract class RouteGenerator {
9899
return PageSlideTransition(child: BodyFatSelectionView());
99100
},
100101
),
102+
GoRoute(
103+
path: Routes.targetBodyFatSelectionRoute,
104+
pageBuilder: (context, state) {
105+
return PageSlideTransition(child: TargetBodyFatSelectionView());
106+
},
107+
),
101108
];
102109
}

lib/core/utils/enums.dart

Lines changed: 17 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -64,48 +64,45 @@ extension BmiCategoryExtension on BmiCategory {
6464
}
6565
}
6666

67+
enum GoalStatus { sweet, reasonable, challenging }
6768

68-
enum TargetWeightGoalStatus { sweet, reasonable, challenging }
69-
70-
extension TargetWeightGoalStatusExtension on TargetWeightGoalStatus {
69+
extension GoalStatusExtension on GoalStatus {
7170
String get displayName {
7271
switch (this) {
73-
case TargetWeightGoalStatus.sweet:
74-
return "Sweaty Choice!";
75-
case TargetWeightGoalStatus.reasonable:
76-
return "Reasonable Goal!";
77-
case TargetWeightGoalStatus.challenging:
78-
return "Challenging Goal!";
72+
case GoalStatus.sweet:
73+
return AppStrings.goalStatusSweet;
74+
case GoalStatus.reasonable:
75+
return AppStrings.goalStatusReasonable;
76+
case GoalStatus.challenging:
77+
return AppStrings.goalStatusChallenging;
7978
}
8079
}
8180

8281
String get motivationalMessage {
8382
switch (this) {
84-
case TargetWeightGoalStatus.sweet:
85-
return "You're on the right track!";
86-
case TargetWeightGoalStatus.reasonable:
87-
return "Keep up the great work!";
88-
case TargetWeightGoalStatus.challenging:
89-
return "Stay motivated, you've got this!";
83+
case GoalStatus.sweet:
84+
return AppStrings.goalStatusSweetMessage;
85+
case GoalStatus.reasonable:
86+
return AppStrings.goalStatusReasonableMessage;
87+
case GoalStatus.challenging:
88+
return AppStrings.goalStatusChallengingMessage;
9089
}
9190
}
9291

9392
Color get color {
9493
switch (this) {
95-
case TargetWeightGoalStatus.sweet:
94+
case GoalStatus.sweet:
9695
return ColorManager.yellow;
97-
case TargetWeightGoalStatus.reasonable:
96+
case GoalStatus.reasonable:
9897
return ColorManager.green;
99-
case TargetWeightGoalStatus.challenging:
98+
case GoalStatus.challenging:
10099
return ColorManager.red;
101100
}
102101
}
103102
}
104103

105-
106104
enum GoalType { loseWeight, buildMuscle, getFitter }
107105

108-
109106
extension GoalTypeExtension on GoalType {
110107
String get name {
111108
switch (this) {

lib/features/trainee_measurements/domain/entities/body_fat/body_fat_category.dart

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,15 @@
11
import 'package:flutter/material.dart';
2+
import 'package:equatable/equatable.dart';
23

3-
class BodyFatCategoryModel {
4+
class BodyFatCategoryModel extends Equatable {
45
final String status;
56
final int min;
67
final int? max;
78
final String assetPath;
89
final String description;
910
final Color color;
1011

11-
BodyFatCategoryModel({
12+
const BodyFatCategoryModel({
1213
required this.status,
1314
required this.min,
1415
this.max,
@@ -20,4 +21,7 @@ class BodyFatCategoryModel {
2021
bool contains(double value) {
2122
return value >= min && (max == null || value <= max!);
2223
}
24+
25+
@override
26+
List<Object?> get props => [status, min, max, assetPath, description, color];
2327
}

lib/features/trainee_measurements/presentation/cubits/body_fat/body_fat_cubit.dart

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -22,10 +22,6 @@ class BodyFatCubit extends Cubit<BodyFatState> {
2222

2323

2424
void updateIndex(int newIndex) {
25-
assert(
26-
newIndex >= 0 && newIndex < categories.length,
27-
'Index $newIndex is out of bounds for categories list of length ${categories.length}',
28-
);
2925
emit(
3026
state.copyWith(
3127
currentIndex: newIndex,
@@ -36,7 +32,6 @@ class BodyFatCubit extends Cubit<BodyFatState> {
3632

3733
static BodyFatCategoryModel _getInitialCategory(Gender gender) {
3834
final categories = BodyFatCategoryData.get(gender);
39-
assert(categories.isNotEmpty, 'Categories list should not be empty');
4035
return categories[0];
4136
}
4237
}
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
import 'package:bloc/bloc.dart';
2+
import 'package:graduation_project/core/utils/enums.dart';
3+
import 'package:graduation_project/features/trainee_measurements/domain/entities/body_fat/body_fat_category.dart';
4+
import 'package:graduation_project/features/trainee_measurements/domain/helpers/body_fat_categories_data.dart';
5+
import 'package:graduation_project/features/trainee_measurements/presentation/cubits/target_body_fat/target_body_fat_params.dart';
6+
import 'package:graduation_project/features/trainee_measurements/presentation/cubits/target_body_fat/target_body_fat_state.dart';
7+
import 'package:injectable/injectable.dart';
8+
9+
@injectable
10+
class TargetBodyFatCubit extends Cubit<TargetBodyFatState> {
11+
final TargetBodyFatParams _params;
12+
13+
TargetBodyFatCubit(@factoryParam this._params)
14+
: super(
15+
TargetBodyFatState(
16+
currentIndex: getInitialCategoryIndex(
17+
_params.gender,
18+
_params.currentCategory,
19+
),
20+
selectedCategory: _params.currentCategory,
21+
),
22+
);
23+
24+
List<BodyFatCategoryModel> get categories =>
25+
BodyFatCategoryData.get(_params.gender);
26+
27+
void updateIndex(int newIndex) {
28+
emit(
29+
state.copyWith(
30+
currentIndex: newIndex,
31+
selectedCategory: categories[newIndex],
32+
goalStatus: _goalStatus(newIndex),
33+
),
34+
);
35+
}
36+
37+
GoalStatus _goalStatus(int targetIndex) {
38+
final currentIndex = categories.indexOf(_params.currentCategory);
39+
if (currentIndex == -1) return GoalStatus.challenging;
40+
41+
final direction = targetIndex - currentIndex;
42+
final categoryGap = direction.abs();
43+
44+
switch (_params.goalType) {
45+
case GoalType.loseWeight:
46+
return _evaluateGoal(direction < 0, direction == 0, categoryGap);
47+
48+
case GoalType.buildMuscle:
49+
return _evaluateGoal(direction > 0, direction == 0, categoryGap);
50+
51+
case GoalType.getFitter:
52+
return _evaluateGapOnly(categoryGap);
53+
}
54+
}
55+
56+
GoalStatus _evaluateGoal(bool isDesiredDirection, bool isSame, int gap) {
57+
if (isSame) return GoalStatus.reasonable;
58+
if (!isDesiredDirection) return GoalStatus.challenging;
59+
60+
if (gap == 1) return GoalStatus.reasonable;
61+
if (gap == 2) return GoalStatus.sweet;
62+
return GoalStatus.challenging;
63+
}
64+
65+
GoalStatus _evaluateGapOnly(int gap) {
66+
if (gap == 0 || gap == 1) return GoalStatus.reasonable;
67+
if (gap == 2) return GoalStatus.sweet;
68+
return GoalStatus.challenging;
69+
}
70+
71+
static int getInitialCategoryIndex(
72+
Gender gender,
73+
BodyFatCategoryModel currentCategory,
74+
) {
75+
return BodyFatCategoryData.get(gender).indexOf(currentCategory);
76+
}
77+
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import 'package:graduation_project/core/utils/enums.dart';
2+
import 'package:graduation_project/features/trainee_measurements/domain/entities/body_fat/body_fat_category.dart';
3+
4+
class TargetBodyFatParams {
5+
final Gender gender;
6+
final BodyFatCategoryModel currentCategory;
7+
final GoalType goalType;
8+
9+
TargetBodyFatParams({
10+
required this.gender,
11+
required this.currentCategory,
12+
required this.goalType,
13+
});
14+
15+
factory TargetBodyFatParams.fromMap(Map<String, dynamic> json) {
16+
return TargetBodyFatParams(
17+
gender: json['gender'],
18+
currentCategory: json['currentCategory'],
19+
goalType: json['goalType'],
20+
);
21+
}
22+
23+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import 'package:freezed_annotation/freezed_annotation.dart';
2+
import 'package:graduation_project/core/utils/enums.dart';
3+
import 'package:graduation_project/features/trainee_measurements/domain/entities/body_fat/body_fat_category.dart';
4+
5+
part 'target_body_fat_state.freezed.dart';
6+
7+
@freezed
8+
sealed class TargetBodyFatState with _$TargetBodyFatState {
9+
const factory TargetBodyFatState({
10+
required int currentIndex,
11+
@Default(GoalStatus.reasonable) GoalStatus goalStatus,
12+
required BodyFatCategoryModel selectedCategory,
13+
}) = _BodyFatState;
14+
}

0 commit comments

Comments
 (0)