Merge branch 'refactor-exercise-provider' into 'master'
Fix: Correct matching pairs answer formatting in review mode and refactor... See merge request profile-image/kedaireka/polinema-adapative-learning/mobile-adaptive-learning!23
This commit is contained in:
commit
6c265fec33
|
|
@ -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/';
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user