-
Notifications
You must be signed in to change notification settings - Fork 5
Feat/student discharge #6
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from 2 commits
Commits
Show all changes
5 commits
Select commit
Hold shift + click to select a range
e8e9727
chore: remove comment
AliAkrem 7c4c420
feat: allow student to review his discharge
AliAkrem cd8c5fe
refactor: initialize cache manager cleanly
AliAkrem 74fb4c3
fix: remove discharge cache
AliAkrem 2116706
doc: add discharge change docs
AliAkrem File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,36 @@ | ||
| class StudentDischarge { | ||
| final bool sitDep; | ||
| final bool sitBf; | ||
| final bool sitBc; | ||
| final bool sitRu; | ||
| final bool sitBr; | ||
|
|
||
| StudentDischarge({ | ||
| this.sitDep = false, | ||
| this.sitBf = false, | ||
| this.sitBc = false, | ||
| this.sitRu = false, | ||
| this.sitBr = false, | ||
| }); | ||
|
|
||
| factory StudentDischarge.fromJson(Map<String, dynamic> json) { | ||
| return StudentDischarge( | ||
| sitBc: (json['sitBc'] as int?) == 1, | ||
| sitBr: | ||
| (json['sitBrs'] as int?) == 1, // Note: API uses 'sitBrs' not 'sitBr' | ||
| sitDep: (json['sitDep'] as int?) == 1, | ||
| sitBf: (json['sitBf'] as int?) == 1, | ||
| sitRu: (json['sitRu'] as int?) == 1, | ||
| ); | ||
| } | ||
|
|
||
| Map<String, dynamic> toJson() { | ||
| return { | ||
| 'sitDep': sitDep, | ||
| 'sitBf': sitBf, | ||
| 'sitBc': sitBc, | ||
| 'sitRu': sitRu, | ||
| 'sitBr': sitBr, | ||
| }; | ||
| } | ||
| } | ||
36 changes: 36 additions & 0 deletions
36
lib/features/discharge/data/repository/discharge_repository_impl.dart
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,36 @@ | ||
| import 'package:progres/features/discharge/data/models/dischage.dart'; | ||
| import 'package:progres/features/discharge/data/services/discharge_api_client.dart'; | ||
|
|
||
| class StudentDischargeRepositoryImpl { | ||
| final DischargeApiClient _apiClient; | ||
|
|
||
| StudentDischargeRepositoryImpl({DischargeApiClient? apiClient}) | ||
| : _apiClient = apiClient ?? DischargeApiClient(); | ||
|
|
||
| Future<StudentDischarge> getStudentDischarge() async { | ||
| try { | ||
| final uuid = await _apiClient.getUuid(); | ||
| final response = await _apiClient.get('/$uuid/qitus'); | ||
|
|
||
| final List<dynamic> 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; | ||
| } |
141 changes: 141 additions & 0 deletions
141
lib/features/discharge/data/services/discharge_api_client.dart
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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'; | ||
|
AliAkrem marked this conversation as resolved.
|
||
|
|
||
| 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); | ||
|
AliAkrem marked this conversation as resolved.
|
||
| } | ||
|
|
||
| Future<bool> get isConnected async { | ||
| final result = await _connectivity.checkConnectivity(); | ||
| return result != ConnectivityResult.none; | ||
| } | ||
|
|
||
| Future<void> saveToken(String token) async { | ||
| await _secureStorage.write(key: 'auth_token', value: token); | ||
| } | ||
|
|
||
| Future<void> saveUuid(String uuid) async { | ||
| await _secureStorage.write(key: 'uuid', value: uuid); | ||
| } | ||
|
|
||
| Future<void> saveEtablissementId(String etablissementId) async { | ||
| await _secureStorage.write(key: 'etablissement_id', value: etablissementId); | ||
| } | ||
|
|
||
| Future<String?> getUuid() async { | ||
| return await _secureStorage.read(key: 'uuid'); | ||
| } | ||
|
|
||
| Future<String?> getEtablissementId() async { | ||
| return await _secureStorage.read(key: 'etablissement_id'); | ||
| } | ||
|
|
||
| Future<bool> 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<String, dynamic>? queryParameters) { | ||
| final queryStr = | ||
| queryParameters != null | ||
| ? Uri(queryParameters: queryParameters).query | ||
| : ''; | ||
| return '$path?$queryStr'; | ||
| } | ||
|
|
||
| Future<Response> get( | ||
| String path, { | ||
| Map<String, dynamic>? 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<Response> post(String path, {dynamic data}) async { | ||
| try { | ||
| final response = await _dio.post(path, data: data); | ||
| return response; | ||
| } catch (e) { | ||
| rethrow; | ||
| } | ||
| } | ||
| } | ||
71 changes: 71 additions & 0 deletions
71
lib/features/discharge/data/services/discharge_cache_service.dart
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,71 @@ | ||
| import 'dart:convert'; | ||
| import 'package:shared_preferences/shared_preferences.dart'; | ||
| import 'package:progres/features/discharge/data/models/dischage.dart'; | ||
|
|
||
| class DischargeCacheService { | ||
| // Keys for SharedPreferences | ||
| static const String _dischargeKey = 'cached_discharge'; | ||
| static const String _lastUpdatedKeyPrefix = 'last_updated_'; | ||
|
|
||
| // Save discharge to cache | ||
| Future<bool> cacheDischarge(StudentDischarge discharge) async { | ||
| try { | ||
| final prefs = await SharedPreferences.getInstance(); | ||
| final dischargeJson = discharge.toJson(); | ||
| await prefs.setString(_dischargeKey, jsonEncode(dischargeJson)); | ||
| await prefs.setString( | ||
| '${_lastUpdatedKeyPrefix}discharge', | ||
| DateTime.now().toIso8601String(), | ||
| ); | ||
| return true; | ||
| } catch (e) { | ||
| print('Error caching discharge: $e'); | ||
|
AliAkrem marked this conversation as resolved.
Outdated
|
||
| return false; | ||
| } | ||
| } | ||
|
|
||
| // Retrieve discharge from cache | ||
| Future<StudentDischarge?> getCachedDischarge() async { | ||
| try { | ||
| final prefs = await SharedPreferences.getInstance(); | ||
| final dischargeString = prefs.getString(_dischargeKey); | ||
|
|
||
| if (dischargeString == null) return null; | ||
|
|
||
| final Map<String, dynamic> decodedJson = jsonDecode(dischargeString); | ||
| return StudentDischarge.fromJson(decodedJson); | ||
| } catch (e) { | ||
| print('Error retrieving cached discharge: $e'); | ||
| return null; | ||
| } | ||
|
AliAkrem marked this conversation as resolved.
Outdated
|
||
| } | ||
|
|
||
| // Get last update timestamp for discharge data | ||
| Future<DateTime?> getLastUpdated() async { | ||
| try { | ||
| final prefs = await SharedPreferences.getInstance(); | ||
| const key = '${_lastUpdatedKeyPrefix}discharge'; | ||
|
AliAkrem marked this conversation as resolved.
Outdated
|
||
|
|
||
| final timestamp = prefs.getString(key); | ||
| if (timestamp == null) return null; | ||
|
|
||
| return DateTime.parse(timestamp); | ||
| } catch (e) { | ||
| print('Error getting last updated time: $e'); | ||
| return null; | ||
| } | ||
| } | ||
|
|
||
| // Clear discharge cache | ||
| Future<bool> clearCache() async { | ||
| try { | ||
| final prefs = await SharedPreferences.getInstance(); | ||
| await prefs.remove(_dischargeKey); | ||
| await prefs.remove('${_lastUpdatedKeyPrefix}discharge'); | ||
| return true; | ||
| } catch (e) { | ||
| print('Error clearing discharge cache: $e'); | ||
| return false; | ||
| } | ||
| } | ||
| } | ||
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.