Fix: Correct matching pairs answer formatting in review mode and refactor navigation behavior in exercise screen

This commit is contained in:
Naresh Pratista 2024-12-04 16:52:50 +07:00
parent f396ad1c07
commit 0fe7a13c0b
4 changed files with 154 additions and 82 deletions

View File

@ -1,5 +1,3 @@
const String baseUrl =
'https://ba47-2001-448a-50a0-14bf-701c-6eb0-9412-6ff4.ngrok-free.app/';
const String baseUrl = 'https://c519-114-6-25-184.ngrok-free.app/';
const String mediaUrl =
'https://ba47-2001-448a-50a0-14bf-701c-6eb0-9412-6ff4.ngrok-free.app/api/uploads/';
const String mediaUrl = 'https://c519-114-6-25-184.ngrok-free.app/api/uploads/';

View File

@ -426,8 +426,8 @@ class ExerciseProvider extends ChangeNotifier {
return exercise.answerStudent == '1' ? 'True' : 'False';
case 'MPQ':
if (exercise.answerStudent.isEmpty) return '';
return exercise.answerStudent.split(', ').map((pair) {
final parts = pair.split('-');
return exercise.answerStudent.split('| ').map((pair) {
final parts = pair.split('>');
if (parts.length == 2) {
return '${parts[0]}${parts[1]}';
}
@ -462,6 +462,10 @@ class ExerciseProvider extends ChangeNotifier {
formattedAnswer = formattedAnswer;
} else if (exercise.choices is TrueFalse) {
formattedAnswer = formattedAnswer.toLowerCase() == 'true' ? '1' : '0';
} else if (exercise.choices is MatchingPair) {
// Change '-' to '>' and ', ' to '|'
formattedAnswer =
formattedAnswer.replaceAll('-', '>').replaceAll(', ', '|');
}
return {

View File

@ -6,6 +6,7 @@ import 'package:english_learning/features/learning/modules/exercises/widgets/exe
import 'package:english_learning/features/learning/modules/exercises/widgets/instruction_dialog.dart';
import 'package:flutter/material.dart';
import 'package:english_learning/core/utils/styles/theme.dart';
import 'package:flutter/services.dart';
import 'package:provider/provider.dart';
class ExerciseScreen extends StatefulWidget {
@ -34,6 +35,17 @@ class _ExerciseScreenState extends State<ExerciseScreen> {
void initState() {
super.initState();
_scrollController = ScrollController();
// Nonaktifkan tombol back pada Android
SystemChannels.platform.setMethodCallHandler((call) async {
if (call.method == 'hardwareKeyboardEvent') {
// Cek apakah tombol back ditekan
if (call.arguments['keyCode'] == 4) {
// 4 adalah kode tombol back di Android
return true; // Mencegah default back behavior
}
}
return false;
});
WidgetsBinding.instance.addPostFrameCallback((_) {
final provider = context.read<ExerciseProvider>();
provider.fetchExercises(widget.levelId!);
@ -44,6 +56,7 @@ class _ExerciseScreenState extends State<ExerciseScreen> {
@override
void dispose() {
_scrollController.dispose();
SystemChannels.platform.setMethodCallHandler(null);
super.dispose();
}
@ -57,89 +70,143 @@ class _ExerciseScreenState extends State<ExerciseScreen> {
@override
Widget build(BuildContext context) {
return Consumer<ExerciseProvider>(builder: (context, provider, child) {
if (provider.isLoading) {
return const Scaffold(
body: Center(child: CircularProgressIndicator()),
);
}
return PopScope(
canPop: false, // Prevent default pop behavior
onPopInvokedWithResult: (didPop, result) async {
if (didPop) return;
final currentExercise = provider.currentExercise;
final hasExercises = provider.exercises.isNotEmpty;
// Show exit confirmation dialog
final shouldPop = await _showExitConfirmationDialog() ?? false;
if (shouldPop) {
Navigator.of(context).pop();
}
},
if (!hasExercises || currentExercise == null) {
return const Scaffold(
body: Center(child: Text('No exercises available')),
);
}
child: Consumer<ExerciseProvider>(builder: (context, provider, child) {
if (provider.isLoading) {
return const Scaffold(
body: Center(child: CircularProgressIndicator()),
);
}
return Scaffold(
backgroundColor: AppColors.bgSoftColor,
appBar: AppBar(
elevation: 0,
automaticallyImplyLeading: false,
iconTheme: const IconThemeData(color: AppColors.whiteColor),
centerTitle: true,
title: Text(
'${provider.nameTopic} - ${provider.nameLevel}',
style: AppTextStyles.whiteTextStyle.copyWith(
fontSize: 14,
fontWeight: FontWeight.w900,
),
),
flexibleSpace: Container(
decoration: BoxDecoration(
gradient: AppColors.gradientTheme,
),
),
actions: [
IconButton(
icon: const Icon(
BootstrapIcons.info_circle,
color: AppColors.whiteColor,
size: 22,
final currentExercise = provider.currentExercise;
final hasExercises = provider.exercises.isNotEmpty;
if (!hasExercises || currentExercise == null) {
return const Scaffold(
body: Center(child: Text('No exercises available')),
);
}
return Scaffold(
backgroundColor: AppColors.bgSoftColor,
appBar: AppBar(
elevation: 0,
automaticallyImplyLeading: false,
iconTheme: const IconThemeData(color: AppColors.whiteColor),
centerTitle: true,
title: Text(
'${provider.nameTopic} - ${provider.nameLevel}',
style: AppTextStyles.whiteTextStyle.copyWith(
fontSize: 14,
fontWeight: FontWeight.w900,
),
onPressed: () => _showInstructions(context),
),
],
),
body: SafeArea(
child: Column(
children: [
const Padding(
padding: EdgeInsets.all(16.0),
child: ExerciseProgress(),
flexibleSpace: Container(
decoration: BoxDecoration(
gradient: AppColors.gradientTheme,
),
Expanded(
child: SingleChildScrollView(
controller: _scrollController,
physics: const AlwaysScrollableScrollPhysics(),
padding: const EdgeInsets.symmetric(horizontal: 16.0),
child: Column(
children: [
const SizedBox(height: 16),
ExerciseContent(
key: ValueKey(provider.currentExerciseIndex),
exercise: currentExercise,
isReview: false,
),
const SizedBox(height: 24),
ExerciseNavigator(
onScrollToTop: _scrollToTop,
isReview: false,
topicId: widget.topicId,
topicTitle: widget.topicTitle,
),
const SizedBox(height: 32),
],
),
),
actions: [
IconButton(
icon: const Icon(
BootstrapIcons.info_circle,
color: AppColors.whiteColor,
size: 22,
),
onPressed: () => _showInstructions(context),
),
],
),
body: SafeArea(
child: Column(
children: [
const Padding(
padding: EdgeInsets.all(16.0),
child: ExerciseProgress(),
),
Expanded(
child: SingleChildScrollView(
controller: _scrollController,
physics: const AlwaysScrollableScrollPhysics(),
padding: const EdgeInsets.symmetric(horizontal: 16.0),
child: Column(
children: [
const SizedBox(height: 16),
ExerciseContent(
key: ValueKey(provider.currentExerciseIndex),
exercise: currentExercise,
isReview: false,
),
const SizedBox(height: 24),
ExerciseNavigator(
onScrollToTop: _scrollToTop,
isReview: false,
topicId: widget.topicId,
topicTitle: widget.topicTitle,
),
const SizedBox(height: 32),
],
),
),
),
],
),
),
);
}),
);
}
// Metode untuk menampilkan dialog konfirmasi keluar
Future<bool?> _showExitConfirmationDialog() async {
return showDialog<bool>(
context: context,
builder: (context) => AlertDialog(
backgroundColor: AppColors.whiteColor,
title: Text(
'Exit Exercise',
style: AppTextStyles.blackTextStyle
.copyWith(fontWeight: FontWeight.bold),
),
);
});
content: Text(
'Are you sure you want to exit? Your progress will be lost.',
style: AppTextStyles.blackTextStyle,
),
actions: [
TextButton(
onPressed: () =>
Navigator.of(context).pop(false), // Tetap di halaman
child: Text(
'Cancel',
style: AppTextStyles.greyTextStyle,
),
),
ElevatedButton(
onPressed: () {
// Kembali ke halaman sebelumnya
Navigator.of(context).pop(true);
},
style:
ElevatedButton.styleFrom(backgroundColor: AppColors.redColor),
child: Text(
'Exit',
style: AppTextStyles.whiteTextStyle,
),
),
],
),
);
}
void _showInstructions(BuildContext context) {

View File

@ -152,10 +152,13 @@ class _MatchingPairsQuestionState extends State<MatchingPairsQuestion> {
// Rekonstruksi jawaban siswa
if (widget.exercise.answerStudent.isNotEmpty) {
final answerPairs = widget.exercise.answerStudent.split(', ');
studentAnswers = {
for (var pair in answerPairs) pair.split('-')[0]: pair.split('-')[1]
};
final answerPairs = widget.exercise.answerStudent.split('|');
for (var pair in answerPairs) {
final parts = pair.split('>');
if (parts.length == 2) {
studentAnswers[parts[0].trim()] = parts[1].trim();
}
}
}
}
}