diff --git a/README.md b/README.md
index 4e17b30..da6c668 100644
--- a/README.md
+++ b/README.md
@@ -32,6 +32,7 @@ So I decided to rebuild it from scratch, with a better design and offline suppor
+
## App Modules
@@ -42,6 +43,8 @@ So I decided to rebuild it from scratch, with a better design and offline suppor
- **Performance Tracking**
- **Academic history**
- **Weekly Timeline**
+ - **Student Discharge**
+
## Technical Details
diff --git a/lib/app.dart b/lib/app.dart
index 9b38317..d5c6036 100644
--- a/lib/app.dart
+++ b/lib/app.dart
@@ -14,6 +14,7 @@ import 'package:progres/features/profile/presentation/bloc/profile_bloc.dart';
import 'package:progres/features/subject/presentation/bloc/subject_bloc.dart';
import 'package:progres/features/timeline/presentation/blocs/timeline_bloc.dart';
import 'package:progres/features/transcript/presentation/bloc/transcript_bloc.dart';
+import 'package:progres/features/discharge/presentation/bloc/discharge_bloc.dart';
import 'package:flutter_gen/gen_l10n/gallery_localizations.dart';
class ProgresApp extends StatelessWidget {
@@ -32,6 +33,7 @@ class ProgresApp extends StatelessWidget {
BlocProvider(create: (context) => injector()),
BlocProvider(create: (context) => injector()),
BlocProvider(create: (context) => injector()),
+ BlocProvider(create: (context) => injector()),
],
child: CalendarControllerProvider(
controller: EventController(),
diff --git a/lib/config/routes/app_router.dart b/lib/config/routes/app_router.dart
index 6f3908d..7f3a77e 100644
--- a/lib/config/routes/app_router.dart
+++ b/lib/config/routes/app_router.dart
@@ -3,6 +3,7 @@ import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:go_router/go_router.dart';
import 'package:progres/features/academics/presentation/pages/academic_performance_page.dart';
import 'package:progres/features/groups/presentation/pages/groups_page.dart';
+import 'package:progres/features/discharge/presentation/pages/discharge_page.dart';
import 'package:progres/features/subject/presentation/pages/subject_page.dart';
import 'package:progres/features/timeline/presentation/pages/timeline_page.dart';
import 'package:progres/features/transcript/presentation/pages/transcript_page.dart';
@@ -27,6 +28,7 @@ class AppRouter {
static const String enrollments = 'enrollments';
static const String timeline = 'timeline';
static const String transcripts = 'transcripts';
+ static const String discharge = 'discharge';
static const String about = 'about';
// Route paths
@@ -41,6 +43,7 @@ class AppRouter {
static const String enrollmentsPath = 'enrollments';
static const String timelinePath = 'timeline';
static const String transcriptsPath = 'transcripts';
+ static const String dischargePath = 'discharge';
static const String aboutPath = 'about';
late final GoRouter router;
@@ -108,6 +111,11 @@ class AppRouter {
name: transcripts,
builder: (context, state) => const TranscriptPage(),
),
+ GoRoute(
+ path: dischargePath,
+ name: discharge,
+ builder: (context, state) => const DischargePage(),
+ ),
],
),
GoRoute(
diff --git a/lib/core/di/injector.dart b/lib/core/di/injector.dart
index fd302e2..2636e28 100644
--- a/lib/core/di/injector.dart
+++ b/lib/core/di/injector.dart
@@ -24,6 +24,8 @@ import 'package:progres/features/timeline/presentation/blocs/timeline_bloc.dart'
import 'package:progres/features/transcript/data/repositories/transcript_repository_impl.dart';
import 'package:progres/features/transcript/data/services/transcript_cache_service.dart';
import 'package:progres/features/transcript/presentation/bloc/transcript_bloc.dart';
+import 'package:progres/features/discharge/data/repository/discharge_repository_impl.dart';
+import 'package:progres/features/discharge/presentation/bloc/discharge_bloc.dart';
final injector = GetIt.instance;
@@ -53,6 +55,7 @@ Future initDependencies() async {
injector.registerLazySingleton(
() => AcademicPerformencetRepositoryImpl(apiClient: injector()),
);
+ injector.registerLazySingleton(() => StudentDischargeRepositoryImpl());
injector.registerLazySingleton(() => TimelineCacheService());
injector.registerLazySingleton(() => EnrollmentCacheService());
injector.registerLazySingleton(() => TranscriptCacheService());
@@ -105,4 +108,7 @@ Future initDependencies() async {
cacheService: injector(),
),
);
+ injector.registerFactory(
+ () => StudentDischargeBloc(studentDischargeRepository: injector()),
+ );
}
diff --git a/lib/core/network/api_client.dart b/lib/core/network/api_client.dart
index 14ccff7..b22f9e0 100644
--- a/lib/core/network/api_client.dart
+++ b/lib/core/network/api_client.dart
@@ -37,6 +37,10 @@ class ApiClient {
},
),
);
+ _initializeCacheManager();
+ }
+
+ void _initializeCacheManager() {
CacheManager.getInstance().then((value) => _cacheManager = value);
}
diff --git a/lib/features/academics/data/services/academics_cache_service.dart b/lib/features/academics/data/services/academics_cache_service.dart
index 5ff769e..b787c45 100644
--- a/lib/features/academics/data/services/academics_cache_service.dart
+++ b/lib/features/academics/data/services/academics_cache_service.dart
@@ -1,4 +1,5 @@
import 'dart:convert';
+import 'package:flutter/foundation.dart';
import 'package:shared_preferences/shared_preferences.dart';
class AcademicsCacheService {
@@ -20,7 +21,7 @@ class AcademicsCacheService {
);
return true;
} catch (e) {
- print('Error caching academics data: $e');
+ debugPrint('Error caching academics data: $e');
return false;
}
}
@@ -35,7 +36,7 @@ class AcademicsCacheService {
return jsonDecode(dataString) as List;
} catch (e) {
- print('Error retrieving cached academics data: $e');
+ debugPrint('Error retrieving cached academics data: $e');
return null;
}
}
@@ -51,7 +52,7 @@ class AcademicsCacheService {
return DateTime.parse(timestamp);
} catch (e) {
- print('Error getting last updated time: $e');
+ debugPrint('Error getting last updated time: $e');
return null;
}
}
@@ -70,7 +71,7 @@ class AcademicsCacheService {
}
return true;
} catch (e) {
- print('Error clearing academics cache: $e');
+ debugPrint('Error clearing academics cache: $e');
return false;
}
}
diff --git a/lib/features/auth/presentation/bloc/auth_bloc.dart b/lib/features/auth/presentation/bloc/auth_bloc.dart
index 3c2f935..a2d0e72 100644
--- a/lib/features/auth/presentation/bloc/auth_bloc.dart
+++ b/lib/features/auth/presentation/bloc/auth_bloc.dart
@@ -81,35 +81,35 @@ class AuthBloc extends Bloc {
try {
event.context?.read().add(const ClearTranscriptCache());
} catch (e) {
- print('Note: Could not clear transcript cache. ${e.toString()}');
+ debugPrint('Note: Could not clear transcript cache. ${e.toString()}');
}
try {
event.context?.read().add(ClearTimelineCache());
} catch (e) {
- print('Note: Could not clear timeline cache. ${e.toString()}');
+ debugPrint('Note: Could not clear timeline cache. ${e.toString()}');
}
try {
event.context?.read().add(ClearEnrollmentsCache());
} catch (e) {
- print('Note: Could not clear enrollment cache. ${e.toString()}');
+ debugPrint('Note: Could not clear enrollment cache. ${e.toString()}');
}
try {
event.context?.read().add(ClearGroupsCache());
} catch (e) {
- print('Note: Could not clear groups cache. ${e.toString()}');
+ debugPrint('Note: Could not clear groups cache. ${e.toString()}');
}
try {
event.context?.read().add(ClearSubjectCache());
} catch (e) {
- print('Note: Could not clear subject cache. ${e.toString()}');
+ debugPrint('Note: Could not clear subject cache. ${e.toString()}');
}
try {
event.context?.read().add(ClearProfileCacheEvent());
} catch (e) {
- print('Note: Could not clear profile cache. ${e.toString()}');
+ debugPrint('Note: Could not clear profile cache. ${e.toString()}');
}
emit(AuthLoggedOut());
diff --git a/lib/features/dashboard/presentation/widgets/dashboard.dart b/lib/features/dashboard/presentation/widgets/dashboard.dart
index 97c5e73..9e64a40 100644
--- a/lib/features/dashboard/presentation/widgets/dashboard.dart
+++ b/lib/features/dashboard/presentation/widgets/dashboard.dart
@@ -136,6 +136,13 @@ Widget buildDashboard(ProfileLoaded state, BuildContext context) {
color: AppTheme.AppPrimary,
onTap: () => context.goNamed(AppRouter.transcripts),
),
+ buildGridCard(
+ context,
+ title: GalleryLocalizations.of(context)!.myDischarge,
+ icon: Icons.assignment_turned_in_outlined,
+ color: AppTheme.AppPrimary,
+ onTap: () => context.goNamed(AppRouter.discharge),
+ ),
],
),
diff --git a/lib/features/discharge/data/models/discharge.dart b/lib/features/discharge/data/models/discharge.dart
new file mode 100644
index 0000000..7c887ef
--- /dev/null
+++ b/lib/features/discharge/data/models/discharge.dart
@@ -0,0 +1,35 @@
+class StudentDischarge {
+ final bool sitDep;
+ final bool sitBf;
+ final bool sitBc;
+ final bool sitRu;
+ final bool sitBrs;
+
+ StudentDischarge({
+ this.sitDep = false,
+ this.sitBf = false,
+ this.sitBc = false,
+ this.sitRu = false,
+ this.sitBrs = false,
+ });
+
+ factory StudentDischarge.fromJson(Map json) {
+ return StudentDischarge(
+ sitBc: (json['sitBc'] as int?) == 1,
+ sitBrs: (json['sitBrs'] as int?) == 1,
+ sitDep: (json['sitDep'] as int?) == 1,
+ sitBf: (json['sitBf'] as int?) == 1,
+ sitRu: (json['sitRu'] as int?) == 1,
+ );
+ }
+
+ Map toJson() {
+ return {
+ 'sitDep': sitDep,
+ 'sitBf': sitBf,
+ 'sitBc': sitBc,
+ 'sitRu': sitRu,
+ 'sitBrs': sitBrs,
+ };
+ }
+}
diff --git a/lib/features/discharge/data/repository/discharge_repository_impl.dart b/lib/features/discharge/data/repository/discharge_repository_impl.dart
new file mode 100644
index 0000000..1ba3051
--- /dev/null
+++ b/lib/features/discharge/data/repository/discharge_repository_impl.dart
@@ -0,0 +1,36 @@
+import 'package:progres/features/discharge/data/models/discharge.dart';
+import 'package:progres/features/discharge/data/services/discharge_api_client.dart';
+
+class StudentDischargeRepositoryImpl {
+ final DischargeApiClient _apiClient;
+
+ StudentDischargeRepositoryImpl({DischargeApiClient? apiClient})
+ : _apiClient = apiClient ?? DischargeApiClient();
+
+ Future getStudentDischarge() async {
+ try {
+ final uuid = await _apiClient.getUuid();
+ final response = await _apiClient.get('/$uuid/qitus');
+
+ final List dischargeJson = response.data;
+
+ if (dischargeJson.isEmpty) {
+ throw DischargeNotRequiredException(
+ 'Discharge is not required for this student',
+ );
+ }
+
+ return StudentDischarge.fromJson(dischargeJson[0]);
+ } catch (e) {
+ rethrow;
+ }
+ }
+}
+
+class DischargeNotRequiredException implements Exception {
+ final String message;
+ DischargeNotRequiredException(this.message);
+
+ @override
+ String toString() => message;
+}
diff --git a/lib/features/discharge/data/services/discharge_api_client.dart b/lib/features/discharge/data/services/discharge_api_client.dart
new file mode 100644
index 0000000..c550134
--- /dev/null
+++ b/lib/features/discharge/data/services/discharge_api_client.dart
@@ -0,0 +1,141 @@
+import 'dart:async';
+import 'package:dio/dio.dart';
+import 'package:flutter_secure_storage/flutter_secure_storage.dart';
+import 'package:connectivity_plus/connectivity_plus.dart';
+import 'package:progres/core/network/cache_manager.dart';
+
+class DischargeApiClient {
+ static const String baseUrl = 'https://quittance.mesrs.dz/api';
+
+ late final Dio _dio;
+ final FlutterSecureStorage _secureStorage;
+ late final CacheManager _cacheManager;
+ final Duration _shortTimeout = const Duration(seconds: 5);
+ final Connectivity _connectivity = Connectivity();
+
+ DischargeApiClient({FlutterSecureStorage? secureStorage})
+ : _secureStorage = secureStorage ?? const FlutterSecureStorage() {
+ _dio = Dio(
+ BaseOptions(
+ baseUrl: baseUrl,
+ connectTimeout: const Duration(seconds: 30),
+ receiveTimeout: const Duration(seconds: 30),
+ headers: {'Content-Type': 'application/json'},
+ ),
+ );
+ _dio.interceptors.add(
+ InterceptorsWrapper(
+ onRequest: (options, handler) async {
+ final token = await _secureStorage.read(key: 'auth_token');
+ if (token != null) {
+ options.headers['authorization'] = token;
+ }
+ return handler.next(options);
+ },
+ onError: (error, handler) {
+ // Handle errors
+ return handler.next(error);
+ },
+ ),
+ );
+ CacheManager.getInstance().then((value) => _cacheManager = value);
+ }
+
+ Future get isConnected async {
+ final result = await _connectivity.checkConnectivity();
+ return result != ConnectivityResult.none;
+ }
+
+ Future saveToken(String token) async {
+ await _secureStorage.write(key: 'auth_token', value: token);
+ }
+
+ Future saveUuid(String uuid) async {
+ await _secureStorage.write(key: 'uuid', value: uuid);
+ }
+
+ Future saveEtablissementId(String etablissementId) async {
+ await _secureStorage.write(key: 'etablissement_id', value: etablissementId);
+ }
+
+ Future getUuid() async {
+ return await _secureStorage.read(key: 'uuid');
+ }
+
+ Future getEtablissementId() async {
+ return await _secureStorage.read(key: 'etablissement_id');
+ }
+
+ Future isLoggedIn() async {
+ final token = await _secureStorage.read(key: 'auth_token');
+ return token != null;
+ }
+
+ // Generate a cache key string based on path and query parameters
+ String _cacheKey(String path, Map? queryParameters) {
+ final queryStr =
+ queryParameters != null
+ ? Uri(queryParameters: queryParameters).query
+ : '';
+ return '$path?$queryStr';
+ }
+
+ Future get(
+ String path, {
+ Map? queryParameters,
+ }) async {
+ final key = _cacheKey(path, queryParameters);
+
+ if (!await isConnected) {
+ // offline - use cached data if available
+ final cachedData = _cacheManager.getCache(key);
+ if (cachedData != null) {
+ return Response(
+ requestOptions: RequestOptions(path: path),
+ data: cachedData,
+ statusCode: 200,
+ );
+ } else {
+ // No cache, throw offline error
+ throw DioException(
+ requestOptions: RequestOptions(path: path),
+ error: 'No internet connection and no cached data',
+ );
+ }
+ }
+
+ try {
+ // Try to get fresh data with a short timeout for fast fallback on slow responses
+ final response = await _dio.get(
+ path,
+ queryParameters: queryParameters,
+ options: Options(
+ sendTimeout: _shortTimeout,
+ receiveTimeout: _shortTimeout,
+ ),
+ );
+ await _cacheManager.saveCache(key, response.data);
+ return response;
+ } catch (e) {
+ // On failure, return cached data if available
+ final cachedData = _cacheManager.getCache(key);
+ if (cachedData != null) {
+ return Response(
+ requestOptions: RequestOptions(path: path),
+ data: cachedData,
+ statusCode: 200,
+ );
+ }
+ rethrow;
+ }
+ }
+
+ Future post(String path, {dynamic data}) async {
+ try {
+ final response = await _dio.post(path, data: data);
+ return response;
+ } catch (e) {
+ rethrow;
+ }
+ }
+}
diff --git a/lib/features/discharge/presentation/bloc/discharge_bloc.dart b/lib/features/discharge/presentation/bloc/discharge_bloc.dart
new file mode 100644
index 0000000..dd4f141
--- /dev/null
+++ b/lib/features/discharge/presentation/bloc/discharge_bloc.dart
@@ -0,0 +1,69 @@
+import 'dart:async';
+import 'package:equatable/equatable.dart';
+import 'package:flutter_bloc/flutter_bloc.dart';
+import 'package:progres/features/discharge/data/models/discharge.dart';
+import 'package:progres/features/discharge/data/repository/discharge_repository_impl.dart';
+
+class StudentDischargeEvent extends Equatable {
+ @override
+ List get props => [];
+}
+
+class LoadStudentDischarge extends StudentDischargeEvent {}
+
+class StudentDischargeState extends Equatable {
+ @override
+ List get props => [];
+}
+
+class StudentDischargeInitial extends StudentDischargeState {}
+
+class StudentDischargeLoading extends StudentDischargeState {}
+
+class StudentDischargeLoaded extends StudentDischargeState {
+ final StudentDischarge studentDischarge;
+
+ StudentDischargeLoaded({required this.studentDischarge});
+
+ @override
+ List get props => [studentDischarge];
+}
+
+class StudentDischargeNotRequired extends StudentDischargeState {}
+
+class StudentDischargeError extends StudentDischargeState {
+ final String message;
+
+ StudentDischargeError(this.message);
+
+ @override
+ List get props => [message];
+}
+
+class StudentDischargeBloc
+ extends Bloc {
+ final StudentDischargeRepositoryImpl studentDischargeRepository;
+
+ StudentDischargeBloc({required this.studentDischargeRepository})
+ : super(StudentDischargeInitial()) {
+ on(_onLoadStudentDischarge);
+ }
+
+ Future _onLoadStudentDischarge(
+ LoadStudentDischarge event,
+ Emitter emit,
+ ) async {
+ emit(StudentDischargeLoading());
+ try {
+ // Always fetch fresh data from API
+ final studentDischarge =
+ await studentDischargeRepository.getStudentDischarge();
+
+ emit(StudentDischargeLoaded(studentDischarge: studentDischarge));
+ } on DischargeNotRequiredException {
+ emit(StudentDischargeNotRequired());
+ } catch (e) {
+ emit(StudentDischargeError(e.toString()));
+ }
+ }
+}
diff --git a/lib/features/discharge/presentation/pages/discharge_page.dart b/lib/features/discharge/presentation/pages/discharge_page.dart
new file mode 100644
index 0000000..e5f423d
--- /dev/null
+++ b/lib/features/discharge/presentation/pages/discharge_page.dart
@@ -0,0 +1,95 @@
+import 'package:flutter/material.dart';
+import 'package:flutter_bloc/flutter_bloc.dart';
+import 'package:progres/features/discharge/presentation/bloc/discharge_bloc.dart';
+import 'package:progres/features/discharge/presentation/widgets/error.dart';
+import 'package:progres/features/discharge/presentation/widgets/discharge.dart';
+import 'package:flutter_gen/gen_l10n/gallery_localizations.dart';
+
+class DischargePage extends StatefulWidget {
+ const DischargePage({super.key});
+
+ @override
+ State createState() => _DischargePageState();
+}
+
+class _DischargePageState extends State {
+ @override
+ void initState() {
+ super.initState();
+ _loadDischarge();
+ }
+
+ void _loadDischarge() {
+ context.read().add(LoadStudentDischarge());
+ }
+
+ Future _refreshDischarge() async {
+ // Reload fresh data from API
+ context.read().add(LoadStudentDischarge());
+ // Simulating network delay for better UX
+ return Future.delayed(const Duration(milliseconds: 500));
+ }
+
+ @override
+ Widget build(BuildContext context) {
+ return Scaffold(
+ appBar: AppBar(
+ title: Text(GalleryLocalizations.of(context)!.myDischarge),
+ actions: [
+ IconButton(
+ icon: const Icon(Icons.refresh),
+ onPressed: _refreshDischarge,
+ tooltip: GalleryLocalizations.of(context)!.refreshDischarge,
+ ),
+ ],
+ ),
+ body: BlocBuilder(
+ builder: (context, state) {
+ if (state is StudentDischargeLoading) {
+ return _buildLoadingState();
+ } else if (state is StudentDischargeError) {
+ return ErrorState(state: state);
+ } else if (state is StudentDischargeLoaded) {
+ return RefreshIndicator(
+ onRefresh: _refreshDischarge,
+ child: DischargeContent(discharge: state.studentDischarge),
+ );
+ } else if (state is StudentDischargeNotRequired) {
+ return RefreshIndicator(
+ onRefresh: _refreshDischarge,
+ child: const DischargeNotRequired(),
+ );
+ } else {
+ return _buildInitialState();
+ }
+ },
+ ),
+ );
+ }
+
+ Widget _buildInitialState() {
+ final screenSize = MediaQuery.of(context).size;
+ final isSmallScreen = screenSize.width < 360;
+
+ return Center(
+ child: Column(
+ mainAxisAlignment: MainAxisAlignment.center,
+ children: [
+ Text(
+ GalleryLocalizations.of(context)!.noDischargeData,
+ style: TextStyle(fontSize: isSmallScreen ? 14 : 16),
+ ),
+ const SizedBox(height: 16),
+ ElevatedButton(
+ onPressed: _loadDischarge,
+ child: Text(GalleryLocalizations.of(context)!.loadDischarge),
+ ),
+ ],
+ ),
+ );
+ }
+
+ Widget _buildLoadingState() {
+ return const Center(child: CircularProgressIndicator());
+ }
+}
diff --git a/lib/features/discharge/presentation/widgets/discharge.dart b/lib/features/discharge/presentation/widgets/discharge.dart
new file mode 100644
index 0000000..16c4ccd
--- /dev/null
+++ b/lib/features/discharge/presentation/widgets/discharge.dart
@@ -0,0 +1,350 @@
+import 'package:flutter/material.dart';
+import 'package:progres/config/theme/app_theme.dart';
+import 'package:progres/features/discharge/data/models/discharge.dart';
+import 'package:flutter_gen/gen_l10n/gallery_localizations.dart';
+
+class DischargeContent extends StatelessWidget {
+ final StudentDischarge discharge;
+ const DischargeContent({super.key, required this.discharge});
+
+ @override
+ Widget build(BuildContext context) {
+ final screenSize = MediaQuery.of(context).size;
+ final isSmallScreen = screenSize.width < 360;
+ final horizontalPadding = isSmallScreen ? 12.0 : 16.0;
+
+ return SingleChildScrollView(
+ padding: EdgeInsets.all(horizontalPadding),
+ child: Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ _buildHeaderSection(context, isSmallScreen),
+ SizedBox(height: isSmallScreen ? 16 : 20),
+ _buildDischargeCards(context, isSmallScreen),
+ ],
+ ),
+ );
+ }
+
+ Widget _buildHeaderSection(BuildContext context, bool isSmallScreen) {
+ final theme = Theme.of(context);
+ return Container(
+ width: double.infinity,
+ padding: EdgeInsets.symmetric(
+ horizontal: isSmallScreen ? 12 : 16,
+ vertical: isSmallScreen ? 12 : 16,
+ ),
+ decoration: BoxDecoration(
+ color: AppTheme.AppPrimary.withValues(alpha: 0.1),
+ borderRadius: BorderRadius.circular(12),
+ ),
+ child: Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ Row(
+ children: [
+ Container(
+ width: isSmallScreen ? 32 : 36,
+ height: isSmallScreen ? 32 : 36,
+ decoration: BoxDecoration(
+ color: AppTheme.AppPrimary.withValues(alpha: 0.2),
+ borderRadius: BorderRadius.circular(10),
+ ),
+ child: Icon(
+ Icons.assignment_turned_in_outlined,
+ color: AppTheme.AppPrimary,
+ size: isSmallScreen ? 18 : 20,
+ ),
+ ),
+ SizedBox(width: isSmallScreen ? 10 : 12),
+ Expanded(
+ child: Text(
+ GalleryLocalizations.of(context)!.dischargeStatus,
+ style: theme.textTheme.titleLarge?.copyWith(
+ fontWeight: FontWeight.bold,
+ color: AppTheme.AppPrimary,
+ fontSize: isSmallScreen ? 16 : 18,
+ ),
+ ),
+ ),
+ ],
+ ),
+ SizedBox(height: isSmallScreen ? 6 : 8),
+ Text(
+ GalleryLocalizations.of(context)!.dischargeStatusDescription,
+ style: TextStyle(
+ fontSize: isSmallScreen ? 12 : 14,
+ color:
+ theme.brightness == Brightness.light
+ ? Colors.grey.shade700
+ : Colors.grey.shade300,
+ ),
+ ),
+ ],
+ ),
+ );
+ }
+
+ Widget _buildDischargeCards(BuildContext context, bool isSmallScreen) {
+ return Column(
+ children: [
+ DischargeCard(
+ title: GalleryLocalizations.of(context)!.departmentLevel,
+ description: GalleryLocalizations.of(context)!.departmentDescription,
+ isCleared: discharge.sitDep,
+ icon: Icons.domain_outlined,
+ isSmallScreen: isSmallScreen,
+ ),
+ SizedBox(height: isSmallScreen ? 10 : 12),
+ DischargeCard(
+ title: GalleryLocalizations.of(context)!.facultyLibraryLevel,
+ description:
+ GalleryLocalizations.of(context)!.facultyLibraryDescription,
+ isCleared: discharge.sitBf,
+ icon: Icons.local_library_outlined,
+ isSmallScreen: isSmallScreen,
+ ),
+ SizedBox(height: isSmallScreen ? 10 : 12),
+ DischargeCard(
+ title: GalleryLocalizations.of(context)!.centralLibraryLevel,
+ description:
+ GalleryLocalizations.of(context)!.centralLibraryDescription,
+ isCleared: discharge.sitBc,
+ icon: Icons.library_books_outlined,
+ isSmallScreen: isSmallScreen,
+ ),
+ SizedBox(height: isSmallScreen ? 10 : 12),
+ DischargeCard(
+ title: GalleryLocalizations.of(context)!.residenceLevel,
+ description: GalleryLocalizations.of(context)!.residenceDescription,
+ isCleared: discharge.sitRu,
+ icon: Icons.home_outlined,
+ isSmallScreen: isSmallScreen,
+ ),
+ SizedBox(height: isSmallScreen ? 10 : 12),
+ DischargeCard(
+ title: GalleryLocalizations.of(context)!.scholarshipServiceLevel,
+ description:
+ GalleryLocalizations.of(context)!.scholarshipServiceDescription,
+ isCleared: discharge.sitBrs,
+ icon: Icons.school_outlined,
+ isSmallScreen: isSmallScreen,
+ ),
+ ],
+ );
+ }
+}
+
+class DischargeCard extends StatelessWidget {
+ final String title;
+ final String description;
+ final bool isCleared;
+ final IconData icon;
+ final bool isSmallScreen;
+
+ const DischargeCard({
+ super.key,
+ required this.title,
+ required this.description,
+ required this.isCleared,
+ required this.icon,
+ required this.isSmallScreen,
+ });
+
+ @override
+ Widget build(BuildContext context) {
+ final theme = Theme.of(context);
+ final statusColor = isCleared ? Colors.green : Colors.orange;
+ final statusIcon = isCleared ? Icons.check_circle : Icons.pending;
+
+ return Card(
+ elevation: 0,
+ shape: RoundedRectangleBorder(
+ borderRadius: BorderRadius.circular(12),
+ side: BorderSide(
+ color:
+ theme.brightness == Brightness.light
+ ? AppTheme.AppBorder
+ : Colors.grey.shade800,
+ ),
+ ),
+ child: Padding(
+ padding: EdgeInsets.all(isSmallScreen ? 14 : 16),
+ child: Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ Row(
+ children: [
+ Container(
+ width: isSmallScreen ? 36 : 40,
+ height: isSmallScreen ? 36 : 40,
+ decoration: BoxDecoration(
+ color: statusColor.withValues(alpha: 0.1),
+ borderRadius: BorderRadius.circular(12),
+ ),
+ child: Icon(
+ icon,
+ color: statusColor,
+ size: isSmallScreen ? 20 : 24,
+ ),
+ ),
+ SizedBox(width: isSmallScreen ? 12 : 16),
+ Expanded(
+ child: Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ Text(
+ title,
+ style: theme.textTheme.titleMedium?.copyWith(
+ fontWeight: FontWeight.bold,
+ fontSize: isSmallScreen ? 14 : 16,
+ ),
+ ),
+ SizedBox(height: isSmallScreen ? 2 : 4),
+ Text(
+ description,
+ style: TextStyle(
+ fontSize: isSmallScreen ? 11 : 12,
+ color:
+ theme.brightness == Brightness.light
+ ? Colors.grey.shade600
+ : Colors.grey.shade400,
+ ),
+ ),
+ ],
+ ),
+ ),
+ Container(
+ padding: EdgeInsets.symmetric(
+ horizontal: isSmallScreen ? 8 : 10,
+ vertical: isSmallScreen ? 4 : 6,
+ ),
+ decoration: BoxDecoration(
+ color: statusColor.withValues(alpha: 0.1),
+ borderRadius: BorderRadius.circular(20),
+ ),
+ child: Row(
+ mainAxisSize: MainAxisSize.min,
+ children: [
+ Icon(
+ statusIcon,
+ size: isSmallScreen ? 14 : 16,
+ color: statusColor,
+ ),
+ SizedBox(width: isSmallScreen ? 4 : 6),
+ Text(
+ isCleared
+ ? GalleryLocalizations.of(context)!.cleared
+ : GalleryLocalizations.of(context)!.pending,
+ style: TextStyle(
+ fontSize: isSmallScreen ? 10 : 12,
+ fontWeight: FontWeight.w600,
+ color: statusColor,
+ ),
+ ),
+ ],
+ ),
+ ),
+ ],
+ ),
+ ],
+ ),
+ ),
+ );
+ }
+}
+
+class EmptyDischarge extends StatelessWidget {
+ const EmptyDischarge({super.key});
+
+ @override
+ Widget build(BuildContext context) {
+ final theme = Theme.of(context);
+ final screenSize = MediaQuery.of(context).size;
+ final isSmallScreen = screenSize.width < 360;
+ return Center(
+ child: Column(
+ mainAxisAlignment: MainAxisAlignment.center,
+ children: [
+ Icon(
+ Icons.assignment_outlined,
+ size: isSmallScreen ? 56 : 64,
+ color: theme.disabledColor,
+ ),
+ const SizedBox(height: 16),
+ Text(
+ GalleryLocalizations.of(context)!.noDischargeData,
+ style: theme.textTheme.titleLarge?.copyWith(
+ fontSize: isSmallScreen ? 18 : 20,
+ ),
+ ),
+ const SizedBox(height: 8),
+ Text(
+ GalleryLocalizations.of(context)!.dischargeNotAvailable,
+ textAlign: TextAlign.center,
+ style: TextStyle(
+ color: theme.textTheme.bodyMedium?.color?.withValues(alpha: 0.7),
+ fontSize: isSmallScreen ? 14 : 16,
+ ),
+ ),
+ ],
+ ),
+ );
+ }
+}
+
+class DischargeNotRequired extends StatelessWidget {
+ const DischargeNotRequired({super.key});
+
+ @override
+ Widget build(BuildContext context) {
+ final theme = Theme.of(context);
+ final screenSize = MediaQuery.of(context).size;
+ final isSmallScreen = screenSize.width < 360;
+ return Center(
+ child: Column(
+ mainAxisAlignment: MainAxisAlignment.center,
+ children: [
+ Container(
+ width: isSmallScreen ? 80 : 96,
+ height: isSmallScreen ? 80 : 96,
+ decoration: BoxDecoration(
+ color: Colors.green.withValues(alpha: 0.1),
+ shape: BoxShape.circle,
+ ),
+ child: Icon(
+ Icons.check_circle_outline,
+ size: isSmallScreen ? 48 : 56,
+ color: Colors.green,
+ ),
+ ),
+ const SizedBox(height: 24),
+ Text(
+ GalleryLocalizations.of(context)!.dischargeNotRequiredTitle,
+ style: theme.textTheme.titleLarge?.copyWith(
+ fontSize: isSmallScreen ? 18 : 20,
+ fontWeight: FontWeight.bold,
+ color: Colors.green,
+ ),
+ textAlign: TextAlign.center,
+ ),
+ const SizedBox(height: 12),
+ Padding(
+ padding: EdgeInsets.symmetric(horizontal: isSmallScreen ? 24 : 32),
+ child: Text(
+ GalleryLocalizations.of(context)!.dischargeNotRequiredDescription,
+ textAlign: TextAlign.center,
+ style: TextStyle(
+ color: theme.textTheme.bodyMedium?.color?.withValues(
+ alpha: 0.7,
+ ),
+ fontSize: isSmallScreen ? 14 : 16,
+ height: 1.5,
+ ),
+ ),
+ ),
+ ],
+ ),
+ );
+ }
+}
diff --git a/lib/features/discharge/presentation/widgets/error.dart b/lib/features/discharge/presentation/widgets/error.dart
new file mode 100644
index 0000000..5b762b9
--- /dev/null
+++ b/lib/features/discharge/presentation/widgets/error.dart
@@ -0,0 +1,41 @@
+import 'package:flutter/material.dart';
+import 'package:flutter_bloc/flutter_bloc.dart';
+import 'package:progres/features/discharge/presentation/bloc/discharge_bloc.dart';
+import 'package:flutter_gen/gen_l10n/gallery_localizations.dart';
+
+class ErrorState extends StatelessWidget {
+ final StudentDischargeError state;
+
+ const ErrorState({super.key, required this.state});
+
+ @override
+ Widget build(BuildContext context) {
+ final screenSize = MediaQuery.of(context).size;
+
+ final isSmallScreen = screenSize.width < 360;
+ return Center(
+ child: Padding(
+ padding: EdgeInsets.symmetric(horizontal: isSmallScreen ? 16.0 : 24.0),
+ child: Column(
+ mainAxisAlignment: MainAxisAlignment.center,
+ children: [
+ Text(
+ GalleryLocalizations.of(context)!.somthingWentWrong,
+ textAlign: TextAlign.center,
+ style: TextStyle(fontSize: isSmallScreen ? 14 : 16),
+ ),
+ const SizedBox(height: 16),
+ ElevatedButton(
+ onPressed: () {
+ context.read().add(
+ LoadStudentDischarge(),
+ );
+ },
+ child: Text(GalleryLocalizations.of(context)!.retry),
+ ),
+ ],
+ ),
+ ),
+ );
+ }
+}
diff --git a/lib/features/enrollment/data/services/enrollment_cache_service.dart b/lib/features/enrollment/data/services/enrollment_cache_service.dart
index 6069f19..98ba9f4 100644
--- a/lib/features/enrollment/data/services/enrollment_cache_service.dart
+++ b/lib/features/enrollment/data/services/enrollment_cache_service.dart
@@ -1,4 +1,5 @@
import 'dart:convert';
+import 'package:flutter/foundation.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:progres/features/enrollment/data/models/enrollment.dart';
@@ -19,7 +20,7 @@ class EnrollmentCacheService {
);
return true;
} catch (e) {
- print('Error caching enrollments: $e');
+ debugPrint('Error caching enrollments: $e');
return false;
}
}
@@ -35,7 +36,7 @@ class EnrollmentCacheService {
final List decodedJson = jsonDecode(enrollmentsString);
return decodedJson.map((json) => Enrollment.fromJson(json)).toList();
} catch (e) {
- print('Error retrieving cached enrollments: $e');
+ debugPrint('Error retrieving cached enrollments: $e');
return null;
}
}
@@ -51,7 +52,7 @@ class EnrollmentCacheService {
return DateTime.parse(timestamp);
} catch (e) {
- print('Error getting last updated time: $e');
+ debugPrint('Error getting last updated time: $e');
return null;
}
}
@@ -64,7 +65,7 @@ class EnrollmentCacheService {
await prefs.remove('${_lastUpdatedKeyPrefix}enrollments');
return true;
} catch (e) {
- print('Error clearing enrollment cache: $e');
+ debugPrint('Error clearing enrollment cache: $e');
return false;
}
}
diff --git a/lib/features/enrollment/presentation/bloc/enrollment_bloc.dart b/lib/features/enrollment/presentation/bloc/enrollment_bloc.dart
index ec7deb2..c58f14a 100644
--- a/lib/features/enrollment/presentation/bloc/enrollment_bloc.dart
+++ b/lib/features/enrollment/presentation/bloc/enrollment_bloc.dart
@@ -1,3 +1,4 @@
+import 'package:flutter/foundation.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:progres/features/enrollment/data/repositories/enrollment_repository_impl.dart';
import 'package:progres/features/enrollment/data/services/enrollment_cache_service.dart';
@@ -41,7 +42,7 @@ class EnrollmentBloc extends Bloc {
emit(EnrollmentsLoaded(enrollments: enrollments, fromCache: false));
} catch (e) {
- print('Error loading enrollments: $e');
+ debugPrint('Error loading enrollments: $e');
final cachedEnrollments = await cacheService.getCachedEnrollments();
if (cachedEnrollments != null && cachedEnrollments.isNotEmpty) {
diff --git a/lib/features/groups/data/services/groups_cache_service.dart b/lib/features/groups/data/services/groups_cache_service.dart
index a3327eb..ce2836e 100644
--- a/lib/features/groups/data/services/groups_cache_service.dart
+++ b/lib/features/groups/data/services/groups_cache_service.dart
@@ -1,4 +1,5 @@
import 'dart:convert';
+import 'package:flutter/foundation.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:progres/features/groups/data/models/group.dart';
@@ -19,7 +20,7 @@ class GroupsCacheService {
);
return true;
} catch (e) {
- print('Error caching groups: $e');
+ debugPrint('Error caching groups: $e');
return false;
}
}
@@ -35,7 +36,7 @@ class GroupsCacheService {
final List decodedJson = jsonDecode(groupsString);
return decodedJson.map((json) => StudentGroup.fromJson(json)).toList();
} catch (e) {
- print('Error retrieving cached groups: $e');
+ debugPrint('Error retrieving cached groups: $e');
return null;
}
}
@@ -51,7 +52,7 @@ class GroupsCacheService {
return DateTime.parse(timestamp);
} catch (e) {
- print('Error getting last updated time: $e');
+ debugPrint('Error getting last updated time: $e');
return null;
}
}
@@ -64,7 +65,7 @@ class GroupsCacheService {
await prefs.remove('${_lastUpdatedKeyPrefix}groups');
return true;
} catch (e) {
- print('Error clearing groups cache: $e');
+ debugPrint('Error clearing groups cache: $e');
return false;
}
}
diff --git a/lib/features/profile/data/services/profile_cache_service.dart b/lib/features/profile/data/services/profile_cache_service.dart
index a8ab523..c471843 100644
--- a/lib/features/profile/data/services/profile_cache_service.dart
+++ b/lib/features/profile/data/services/profile_cache_service.dart
@@ -1,4 +1,5 @@
import 'dart:convert';
+import 'package:flutter/foundation.dart';
import 'package:shared_preferences/shared_preferences.dart';
class ProfileCacheService {
@@ -14,7 +15,7 @@ class ProfileCacheService {
await prefs.setString(_lastUpdatedKey, DateTime.now().toIso8601String());
return true;
} catch (e) {
- print('Error caching profile data: $e');
+ debugPrint('Error caching profile data: $e');
return false;
}
}
@@ -29,7 +30,7 @@ class ProfileCacheService {
return jsonDecode(profileDataString) as Map;
} catch (e) {
- print('Error retrieving cached profile data: $e');
+ debugPrint('Error retrieving cached profile data: $e');
return null;
}
}
@@ -43,7 +44,7 @@ class ProfileCacheService {
return DateTime.parse(timestamp);
} catch (e) {
- print('Error getting last updated time: $e');
+ debugPrint('Error getting last updated time: $e');
return null;
}
}
@@ -56,7 +57,7 @@ class ProfileCacheService {
await prefs.remove(_lastUpdatedKey);
return true;
} catch (e) {
- print('Error clearing profile cache: $e');
+ debugPrint('Error clearing profile cache: $e');
return false;
}
}
diff --git a/lib/features/subject/data/services/subject_cache_service.dart b/lib/features/subject/data/services/subject_cache_service.dart
index 670e44d..3917a5e 100644
--- a/lib/features/subject/data/services/subject_cache_service.dart
+++ b/lib/features/subject/data/services/subject_cache_service.dart
@@ -1,4 +1,5 @@
import 'dart:convert';
+import 'package:flutter/foundation.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:progres/features/subject/data/models/course_coefficient.dart';
@@ -35,7 +36,7 @@ class SubjectCacheService {
await prefs.setString(lastUpdatedKey, DateTime.now().toIso8601String());
return true;
} catch (e) {
- print('Error caching subject coefficients: $e');
+ debugPrint('Error caching subject coefficients: $e');
return false;
}
}
@@ -57,7 +58,7 @@ class SubjectCacheService {
.map((json) => CourseCoefficient.fromJson(json))
.toList();
} catch (e) {
- print('Error retrieving cached subject coefficients: $e');
+ debugPrint('Error retrieving cached subject coefficients: $e');
return null;
}
}
@@ -76,7 +77,7 @@ class SubjectCacheService {
return DateTime.parse(timestamp);
} catch (e) {
- print('Error getting last updated time: $e');
+ debugPrint('Error getting last updated time: $e');
return null;
}
}
@@ -98,7 +99,7 @@ class SubjectCacheService {
await prefs.remove(lastUpdatedKey);
return true;
} catch (e) {
- print('Error clearing specific subject cache: $e');
+ debugPrint('Error clearing specific subject cache: $e');
return false;
}
}
@@ -117,7 +118,7 @@ class SubjectCacheService {
}
return true;
} catch (e) {
- print('Error clearing all subject caches: $e');
+ debugPrint('Error clearing all subject caches: $e');
return false;
}
}
diff --git a/lib/features/subject/presentation/bloc/subject_bloc.dart b/lib/features/subject/presentation/bloc/subject_bloc.dart
index b605161..1b5f03f 100644
--- a/lib/features/subject/presentation/bloc/subject_bloc.dart
+++ b/lib/features/subject/presentation/bloc/subject_bloc.dart
@@ -1,3 +1,4 @@
+import 'package:flutter/foundation.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:equatable/equatable.dart';
import 'package:progres/features/subject/data/repositories/subject_repository_impl.dart';
@@ -121,7 +122,7 @@ class SubjectBloc extends Bloc {
await cacheService.clearAllCache();
}
} catch (e) {
- print('Error clearing subject cache: $e');
+ debugPrint('Error clearing subject cache: $e');
}
}
}
diff --git a/lib/features/timeline/data/models/course_session.dart b/lib/features/timeline/data/models/course_session.dart
index 8c09d7b..f88e642 100644
--- a/lib/features/timeline/data/models/course_session.dart
+++ b/lib/features/timeline/data/models/course_session.dart
@@ -113,7 +113,7 @@ class CourseSession {
// Print debug info for Sunday specifically
if (jourId == 2) {
// Sunday
- print(
+ debugPrint(
'SUNDAY EVENT: jourId $jourId ($jourLibelleFr) mapped to ${_formatDate(result)} (weekday: ${result.weekday})',
);
}
diff --git a/lib/features/timeline/data/services/timeline_cache_service.dart b/lib/features/timeline/data/services/timeline_cache_service.dart
index c12af67..a4b9325 100644
--- a/lib/features/timeline/data/services/timeline_cache_service.dart
+++ b/lib/features/timeline/data/services/timeline_cache_service.dart
@@ -1,4 +1,5 @@
import 'dart:convert';
+import 'package:flutter/foundation.dart';
import 'package:shared_preferences/shared_preferences.dart';
class TimelineCacheService {
@@ -23,7 +24,7 @@ class TimelineCacheService {
);
return true;
} catch (e) {
- print('Error caching timeline events: $e');
+ debugPrint('Error caching timeline events: $e');
return false;
}
}
@@ -42,7 +43,7 @@ class TimelineCacheService {
return jsonDecode(eventsString) as List;
} catch (e) {
- print('Error retrieving cached timeline events: $e');
+ debugPrint('Error retrieving cached timeline events: $e');
return null;
}
}
@@ -58,7 +59,7 @@ class TimelineCacheService {
return DateTime.parse(timestamp);
} catch (e) {
- print('Error getting last updated time: $e');
+ debugPrint('Error getting last updated time: $e');
return null;
}
}
@@ -77,7 +78,7 @@ class TimelineCacheService {
}
return true;
} catch (e) {
- print('Error clearing timeline cache: $e');
+ debugPrint('Error clearing timeline cache: $e');
return false;
}
}
diff --git a/lib/features/transcript/data/services/transcript_cache_service.dart b/lib/features/transcript/data/services/transcript_cache_service.dart
index eb72ec6..c89bc42 100644
--- a/lib/features/transcript/data/services/transcript_cache_service.dart
+++ b/lib/features/transcript/data/services/transcript_cache_service.dart
@@ -1,4 +1,5 @@
import 'dart:convert';
+import 'package:flutter/foundation.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:progres/features/transcript/data/models/academic_transcript.dart';
import 'package:progres/features/transcript/data/models/annual_transcript_summary.dart';
@@ -27,7 +28,7 @@ class TranscriptCacheService {
);
return true;
} catch (e) {
- print('Error caching transcripts: $e');
+ debugPrint('Error caching transcripts: $e');
return false;
}
}
@@ -49,7 +50,7 @@ class TranscriptCacheService {
.map((json) => AcademicTranscript.fromJson(json))
.toList();
} catch (e) {
- print('Error retrieving cached transcripts: $e');
+ debugPrint('Error retrieving cached transcripts: $e');
return null;
}
}
@@ -71,7 +72,7 @@ class TranscriptCacheService {
);
return true;
} catch (e) {
- print('Error caching annual summary: $e');
+ debugPrint('Error caching annual summary: $e');
return false;
}
}
@@ -91,7 +92,7 @@ class TranscriptCacheService {
final decodedJson = jsonDecode(summaryString);
return AnnualTranscriptSummary.fromJson(decodedJson);
} catch (e) {
- print('Error retrieving cached annual summary: $e');
+ debugPrint('Error retrieving cached annual summary: $e');
return null;
}
}
@@ -107,7 +108,7 @@ class TranscriptCacheService {
return DateTime.parse(timestamp);
} catch (e) {
- print('Error getting last updated time: $e');
+ debugPrint('Error getting last updated time: $e');
return null;
}
}
@@ -128,7 +129,7 @@ class TranscriptCacheService {
}
return true;
} catch (e) {
- print('Error clearing cache: $e');
+ debugPrint('Error clearing cache: $e');
return false;
}
}
diff --git a/lib/features/transcript/presentation/bloc/transcript_bloc.dart b/lib/features/transcript/presentation/bloc/transcript_bloc.dart
index deefa2e..caa0c3d 100644
--- a/lib/features/transcript/presentation/bloc/transcript_bloc.dart
+++ b/lib/features/transcript/presentation/bloc/transcript_bloc.dart
@@ -1,3 +1,4 @@
+import 'package:flutter/foundation.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:progres/features/transcript/data/models/annual_transcript_summary.dart';
import 'package:progres/features/transcript/data/repositories/transcript_repository_impl.dart';
@@ -54,7 +55,7 @@ class TranscriptBloc extends Bloc {
emit(EnrollmentsLoaded(enrollments: enrollments, fromCache: false));
} catch (e) {
- print('Error loading enrollments: $e');
+ debugPrint('Error loading enrollments: $e');
final cachedEnrollments =
await enrollmentCacheService.getCachedEnrollments();
@@ -91,7 +92,7 @@ class TranscriptBloc extends Bloc {
.getCachedAnnualSummary(event.enrollmentId);
if (cachedTranscripts != null && cachedTranscripts.isNotEmpty) {
- print(
+ debugPrint(
'Using cached transcripts and summary for enrollment ID: ${event.enrollmentId}',
);
emit(
@@ -132,7 +133,7 @@ class TranscriptBloc extends Bloc {
annualSummary,
);
} catch (e) {
- print('Error loading annual summary: $e');
+ debugPrint('Error loading annual summary: $e');
// Try to get from cache if network request fails
annualSummary = await transcriptCacheService.getCachedAnnualSummary(
event.enrollmentId,
@@ -148,7 +149,7 @@ class TranscriptBloc extends Bloc {
),
);
} catch (e) {
- print('Error loading transcripts: $e');
+ debugPrint('Error loading transcripts: $e');
// Try to load from cache if network request fails
final cachedTranscripts = await transcriptCacheService
@@ -222,7 +223,7 @@ class TranscriptBloc extends Bloc {
);
}
} catch (e) {
- print('Error loading annual summary: $e');
+ debugPrint('Error loading annual summary: $e');
// Don't change state on error, just log
}
}
diff --git a/lib/l10n/app_ar.arb b/lib/l10n/app_ar.arb
index 3dcfd28..3470cee 100644
--- a/lib/l10n/app_ar.arb
+++ b/lib/l10n/app_ar.arb
@@ -552,5 +552,89 @@
"viewonGitHub": "عرض على github",
"@viewonGitHub": {
"description": "رابط إلى github"
+ },
+ "myDischarge": "براءة الذمة",
+ "@myDischarge": {
+ "description": "عنوان صفحة براءة الذمة"
+ },
+ "refreshDischarge": "تحديث براءة الذمة",
+ "@refreshDischarge": {
+ "description": "تلميح لزر تحديث براءة الذمة"
+ },
+ "loadDischarge": "تحميل براءة الذمة",
+ "@loadDischarge": {
+ "description": "نص زر تحميل براءة الذمة"
+ },
+ "noDischargeData": "لا توجد بيانات براءة ذمة متاحة",
+ "@noDischargeData": {
+ "description": "رسالة تظهر عندما لا تتوفر بيانات براءة الذمة"
+ },
+ "dischargeNotAvailable": "معلومات براءة الذمة غير متاحة في الوقت الحالي.",
+ "@dischargeNotAvailable": {
+ "description": "وصف يظهر عندما لا تتوفر بيانات براءة الذمة"
+ },
+ "dischargeStatus": "حالة براءة الذمة",
+ "@dischargeStatus": {
+ "description": "عنوان قسم حالة براءة الذمة"
+ },
+ "dischargeStatusDescription": "حالة براءة الذمة الحالية عبر جميع خدمات الجامعة",
+ "@dischargeStatusDescription": {
+ "description": "وصف قسم حالة براءة الذمة"
+ },
+ "departmentLevel": "مستوى القسم",
+ "@departmentLevel": {
+ "description": "عنوان براءة الذمة على مستوى القسم"
+ },
+ "departmentDescription": "تسوية القسم الأكاديمي",
+ "@departmentDescription": {
+ "description": "وصف براءة الذمة على مستوى القسم"
+ },
+ "facultyLibraryLevel": "مستوى مكتبة الكلية",
+ "@facultyLibraryLevel": {
+ "description": "عنوان براءة الذمة على مستوى مكتبة الكلية"
+ },
+ "facultyLibraryDescription": "تسوية كتب ومواد مكتبة الكلية",
+ "@facultyLibraryDescription": {
+ "description": "وصف براءة الذمة على مستوى مكتبة الكلية"
+ },
+ "centralLibraryLevel": "مستوى المكتبة المركزية",
+ "@centralLibraryLevel": {
+ "description": "عنوان براءة الذمة على مستوى المكتبة المركزية"
+ },
+ "centralLibraryDescription": "تسوية كتب ومواد المكتبة المركزية",
+ "@centralLibraryDescription": {
+ "description": "وصف براءة الذمة على مستوى المكتبة المركزية"
+ },
+ "residenceLevel": "مستوى الإقامة",
+ "@residenceLevel": {
+ "description": "عنوان براءة الذمة على مستوى الإقامة"
+ },
+ "residenceDescription": "تسوية سكن الإقامة الجامعية",
+ "@residenceDescription": {
+ "description": "وصف براءة الذمة على مستوى الإقامة"
+ },
+ "scholarshipServiceLevel": "مستوى خدمة المنح الدراسية",
+ "@scholarshipServiceLevel": {
+ "description": "عنوان براءة الذمة على مستوى خدمة المنح الدراسية"
+ },
+ "scholarshipServiceDescription": "تسوية المنح الدراسية والمساعدات المالية",
+ "@scholarshipServiceDescription": {
+ "description": "وصف براءة الذمة على مستوى خدمة المنح الدراسية"
+ },
+ "cleared": "مسوى",
+ "@cleared": {
+ "description": "حالة عندما تكون براءة الذمة مسواة"
+ },
+ "pending": "في الانتظار",
+ "@pending": {
+ "description": "حالة عندما تكون براءة الذمة في الانتظار"
+ },
+ "dischargeNotRequiredTitle": "لا تحتاج براءة ذمة",
+ "@dischargeNotRequiredTitle": {
+ "description": "عنوان عندما لا تكون براءة الذمة مطلوبة للطالب"
+ },
+ "dischargeNotRequiredDescription": "لست مطالباً بإكمال أي إجراءات براءة ذمة في الوقت الحالي. وضعك الأكاديمي لا يتطلب تسوية من خدمات الجامعة.",
+ "@dischargeNotRequiredDescription": {
+ "description": "وصف عندما لا تكون براءة الذمة مطلوبة للطالب"
}
-}
\ No newline at end of file
+}
diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb
index 50131dc..99cd67a 100644
--- a/lib/l10n/app_en.arb
+++ b/lib/l10n/app_en.arb
@@ -533,5 +533,89 @@
"viewonGitHub": "View on GitHub",
"@viewonGitHub": {
"description": "link to github"
+ },
+ "myDischarge": "My Discharge",
+ "@myDischarge": {
+ "description": "Title for discharge page"
+ },
+ "refreshDischarge": "Refresh discharge",
+ "@refreshDischarge": {
+ "description": "Tooltip for refresh discharge button"
+ },
+ "loadDischarge": "Load Discharge",
+ "@loadDischarge": {
+ "description": "Button text to load discharge"
+ },
+ "noDischargeData": "No discharge data available",
+ "@noDischargeData": {
+ "description": "Message shown when no discharge data is available"
+ },
+ "dischargeNotAvailable": "Discharge information is not available at this time.",
+ "@dischargeNotAvailable": {
+ "description": "Description shown when discharge data is not available"
+ },
+ "dischargeStatus": "Discharge Status",
+ "@dischargeStatus": {
+ "description": "Title for discharge status section"
+ },
+ "dischargeStatusDescription": "Your current discharge status across all university services",
+ "@dischargeStatusDescription": {
+ "description": "Description for discharge status section"
+ },
+ "departmentLevel": "Department Level",
+ "@departmentLevel": {
+ "description": "Title for department level discharge"
+ },
+ "departmentDescription": "Academic department clearance",
+ "@departmentDescription": {
+ "description": "Description for department level discharge"
+ },
+ "facultyLibraryLevel": "Faculty Library Level",
+ "@facultyLibraryLevel": {
+ "description": "Title for faculty library level discharge"
+ },
+ "facultyLibraryDescription": "Faculty library books and materials clearance",
+ "@facultyLibraryDescription": {
+ "description": "Description for faculty library level discharge"
+ },
+ "centralLibraryLevel": "Central Library Level",
+ "@centralLibraryLevel": {
+ "description": "Title for central library level discharge"
+ },
+ "centralLibraryDescription": "Central library books and materials clearance",
+ "@centralLibraryDescription": {
+ "description": "Description for central library level discharge"
+ },
+ "residenceLevel": "Residence Level",
+ "@residenceLevel": {
+ "description": "Title for residence level discharge"
+ },
+ "residenceDescription": "University residence accommodation clearance",
+ "@residenceDescription": {
+ "description": "Description for residence level discharge"
+ },
+ "scholarshipServiceLevel": "Scholarship Service Level",
+ "@scholarshipServiceLevel": {
+ "description": "Title for scholarship service level discharge"
+ },
+ "scholarshipServiceDescription": "Scholarship and financial aid clearance",
+ "@scholarshipServiceDescription": {
+ "description": "Description for scholarship service level discharge"
+ },
+ "cleared": "Cleared",
+ "@cleared": {
+ "description": "Status when discharge is cleared"
+ },
+ "pending": "Pending",
+ "@pending": {
+ "description": "Status when discharge is pending"
+ },
+ "dischargeNotRequiredTitle": "No Discharge Required",
+ "@dischargeNotRequiredTitle": {
+ "description": "Title when discharge is not required for the student"
+ },
+ "dischargeNotRequiredDescription": "You are not required to complete any discharge procedures at this time. Your academic status does not require clearance from university services.",
+ "@dischargeNotRequiredDescription": {
+ "description": "Description when discharge is not required for the student"
}
-}
\ No newline at end of file
+}
diff --git a/pubspec.yaml b/pubspec.yaml
index 1aa2d25..778ebdc 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -6,7 +6,7 @@ version: 1.1.0+2
environment:
sdk: ^3.7.2
- flutter: "3.22.3" # Updated to latest stable version
+ flutter: "3.22.3"
dependencies:
flutter:
diff --git a/screenshot/discharge.jpg b/screenshot/discharge.jpg
new file mode 100644
index 0000000..4aabb4e
Binary files /dev/null and b/screenshot/discharge.jpg differ