Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
27 changes: 25 additions & 2 deletions lib/config/routes/app_router.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:go_router/go_router.dart';
import 'package:progres/core/splash_screen/splash_screen.dart';
import 'package:progres/core/services/year_selection_service.dart';
import 'package:progres/features/academics/presentation/pages/academic_performance_page.dart';
import 'package:progres/features/debts/presentation/pages/debts_page.dart';
import 'package:progres/features/groups/presentation/pages/groups_page.dart';
Expand All @@ -15,12 +16,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 +41,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 All @@ -57,21 +61,35 @@ class AppRouter {
AppRouter({required BuildContext context}) {
router = GoRouter(
initialLocation: splashPath,
redirect: (context, state) {
redirect: (context, state) async {
// Skip redirection logic for splash screen
if (state.matchedLocation == splashPath) {
return null;
}

final authState = context.read<AuthBloc>().state;
final isLoginRoute = state.matchedLocation == loginPath;
final isYearSelectionRoute = state.matchedLocation == yearSelectionPath;

// Not authenticated - redirect to login
if (authState is! AuthSuccess && !isLoginRoute) {
return loginPath;
}

// Authenticated but on login page - check year selection
if (authState is AuthSuccess && isLoginRoute) {
return dashboardPath;
final yearService = YearSelectionService();
final hasSelectedYear = await yearService.hasSelectedYear();
return hasSelectedYear ? dashboardPath : yearSelectionPath;
}

// Authenticated - check if year is selected for protected routes
if (authState is AuthSuccess && !isYearSelectionRoute) {
final yearService = YearSelectionService();
final hasSelectedYear = await yearService.hasSelectedYear();
if (!hasSelectedYear) {
return yearSelectionPath;
}
}

return null;
Expand All @@ -87,6 +105,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({id, code}) {
return new AcademicYear(id: id, code: code);
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Outdated

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'] ?? false) as bool,
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Outdated
);
}

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,54 @@ 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);
}
Comment thread
AliAkrem marked this conversation as resolved.

// 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',
);

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

// Find the biggest year ID from enrollments
int maxEnrollmentYearId = 0;
String maxEnrollmentCode = "";
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) {
return currentAcademicYear.copyWith(
id: maxEnrollmentYearId,
code: maxEnrollmentCode,
);
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.

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