From 210c40812b9b965cd0902940cf1027ca999a7f30 Mon Sep 17 00:00:00 2001 From: Naresh Pratista <2141720057@student.polinema.ac.id> Date: Wed, 23 Oct 2024 15:44:33 +0700 Subject: [PATCH] fix: correct learning history data handling and model parsing --- lib/core/services/dio_client.dart | 11 +- .../repositories/history_repository.dart | 29 ++-- lib/core/utils/styles/theme.dart | 1 + .../history/models/history_model.dart | 5 +- .../history/provider/history_provider.dart | 23 ++- .../history/screens/history_screen.dart | 137 ++++++++++-------- .../widgets/exercise_history_card.dart | 9 +- 7 files changed, 139 insertions(+), 76 deletions(-) diff --git a/lib/core/services/dio_client.dart b/lib/core/services/dio_client.dart index 2c7bde1..4893892 100644 --- a/lib/core/services/dio_client.dart +++ b/lib/core/services/dio_client.dart @@ -320,10 +320,19 @@ class DioClient { } } - Future getLearningHistory(String sectionId, String token) async { + Future getLearningHistory( + String sectionId, + String token, { + int page = 1, + int limit = 5, + }) async { try { final response = await _dio.get( '/learningHistory/section/$sectionId', + queryParameters: { + 'page': page, + 'limit': limit, + }, options: Options( headers: { 'Authorization': 'Bearer $token', diff --git a/lib/core/services/repositories/history_repository.dart b/lib/core/services/repositories/history_repository.dart index 8df6b70..f35814d 100644 --- a/lib/core/services/repositories/history_repository.dart +++ b/lib/core/services/repositories/history_repository.dart @@ -8,24 +8,33 @@ class HistoryRepository { HistoryRepository(this._dioClient); Future> getLearningHistory( - String sectionId, String token) async { + String sectionId, + String token, + ) async { try { - final response = await _dioClient.getLearningHistory(sectionId, token); + final response = await _dioClient.getLearningHistory( + sectionId, + token, + page: 1, + limit: 1000, + ); if (response.statusCode == 200 && response.data != null) { - if (response.data['payload'] != null) { - final List historyData = response.data['payload']; - return historyData - .map((json) => LearningHistory.fromJson(json)) - .toList(); - } else { - throw Exception('No history data available'); + // Perbaikan disini: langsung mengakses ['payload']['history'] + final List historyData = response.data['payload']['history']; + if (historyData.isEmpty) { + return []; // Mengembalikan list kosong jika tidak ada data } + return historyData + .map((json) => LearningHistory.fromJson(json)) + .toList(); } else { throw Exception( 'Failed to load learning history: ${response.statusMessage}'); } } on DioException catch (e) { - if (e.response != null) { + if (e.response?.statusCode == 404) { + return []; + } else if (e.response != null) { throw Exception('Server error: ${e.response?.statusMessage}'); } else { throw Exception('Network error: ${e.message}'); diff --git a/lib/core/utils/styles/theme.dart b/lib/core/utils/styles/theme.dart index 3fe0619..bde2dc5 100644 --- a/lib/core/utils/styles/theme.dart +++ b/lib/core/utils/styles/theme.dart @@ -19,6 +19,7 @@ class AppColors { static const Color secondaryColor = Color(0xFF5674ED); static const Color primaryColor = Color(0xFF34C3F9); static const Color yellowButtonColor = Color(0xFFFACC15); + static const Color greenColor = Color(0xFF00BC65); static LinearGradient get gradientTheme => const LinearGradient( colors: [secondaryColor, primaryColor], diff --git a/lib/features/history/models/history_model.dart b/lib/features/history/models/history_model.dart index 7cfc985..0d749da 100644 --- a/lib/features/history/models/history_model.dart +++ b/lib/features/history/models/history_model.dart @@ -1,12 +1,13 @@ import 'package:intl/intl.dart'; class LearningHistory { - final int score; + final int? score; final String currentLevel; final String? nextLevel; final DateTime? studentFinish; final String topicName; final String sectionName; + final bool isPass; LearningHistory({ required this.score, @@ -15,6 +16,7 @@ class LearningHistory { required this.studentFinish, required this.topicName, required this.sectionName, + required this.isPass, }); factory LearningHistory.fromJson(Map json) { @@ -27,6 +29,7 @@ class LearningHistory { : null, topicName: json['TOPIC_NAME'] ?? 'Unknown Topic', sectionName: json['SECTION_NAME'] ?? 'Unknown Section', + isPass: json['IS_PASS'] == 1, ); } diff --git a/lib/features/history/provider/history_provider.dart b/lib/features/history/provider/history_provider.dart index 569cc41..4d91eb7 100644 --- a/lib/features/history/provider/history_provider.dart +++ b/lib/features/history/provider/history_provider.dart @@ -27,7 +27,10 @@ class HistoryProvider with ChangeNotifier { notifyListeners(); } - Future fetchLearningHistory(String token) async { + Future fetchLearningHistory( + String token, { + bool refresh = false, + }) async { if (_sectionProvider.sections.isEmpty) { _error = 'No sections available'; notifyListeners(); @@ -40,8 +43,12 @@ class HistoryProvider with ChangeNotifier { notifyListeners(); try { - final history = await _repository.getLearningHistory(sectionId, token); + final history = await _repository.getLearningHistory( + sectionId, + token, + ); _learningHistory = history; + _error = null; } catch (e) { _error = 'Error fetching learning history: ${e.toString()}'; } finally { @@ -50,12 +57,20 @@ class HistoryProvider with ChangeNotifier { } } - Color getColorForLevels(dynamic currentLevel, dynamic nextLevel) { + Color getColorForLevels( + dynamic currentLevel, + dynamic nextLevel, + bool isPass, + ) { + if (isPass) { + return AppColors.greenColor; + } + int? current = _parseLevel(currentLevel); int? next = _parseLevel(nextLevel); if (current == null || next == null) { - return AppColors.blackColor; // Default color if parsing fails + return AppColors.blackColor; } if (current == next) { diff --git a/lib/features/history/screens/history_screen.dart b/lib/features/history/screens/history_screen.dart index 36c0162..9c81b12 100644 --- a/lib/features/history/screens/history_screen.dart +++ b/lib/features/history/screens/history_screen.dart @@ -19,6 +19,17 @@ class HistoryScreen extends StatefulWidget { } class _HistoryScreenState extends State { + @override + void initState() { + super.initState(); + WidgetsBinding.instance.addPostFrameCallback((_) { + final historyProvider = + Provider.of(context, listen: false); + final userProvider = Provider.of(context, listen: false); + historyProvider.fetchLearningHistory(userProvider.jwtToken!); + }); + } + bool isNotFoundError(String error) { return error.toLowerCase().contains('no learning history found') || error.toLowerCase().contains('not found'); @@ -52,68 +63,12 @@ class _HistoryScreenState extends State { ), child: Column( children: [ - Container( - padding: const EdgeInsets.all(16.0), - decoration: BoxDecoration( - color: AppColors.whiteColor, - borderRadius: BorderRadius.circular(8), - ), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - 'Your Exercise History!', - style: AppTextStyles.blueTextStyle.copyWith( - fontSize: 18, - fontWeight: FontWeight.bold, - ), - ), - const SizedBox(height: 8), - Text( - 'Track your progress with a personalized overview of all your workouts.', - style: AppTextStyles.greyTextStyle.copyWith( - fontSize: 13, - fontWeight: FontWeight.w500, - ), - ), - ], - ), - ), + _buildHeader(), const SizedBox(height: 8), const CustomTabBar(), const SizedBox(height: 8), Expanded( - child: Consumer( - builder: (context, historyProvider, child) { - if (historyProvider.isLoading) { - return const Center( - child: CircularProgressIndicator()); - } else if (historyProvider.error != null) { - if (isNotFoundError(historyProvider.error!)) { - return _buildEmptyState(context); - } else { - return _buildErrorState(historyProvider.error!); - } - } else if (historyProvider.learningHistory.isEmpty) { - return _buildEmptyState(context); - } else { - return ListView.builder( - itemCount: historyProvider.learningHistory.length, - itemBuilder: (context, index) { - return Column( - children: [ - ExerciseHistoryCard( - exercise: - historyProvider.learningHistory[index], - ), - const SizedBox(height: 8.0), - ], - ); - }, - ); - } - }, - ), + child: _buildContent(historyProvider), ), ], ), @@ -124,6 +79,72 @@ class _HistoryScreenState extends State { }); } + Widget _buildContent(HistoryProvider historyProvider) { + if (historyProvider.isLoading) { + return const Center(child: CircularProgressIndicator()); + } + + if (historyProvider.error != null) { + return isNotFoundError(historyProvider.error!) + ? _buildEmptyState(context) + : _buildErrorState(historyProvider.error!); + } + + if (historyProvider.learningHistory.isEmpty) { + return _buildEmptyState(context); + } + + return RefreshIndicator( + onRefresh: () async { + final userProvider = Provider.of(context, listen: false); + await historyProvider.fetchLearningHistory(userProvider.jwtToken!); + }, + child: ListView.builder( + itemCount: historyProvider.learningHistory.length, + itemBuilder: (context, index) { + return Column( + children: [ + ExerciseHistoryCard( + exercise: historyProvider.learningHistory[index], + ), + const SizedBox(height: 8.0), + ], + ); + }, + ), + ); + } + + Widget _buildHeader() { + return Container( + padding: const EdgeInsets.all(16.0), + decoration: BoxDecoration( + color: AppColors.whiteColor, + borderRadius: BorderRadius.circular(8), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + 'Your Exercise History!', + style: AppTextStyles.blueTextStyle.copyWith( + fontSize: 18, + fontWeight: FontWeight.bold, + ), + ), + const SizedBox(height: 8), + Text( + 'Track your progress with a personalized overview of all your workouts.', + style: AppTextStyles.greyTextStyle.copyWith( + fontSize: 13, + fontWeight: FontWeight.w500, + ), + ), + ], + ), + ); + } + Widget _buildErrorState(String error) { return Center( child: Column( diff --git a/lib/features/history/widgets/exercise_history_card.dart b/lib/features/history/widgets/exercise_history_card.dart index dd971d4..e704e8c 100644 --- a/lib/features/history/widgets/exercise_history_card.dart +++ b/lib/features/history/widgets/exercise_history_card.dart @@ -17,7 +17,10 @@ class ExerciseHistoryCard extends StatelessWidget { final historyProvider = Provider.of(context, listen: false); final color = historyProvider.getColorForLevels( - exercise.currentLevel, exercise.nextLevel); + exercise.currentLevel, + exercise.nextLevel, + exercise.isPass, + ); return Card( color: AppColors.whiteColor, @@ -54,7 +57,9 @@ class ExerciseHistoryCard extends StatelessWidget { style: AppTextStyles.blackTextStyle.copyWith(), ), TextSpan( - text: '${exercise.nextLevel}', + text: exercise.isPass + ? 'Topic Finished' + : '${exercise.nextLevel}', style: AppTextStyles.blackTextStyle.copyWith( color: color, ),