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
8 changes: 8 additions & 0 deletions lib/config/routes/app_router.dart
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,14 @@ import 'package:progres/features/dashboard/presentation/pages/dashboard_page.dar
import 'package:progres/features/enrollment/presentation/pages/enrollments_page.dart';
import 'package:progres/features/profile/presentation/pages/profile_page.dart';
import 'package:progres/features/settings/presentation/pages/settings_page.dart';
import 'package:progres/features/settings/presentation/pages/year_selection_page.dart';
import 'package:progres/layouts/main_shell.dart';

class AppRouter {
// Route names as static constants
static const String splash = 'splash';
static const String login = 'login';
static const String yearSelection = 'year_selection';
static const String dashboard = 'dashboard';
static const String profile = 'profile';
static const String settings = 'settings';
Expand All @@ -38,6 +40,7 @@ class AppRouter {
// Route paths
static const String splashPath = '/';
static const String loginPath = '/login';
static const String yearSelectionPath = '/year-selection';
static const String dashboardPath = '/dashboard';
static const String profilePath = '/profile';
static const String settingsPath = '/settings';
Expand Down Expand Up @@ -87,6 +90,11 @@ class AppRouter {
name: login,
builder: (context, state) => const LoginPage(),
),
GoRoute(
path: yearSelectionPath,
name: yearSelection,
builder: (context, state) => const YearSelectionPage(),
),
ShellRoute(
builder: (context, state, child) {
return MainShell(child: child);
Expand Down
33 changes: 33 additions & 0 deletions lib/core/services/year_selection_service.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import 'package:shared_preferences/shared_preferences.dart';

class YearSelectionService {
static const String _selectedYearIdKey = 'selected_academic_year_id';
static const String _selectedYearCodeKey = 'selected_academic_year_code';

Future<void> saveSelectedYear(int yearId, String yearCode) async {
final prefs = await SharedPreferences.getInstance();
await prefs.setInt(_selectedYearIdKey, yearId);
await prefs.setString(_selectedYearCodeKey, yearCode);
}

Future<int?> getSelectedYearId() async {
final prefs = await SharedPreferences.getInstance();
return prefs.getInt(_selectedYearIdKey);
}

Future<String?> getSelectedYearCode() async {
final prefs = await SharedPreferences.getInstance();
return prefs.getString(_selectedYearCodeKey);
}

Future<bool> hasSelectedYear() async {
final yearId = await getSelectedYearId();
return yearId != null;
}

Future<void> clearSelectedYear() async {
final prefs = await SharedPreferences.getInstance();
await prefs.remove(_selectedYearIdKey);
await prefs.remove(_selectedYearCodeKey);
}
}
9 changes: 9 additions & 0 deletions lib/features/auth/presentation/bloc/auth_bloc.dart
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:progres/core/services/year_selection_service.dart';
import 'package:progres/features/auth/data/repositories/auth_repository_impl.dart';
import 'package:progres/features/auth/data/models/auth_response.dart';
import 'package:progres/features/debts/presentation/bloc/debts_bloc.dart';
Expand Down Expand Up @@ -80,6 +81,14 @@ class AuthBloc extends Bloc<AuthEvent, AuthState> {
emit(AuthLoading());
await authRepository.logout();

// Clear selected year
try {
final yearService = YearSelectionService();
await yearService.clearSelectedYear();
} catch (e) {
debugPrint('Note: Could not clear selected year. ${e.toString()}');
}

try {
event.context?.read<TranscriptBloc>().add(const ClearTranscriptCache());
} catch (e) {
Expand Down
4 changes: 4 additions & 0 deletions lib/features/profile/data/models/academic_year.dart
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,10 @@ class AcademicYear {
return AcademicYear(id: json['id'] as int, code: json['code'] as String);
}

AcademicYear copyWith({int? id, String? code}) {
return AcademicYear(id: id ?? this.id, code: code ?? this.code);
}

Map<String, dynamic> toJson() {
return {'id': id, 'code': code};
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ class StudentDetailedInfo {
refLibelleCycle: json['refLibelleCycle'] as String,
refLibelleCycleAr: json['refLibelleCycleAr'] as String,
situationId: json['situationId'] as int,
transportPaye: json['transportPaye'] as bool,
transportPaye: json['transportPaye'] as bool? ?? false,
);
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,20 @@
import 'package:progres/core/network/api_client.dart';
import 'package:progres/core/services/year_selection_service.dart';
import 'package:progres/features/enrollment/data/models/enrollment.dart';
import 'package:progres/features/profile/data/models/academic_period.dart';
import 'package:progres/features/profile/data/models/academic_year.dart';
import 'package:progres/features/profile/data/models/student_basic_info.dart';
import 'package:progres/features/profile/data/models/student_detailed_info.dart';

class StudentRepositoryImpl {
final ApiClient _apiClient;
final YearSelectionService _yearSelectionService;

StudentRepositoryImpl({ApiClient? apiClient})
: _apiClient = apiClient ?? ApiClient();
StudentRepositoryImpl({
ApiClient? apiClient,
YearSelectionService? yearSelectionService,
}) : _apiClient = apiClient ?? ApiClient(),
_yearSelectionService = yearSelectionService ?? YearSelectionService();

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

Future<AcademicYear> getCurrentAcademicYear() async {
try {
final response = await _apiClient.get('/infos/AnneeAcademiqueEncours');
return AcademicYear.fromJson(response.data);
// Check if student has manually selected a year
final selectedYearId = await _yearSelectionService.getSelectedYearId();
final selectedYearCode = await _yearSelectionService
.getSelectedYearCode();

if (selectedYearId != null && selectedYearCode != null) {
// Return the manually selected year
return AcademicYear(id: selectedYearId, code: selectedYearCode);
}

// If no manual selection, proceed with automatic logic
final uuid = await _apiClient.getUuid();
if (uuid == null) {
throw Exception('UUID not found, please login again');
}

final enrollmentRes = await _apiClient.get('/infos/bac/$uuid/dias');

final List<dynamic> enrollmentsJson = enrollmentRes.data;
final enrollments = enrollmentsJson
.map((enrollmentJson) => Enrollment.fromJson(enrollmentJson))
.toList();

final currentYearRes = await _apiClient.get(
'/infos/AnneeAcademiqueEncours',
);

var currentAcademicYear = AcademicYear.fromJson(currentYearRes.data);

// Find the biggest year ID from enrollments
int maxEnrollmentYearId = 0;
String maxEnrollmentCode = "";

if (enrollments.isEmpty) {
return currentAcademicYear;
}

for (var enrollment in enrollments) {
if (enrollment.anneeAcademiqueId > maxEnrollmentYearId) {
maxEnrollmentYearId = enrollment.anneeAcademiqueId;
maxEnrollmentCode = enrollment.anneeAcademiqueCode;
}
}

// If current year is bigger than the biggest enrollment year,
// it means student has graduated or left college, so fall back to max enrollment year
if (currentAcademicYear.id > maxEnrollmentYearId) {
currentAcademicYear = currentAcademicYear.copyWith(
id: maxEnrollmentYearId,
code: maxEnrollmentCode,
);
}

await _yearSelectionService.saveSelectedYear(
currentAcademicYear.id,
currentAcademicYear.code,
);

return currentAcademicYear;
} catch (e) {
rethrow;
}
Expand Down
58 changes: 42 additions & 16 deletions lib/features/profile/data/services/profile_cache_service.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,27 +4,39 @@ import 'package:shared_preferences/shared_preferences.dart';

class ProfileCacheService {
// Keys for SharedPreferences
static const String _profileKey = 'cached_profile_data';
static const String _lastUpdatedKey = 'last_updated_profile';
static const String _profileKeyPrefix = 'cached_profile_data';
static const String _lastUpdatedKeyPrefix = 'last_updated_profile';
static const String _currentYearKey = 'cached_year_id';

// Save profile data to cache
Future<bool> cacheProfileData(Map<String, dynamic> profileData) async {
// Get cache key for specific year
String _getProfileKey(int yearId) => '${_profileKeyPrefix}_$yearId';
String _getLastUpdatedKey(int yearId) => '${_lastUpdatedKeyPrefix}_$yearId';

// Save profile data to cache with year ID
Future<bool> cacheProfileData(
Map<String, dynamic> profileData,
int yearId,
) async {
try {
final prefs = await SharedPreferences.getInstance();
await prefs.setString(_profileKey, jsonEncode(profileData));
await prefs.setString(_lastUpdatedKey, DateTime.now().toIso8601String());
await prefs.setString(_getProfileKey(yearId), jsonEncode(profileData));
await prefs.setString(
_getLastUpdatedKey(yearId),
DateTime.now().toIso8601String(),
);
await prefs.setInt(_currentYearKey, yearId);
return true;
} catch (e) {
debugPrint('Error caching profile data: $e');
return false;
}
}

// Retrieve profile data from cache
Future<Map<String, dynamic>?> getCachedProfileData() async {
// Retrieve profile data from cache for specific year
Future<Map<String, dynamic>?> getCachedProfileData(int yearId) async {
try {
final prefs = await SharedPreferences.getInstance();
final profileDataString = prefs.getString(_profileKey);
final profileDataString = prefs.getString(_getProfileKey(yearId));

if (profileDataString == null) return null;

Expand All @@ -35,11 +47,11 @@ class ProfileCacheService {
}
}

// Get last update timestamp for profile data
Future<DateTime?> getLastUpdated() async {
// Get last update timestamp for profile data of specific year
Future<DateTime?> getLastUpdated(int yearId) async {
try {
final prefs = await SharedPreferences.getInstance();
final timestamp = prefs.getString(_lastUpdatedKey);
final timestamp = prefs.getString(_getLastUpdatedKey(yearId));
if (timestamp == null) return null;

return DateTime.parse(timestamp);
Expand All @@ -49,12 +61,26 @@ class ProfileCacheService {
}
}

// Clear profile data cache
Future<bool> clearCache() async {
// Clear profile data cache for specific year
Future<bool> clearCache([int? yearId]) async {
try {
final prefs = await SharedPreferences.getInstance();
await prefs.remove(_profileKey);
await prefs.remove(_lastUpdatedKey);

if (yearId != null) {
// Clear specific year cache
await prefs.remove(_getProfileKey(yearId));
await prefs.remove(_getLastUpdatedKey(yearId));
} else {
// Clear all profile caches
final keys = prefs.getKeys();
for (final key in keys) {
if (key.startsWith(_profileKeyPrefix) ||
key.startsWith(_lastUpdatedKeyPrefix)) {
await prefs.remove(key);
}
}
await prefs.remove(_currentYearKey);
}
return true;
} catch (e) {
debugPrint('Error clearing profile cache: $e');
Expand Down
Loading