Skip to content

Commit 4be1ac1

Browse files
authored
Merge pull request #8 from AliAkrem/Feat/add-debts-feature
Feat/add academic debts feature
2 parents af267c2 + a30332b commit 4be1ac1

20 files changed

Lines changed: 1197 additions & 1 deletion

lib/app.dart

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import 'package:progres/core/di/injector.dart';
88
import 'package:progres/core/theme/theme_bloc.dart';
99
import 'package:progres/features/academics/presentation/bloc/academics_bloc.dart';
1010
import 'package:progres/features/auth/presentation/bloc/auth_bloc.dart';
11+
import 'package:progres/features/debts/presentation/bloc/debts_bloc.dart';
1112
import 'package:progres/features/enrollment/presentation/bloc/enrollment_bloc.dart';
1213
import 'package:progres/features/groups/presentation/bloc/groups_bloc.dart';
1314
import 'package:progres/features/profile/presentation/bloc/profile_bloc.dart';
@@ -34,6 +35,7 @@ class ProgresApp extends StatelessWidget {
3435
BlocProvider(create: (context) => injector<TranscriptBloc>()),
3536
BlocProvider(create: (context) => injector<EnrollmentBloc>()),
3637
BlocProvider(create: (context) => injector<StudentDischargeBloc>()),
38+
BlocProvider(create: (context) => injector<DebtsBloc>()),
3739
],
3840
child: CalendarControllerProvider(
3941
controller: EventController(),

lib/config/routes/app_router.dart

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import 'package:flutter_bloc/flutter_bloc.dart';
33
import 'package:go_router/go_router.dart';
44
import 'package:progres/core/splash_screen/splash_screen.dart';
55
import 'package:progres/features/academics/presentation/pages/academic_performance_page.dart';
6+
import 'package:progres/features/debts/presentation/pages/debts_page.dart';
67
import 'package:progres/features/groups/presentation/pages/groups_page.dart';
78
import 'package:progres/features/discharge/presentation/pages/discharge_page.dart';
89
import 'package:progres/features/subject/presentation/pages/subject_page.dart';
@@ -32,6 +33,7 @@ class AppRouter {
3233
static const String transcripts = 'transcripts';
3334
static const String discharge = 'discharge';
3435
static const String about = 'about';
36+
static const String debts = 'debts';
3537

3638
// Route paths
3739
static const String splashPath = '/';
@@ -48,6 +50,7 @@ class AppRouter {
4850
static const String transcriptsPath = 'transcripts';
4951
static const String dischargePath = 'discharge';
5052
static const String aboutPath = 'about';
53+
static const String debtsPath = 'debts';
5154

5255
late final GoRouter router;
5356

@@ -129,6 +132,11 @@ class AppRouter {
129132
name: discharge,
130133
builder: (context, state) => const DischargePage(),
131134
),
135+
GoRoute(
136+
path: debtsPath,
137+
name: debts,
138+
builder: (context, state) => const DebtsPage(),
139+
),
132140
],
133141
),
134142
GoRoute(

lib/core/di/injector.dart

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,11 +25,14 @@ import 'package:progres/features/transcript/data/services/transcript_cache_servi
2525
import 'package:progres/features/transcript/presentation/bloc/transcript_bloc.dart';
2626
import 'package:progres/features/discharge/data/repository/discharge_repository_impl.dart';
2727
import 'package:progres/features/discharge/presentation/bloc/discharge_bloc.dart';
28+
import 'package:progres/features/debts/data/repositories/debts_repository_impl.dart';
29+
import 'package:progres/features/debts/data/services/debts_cache_service.dart';
30+
import 'package:progres/features/debts/presentation/bloc/debts_bloc.dart';
2831

2932
final injector = GetIt.instance;
3033

3134
Future<void> initDependencies() async {
32-
injector.registerLazySingleton(() => ApiClient());
35+
injector.registerLazySingleton(() => ApiClient());
3336
injector.registerLazySingleton(
3437
() => AuthRepositoryImpl(apiClient: injector()),
3538
);
@@ -55,11 +58,15 @@ Future<void> initDependencies() async {
5558
() => AcademicPerformanceRepositoryImpl(apiClient: injector()),
5659
);
5760
injector.registerLazySingleton(() => StudentDischargeRepositoryImpl());
61+
injector.registerLazySingleton(
62+
() => DebtsRepositoryImpl(apiClient: injector()),
63+
);
5864
injector.registerLazySingleton(() => TimelineCacheService());
5965
injector.registerLazySingleton(() => EnrollmentCacheService());
6066
injector.registerLazySingleton(() => TranscriptCacheService());
6167
injector.registerLazySingleton(() => GroupsCacheService());
6268
injector.registerLazySingleton(() => SubjectCacheService());
69+
injector.registerLazySingleton(() => DebtsCacheService());
6370

6471
// Register BLoCs
6572
injector.registerFactory(() => ThemeBloc()..add(LoadTheme()));
@@ -110,4 +117,7 @@ Future<void> initDependencies() async {
110117
injector.registerFactory(
111118
() => StudentDischargeBloc(studentDischargeRepository: injector()),
112119
);
120+
injector.registerFactory(
121+
() => DebtsBloc(debtsRepository: injector(), debtsCacheService: injector()),
122+
);
113123
}

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

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@ import 'package:flutter/material.dart';
22
import 'package:flutter_bloc/flutter_bloc.dart';
33
import 'package:progres/features/auth/data/repositories/auth_repository_impl.dart';
44
import 'package:progres/features/auth/data/models/auth_response.dart';
5+
import 'package:progres/features/debts/presentation/bloc/debts_bloc.dart';
6+
import 'package:progres/features/debts/presentation/bloc/debts_event.dart';
57
import 'package:progres/features/enrollment/presentation/bloc/enrollment_bloc.dart';
68
import 'package:progres/features/enrollment/presentation/bloc/enrollment_event.dart';
79
import 'package:progres/features/groups/presentation/bloc/groups_bloc.dart';
@@ -111,6 +113,11 @@ class AuthBloc extends Bloc<AuthEvent, AuthState> {
111113
} catch (e) {
112114
debugPrint('Note: Could not clear profile cache. ${e.toString()}');
113115
}
116+
try {
117+
event.context?.read<DebtsBloc>().add(ClearDebtsCache());
118+
} catch (e) {
119+
debugPrint('Note: Could not clear debts cache. ${e.toString()}');
120+
}
114121

115122
emit(AuthLoggedOut());
116123
} catch (e) {

lib/features/dashboard/presentation/widgets/dashboard.dart

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,14 @@ Widget buildDashboard(ProfileLoaded state, BuildContext context) {
139139
color: AppTheme.AppPrimary,
140140
onTap: () => context.goNamed(AppRouter.discharge),
141141
),
142+
143+
buildGridCard(
144+
context,
145+
title: AppLocalizations.of(context)!.academicDebts,
146+
icon: Icons.assignment_late,
147+
color: AppTheme.AppPrimary,
148+
onTap: () => context.goNamed(AppRouter.debts),
149+
),
142150
],
143151
),
144152

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
import 'package:progres/features/debts/data/models/debt_course.dart';
2+
3+
class AcademicYearDebt {
4+
final String annee;
5+
final int idAnneeAcademique;
6+
final int rang;
7+
final List<DebtCourse> dette;
8+
9+
AcademicYearDebt({
10+
required this.annee,
11+
required this.idAnneeAcademique,
12+
required this.rang,
13+
required this.dette,
14+
});
15+
16+
factory AcademicYearDebt.fromJson(Map<String, dynamic> json) {
17+
return AcademicYearDebt(
18+
annee: json['annee'] as String,
19+
idAnneeAcademique: json['id_annee_academique'] as int,
20+
rang: json['rang'] as int,
21+
dette: (json['dette'] as List<dynamic>)
22+
.map((e) => DebtCourse.fromJson(e as Map<String, dynamic>))
23+
.toList(),
24+
);
25+
}
26+
27+
Map<String, dynamic> toJson() {
28+
return {
29+
'annee': annee,
30+
'id_annee_academique': idAnneeAcademique,
31+
'rang': rang,
32+
'dette': dette.map((e) => e.toJson()).toList(),
33+
};
34+
}
35+
}
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
class DebtCourse {
2+
final String mcFr;
3+
final String mcAr;
4+
final String nfr;
5+
final String nar;
6+
final String pfr;
7+
final String par;
8+
final double m;
9+
final double md;
10+
final double cc;
11+
final double ccd;
12+
final double ex;
13+
final double exd;
14+
15+
DebtCourse({
16+
required this.mcFr,
17+
required this.mcAr,
18+
required this.nfr,
19+
required this.nar,
20+
required this.pfr,
21+
required this.par,
22+
required this.m,
23+
required this.md,
24+
required this.cc,
25+
required this.ccd,
26+
required this.ex,
27+
required this.exd,
28+
});
29+
30+
factory DebtCourse.fromJson(Map<String, dynamic> json) {
31+
return DebtCourse(
32+
mcFr: json['mcFr'] as String,
33+
mcAr: json['mcAr'] as String,
34+
nfr: json['nfr'] as String,
35+
nar: json['nar'] as String,
36+
pfr: json['pfr'] as String,
37+
par: json['par'] as String,
38+
m: (json['m'] as num).toDouble(),
39+
md: (json['md'] as num).toDouble(),
40+
cc: (json['cc'] as num).toDouble(),
41+
ccd: (json['ccd'] as num).toDouble(),
42+
ex: (json['ex'] as num).toDouble(),
43+
exd: (json['exd'] as num).toDouble(),
44+
);
45+
}
46+
47+
Map<String, dynamic> toJson() {
48+
return {
49+
'mcFr': mcFr,
50+
'mcAr': mcAr,
51+
'nfr': nfr,
52+
'nar': nar,
53+
'pfr': pfr,
54+
'par': par,
55+
'm': m,
56+
'md': md,
57+
'cc': cc,
58+
'ccd': ccd,
59+
'ex': ex,
60+
'exd': exd,
61+
};
62+
}
63+
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
import 'package:progres/core/network/api_client.dart';
2+
import 'package:progres/features/debts/data/models/academic_year_debt.dart';
3+
4+
class DebtsRepositoryImpl {
5+
final ApiClient _apiClient;
6+
7+
DebtsRepositoryImpl({ApiClient? apiClient})
8+
: _apiClient = apiClient ?? ApiClient();
9+
10+
Future<List<AcademicYearDebt>> getStudentDebts() async {
11+
try {
12+
final uuid = await _apiClient.getUuid();
13+
if (uuid == null) {
14+
throw Exception('UUID not found, please login again');
15+
}
16+
final response = await _apiClient.get('/infos/dettes/$uuid');
17+
if (response.data == null) {
18+
return [];
19+
}
20+
final List<dynamic> debtsJson = response.data;
21+
final debts = debtsJson
22+
.map((debtJson) => AcademicYearDebt.fromJson(debtJson))
23+
.toList();
24+
debts.sort((a, b) => b.idAnneeAcademique.compareTo(a.idAnneeAcademique));
25+
return debts;
26+
} catch (e) {
27+
rethrow;
28+
}
29+
}
30+
}
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
import 'dart:convert';
2+
import 'package:flutter/foundation.dart';
3+
import 'package:shared_preferences/shared_preferences.dart';
4+
import 'package:progres/features/debts/data/models/academic_year_debt.dart';
5+
6+
class DebtsCacheService {
7+
// Keys for SharedPreferences
8+
static const String _debtsKey = 'cached_debts';
9+
static const String _lastUpdatedKey = 'last_updated_debts';
10+
11+
// Save debts
12+
Future<bool> cacheDebts(List<AcademicYearDebt> debts) async {
13+
try {
14+
final prefs = await SharedPreferences.getInstance();
15+
final debtsJson = debts.map((d) => d.toJson()).toList();
16+
await prefs.setString(_debtsKey, jsonEncode(debtsJson));
17+
await prefs.setString(_lastUpdatedKey, DateTime.now().toIso8601String());
18+
return true;
19+
} catch (e) {
20+
debugPrint('Error caching debts: $e');
21+
return false;
22+
}
23+
}
24+
25+
// Retrieve debts
26+
Future<List<AcademicYearDebt>?> getCachedDebts() async {
27+
try {
28+
final prefs = await SharedPreferences.getInstance();
29+
final debtsString = prefs.getString(_debtsKey);
30+
31+
if (debtsString == null) return null;
32+
33+
final List<dynamic> decodedJson = jsonDecode(debtsString);
34+
return decodedJson
35+
.map((json) => AcademicYearDebt.fromJson(json))
36+
.toList();
37+
} catch (e) {
38+
debugPrint('Error retrieving cached debts: $e');
39+
return null;
40+
}
41+
}
42+
43+
// Get last update timestamp
44+
Future<DateTime?> getLastUpdated() async {
45+
try {
46+
final prefs = await SharedPreferences.getInstance();
47+
final timestamp = prefs.getString(_lastUpdatedKey);
48+
if (timestamp == null) return null;
49+
50+
return DateTime.parse(timestamp);
51+
} catch (e) {
52+
debugPrint('Error getting last updated time: $e');
53+
return null;
54+
}
55+
}
56+
57+
// Clear all debts cached data
58+
Future<bool> clearCache() async {
59+
try {
60+
final prefs = await SharedPreferences.getInstance();
61+
await prefs.remove(_debtsKey);
62+
await prefs.remove(_lastUpdatedKey);
63+
return true;
64+
} catch (e) {
65+
debugPrint('Error clearing cache: $e');
66+
return false;
67+
}
68+
}
69+
70+
// Check if data is stale (older than specified duration)
71+
Future<bool> isDataStale({
72+
Duration staleDuration = const Duration(hours: 12),
73+
}) async {
74+
final lastUpdated = await getLastUpdated();
75+
if (lastUpdated == null) return true;
76+
77+
final now = DateTime.now();
78+
return now.difference(lastUpdated) > staleDuration;
79+
}
80+
}

0 commit comments

Comments
 (0)