diff --git a/lib/core/services/constants.dart b/lib/core/services/constants.dart index ce6fbd5..726020d 100644 --- a/lib/core/services/constants.dart +++ b/lib/core/services/constants.dart @@ -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/'; diff --git a/lib/features/learning/modules/exercises/providers/exercise_provider.dart b/lib/features/learning/modules/exercises/providers/exercise_provider.dart index 668a29f..6b1ce5f 100644 --- a/lib/features/learning/modules/exercises/providers/exercise_provider.dart +++ b/lib/features/learning/modules/exercises/providers/exercise_provider.dart @@ -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 { diff --git a/lib/features/learning/modules/exercises/screens/exercise_screen.dart b/lib/features/learning/modules/exercises/screens/exercise_screen.dart index 5ea1c2b..3c34011 100644 --- a/lib/features/learning/modules/exercises/screens/exercise_screen.dart +++ b/lib/features/learning/modules/exercises/screens/exercise_screen.dart @@ -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 { 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(); provider.fetchExercises(widget.levelId!); @@ -44,6 +56,7 @@ class _ExerciseScreenState extends State { @override void dispose() { _scrollController.dispose(); + SystemChannels.platform.setMethodCallHandler(null); super.dispose(); } @@ -57,89 +70,143 @@ class _ExerciseScreenState extends State { @override Widget build(BuildContext context) { - return Consumer(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(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 _showExitConfirmationDialog() async { + return showDialog( + 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) { diff --git a/lib/features/learning/modules/exercises/widgets/question/matching_pairs_question.dart b/lib/features/learning/modules/exercises/widgets/question/matching_pairs_question.dart index a07e9ff..369dd29 100644 --- a/lib/features/learning/modules/exercises/widgets/question/matching_pairs_question.dart +++ b/lib/features/learning/modules/exercises/widgets/question/matching_pairs_question.dart @@ -152,10 +152,13 @@ class _MatchingPairsQuestionState extends State { // 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(); + } + } } } }