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 =
'https://4317-2001-448a-50a0-3463-1809-e54b-6523-d37.ngrok-free.app/';
const String baseUrl = 'https://ea80-114-6-25-184.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/validator_provider.dart';
import 'package:english_learning/features/auth/screens/signin/signin_screen.dart';
@ -50,11 +51,10 @@ class _ForgotPasswordScreenState extends State<ForgotPasswordScreen> {
},
);
} else {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('Email is not registered!'),
backgroundColor: AppColors.redColor,
),
CustomSnackBar.show(
context,
message: 'Email is not registered!',
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/validator_provider.dart';
import 'package:english_learning/features/auth/screens/forgot_password/forgot_password_screen.dart';
@ -169,13 +170,11 @@ class SigninScreen extends StatelessWidget {
_passwordController.clear();
});
} else {
// Show error message
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text(
'Login failed, please check your credentials'),
backgroundColor: Colors.red,
),
CustomSnackBar.show(
context,
message:
'Login failed, please check your credentials',
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/validator_provider.dart';
import 'package:english_learning/features/auth/screens/signin/signin_screen.dart';
@ -71,11 +72,10 @@ class _SignupScreenState extends State<SignupScreen> {
);
} else {
// Show error message
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('Registration failed'),
backgroundColor: Colors.red,
),
CustomSnackBar.show(
context,
message: 'Registration failed',
isError: true,
);
}
} finally {

View File

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

View File

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

View File

@ -1,5 +1,6 @@
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/widgets/custom_snackbar.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/learning/modules/exercises/screens/exercise_screen.dart';
@ -86,9 +87,7 @@ class _MaterialScreenState extends State<MaterialScreen>
await _repository.createStudentLearning(widget.levelId, token);
_navigateToExercise(result['payload']['ID_STUDENT_LEARNING']);
} catch (e) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Error: $e')),
);
CustomSnackBar.show(context, message: 'Error: $e', isError: true);
} finally {
setState(() {
_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/validator_provider.dart';
import 'package:english_learning/core/widgets/form_field/custom_field_widget.dart';
@ -121,10 +122,11 @@ class _ChangePasswordScreenState extends State<ChangePasswordScreen> {
},
);
} else {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(
'Failed to update password. Please try again.')),
CustomSnackBar.show(
context,
message:
'Failed to update password. Please try again.',
isError: true,
);
}
},

View File

@ -1,6 +1,7 @@
import 'dart:io';
import 'package:english_learning/core/services/constants.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/global_button.dart';
import 'package:english_learning/features/auth/provider/user_provider.dart';
@ -76,10 +77,9 @@ class _EditProfileScreenState extends State<EditProfileScreen> {
},
);
} catch (e) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('Failed to update profile. Please try again.')),
);
CustomSnackBar.show(context,
message: '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/widgets/custom_snackbar.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/settings/modules/report_issue/widgets/report_issue_dialog.dart';
@ -61,10 +62,8 @@ class _ReportIssueScreenState extends State<ReportIssueScreen> {
},
);
} else {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('Failed to submit report. Please try again.')),
);
CustomSnackBar.show(context,
message: 'Failed to submit report. Please try again.', isError: true);
}
}

View File

@ -1,5 +1,6 @@
import 'package:bootstrap_icons/bootstrap_icons.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/screens/signin/signin_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,
);
} else {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text(
'Logout failed. Please try again.')),
CustomSnackBar.show(
context,
message: 'Logout failed. Please try again.',
isError: true,
);
}
},