Comprehensive technical guide for developers contributing to or learning from SYHA Forge.
Flutter 3.6.2+ - Cross-platform mobile development framework using Dart
Firebase - Authentication, Cloud Firestore for data storage
Provider 6.0.5 - Reactive state management and dependency injection
SharedPreferences 2.2.2 - Persistent key-value storage for offline data
FL Chart 0.69.2 - Beautiful, customizable charts for progress visualization
Table Calendar 3.0.9 - Highly customizable calendar widget
SYHA Forge follows a clean architecture pattern with clear separation of concerns.
lib/
├── constants/ # App-wide constants
│ ├── app_colors.dart # Color definitions
│ ├── app_strings.dart # Localized strings
│ └── app_text_styles.dart # Text styling
├── models/ # Data models
│ ├── exercise.dart # Exercise entity
│ ├── workout.dart # Workout program
│ ├── workout_history.dart # Completed workout records
│ ├── chart_data.dart # Chart data structures
│ └── user.dart # User profile model
├── screens/ # UI screens
│ ├── home_screen.dart
│ ├── profile_screen.dart
│ ├── programs_screen.dart
│ ├── full_calendar_screen.dart
│ ├── progress_charts_screen.dart
│ └── ...
├── services/ # Business logic
│ ├── data_manager.dart # Central data management
│ ├── progress_analytics_service.dart # Progress calculations
│ ├── muscle_recovery_tracker.dart # Muscle group recovery tracking
│ ├── workout_recommendation_service.dart # Smart workout recommendations
│ ├── profile_service.dart # User profile management
│ ├── auth_service.dart # Firebase authentication
│ └── theme_service.dart # Theme customization
├── widgets/ # Reusable UI components
│ ├── muscle_recovery_card.dart # Recovery status display
│ └── compact_calendar.dart # Week-view calendar
└── main.dart # App entry point
SYHA Forge uses Provider for reactive state management:
// Service definition
class DataManager extends ChangeNotifier {
List _workouts = [];
void addWorkout(Workout workout) {
_workouts.add(workout);
notifyListeners(); // Triggers UI rebuild
}
}
// Provider setup in main.dart
MultiProvider(
providers: [
ChangeNotifierProvider(create: (_) => DataManager()),
ChangeNotifierProvider(create: (_) => ProfileService()),
// ... other providers
],
child: MyApp(),
)
// Consumption in screens
final dataManager = Provider.of(context);
// OR
Consumer(
builder: (context, dataManager, child) => ...
)
Data is persisted using a hybrid approach:
Central hub for all workout-related data. Implements the Singleton pattern.
class DataManager extends ChangeNotifier {
static final DataManager _instance = DataManager._internal();
factory DataManager() => _instance;
List _exercises = [];
List _workouts = [];
List _workoutHistory = [];
// CRUD operations with automatic persistence
void addWorkout(Workout workout) { ... }
void updateWorkout(int index, Workout workout) { ... }
void removeWorkout(int index) { ... }
// Query methods
List getWorkoutHistoryForDate(DateTime date) { ... }
bool hasWorkoutOnDate(DateTime date) { ... }
int workoutsThisMonth([DateTime? forDate]) { ... }
}
Stateless service that performs all analytics calculations. See Formulas page for detailed explanations.
class ProgressAnalyticsService {
// Analyzes overall strength across all exercises
OverallStrengthData analyzeOverallStrength(
List histories,
{int lookbackDays = 90}
) { ... }
// Tracks individual exercise progress
ExerciseProgressData analyzeExerciseProgress(
String exerciseId,
String exerciseName,
List histories,
{int lookbackDays = 90}
) { ... }
// Additional analytics methods...
}
Manages user profile data including body weight history.
class ProfileService extends ChangeNotifier {
double? _weightKg;
List _weightHistory = [];
Future setWeightKg(double? kg) async {
_weightKg = kg;
// Auto-save to weight history
final entry = ChartDataPoint(
date: DateTime.now(),
value: kg
);
_weightHistory.insert(0, entry);
// Persist to SharedPreferences
await _saveToStorage();
notifyListeners();
}
}
Tracks muscle group recovery status and calculates training readiness. Uses optimal recovery days per muscle group to determine which muscles are ready to train. See Muscle Recovery Formulas for details.
class MuscleRecoveryTracker {
// Optimal recovery days per muscle group (legs: 3, chest: 2, forearms: 1, etc.)
static const Map<MuscleGroup, int> _optimalRecoveryDays = { ... };
// Scans all workout history, cross-referencing old exercises
// with current muscle group assignments
Map<MuscleGroup, int> calculateDaysSinceLastTraining(
List<WorkoutHistory> histories,
{Map<String, Exercise>? currentExercises}
) { ... }
// Converts days-since-training into 0.0-1.5 priority score
Map<MuscleGroup, double> calculateRecoveryPriority(
Map<MuscleGroup, int> daysSinceTraining
) { ... }
// Returns muscles with priority >= 0.7 (ready to train)
List<MuscleGroup> getMusclesToTrain(...) { ... }
// Returns muscles with priority < 0.3 (need rest)
List<MuscleGroup> getMusclesToRest(...) { ... }
}
Generates daily workout recommendations by combining muscle recovery data, wellness status, and training history. Passes current exercise definitions to the recovery tracker to ensure old history entries without muscle groups are properly accounted for.
class WorkoutRecommendationService extends ChangeNotifier {
final MuscleRecoveryTracker _recoveryTracker;
final DataManager _dataManager;
// Builds exercise ID -> Exercise map for cross-referencing
Map<String, Exercise> _buildCurrentExercisesMap() {
return {for (var e in _dataManager.exercises) e.id: e};
}
// All public methods pass currentExercises to the tracker
Map<MuscleGroup, int> getDaysSinceLastTraining() { ... }
Map<MuscleGroup, double> getMuscleRecoveryPriorities() { ... }
// Analyzes wellness, recovery, history to recommend workout
Future<WorkoutRecommendation?> generateTodaysRecommendation() { ... }
}
Forge uses color-coded visual indicators to provide instant feedback on exercise performance and completion.
When viewing a program's exercise list (programs_screen.dart), each exercise is checked against the last completed session:
// Find last session for this workout
final lastSession = _findLastSessionForWorkout(dataManager, workout.id);
final completedExerciseIds = lastSession.exerciseResults
.map((r) => r.exercise.id).toSet();
// For each exercise in the program
final wasCompleted = completedExerciseIds.contains(exercise.id);
// Color: wasCompleted ? programColor : AppColors.error
When viewing expanded workout details in the calendar (full_calendar_screen.dart), exercises are compared against the previous session of the same workout:
// Compare total reps against previous session
final currentTotalReps = result.setResults.fold(0, (sum, s) => sum + s.actualReps);
final prevReps = previousSessionReps[exercise.id];
final isRegression = prevReps != null && currentTotalReps < prevReps;
class Exercise {
final String id;
final String name;
final String description;
final ExerciseDifficulty difficulty;
final List muscleGroups;
final DateTime createdAt;
// JSON serialization
Map toJson() { ... }
factory Exercise.fromJson(Map json) { ... }
}
class Workout {
final String id;
final String name;
final List exercises;
final DateTime createdAt;
}
class WorkoutExercise {
final Exercise exercise;
final int sets;
final int targetReps;
final double weight;
final Exercise? alternativeExercise;
}
class WorkoutHistory {
final String id;
final DateTime date;
final WorkoutSession session;
// Computed property for date-only comparisons
DateTime get dateOnly => DateTime(
date.year,
date.month,
date.day,
);
}
class WorkoutSession {
final String workoutId;
final String workoutName;
final List exerciseResults;
final int totalDurationSeconds;
}
SYHA Forge uses a dynamic theme system with user-customizable colors:
// AppColor provider for dynamic theming
class AppColor extends ChangeNotifier {
Color _color = AppColors.primary;
Color get color => _color;
void setColor(Color color) {
_color = color;
notifyListeners();
}
}
// Usage in widgets
Consumer(
builder: (context, appColor, _) => Container(
color: appColor.color,
child: ...
),
)
All screens adapt to different screen sizes using MediaQuery:
// Example from calendar screen
padding: EdgeInsets.fromLTRB(16, 16, 16, 100), // Extra bottom padding
// Dialog constraints
ConstrainedBox(
constraints: BoxConstraints(
maxHeight: MediaQuery.of(context).size.height * 0.7,
),
child: ...
)
Reusable card components throughout the app:
Widget _buildStatCard(String label, String value, IconData icon) {
return Card(
elevation: 2,
child: Padding(
padding: const EdgeInsets.all(16),
child: Column(
children: [
Icon(icon, color: AppColors.primary),
Text(value, style: AppTextStyles.h3),
Text(label, style: AppTextStyles.caption),
],
),
),
);
}
class AuthService extends ChangeNotifier {
final FirebaseAuth _auth = FirebaseAuth.instance;
Future signInWithEmail(String email, String password) async {
final credential = await _auth.signInWithEmailAndPassword(
email: email,
password: password,
);
return credential.user;
}
Future signOut() async {
await _auth.signOut();
notifyListeners();
}
}
// User collection
users/{userId}/
- name: string
- email: string
- createdAt: timestamp
- profile: {
weightKg: number
goals: array
experienceLevel: string
}
// Leaderboards collection
leaderboards/powerlifting/
- entries: array of {userId, totalKg, date}
leaderboards/streetlifting/
- entries: array of {userId, score, date}
FutureBuilder and StreamBuilderListView.builder for efficient rendering// DataManager prevents duplicate workout history entries
final seen = {};
_workoutHistory = [];
for (var history in loadedHistory) {
final dateKey = history.dateOnly.toIso8601String();
final workoutKey = history.session.workoutName;
final uniqueKey = '$dateKey-$workoutKey-${history.session.exerciseResults.length}';
if (!seen.contains(uniqueKey)) {
seen.add(uniqueKey);
_workoutHistory.add(history);
}
}
Service logic should be tested with unit tests:
// Example test for ProgressAnalyticsService
test('calculates strength coefficient correctly', () {
final histories = [
// Create test workout histories
];
final service = ProgressAnalyticsService();
final result = service.analyzeOverallStrength(histories);
expect(result.currentTotalStrength, closeTo(150.0, 0.1));
});
Screen components should have widget tests:
testWidgets('displays workout cards', (tester) async {
await tester.pumpWidget(
MaterialApp(home: FullCalendarScreen()),
);
expect(find.text('Workout Calendar'), findsOneWidget);
});
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /users/{userId} {
allow read, write: if request.auth != null
&& request.auth.uid == userId;
}
}
}
SYHA Forge uses Flutter's built-in localization system:
// l10n.yaml configuration
arb-dir: lib/l10n
template-arb-file: app_en.arb
output-localization-file: app_localizations.dart
// Usage in code
final l10n = AppLocalizations.of(context)!;
Text(l10n.workoutName); // Automatically localized
Currently supported languages: English (more coming soon!)
# Run in debug mode
flutter run
# Run with specific device
flutter run -d [device-id]
# Android APK
flutter build apk --release
# Android App Bundle (for Play Store)
flutter build appbundle --release
# iOS
flutter build ios --release
Version is defined in pubspec.yaml:
version: 1.0.0+1
# Format: major.minor.patch+buildNumber