Skip to content

Commit 923b4de

Browse files
authored
Merge pull request #9 from AliAkrem/academic-year
feat(academic year): add academic year selection feature
2 parents 4be1ac1 + be4ea63 commit 923b4de

File tree

16 files changed

+687
-95
lines changed

16 files changed

+687
-95
lines changed

lib/config/routes/app_router.dart

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,12 +15,14 @@ import 'package:progres/features/dashboard/presentation/pages/dashboard_page.dar
1515
import 'package:progres/features/enrollment/presentation/pages/enrollments_page.dart';
1616
import 'package:progres/features/profile/presentation/pages/profile_page.dart';
1717
import 'package:progres/features/settings/presentation/pages/settings_page.dart';
18+
import 'package:progres/features/settings/presentation/pages/year_selection_page.dart';
1819
import 'package:progres/layouts/main_shell.dart';
1920

2021
class AppRouter {
2122
// Route names as static constants
2223
static const String splash = 'splash';
2324
static const String login = 'login';
25+
static const String yearSelection = 'year_selection';
2426
static const String dashboard = 'dashboard';
2527
static const String profile = 'profile';
2628
static const String settings = 'settings';
@@ -38,6 +40,7 @@ class AppRouter {
3840
// Route paths
3941
static const String splashPath = '/';
4042
static const String loginPath = '/login';
43+
static const String yearSelectionPath = '/year-selection';
4144
static const String dashboardPath = '/dashboard';
4245
static const String profilePath = '/profile';
4346
static const String settingsPath = '/settings';
@@ -87,6 +90,11 @@ class AppRouter {
8790
name: login,
8891
builder: (context, state) => const LoginPage(),
8992
),
93+
GoRoute(
94+
path: yearSelectionPath,
95+
name: yearSelection,
96+
builder: (context, state) => const YearSelectionPage(),
97+
),
9098
ShellRoute(
9199
builder: (context, state, child) {
92100
return MainShell(child: child);
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
import 'package:shared_preferences/shared_preferences.dart';
2+
3+
class YearSelectionService {
4+
static const String _selectedYearIdKey = 'selected_academic_year_id';
5+
static const String _selectedYearCodeKey = 'selected_academic_year_code';
6+
7+
Future<void> saveSelectedYear(int yearId, String yearCode) async {
8+
final prefs = await SharedPreferences.getInstance();
9+
await prefs.setInt(_selectedYearIdKey, yearId);
10+
await prefs.setString(_selectedYearCodeKey, yearCode);
11+
}
12+
13+
Future<int?> getSelectedYearId() async {
14+
final prefs = await SharedPreferences.getInstance();
15+
return prefs.getInt(_selectedYearIdKey);
16+
}
17+
18+
Future<String?> getSelectedYearCode() async {
19+
final prefs = await SharedPreferences.getInstance();
20+
return prefs.getString(_selectedYearCodeKey);
21+
}
22+
23+
Future<bool> hasSelectedYear() async {
24+
final yearId = await getSelectedYearId();
25+
return yearId != null;
26+
}
27+
28+
Future<void> clearSelectedYear() async {
29+
final prefs = await SharedPreferences.getInstance();
30+
await prefs.remove(_selectedYearIdKey);
31+
await prefs.remove(_selectedYearCodeKey);
32+
}
33+
}

lib/features/auth/presentation/bloc/auth_bloc.dart

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import 'package:flutter/material.dart';
22
import 'package:flutter_bloc/flutter_bloc.dart';
3+
import 'package:progres/core/services/year_selection_service.dart';
34
import 'package:progres/features/auth/data/repositories/auth_repository_impl.dart';
45
import 'package:progres/features/auth/data/models/auth_response.dart';
56
import 'package:progres/features/debts/presentation/bloc/debts_bloc.dart';
@@ -80,6 +81,14 @@ class AuthBloc extends Bloc<AuthEvent, AuthState> {
8081
emit(AuthLoading());
8182
await authRepository.logout();
8283

84+
// Clear selected year
85+
try {
86+
final yearService = YearSelectionService();
87+
await yearService.clearSelectedYear();
88+
} catch (e) {
89+
debugPrint('Note: Could not clear selected year. ${e.toString()}');
90+
}
91+
8392
try {
8493
event.context?.read<TranscriptBloc>().add(const ClearTranscriptCache());
8594
} catch (e) {

lib/features/profile/data/models/academic_year.dart

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,10 @@ class AcademicYear {
88
return AcademicYear(id: json['id'] as int, code: json['code'] as String);
99
}
1010

11+
AcademicYear copyWith({int? id, String? code}) {
12+
return AcademicYear(id: id ?? this.id, code: code ?? this.code);
13+
}
14+
1115
Map<String, dynamic> toJson() {
1216
return {'id': id, 'code': code};
1317
}

lib/features/profile/data/models/student_detailed_info.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ class StudentDetailedInfo {
5555
refLibelleCycle: json['refLibelleCycle'] as String,
5656
refLibelleCycleAr: json['refLibelleCycleAr'] as String,
5757
situationId: json['situationId'] as int,
58-
transportPaye: json['transportPaye'] as bool,
58+
transportPaye: json['transportPaye'] as bool? ?? false,
5959
);
6060
}
6161

lib/features/profile/data/repositories/student_repository_impl.dart

Lines changed: 67 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,20 @@
11
import 'package:progres/core/network/api_client.dart';
2+
import 'package:progres/core/services/year_selection_service.dart';
3+
import 'package:progres/features/enrollment/data/models/enrollment.dart';
24
import 'package:progres/features/profile/data/models/academic_period.dart';
35
import 'package:progres/features/profile/data/models/academic_year.dart';
46
import 'package:progres/features/profile/data/models/student_basic_info.dart';
57
import 'package:progres/features/profile/data/models/student_detailed_info.dart';
68

79
class StudentRepositoryImpl {
810
final ApiClient _apiClient;
11+
final YearSelectionService _yearSelectionService;
912

10-
StudentRepositoryImpl({ApiClient? apiClient})
11-
: _apiClient = apiClient ?? ApiClient();
13+
StudentRepositoryImpl({
14+
ApiClient? apiClient,
15+
YearSelectionService? yearSelectionService,
16+
}) : _apiClient = apiClient ?? ApiClient(),
17+
_yearSelectionService = yearSelectionService ?? YearSelectionService();
1218

1319
Future<StudentBasicInfo> getStudentBasicInfo() async {
1420
try {
@@ -26,8 +32,65 @@ class StudentRepositoryImpl {
2632

2733
Future<AcademicYear> getCurrentAcademicYear() async {
2834
try {
29-
final response = await _apiClient.get('/infos/AnneeAcademiqueEncours');
30-
return AcademicYear.fromJson(response.data);
35+
// Check if student has manually selected a year
36+
final selectedYearId = await _yearSelectionService.getSelectedYearId();
37+
final selectedYearCode = await _yearSelectionService
38+
.getSelectedYearCode();
39+
40+
if (selectedYearId != null && selectedYearCode != null) {
41+
// Return the manually selected year
42+
return AcademicYear(id: selectedYearId, code: selectedYearCode);
43+
}
44+
45+
// If no manual selection, proceed with automatic logic
46+
final uuid = await _apiClient.getUuid();
47+
if (uuid == null) {
48+
throw Exception('UUID not found, please login again');
49+
}
50+
51+
final enrollmentRes = await _apiClient.get('/infos/bac/$uuid/dias');
52+
53+
final List<dynamic> enrollmentsJson = enrollmentRes.data;
54+
final enrollments = enrollmentsJson
55+
.map((enrollmentJson) => Enrollment.fromJson(enrollmentJson))
56+
.toList();
57+
58+
final currentYearRes = await _apiClient.get(
59+
'/infos/AnneeAcademiqueEncours',
60+
);
61+
62+
var currentAcademicYear = AcademicYear.fromJson(currentYearRes.data);
63+
64+
// Find the biggest year ID from enrollments
65+
int maxEnrollmentYearId = 0;
66+
String maxEnrollmentCode = "";
67+
68+
if (enrollments.isEmpty) {
69+
return currentAcademicYear;
70+
}
71+
72+
for (var enrollment in enrollments) {
73+
if (enrollment.anneeAcademiqueId > maxEnrollmentYearId) {
74+
maxEnrollmentYearId = enrollment.anneeAcademiqueId;
75+
maxEnrollmentCode = enrollment.anneeAcademiqueCode;
76+
}
77+
}
78+
79+
// If current year is bigger than the biggest enrollment year,
80+
// it means student has graduated or left college, so fall back to max enrollment year
81+
if (currentAcademicYear.id > maxEnrollmentYearId) {
82+
currentAcademicYear = currentAcademicYear.copyWith(
83+
id: maxEnrollmentYearId,
84+
code: maxEnrollmentCode,
85+
);
86+
}
87+
88+
await _yearSelectionService.saveSelectedYear(
89+
currentAcademicYear.id,
90+
currentAcademicYear.code,
91+
);
92+
93+
return currentAcademicYear;
3194
} catch (e) {
3295
rethrow;
3396
}

lib/features/profile/data/services/profile_cache_service.dart

Lines changed: 42 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -4,27 +4,39 @@ import 'package:shared_preferences/shared_preferences.dart';
44

55
class ProfileCacheService {
66
// Keys for SharedPreferences
7-
static const String _profileKey = 'cached_profile_data';
8-
static const String _lastUpdatedKey = 'last_updated_profile';
7+
static const String _profileKeyPrefix = 'cached_profile_data';
8+
static const String _lastUpdatedKeyPrefix = 'last_updated_profile';
9+
static const String _currentYearKey = 'cached_year_id';
910

10-
// Save profile data to cache
11-
Future<bool> cacheProfileData(Map<String, dynamic> profileData) async {
11+
// Get cache key for specific year
12+
String _getProfileKey(int yearId) => '${_profileKeyPrefix}_$yearId';
13+
String _getLastUpdatedKey(int yearId) => '${_lastUpdatedKeyPrefix}_$yearId';
14+
15+
// Save profile data to cache with year ID
16+
Future<bool> cacheProfileData(
17+
Map<String, dynamic> profileData,
18+
int yearId,
19+
) async {
1220
try {
1321
final prefs = await SharedPreferences.getInstance();
14-
await prefs.setString(_profileKey, jsonEncode(profileData));
15-
await prefs.setString(_lastUpdatedKey, DateTime.now().toIso8601String());
22+
await prefs.setString(_getProfileKey(yearId), jsonEncode(profileData));
23+
await prefs.setString(
24+
_getLastUpdatedKey(yearId),
25+
DateTime.now().toIso8601String(),
26+
);
27+
await prefs.setInt(_currentYearKey, yearId);
1628
return true;
1729
} catch (e) {
1830
debugPrint('Error caching profile data: $e');
1931
return false;
2032
}
2133
}
2234

23-
// Retrieve profile data from cache
24-
Future<Map<String, dynamic>?> getCachedProfileData() async {
35+
// Retrieve profile data from cache for specific year
36+
Future<Map<String, dynamic>?> getCachedProfileData(int yearId) async {
2537
try {
2638
final prefs = await SharedPreferences.getInstance();
27-
final profileDataString = prefs.getString(_profileKey);
39+
final profileDataString = prefs.getString(_getProfileKey(yearId));
2840

2941
if (profileDataString == null) return null;
3042

@@ -35,11 +47,11 @@ class ProfileCacheService {
3547
}
3648
}
3749

38-
// Get last update timestamp for profile data
39-
Future<DateTime?> getLastUpdated() async {
50+
// Get last update timestamp for profile data of specific year
51+
Future<DateTime?> getLastUpdated(int yearId) async {
4052
try {
4153
final prefs = await SharedPreferences.getInstance();
42-
final timestamp = prefs.getString(_lastUpdatedKey);
54+
final timestamp = prefs.getString(_getLastUpdatedKey(yearId));
4355
if (timestamp == null) return null;
4456

4557
return DateTime.parse(timestamp);
@@ -49,12 +61,26 @@ class ProfileCacheService {
4961
}
5062
}
5163

52-
// Clear profile data cache
53-
Future<bool> clearCache() async {
64+
// Clear profile data cache for specific year
65+
Future<bool> clearCache([int? yearId]) async {
5466
try {
5567
final prefs = await SharedPreferences.getInstance();
56-
await prefs.remove(_profileKey);
57-
await prefs.remove(_lastUpdatedKey);
68+
69+
if (yearId != null) {
70+
// Clear specific year cache
71+
await prefs.remove(_getProfileKey(yearId));
72+
await prefs.remove(_getLastUpdatedKey(yearId));
73+
} else {
74+
// Clear all profile caches
75+
final keys = prefs.getKeys();
76+
for (final key in keys) {
77+
if (key.startsWith(_profileKeyPrefix) ||
78+
key.startsWith(_lastUpdatedKeyPrefix)) {
79+
await prefs.remove(key);
80+
}
81+
}
82+
await prefs.remove(_currentYearKey);
83+
}
5884
return true;
5985
} catch (e) {
6086
debugPrint('Error clearing profile cache: $e');

0 commit comments

Comments
 (0)