Merge branch 'review-mode-fix' into 'master'
feat: HistoryCard navigation to direct users to ReviewScreen, and refactor... See merge request profile-image/kedaireka/polinema-adapative-learning/mobile-adaptive-learning!19
This commit is contained in:
commit
09d264ed59
|
|
@ -1 +1 @@
|
||||||
const String baseUrl = 'http://54.173.167.62/';
|
const String baseUrl = 'https://1252-114-6-25-184.ngrok-free.app/';
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@ class HistoryRepository {
|
||||||
|
|
||||||
HistoryRepository(this._dioClient);
|
HistoryRepository(this._dioClient);
|
||||||
|
|
||||||
Future<List<LearningHistory>> getLearningHistory(
|
Future<List<HistoryModel>> getLearningHistory(
|
||||||
String sectionId,
|
String sectionId,
|
||||||
String token,
|
String token,
|
||||||
) async {
|
) async {
|
||||||
|
|
@ -23,9 +23,7 @@ class HistoryRepository {
|
||||||
if (historyData.isEmpty) {
|
if (historyData.isEmpty) {
|
||||||
return []; // Mengembalikan list kosong jika tidak ada data
|
return []; // Mengembalikan list kosong jika tidak ada data
|
||||||
}
|
}
|
||||||
return historyData
|
return historyData.map((json) => HistoryModel.fromJson(json)).toList();
|
||||||
.map((json) => LearningHistory.fromJson(json))
|
|
||||||
.toList();
|
|
||||||
} else {
|
} else {
|
||||||
throw Exception(
|
throw Exception(
|
||||||
'Failed to load learning history: ${response.statusMessage}');
|
'Failed to load learning history: ${response.statusMessage}');
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
import 'package:intl/intl.dart';
|
import 'package:intl/intl.dart';
|
||||||
|
|
||||||
class LearningHistory {
|
class HistoryModel {
|
||||||
|
final String idStudentLearning;
|
||||||
final int? score;
|
final int? score;
|
||||||
final String currentLevel;
|
final String currentLevel;
|
||||||
final String? nextLevel;
|
final String? nextLevel;
|
||||||
|
|
@ -9,7 +10,8 @@ class LearningHistory {
|
||||||
final String sectionName;
|
final String sectionName;
|
||||||
final bool isPass;
|
final bool isPass;
|
||||||
|
|
||||||
LearningHistory({
|
HistoryModel({
|
||||||
|
required this.idStudentLearning,
|
||||||
required this.score,
|
required this.score,
|
||||||
required this.currentLevel,
|
required this.currentLevel,
|
||||||
required this.nextLevel,
|
required this.nextLevel,
|
||||||
|
|
@ -19,8 +21,9 @@ class LearningHistory {
|
||||||
required this.isPass,
|
required this.isPass,
|
||||||
});
|
});
|
||||||
|
|
||||||
factory LearningHistory.fromJson(Map<String, dynamic> json) {
|
factory HistoryModel.fromJson(Map<String, dynamic> json) {
|
||||||
return LearningHistory(
|
return HistoryModel(
|
||||||
|
idStudentLearning: json['ID_STUDENT_LEARNING'],
|
||||||
score: json['SCORE'] ?? 0,
|
score: json['SCORE'] ?? 0,
|
||||||
currentLevel: json['CURRENT_LEVEL'] ?? 'Unknown',
|
currentLevel: json['CURRENT_LEVEL'] ?? 'Unknown',
|
||||||
nextLevel: json['NEXT_LEVEL'],
|
nextLevel: json['NEXT_LEVEL'],
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@ import 'package:english_learning/features/history/models/history_model.dart';
|
||||||
class HistoryProvider with ChangeNotifier {
|
class HistoryProvider with ChangeNotifier {
|
||||||
final HistoryRepository _repository;
|
final HistoryRepository _repository;
|
||||||
final SectionProvider _sectionProvider;
|
final SectionProvider _sectionProvider;
|
||||||
List<LearningHistory> _learningHistory = [];
|
List<HistoryModel> _historyModel = [];
|
||||||
int _selectedPageIndex = 0;
|
int _selectedPageIndex = 0;
|
||||||
bool _isLoading = false;
|
bool _isLoading = false;
|
||||||
String? _error;
|
String? _error;
|
||||||
|
|
@ -18,7 +18,7 @@ class HistoryProvider with ChangeNotifier {
|
||||||
this._sectionProvider,
|
this._sectionProvider,
|
||||||
);
|
);
|
||||||
|
|
||||||
List<LearningHistory> get learningHistory => _learningHistory;
|
List<HistoryModel> get historyModel => _historyModel;
|
||||||
int get selectedPageIndex => _selectedPageIndex;
|
int get selectedPageIndex => _selectedPageIndex;
|
||||||
bool get isLoading => _isLoading;
|
bool get isLoading => _isLoading;
|
||||||
String? get error => _error;
|
String? get error => _error;
|
||||||
|
|
@ -46,13 +46,13 @@ class HistoryProvider with ChangeNotifier {
|
||||||
String firstSectionId = _sectionProvider.sections.first.id;
|
String firstSectionId = _sectionProvider.sections.first.id;
|
||||||
|
|
||||||
// Fetch history for the first section
|
// Fetch history for the first section
|
||||||
_learningHistory =
|
_historyModel =
|
||||||
await _repository.getLearningHistory(firstSectionId, token);
|
await _repository.getLearningHistory(firstSectionId, token);
|
||||||
_error = null;
|
_error = null;
|
||||||
_isInitialized = true; // Mark as initialized
|
_isInitialized = true; // Mark as initialized
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
_error = 'Error loading data: ${e.toString()}';
|
_error = 'Error loading data: ${e.toString()}';
|
||||||
_learningHistory = [];
|
_historyModel = [];
|
||||||
} finally {
|
} finally {
|
||||||
_isLoading = false;
|
_isLoading = false;
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
|
|
@ -78,11 +78,11 @@ class HistoryProvider with ChangeNotifier {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
final history = await _repository.getLearningHistory(sectionId, token);
|
final history = await _repository.getLearningHistory(sectionId, token);
|
||||||
_learningHistory = history;
|
_historyModel = history;
|
||||||
_error = null;
|
_error = null;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
_error = 'Error fetching learning history: ${e.toString()}';
|
_error = 'Error fetching learning history: ${e.toString()}';
|
||||||
_learningHistory = [];
|
_historyModel = [];
|
||||||
} finally {
|
} finally {
|
||||||
_isLoading = false;
|
_isLoading = false;
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
|
|
@ -151,14 +151,14 @@ class HistoryProvider with ChangeNotifier {
|
||||||
await _sectionProvider.fetchSections(token);
|
await _sectionProvider.fetchSections(token);
|
||||||
if (_sectionProvider.sections.isNotEmpty) {
|
if (_sectionProvider.sections.isNotEmpty) {
|
||||||
String firstSectionId = _sectionProvider.sections.first.id;
|
String firstSectionId = _sectionProvider.sections.first.id;
|
||||||
_learningHistory =
|
_historyModel =
|
||||||
await _repository.getLearningHistory(firstSectionId, token);
|
await _repository.getLearningHistory(firstSectionId, token);
|
||||||
} else {
|
} else {
|
||||||
_error = 'No sections available';
|
_error = 'No sections available';
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
_error = 'Error loading data: ${e.toString()}';
|
_error = 'Error loading data: ${e.toString()}';
|
||||||
_learningHistory = []; // Clear the list in case of error
|
_historyModel = []; // Clear the list in case of error
|
||||||
} finally {
|
} finally {
|
||||||
_isLoading = false;
|
_isLoading = false;
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
|
|
|
||||||
|
|
@ -144,7 +144,7 @@ class _HistoryScreenState extends State<HistoryScreen> {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Tampilkan empty state jika tidak ada history
|
// Tampilkan empty state jika tidak ada history
|
||||||
if (historyProvider.learningHistory.isEmpty) {
|
if (historyProvider.historyModel.isEmpty) {
|
||||||
return _buildEmptyState(context);
|
return _buildEmptyState(context);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -154,8 +154,8 @@ class _HistoryScreenState extends State<HistoryScreen> {
|
||||||
child: AnimatedSwitcher(
|
child: AnimatedSwitcher(
|
||||||
duration: const Duration(milliseconds: 300),
|
duration: const Duration(milliseconds: 300),
|
||||||
child: ListView.builder(
|
child: ListView.builder(
|
||||||
key: ValueKey(historyProvider.learningHistory.length),
|
key: ValueKey(historyProvider.historyModel.length),
|
||||||
itemCount: historyProvider.learningHistory.length,
|
itemCount: historyProvider.historyModel.length,
|
||||||
itemBuilder: (context, index) {
|
itemBuilder: (context, index) {
|
||||||
return AnimatedOpacity(
|
return AnimatedOpacity(
|
||||||
opacity: 1.0,
|
opacity: 1.0,
|
||||||
|
|
@ -163,7 +163,7 @@ class _HistoryScreenState extends State<HistoryScreen> {
|
||||||
child: Column(
|
child: Column(
|
||||||
children: [
|
children: [
|
||||||
ExerciseHistoryCard(
|
ExerciseHistoryCard(
|
||||||
exercise: historyProvider.learningHistory[index],
|
exercise: historyProvider.historyModel[index],
|
||||||
),
|
),
|
||||||
const SizedBox(height: 8.0),
|
const SizedBox(height: 8.0),
|
||||||
],
|
],
|
||||||
|
|
|
||||||
|
|
@ -1,28 +1,47 @@
|
||||||
import 'package:english_learning/features/history/models/history_model.dart';
|
import 'package:english_learning/features/history/models/history_model.dart';
|
||||||
import 'package:english_learning/features/history/provider/history_provider.dart';
|
import 'package:english_learning/features/history/provider/history_provider.dart';
|
||||||
import 'package:english_learning/core/utils/styles/theme.dart';
|
import 'package:english_learning/core/utils/styles/theme.dart';
|
||||||
|
import 'package:english_learning/features/learning/modules/exercises/screens/review_screen.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
|
|
||||||
class ExerciseHistoryCard extends StatelessWidget {
|
class ExerciseHistoryCard extends StatefulWidget {
|
||||||
final LearningHistory exercise;
|
final HistoryModel exercise;
|
||||||
|
|
||||||
const ExerciseHistoryCard({
|
const ExerciseHistoryCard({
|
||||||
super.key,
|
super.key,
|
||||||
required this.exercise,
|
required this.exercise,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<ExerciseHistoryCard> createState() => _ExerciseHistoryCardState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _ExerciseHistoryCardState extends State<ExerciseHistoryCard> {
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final historyProvider =
|
final historyProvider =
|
||||||
Provider.of<HistoryProvider>(context, listen: false);
|
Provider.of<HistoryProvider>(context, listen: false);
|
||||||
final color = historyProvider.getColorForLevels(
|
final color = historyProvider.getColorForLevels(
|
||||||
exercise.currentLevel,
|
widget.exercise.currentLevel,
|
||||||
exercise.nextLevel,
|
widget.exercise.nextLevel,
|
||||||
exercise.isPass,
|
widget.exercise.isPass,
|
||||||
);
|
);
|
||||||
|
|
||||||
return Card(
|
return GestureDetector(
|
||||||
|
onTap: () {
|
||||||
|
Navigator.push(
|
||||||
|
context,
|
||||||
|
MaterialPageRoute(
|
||||||
|
builder: (context) => ReviewScreen(
|
||||||
|
idStudentLearning: widget.exercise.idStudentLearning,
|
||||||
|
topicTitle: widget.exercise.topicName,
|
||||||
|
topicId: null,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
child: Card(
|
||||||
color: AppColors.whiteColor,
|
color: AppColors.whiteColor,
|
||||||
shape: RoundedRectangleBorder(
|
shape: RoundedRectangleBorder(
|
||||||
borderRadius: BorderRadius.circular(12.0),
|
borderRadius: BorderRadius.circular(12.0),
|
||||||
|
|
@ -38,7 +57,7 @@ class ExerciseHistoryCard extends StatelessWidget {
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
Text(
|
Text(
|
||||||
exercise.topicName,
|
widget.exercise.topicName,
|
||||||
overflow: TextOverflow.ellipsis,
|
overflow: TextOverflow.ellipsis,
|
||||||
style: AppTextStyles.tetriaryTextStyle.copyWith(
|
style: AppTextStyles.tetriaryTextStyle.copyWith(
|
||||||
fontWeight: FontWeight.w500,
|
fontWeight: FontWeight.w500,
|
||||||
|
|
@ -53,13 +72,13 @@ class ExerciseHistoryCard extends StatelessWidget {
|
||||||
),
|
),
|
||||||
children: [
|
children: [
|
||||||
TextSpan(
|
TextSpan(
|
||||||
text: '${exercise.currentLevel} → ',
|
text: '${widget.exercise.currentLevel} → ',
|
||||||
style: AppTextStyles.blackTextStyle.copyWith(),
|
style: AppTextStyles.blackTextStyle.copyWith(),
|
||||||
),
|
),
|
||||||
TextSpan(
|
TextSpan(
|
||||||
text: exercise.isPass
|
text: widget.exercise.isPass
|
||||||
? 'Topic Finished'
|
? 'Topic Finished'
|
||||||
: '${exercise.nextLevel}',
|
: '${widget.exercise.nextLevel}',
|
||||||
style: AppTextStyles.blackTextStyle.copyWith(
|
style: AppTextStyles.blackTextStyle.copyWith(
|
||||||
color: color,
|
color: color,
|
||||||
),
|
),
|
||||||
|
|
@ -69,7 +88,7 @@ class ExerciseHistoryCard extends StatelessWidget {
|
||||||
),
|
),
|
||||||
const SizedBox(height: 8),
|
const SizedBox(height: 8),
|
||||||
Text(
|
Text(
|
||||||
'Submission: ${exercise.formattedDate}',
|
'Submission: ${widget.exercise.formattedDate}',
|
||||||
style: AppTextStyles.disableTextStyle.copyWith(
|
style: AppTextStyles.disableTextStyle.copyWith(
|
||||||
fontSize: 12,
|
fontSize: 12,
|
||||||
fontWeight: FontWeight.w600,
|
fontWeight: FontWeight.w600,
|
||||||
|
|
@ -91,7 +110,7 @@ class ExerciseHistoryCard extends StatelessWidget {
|
||||||
borderRadius: BorderRadius.circular(8.0),
|
borderRadius: BorderRadius.circular(8.0),
|
||||||
),
|
),
|
||||||
child: Text(
|
child: Text(
|
||||||
'${exercise.score}/100',
|
'${widget.exercise.score}/100',
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
color: color,
|
color: color,
|
||||||
fontWeight: FontWeight.bold,
|
fontWeight: FontWeight.bold,
|
||||||
|
|
@ -101,6 +120,7 @@ class ExerciseHistoryCard extends StatelessWidget {
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -36,7 +36,7 @@ class ReviewExerciseModel {
|
||||||
}
|
}
|
||||||
|
|
||||||
class ReviewExerciseDetail {
|
class ReviewExerciseDetail {
|
||||||
final String idStudentExercise;
|
final String? idStudentExercise;
|
||||||
final String idAdminExercise;
|
final String idAdminExercise;
|
||||||
final String title;
|
final String title;
|
||||||
final String question;
|
final String question;
|
||||||
|
|
@ -46,14 +46,14 @@ class ReviewExerciseDetail {
|
||||||
final String? video;
|
final String? video;
|
||||||
final String? audio;
|
final String? audio;
|
||||||
|
|
||||||
final String answerStudent;
|
String answerStudent;
|
||||||
final int isCorrect;
|
int isCorrect;
|
||||||
final double resultScoreStudent;
|
final double resultScoreStudent;
|
||||||
final List<ReviewMultipleChoice>? multipleChoices;
|
final List<ReviewMultipleChoice>? multipleChoices;
|
||||||
final List<ReviewMatchingPair>? matchingPairs;
|
final List<ReviewMatchingPair>? matchingPairs;
|
||||||
|
|
||||||
ReviewExerciseDetail({
|
ReviewExerciseDetail({
|
||||||
required this.idStudentExercise,
|
this.idStudentExercise,
|
||||||
required this.idAdminExercise,
|
required this.idAdminExercise,
|
||||||
required this.title,
|
required this.title,
|
||||||
required this.question,
|
required this.question,
|
||||||
|
|
|
||||||
|
|
@ -374,6 +374,14 @@ class ExerciseProvider extends ChangeNotifier {
|
||||||
|
|
||||||
// Reset current index
|
// Reset current index
|
||||||
_currentExerciseIndex = 0;
|
_currentExerciseIndex = 0;
|
||||||
|
|
||||||
|
// Ensure all exercises are displayed with empty answers for unanswered questions
|
||||||
|
for (var exercise in _reviewExercises) {
|
||||||
|
if (exercise.answerStudent.isEmpty) {
|
||||||
|
exercise.answerStudent = ''; // Set empty answer
|
||||||
|
exercise.isCorrect = 0; // Mark as incorrect
|
||||||
|
}
|
||||||
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
print('Error fetching review exercises: $e');
|
print('Error fetching review exercises: $e');
|
||||||
rethrow;
|
rethrow;
|
||||||
|
|
|
||||||
|
|
@ -12,15 +12,15 @@ class ExerciseScreen extends StatefulWidget {
|
||||||
final String? levelId;
|
final String? levelId;
|
||||||
final String studentLearningId;
|
final String studentLearningId;
|
||||||
final bool isReview;
|
final bool isReview;
|
||||||
final String topicId; // Tambahkan ini
|
final String topicId;
|
||||||
final String topicTitle; // Tambahkan ini
|
final String topicTitle;
|
||||||
|
|
||||||
const ExerciseScreen({
|
const ExerciseScreen({
|
||||||
super.key,
|
super.key,
|
||||||
required this.levelId,
|
required this.levelId,
|
||||||
required this.studentLearningId,
|
required this.studentLearningId,
|
||||||
this.isReview = false,
|
|
||||||
required this.topicId,
|
required this.topicId,
|
||||||
|
required this.isReview,
|
||||||
required this.topicTitle,
|
required this.topicTitle,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -36,12 +36,7 @@ class _ExerciseScreenState extends State<ExerciseScreen> {
|
||||||
_scrollController = ScrollController();
|
_scrollController = ScrollController();
|
||||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||||
final provider = context.read<ExerciseProvider>();
|
final provider = context.read<ExerciseProvider>();
|
||||||
|
|
||||||
if (widget.isReview) {
|
|
||||||
provider.fetchReviewExercises(widget.studentLearningId);
|
|
||||||
} else {
|
|
||||||
provider.fetchExercises(widget.levelId!);
|
provider.fetchExercises(widget.levelId!);
|
||||||
}
|
|
||||||
provider.setStudentLearningId(widget.studentLearningId);
|
provider.setStudentLearningId(widget.studentLearningId);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
@ -70,9 +65,7 @@ class _ExerciseScreenState extends State<ExerciseScreen> {
|
||||||
}
|
}
|
||||||
|
|
||||||
final currentExercise = provider.currentExercise;
|
final currentExercise = provider.currentExercise;
|
||||||
final hasExercises = widget.isReview
|
final hasExercises = provider.exercises.isNotEmpty;
|
||||||
? provider.reviewExercises.isNotEmpty
|
|
||||||
: provider.exercises.isNotEmpty;
|
|
||||||
|
|
||||||
if (!hasExercises || currentExercise == null) {
|
if (!hasExercises || currentExercise == null) {
|
||||||
return const Scaffold(
|
return const Scaffold(
|
||||||
|
|
@ -85,15 +78,6 @@ class _ExerciseScreenState extends State<ExerciseScreen> {
|
||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
elevation: 0,
|
elevation: 0,
|
||||||
automaticallyImplyLeading: false,
|
automaticallyImplyLeading: false,
|
||||||
leading: widget.isReview
|
|
||||||
? IconButton(
|
|
||||||
icon: const Icon(
|
|
||||||
BootstrapIcons.arrow_left,
|
|
||||||
color: AppColors.whiteColor,
|
|
||||||
),
|
|
||||||
onPressed: () => Navigator.of(context).pop(),
|
|
||||||
)
|
|
||||||
: null, // Show back button only in review mode
|
|
||||||
iconTheme: const IconThemeData(color: AppColors.whiteColor),
|
iconTheme: const IconThemeData(color: AppColors.whiteColor),
|
||||||
centerTitle: true,
|
centerTitle: true,
|
||||||
title: Text(
|
title: Text(
|
||||||
|
|
@ -108,8 +92,7 @@ class _ExerciseScreenState extends State<ExerciseScreen> {
|
||||||
gradient: AppColors.gradientTheme,
|
gradient: AppColors.gradientTheme,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
actions: !widget.isReview
|
actions: [
|
||||||
? [
|
|
||||||
IconButton(
|
IconButton(
|
||||||
icon: const Icon(
|
icon: const Icon(
|
||||||
BootstrapIcons.info_circle,
|
BootstrapIcons.info_circle,
|
||||||
|
|
@ -118,13 +101,11 @@ class _ExerciseScreenState extends State<ExerciseScreen> {
|
||||||
),
|
),
|
||||||
onPressed: () => _showInstructions(context),
|
onPressed: () => _showInstructions(context),
|
||||||
),
|
),
|
||||||
]
|
],
|
||||||
: null,
|
|
||||||
),
|
),
|
||||||
body: SafeArea(
|
body: SafeArea(
|
||||||
child: Column(
|
child: Column(
|
||||||
children: [
|
children: [
|
||||||
if (!widget.isReview)
|
|
||||||
const Padding(
|
const Padding(
|
||||||
padding: EdgeInsets.all(16.0),
|
padding: EdgeInsets.all(16.0),
|
||||||
child: ExerciseProgress(),
|
child: ExerciseProgress(),
|
||||||
|
|
@ -140,12 +121,12 @@ class _ExerciseScreenState extends State<ExerciseScreen> {
|
||||||
ExerciseContent(
|
ExerciseContent(
|
||||||
key: ValueKey(provider.currentExerciseIndex),
|
key: ValueKey(provider.currentExerciseIndex),
|
||||||
exercise: currentExercise,
|
exercise: currentExercise,
|
||||||
isReview: widget.isReview,
|
isReview: false,
|
||||||
),
|
),
|
||||||
const SizedBox(height: 24),
|
const SizedBox(height: 24),
|
||||||
ExerciseNavigator(
|
ExerciseNavigator(
|
||||||
onScrollToTop: _scrollToTop,
|
onScrollToTop: _scrollToTop,
|
||||||
isReview: widget.isReview,
|
isReview: false,
|
||||||
topicId: widget.topicId,
|
topicId: widget.topicId,
|
||||||
topicTitle: widget.topicTitle,
|
topicTitle: widget.topicTitle,
|
||||||
),
|
),
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,122 @@
|
||||||
|
import 'package:bootstrap_icons/bootstrap_icons.dart';
|
||||||
|
import 'package:english_learning/features/learning/modules/exercises/providers/exercise_provider.dart';
|
||||||
|
import 'package:english_learning/features/learning/modules/exercises/widgets/content/exercise_content.dart';
|
||||||
|
import 'package:english_learning/features/learning/modules/exercises/widgets/exercise_navigator.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:provider/provider.dart';
|
||||||
|
import 'package:english_learning/core/utils/styles/theme.dart';
|
||||||
|
|
||||||
|
class ReviewScreen extends StatefulWidget {
|
||||||
|
final String idStudentLearning;
|
||||||
|
final String? topicId;
|
||||||
|
final String topicTitle;
|
||||||
|
|
||||||
|
const ReviewScreen({
|
||||||
|
super.key,
|
||||||
|
required this.idStudentLearning,
|
||||||
|
this.topicId,
|
||||||
|
required this.topicTitle,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<ReviewScreen> createState() => _ReviewScreenState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _ReviewScreenState extends State<ReviewScreen> {
|
||||||
|
late ScrollController _scrollController;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
_scrollController = ScrollController();
|
||||||
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||||
|
final provider = context.read<ExerciseProvider>();
|
||||||
|
provider.fetchReviewExercises(widget.idStudentLearning);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
_scrollController.dispose();
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
void _scrollToTop() {
|
||||||
|
_scrollController.animateTo(
|
||||||
|
0,
|
||||||
|
duration: const Duration(milliseconds: 300),
|
||||||
|
curve: Curves.easeOut,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Consumer<ExerciseProvider>(builder: (context, provider, child) {
|
||||||
|
if (provider.isLoading) {
|
||||||
|
return const Scaffold(
|
||||||
|
body: Center(child: CircularProgressIndicator()),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
final currentExercise = provider.currentExercise;
|
||||||
|
final hasExercises = provider.reviewExercises.isNotEmpty;
|
||||||
|
|
||||||
|
if (!hasExercises || currentExercise == null) {
|
||||||
|
return const Scaffold(
|
||||||
|
body: Center(child: Text('No review exercises available')),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Scaffold(
|
||||||
|
backgroundColor: AppColors.bgSoftColor,
|
||||||
|
appBar: AppBar(
|
||||||
|
elevation: 0,
|
||||||
|
centerTitle: true,
|
||||||
|
title: Text(
|
||||||
|
'${provider.nameTopic} - ${provider.nameLevel}',
|
||||||
|
style: AppTextStyles.whiteTextStyle.copyWith(
|
||||||
|
fontSize: 14,
|
||||||
|
fontWeight: FontWeight.w900,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
flexibleSpace: Container(
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
gradient: AppColors.gradientTheme,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
leading: IconButton(
|
||||||
|
icon: const Icon(
|
||||||
|
BootstrapIcons.arrow_left,
|
||||||
|
color: AppColors.whiteColor,
|
||||||
|
),
|
||||||
|
onPressed: () => Navigator.of(context).pop(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
body: SafeArea(
|
||||||
|
child: SingleChildScrollView(
|
||||||
|
controller: _scrollController,
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 16.0),
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
ExerciseContent(
|
||||||
|
key: ValueKey(provider.currentExerciseIndex),
|
||||||
|
exercise: currentExercise,
|
||||||
|
isReview: true,
|
||||||
|
),
|
||||||
|
const SizedBox(height: 24),
|
||||||
|
ExerciseNavigator(
|
||||||
|
onScrollToTop: _scrollToTop,
|
||||||
|
isReview: true,
|
||||||
|
topicId: widget.topicId ?? '',
|
||||||
|
topicTitle: widget.topicTitle,
|
||||||
|
),
|
||||||
|
const SizedBox(height: 32),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -12,14 +12,14 @@ import 'package:provider/provider.dart';
|
||||||
class ExerciseNavigator extends StatelessWidget {
|
class ExerciseNavigator extends StatelessWidget {
|
||||||
final VoidCallback? onScrollToTop;
|
final VoidCallback? onScrollToTop;
|
||||||
final bool isReview;
|
final bool isReview;
|
||||||
final String topicId;
|
final String? topicId;
|
||||||
final String topicTitle;
|
final String topicTitle;
|
||||||
|
|
||||||
const ExerciseNavigator({
|
const ExerciseNavigator({
|
||||||
super.key,
|
super.key,
|
||||||
required this.onScrollToTop,
|
required this.onScrollToTop,
|
||||||
this.isReview = false,
|
this.isReview = false,
|
||||||
required this.topicId,
|
this.topicId,
|
||||||
required this.topicTitle,
|
required this.topicTitle,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -48,7 +48,7 @@ class ExerciseNavigator extends StatelessWidget {
|
||||||
score: int.tryParse(result['SCORE'].toString()) ?? 0,
|
score: int.tryParse(result['SCORE'].toString()) ?? 0,
|
||||||
isCompleted: result['IS_PASS'] == 1,
|
isCompleted: result['IS_PASS'] == 1,
|
||||||
stdLearningId: result['STUDENT_LEARNING_ID']?.toString(),
|
stdLearningId: result['STUDENT_LEARNING_ID']?.toString(),
|
||||||
topicId: topicId,
|
topicId: topicId!,
|
||||||
topicTitle: topicTitle,
|
topicTitle: topicTitle,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
import 'package:bootstrap_icons/bootstrap_icons.dart';
|
import 'package:bootstrap_icons/bootstrap_icons.dart';
|
||||||
import 'package:english_learning/core/utils/styles/theme.dart';
|
import 'package:english_learning/core/utils/styles/theme.dart';
|
||||||
import 'package:english_learning/core/widgets/custom_button.dart';
|
import 'package:english_learning/core/widgets/custom_button.dart';
|
||||||
|
import 'package:english_learning/features/learning/modules/exercises/screens/review_screen.dart';
|
||||||
import 'package:english_learning/features/learning/modules/level/models/level_model.dart';
|
import 'package:english_learning/features/learning/modules/level/models/level_model.dart';
|
||||||
import 'package:english_learning/features/learning/modules/level/providers/level_provider.dart';
|
import 'package:english_learning/features/learning/modules/level/providers/level_provider.dart';
|
||||||
import 'package:english_learning/features/learning/modules/material/screens/material_screen.dart';
|
import 'package:english_learning/features/learning/modules/material/screens/material_screen.dart';
|
||||||
|
|
@ -33,11 +34,9 @@ class PretestCard extends StatelessWidget {
|
||||||
Navigator.push(
|
Navigator.push(
|
||||||
context,
|
context,
|
||||||
MaterialPageRoute(
|
MaterialPageRoute(
|
||||||
builder: (context) => MaterialScreen(
|
builder: (context) => ReviewScreen(
|
||||||
levelId: pretest.idLevel,
|
idStudentLearning: pretest.idStudentLearning!,
|
||||||
isReview: true,
|
topicId: null,
|
||||||
studentLearningId: pretest.idStudentLearning,
|
|
||||||
topicId: pretest.idTopic,
|
|
||||||
topicTitle: pretest.nameTopic,
|
topicTitle: pretest.nameTopic,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ import 'package:english_learning/core/widgets/custom_snackbar.dart';
|
||||||
import 'package:english_learning/core/widgets/global_button.dart';
|
import 'package:english_learning/core/widgets/global_button.dart';
|
||||||
import 'package:english_learning/features/auth/provider/user_provider.dart';
|
import 'package:english_learning/features/auth/provider/user_provider.dart';
|
||||||
import 'package:english_learning/features/learning/modules/exercises/screens/exercise_screen.dart';
|
import 'package:english_learning/features/learning/modules/exercises/screens/exercise_screen.dart';
|
||||||
|
import 'package:english_learning/features/learning/modules/exercises/screens/review_screen.dart';
|
||||||
import 'package:english_learning/features/learning/modules/level/providers/level_provider.dart';
|
import 'package:english_learning/features/learning/modules/level/providers/level_provider.dart';
|
||||||
import 'package:english_learning/features/learning/modules/material/widgets/audio_player_widget.dart';
|
import 'package:english_learning/features/learning/modules/material/widgets/audio_player_widget.dart';
|
||||||
import 'package:english_learning/features/learning/modules/material/widgets/image_widget.dart';
|
import 'package:english_learning/features/learning/modules/material/widgets/image_widget.dart';
|
||||||
|
|
@ -67,8 +68,8 @@ class _MaterialScreenState extends State<MaterialScreen>
|
||||||
|
|
||||||
Future<void> _createStudentLearning() async {
|
Future<void> _createStudentLearning() async {
|
||||||
if (widget.isReview && widget.studentLearningId != null) {
|
if (widget.isReview && widget.studentLearningId != null) {
|
||||||
// Jika mode review dan studentLearningId tersedia, langsung navigasi ke ExerciseScreen
|
// Jika mode review dan studentLearningId tersedia, langsung navigasi ke ReviewScreen
|
||||||
_navigateToExercise(widget.studentLearningId!);
|
_navigateToReview(widget.studentLearningId!);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
setState(() {
|
setState(() {
|
||||||
|
|
@ -102,7 +103,20 @@ class _MaterialScreenState extends State<MaterialScreen>
|
||||||
builder: (context) => ExerciseScreen(
|
builder: (context) => ExerciseScreen(
|
||||||
levelId: widget.levelId,
|
levelId: widget.levelId,
|
||||||
studentLearningId: studentLearningId,
|
studentLearningId: studentLearningId,
|
||||||
isReview: widget.isReview,
|
isReview: false,
|
||||||
|
topicId: widget.topicId,
|
||||||
|
topicTitle: widget.topicTitle,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
void _navigateToReview(String studentLearningId) {
|
||||||
|
Navigator.pushReplacement(
|
||||||
|
context,
|
||||||
|
MaterialPageRoute(
|
||||||
|
builder: (context) => ReviewScreen(
|
||||||
|
idStudentLearning: studentLearningId,
|
||||||
topicId: widget.topicId,
|
topicId: widget.topicId,
|
||||||
topicTitle: widget.topicTitle,
|
topicTitle: widget.topicTitle,
|
||||||
),
|
),
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user