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,42 +47,10 @@ 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,
),
);
return;
}
Future<void> _navigateToLevelList() async {
if (!mounted) return;
final exerciseProvider =
Provider.of<ExerciseProvider>(context, listen: false);
try {
final result = await exerciseProvider.submitFeedback(_controller.text);
print('Feedback submitted successfully: $result');
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('Feedback submitted successfully!'),
backgroundColor: Colors.green,
),
);
// Show the dialog
if (mounted) {
showDialog(
context: context,
barrierDismissible: false,
builder: (BuildContext dialogContext) {
return FeedbackDialog(
onSubmit: () {
Navigator.of(dialogContext).pop(); // Close the dialog
Navigator.pushAndRemoveUntil(
await Navigator.pushAndRemoveUntil(
context,
MaterialPageRoute(
builder: (context) => LevelListScreen(
@ -88,20 +58,71 @@ class _FeedbackScreenState extends State<FeedbackScreen> {
topicTitle: widget.topicTitle,
),
),
(route) => false, // This removes all previous routes
(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;
}
setState(() {
_isLoading = true;
});
try {
final exerciseProvider =
Provider.of<ExerciseProvider>(context, listen: false);
final result =
await exerciseProvider.submitFeedback(_controller.text.trim());
if (!mounted) return;
setState(() {
_isLoading = false;
});
if (result != null) {
await showDialog(
context: context,
barrierDismissible: false,
builder: (BuildContext dialogContext) => PopScope(
canPop: false,
child: FeedbackDialog(
onSubmit: () {
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,9 +150,11 @@ class _FeedbackScreenState extends State<FeedbackScreen> {
),
),
),
body: Padding(
body: SingleChildScrollView(
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
const SizedBox(height: 24),
Text(
@ -148,7 +171,7 @@ class _FeedbackScreenState extends State<FeedbackScreen> {
boxShadow: [
BoxShadow(
color: _focusNode.hasFocus
? AppColors.blueColor.withOpacity(0.3)
? AppColors.blueColor.withOpacity(0.2)
: Colors.transparent,
blurRadius: 8,
offset: const Offset(0, 4),
@ -168,9 +191,7 @@ class _FeedbackScreenState extends State<FeedbackScreen> {
hintStyle:
AppTextStyles.greyTextStyle.copyWith(fontSize: 14),
filled: true,
fillColor: _focusNode.hasFocus
? AppColors.whiteColor
: AppColors.whiteColor,
fillColor: AppColors.whiteColor,
contentPadding: const EdgeInsets.all(16),
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
@ -185,8 +206,9 @@ class _FeedbackScreenState extends State<FeedbackScreen> {
),
enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
borderSide: BorderSide(
color: AppColors.disableColor.withOpacity(0.5)),
borderSide: const BorderSide(
color: AppColors.disableColor,
),
),
),
),
@ -194,7 +216,8 @@ class _FeedbackScreenState extends State<FeedbackScreen> {
const SizedBox(height: 24),
GlobalButton(
text: 'Send',
onPressed: _submitFeedback,
isLoading: _isLoading,
onPressed: _isLoading ? null : _submitFeedback,
),
const SizedBox(height: 16),
GlobalButton(
@ -202,22 +225,14 @@ class _FeedbackScreenState extends State<FeedbackScreen> {
textColor: AppColors.blueColor,
backgroundColor: Colors.transparent,
borderColor: AppColors.blueColor,
onPressed: () {
Navigator.pushAndRemoveUntil(
context,
MaterialPageRoute(
builder: (context) => LevelListScreen(
topicId: widget.topicId,
topicTitle: widget.topicTitle,
onPressed: _isLoading ? null : _navigateToLevelList,
),
),
(Route<dynamic> route) => route.isFirst);
},
)
const SizedBox(height: 24),
],
),
),
),
),
);
}
}

View File

@ -13,7 +13,9 @@ class FeedbackDialog extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Dialog(
return PopScope(
canPop: false,
child: Dialog(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(20),
),
@ -41,7 +43,7 @@ class FeedbackDialog extends StatelessWidget {
),
const SizedBox(height: 20),
Text(
'Thank you for for taking the time to share your perspective. Your feedback is highly valued!',
'Thank you for taking the time to share your perspective. Your feedback is highly valued!',
textAlign: TextAlign.center,
style: AppTextStyles.disableTextStyle.copyWith(
fontSize: 12,
@ -51,24 +53,13 @@ class FeedbackDialog extends StatelessWidget {
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);
},
)
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,
);
}
},