Refactored code to use CustomSnackBar instead of ScaffoldMessenger for displaying snackbars and fix handling when submitting feedback.

This commit is contained in:
Naresh Pratista 2024-11-11 15:44:31 +07:00
parent cf967619e7
commit d8ea9e9164
14 changed files with 280 additions and 217 deletions

View File

@ -1,2 +1 @@
const String baseUrl = const String baseUrl = 'https://ea80-114-6-25-184.ngrok-free.app/';
'https://4317-2001-448a-50a0-3463-1809-e54b-6523-d37.ngrok-free.app/';

View File

@ -0,0 +1,56 @@
import 'package:flutter/material.dart';
class CustomSnackBar {
static void show(
BuildContext context, {
required String message,
bool isError = false,
Duration duration = const Duration(seconds: 3),
}) {
ScaffoldMessenger.of(context).clearSnackBars();
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(
message,
style: const TextStyle(color: Colors.white),
),
backgroundColor: isError ? Colors.red : Colors.green,
behavior: SnackBarBehavior.floating,
margin: const EdgeInsets.all(16),
duration: duration,
dismissDirection: DismissDirection.horizontal,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10),
),
),
);
}
// Versi dengan custom styling
static void showCustom(
BuildContext context, {
required String message,
Color? backgroundColor,
Color? textColor,
Duration duration = const Duration(seconds: 3),
SnackBarBehavior behavior = SnackBarBehavior.floating,
}) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(
message,
style: TextStyle(color: textColor ?? Colors.white),
),
backgroundColor: backgroundColor ?? Colors.black,
behavior: behavior,
margin: const EdgeInsets.all(16),
duration: duration,
dismissDirection: DismissDirection.horizontal,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10),
),
),
);
}
}

View File

@ -1,3 +1,4 @@
import 'package:english_learning/core/widgets/custom_snackbar.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/auth/provider/validator_provider.dart'; import 'package:english_learning/features/auth/provider/validator_provider.dart';
import 'package:english_learning/features/auth/screens/signin/signin_screen.dart'; import 'package:english_learning/features/auth/screens/signin/signin_screen.dart';
@ -50,11 +51,10 @@ class _ForgotPasswordScreenState extends State<ForgotPasswordScreen> {
}, },
); );
} else { } else {
ScaffoldMessenger.of(context).showSnackBar( CustomSnackBar.show(
const SnackBar( context,
content: Text('Email is not registered!'), message: 'Email is not registered!',
backgroundColor: AppColors.redColor, isError: true,
),
); );
} }
} }

View File

@ -1,3 +1,4 @@
import 'package:english_learning/core/widgets/custom_snackbar.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/auth/provider/validator_provider.dart'; import 'package:english_learning/features/auth/provider/validator_provider.dart';
import 'package:english_learning/features/auth/screens/forgot_password/forgot_password_screen.dart'; import 'package:english_learning/features/auth/screens/forgot_password/forgot_password_screen.dart';
@ -169,13 +170,11 @@ class SigninScreen extends StatelessWidget {
_passwordController.clear(); _passwordController.clear();
}); });
} else { } else {
// Show error message CustomSnackBar.show(
ScaffoldMessenger.of(context).showSnackBar( context,
const SnackBar( message:
content: Text( 'Login failed, please check your credentials',
'Login failed, please check your credentials'), isError: true,
backgroundColor: Colors.red,
),
); );
} }
} }

View File

@ -1,3 +1,4 @@
import 'package:english_learning/core/widgets/custom_snackbar.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/auth/provider/validator_provider.dart'; import 'package:english_learning/features/auth/provider/validator_provider.dart';
import 'package:english_learning/features/auth/screens/signin/signin_screen.dart'; import 'package:english_learning/features/auth/screens/signin/signin_screen.dart';
@ -71,11 +72,10 @@ class _SignupScreenState extends State<SignupScreen> {
); );
} else { } else {
// Show error message // Show error message
ScaffoldMessenger.of(context).showSnackBar( CustomSnackBar.show(
const SnackBar( context,
content: Text('Registration failed'), message: 'Registration failed',
backgroundColor: Colors.red, isError: true,
),
); );
} }
} finally { } finally {

View File

@ -1,4 +1,5 @@
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_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/widgets/complete_submission.dart'; import 'package:english_learning/features/learning/modules/exercises/widgets/complete_submission.dart';
@ -56,8 +57,10 @@ class ExerciseNavigator extends StatelessWidget {
} catch (e) { } catch (e) {
print('Error submitting answers: $e'); print('Error submitting answers: $e');
if (context.mounted) { if (context.mounted) {
ScaffoldMessenger.of(context).showSnackBar( CustomSnackBar.show(
SnackBar(content: Text('Error: $e')), context,
message: 'Error submitting answers: $e',
isError: true,
); );
} }
} }
@ -139,12 +142,11 @@ class ExerciseNavigator extends StatelessWidget {
); );
} }
} else { } else {
ScaffoldMessenger.of(context).showSnackBar( CustomSnackBar.show(
const SnackBar( context,
content: Text( message:
'Please answer at least one question before submitting.', 'Please answer at least one question before submitting.',
), isError: true,
),
); );
} }
} }

View File

@ -1,3 +1,4 @@
import 'package:english_learning/core/widgets/custom_snackbar.dart';
import 'package:english_learning/features/learning/modules/exercises/models/review_exercise_model.dart'; import 'package:english_learning/features/learning/modules/exercises/models/review_exercise_model.dart';
import 'package:english_learning/features/learning/modules/exercises/providers/exercise_provider.dart'; import 'package:english_learning/features/learning/modules/exercises/providers/exercise_provider.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
@ -166,11 +167,10 @@ class MatchingPairsQuestion extends StatelessWidget {
? null ? null
: () { : () {
if (!isLeft && provider.activeLeftOption == null) { if (!isLeft && provider.activeLeftOption == null) {
ScaffoldMessenger.of(context).showSnackBar( CustomSnackBar.show(
const SnackBar( context,
content: Text('Please select a left option first.'), message: 'Please select a left option first.',
duration: Duration(seconds: 1), isError: true,
),
); );
} else { } else {
provider.answerQuestion(exerciseIndex, option); provider.answerQuestion(exerciseIndex, option);

View File

@ -1,3 +1,4 @@
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/core/utils/styles/theme.dart'; import 'package:english_learning/core/utils/styles/theme.dart';
import 'package:english_learning/features/learning/modules/exercises/providers/exercise_provider.dart'; import 'package:english_learning/features/learning/modules/exercises/providers/exercise_provider.dart';
@ -25,6 +26,7 @@ class FeedbackScreen extends StatefulWidget {
class _FeedbackScreenState extends State<FeedbackScreen> { class _FeedbackScreenState extends State<FeedbackScreen> {
final FocusNode _focusNode = FocusNode(); final FocusNode _focusNode = FocusNode();
final TextEditingController _controller = TextEditingController(); final TextEditingController _controller = TextEditingController();
bool _isLoading = false;
@override @override
void initState() { void initState() {
@ -45,63 +47,82 @@ class _FeedbackScreenState extends State<FeedbackScreen> {
_focusNode.unfocus(); _focusNode.unfocus();
} }
Future<void> _submitFeedback() async { Future<void> _navigateToLevelList() async {
if (_controller.text.isEmpty) { if (!mounted) return;
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar( await Navigator.pushAndRemoveUntil(
content: Text('Please enter your feedback before submitting.'), context,
backgroundColor: Colors.red, MaterialPageRoute(
builder: (context) => LevelListScreen(
topicId: widget.topicId,
topicTitle: widget.topicTitle,
), ),
),
(Route<dynamic> route) => route.isFirst,
);
}
Future<void> _submitFeedback() async {
_unfocusTextField();
if (_controller.text.trim().isEmpty) {
CustomSnackBar.show(
context,
message: 'Please enter your feedback before submitting.',
isError: true,
); );
return; return;
} }
final exerciseProvider = setState(() {
Provider.of<ExerciseProvider>(context, listen: false); _isLoading = true;
});
try { try {
final result = await exerciseProvider.submitFeedback(_controller.text); final exerciseProvider =
Provider.of<ExerciseProvider>(context, listen: false);
final result =
await exerciseProvider.submitFeedback(_controller.text.trim());
print('Feedback submitted successfully: $result'); if (!mounted) return;
ScaffoldMessenger.of(context).showSnackBar( setState(() {
const SnackBar( _isLoading = false;
content: Text('Feedback submitted successfully!'), });
backgroundColor: Colors.green,
),
);
// Show the dialog if (result != null) {
if (mounted) { await showDialog(
showDialog(
context: context, context: context,
barrierDismissible: false, barrierDismissible: false,
builder: (BuildContext dialogContext) { builder: (BuildContext dialogContext) => PopScope(
return FeedbackDialog( canPop: false,
child: FeedbackDialog(
onSubmit: () { onSubmit: () {
Navigator.of(dialogContext).pop(); // Close the dialog Navigator.pop(dialogContext);
Navigator.pushAndRemoveUntil( _navigateToLevelList();
context,
MaterialPageRoute(
builder: (context) => LevelListScreen(
topicId: widget.topicId,
topicTitle: widget.topicTitle,
),
),
(route) => false, // This removes all previous routes
);
}, },
); ),
}, ),
);
} else {
CustomSnackBar.show(
context,
message: 'Failed to submit feedback. Please try again.',
isError: true,
); );
} }
} catch (e) { } catch (e) {
print('Error submitting feedback: $e'); if (!mounted) return;
ScaffoldMessenger.of(context).showSnackBar(
SnackBar( setState(() {
content: Text('Failed to submit feedback: $e'), _isLoading = false;
backgroundColor: Colors.red, });
),
CustomSnackBar.show(
context,
message: 'An error occurred: ${e.toString()}',
isError: true,
); );
} }
} }
@ -129,92 +150,86 @@ class _FeedbackScreenState extends State<FeedbackScreen> {
), ),
), ),
), ),
body: Padding( body: SingleChildScrollView(
padding: const EdgeInsets.symmetric(horizontal: 16.0), child: Padding(
child: Column( padding: const EdgeInsets.symmetric(horizontal: 16.0),
children: [ child: Column(
const SizedBox(height: 24), crossAxisAlignment: CrossAxisAlignment.stretch,
Text( children: [
'Is there anything you would like to share after completing this exercise?', const SizedBox(height: 24),
style: AppTextStyles.blackTextStyle.copyWith( Text(
fontSize: 14, 'Is there anything you would like to share after completing this exercise?',
),
),
const SizedBox(height: 16),
AnimatedContainer(
duration: const Duration(milliseconds: 200),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(12),
boxShadow: [
BoxShadow(
color: _focusNode.hasFocus
? AppColors.blueColor.withOpacity(0.3)
: Colors.transparent,
blurRadius: 8,
offset: const Offset(0, 4),
),
],
),
child: TextField(
controller: _controller,
focusNode: _focusNode,
maxLines: 8,
style: AppTextStyles.blackTextStyle.copyWith( style: AppTextStyles.blackTextStyle.copyWith(
fontSize: 14, fontSize: 14,
), ),
cursorColor: AppColors.blackColor, ),
decoration: InputDecoration( const SizedBox(height: 16),
hintText: 'Type your message here', AnimatedContainer(
hintStyle: duration: const Duration(milliseconds: 200),
AppTextStyles.greyTextStyle.copyWith(fontSize: 14), decoration: BoxDecoration(
filled: true, borderRadius: BorderRadius.circular(12),
fillColor: _focusNode.hasFocus boxShadow: [
? AppColors.whiteColor BoxShadow(
: AppColors.whiteColor, color: _focusNode.hasFocus
contentPadding: const EdgeInsets.all(16), ? AppColors.blueColor.withOpacity(0.2)
border: OutlineInputBorder( : Colors.transparent,
borderRadius: BorderRadius.circular(12), blurRadius: 8,
borderSide: BorderSide.none, offset: const Offset(0, 4),
),
focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
borderSide: const BorderSide(
color: AppColors.blueColor,
width: 2,
), ),
],
),
child: TextField(
controller: _controller,
focusNode: _focusNode,
maxLines: 8,
style: AppTextStyles.blackTextStyle.copyWith(
fontSize: 14,
), ),
enabledBorder: OutlineInputBorder( cursorColor: AppColors.blackColor,
borderRadius: BorderRadius.circular(12), decoration: InputDecoration(
borderSide: BorderSide( hintText: 'Type your message here',
color: AppColors.disableColor.withOpacity(0.5)), hintStyle:
AppTextStyles.greyTextStyle.copyWith(fontSize: 14),
filled: true,
fillColor: AppColors.whiteColor,
contentPadding: const EdgeInsets.all(16),
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
borderSide: BorderSide.none,
),
focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
borderSide: const BorderSide(
color: AppColors.blueColor,
width: 2,
),
),
enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
borderSide: const BorderSide(
color: AppColors.disableColor,
),
),
), ),
), ),
), ),
), const SizedBox(height: 24),
const SizedBox(height: 24), GlobalButton(
GlobalButton( text: 'Send',
text: 'Send', isLoading: _isLoading,
onPressed: _submitFeedback, onPressed: _isLoading ? null : _submitFeedback,
), ),
const SizedBox(height: 16), const SizedBox(height: 16),
GlobalButton( GlobalButton(
text: 'Skip', text: 'Skip',
textColor: AppColors.blueColor, textColor: AppColors.blueColor,
backgroundColor: Colors.transparent, backgroundColor: Colors.transparent,
borderColor: AppColors.blueColor, borderColor: AppColors.blueColor,
onPressed: () { onPressed: _isLoading ? null : _navigateToLevelList,
Navigator.pushAndRemoveUntil( ),
context, const SizedBox(height: 24),
MaterialPageRoute( ],
builder: (context) => LevelListScreen( ),
topicId: widget.topicId,
topicTitle: widget.topicTitle,
),
),
(Route<dynamic> route) => route.isFirst);
},
)
],
), ),
), ),
), ),

View File

@ -13,59 +13,50 @@ class FeedbackDialog extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Dialog( return PopScope(
shape: RoundedRectangleBorder( canPop: false,
borderRadius: BorderRadius.circular(20), child: Dialog(
), shape: RoundedRectangleBorder(
child: Container(
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(20), borderRadius: BorderRadius.circular(20),
), ),
child: Padding( child: Container(
padding: const EdgeInsets.all(24.0), decoration: BoxDecoration(
child: Column( color: Colors.white,
mainAxisSize: MainAxisSize.min, borderRadius: BorderRadius.circular(20),
children: [ ),
Text( child: Padding(
'Your Thoughts Matter!', padding: const EdgeInsets.all(24.0),
style: AppTextStyles.blackTextStyle.copyWith( child: Column(
fontSize: 18, mainAxisSize: MainAxisSize.min,
fontWeight: FontWeight.w900, children: [
Text(
'Your Thoughts Matter!',
style: AppTextStyles.blackTextStyle.copyWith(
fontSize: 18,
fontWeight: FontWeight.w900,
),
), ),
), const SizedBox(height: 20),
const SizedBox(height: 20), SvgPicture.asset(
SvgPicture.asset( 'lib/features/learning/modules/feedback/assets/images/feedback_illustration.svg',
'lib/features/learning/modules/feedback/assets/images/feedback_illustration.svg', width: 200,
width: 200,
),
const SizedBox(height: 20),
Text(
'Thank you for for taking the time to share your perspective. Your feedback is highly valued!',
textAlign: TextAlign.center,
style: AppTextStyles.disableTextStyle.copyWith(
fontSize: 12,
fontWeight: FontWeight.w500,
), ),
), const SizedBox(height: 20),
const SizedBox(height: 30), Text(
GlobalButton( 'Thank you for taking the time to share your perspective. Your feedback is highly valued!',
text: 'Got It', textAlign: TextAlign.center,
onPressed: () { style: AppTextStyles.disableTextStyle.copyWith(
// Navigator.push( fontSize: 12,
// context, fontWeight: FontWeight.w500,
// MaterialPageRoute( ),
// builder: (context) => const LevelListScreen(), ),
// ), const SizedBox(height: 30),
// ); GlobalButton(
onSubmit(); text: 'Got It',
// Navigator.of(context).popUntil( onPressed: onSubmit,
// (route) => route.isFirst, ),
// ); ],
Navigator.of(context).pop(true); ),
},
)
],
), ),
), ),
), ),

View File

@ -1,5 +1,6 @@
import 'package:english_learning/core/services/dio_client.dart'; import 'package:english_learning/core/services/dio_client.dart';
import 'package:english_learning/core/services/repositories/student_learning_repository.dart'; import 'package:english_learning/core/services/repositories/student_learning_repository.dart';
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';
@ -86,9 +87,7 @@ class _MaterialScreenState extends State<MaterialScreen>
await _repository.createStudentLearning(widget.levelId, token); await _repository.createStudentLearning(widget.levelId, token);
_navigateToExercise(result['payload']['ID_STUDENT_LEARNING']); _navigateToExercise(result['payload']['ID_STUDENT_LEARNING']);
} catch (e) { } catch (e) {
ScaffoldMessenger.of(context).showSnackBar( CustomSnackBar.show(context, message: 'Error: $e', isError: true);
SnackBar(content: Text('Error: $e')),
);
} finally { } finally {
setState(() { setState(() {
_isLoading = false; _isLoading = false;

View File

@ -1,3 +1,4 @@
import 'package:english_learning/core/widgets/custom_snackbar.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/auth/provider/validator_provider.dart'; import 'package:english_learning/features/auth/provider/validator_provider.dart';
import 'package:english_learning/core/widgets/form_field/custom_field_widget.dart'; import 'package:english_learning/core/widgets/form_field/custom_field_widget.dart';
@ -121,10 +122,11 @@ class _ChangePasswordScreenState extends State<ChangePasswordScreen> {
}, },
); );
} else { } else {
ScaffoldMessenger.of(context).showSnackBar( CustomSnackBar.show(
SnackBar( context,
content: Text( message:
'Failed to update password. Please try again.')), 'Failed to update password. Please try again.',
isError: true,
); );
} }
}, },

View File

@ -1,6 +1,7 @@
import 'dart:io'; import 'dart:io';
import 'package:english_learning/core/services/constants.dart'; import 'package:english_learning/core/services/constants.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_snackbar.dart';
import 'package:english_learning/core/widgets/form_field/custom_field_widget.dart'; import 'package:english_learning/core/widgets/form_field/custom_field_widget.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';
@ -76,10 +77,9 @@ class _EditProfileScreenState extends State<EditProfileScreen> {
}, },
); );
} catch (e) { } catch (e) {
ScaffoldMessenger.of(context).showSnackBar( CustomSnackBar.show(context,
const SnackBar( message: 'Failed to update profile. Please try again.',
content: Text('Failed to update profile. Please try again.')), isError: true);
);
} }
} }

View File

@ -1,4 +1,5 @@
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_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/settings/modules/report_issue/widgets/report_issue_dialog.dart'; import 'package:english_learning/features/settings/modules/report_issue/widgets/report_issue_dialog.dart';
@ -61,10 +62,8 @@ class _ReportIssueScreenState extends State<ReportIssueScreen> {
}, },
); );
} else { } else {
ScaffoldMessenger.of(context).showSnackBar( CustomSnackBar.show(context,
const SnackBar( message: 'Failed to submit report. Please try again.', isError: true);
content: Text('Failed to submit report. Please try again.')),
);
} }
} }

View File

@ -1,5 +1,6 @@
import 'package:bootstrap_icons/bootstrap_icons.dart'; import 'package:bootstrap_icons/bootstrap_icons.dart';
import 'package:english_learning/core/services/constants.dart'; import 'package:english_learning/core/services/constants.dart';
import 'package:english_learning/core/widgets/custom_snackbar.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/auth/screens/signin/signin_screen.dart'; import 'package:english_learning/features/auth/screens/signin/signin_screen.dart';
import 'package:english_learning/features/settings/modules/change_password/screens/change_password_screen.dart'; import 'package:english_learning/features/settings/modules/change_password/screens/change_password_screen.dart';
@ -181,10 +182,10 @@ class _SettingsScreenState extends State<SettingsScreen> {
(Route<dynamic> route) => false, (Route<dynamic> route) => false,
); );
} else { } else {
ScaffoldMessenger.of(context).showSnackBar( CustomSnackBar.show(
const SnackBar( context,
content: Text( message: 'Logout failed. Please try again.',
'Logout failed. Please try again.')), isError: true,
); );
} }
}, },