uaestats_fe/lib/presentation/routes/drawer_routes/Drawer Items/feedback.dart
2026-05-25 18:01:41 +05:30

1248 lines
41 KiB
Dart
Executable File

import 'dart:convert';
import 'package:external_repos/external_repos.dart';
import 'package:flutter/material.dart';
import 'package:flutter_rating_bar/flutter_rating_bar.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:go_router/go_router.dart';
import 'package:http/http.dart' as http;
import 'package:uae_stat/config/theme/service.dart';
import 'package:uae_stat/config/theme/theme_provider.dart';
import 'package:pocketbase/pocketbase.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:uae_stat/config/api_config.dart';
import 'package:uae_stat/config/connectivity_provider.dart';
import 'package:uae_stat/config/toggle_lang_service.dart';
import 'package:uae_stat/domain/use_cases/language.dart';
import 'package:uae_stat/infrastructure/services/img_asset_paths/feedback_asset_path.dart';
import 'package:uae_stat/infrastructure/services/packages/go_router.dart';
import 'package:uae_stat/infrastructure/services/pocketbase_service.dart';
import 'package:uae_stat/l10n/app_localizations.dart';
import 'package:uae_stat/presentation/components/indicators/locale_provider.dart';
import 'package:uae_stat/presentation/components/space.dart';
import 'package:uae_stat/presentation/routes/drawer_routes/custom_drawer_routes.dart';
import '../../../../domain/use_cases/preferences_use_case.dart';
import '../../../components/my_toggle.dart';
class FeedbackForm extends ConsumerStatefulWidget {
const FeedbackForm({super.key});
@override
ConsumerState<FeedbackForm> createState() => _FeedbackFormState();
}
class _FeedbackFormState extends ConsumerState<FeedbackForm>
with WidgetsBindingObserver {
final _pb = PocketBase(apiUrl); // Initialize PocketBase client
bool isLoading = false;
// final _pb =
// PocketBase('http://127.0.0.1:8090'); // Initialize PocketBase client
final TextEditingController _feedbackController = TextEditingController();
List<dynamic> homePageData = [];
List<dynamic> filteredData = [];
double _easeOfUseRating = 0;
double _qualityRating = 0;
double _designRating = 0;
double _redundancyRating = 0;
int? _selectedEmojiIndex;
bool _isFeedbackSubmitted = false; // Track if feedback was submitted
bool _isFeedbackFailed = false; // New flag for failed submission
bool _isSmileySelected = true; // Track if smiley is selected
dynamic configEmail;
dynamic userId;
List<Map<String, dynamic>> get _emojiOptions => [
{
// "icon": Icons.emoji_emotions_rounded,
'icon': Image.asset(
FeedBackAssetPath.disappointedfb,
width: 25,
height: 25,
),
"label": context.translate("Terrible", "سئ جدا"), // Translate here
"value": 1,
},
{
// "icon": Icons.sentiment_dissatisfied,
'icon': Image.asset(
FeedBackAssetPath.frowningfb,
width: 25,
height: 25,
),
"label": context.translate("Bad", "سيء"), // Translate here
"value": 2,
},
{
'icon': Image.asset(
FeedBackAssetPath.neutralfb,
width: 25,
height: 25,
),
"label": context.translate("Okay", "مقبول"), // Translate here
"value": 3,
},
{
'icon': Image.asset(
FeedBackAssetPath.smilingfb,
width: 25,
height: 25,
),
"label": context.translate("Good", "جيد"), // Translate here
"value": 4,
},
{
// "icon": Icons.sentiment_very_satisfied,
'icon': Image.asset(
FeedBackAssetPath.smilingFacefb,
width: 25,
height: 25,
),
"label": context.translate("Amazing", "مذهل"), // Translate here
"value": 5,
},
];
static const int _characterLimit = 1200;
final RegExp _allowedCharacters = RegExp(r'^[a-zA-Z0-9 .,!?-]*$');
bool _hasError = false;
String _errorMessage = '';
late UserService _userService;
late ThemeMode currentTheme;
@override
void initState() {
super.initState();
_userService = UserService();
final locale = ref.read(localeProvider);
currentTheme = ref.read(themeProvider);
WidgetsBinding.instance.addPostFrameCallback((_) {
final isDark = currentTheme == ThemeMode.dark ||
(currentTheme == ThemeMode.system &&
MediaQuery.of(context).platformBrightness == Brightness.dark);
final themeMode = (isDark ? 'dark' : 'light');
fetchData(locale?.languageCode ?? 'en', themeMode);
});
WidgetsBinding.instance.addObserver(this);
_loadFeedbackText();
_feedbackController.addListener(_handleTextChange);
// fetchEmailConfiguration();
checkUserId();
}
Future<void> fetchData(locale, themeMode) async {
const baseUrl = '${apiUrl}api/getUAENumbersData';
try {
final response = await http.get(
Uri.parse(baseUrl + '?language=$locale&color_mode=$themeMode'),
headers: {'APP_SIGNATURE': 'fcsc.gov.ae.X7pL9qZm2A'});
if (response.statusCode == 200) {
setState(() {
homePageData = (json.decode(response.body) as List)
.map((e) => Map<String, dynamic>.from(e))
.toList();
filteredData = List.from(homePageData);
isLoading = false;
});
} else {
throw Exception('Failed to load data');
}
} catch (e) {
setState(() {
isLoading = false;
});
}
}
Future<String?> getUserId() async {
final prefs = await SharedPreferences.getInstance();
return prefs.getString('userId'); // Retrieve the userId
}
Future<void> checkUserId() async {
userId = await getUserId();
if (userId != null && userId.isNotEmpty) {
} else {
// Handle case where userId is not available
}
}
@override
void didChangeAppLifecycleState(AppLifecycleState state) {
if (state == AppLifecycleState.detached ||
state == AppLifecycleState.paused) {
_removeFeedbackText();
}
}
// Detect when navigating to another page
@override
void didPushNext() {
// Called when a new page is pushed on top of FeedbackPage
_removeFeedbackText();
}
Future<void> _saveFeedbackText(
int emojiIndex, String ratingKey, double ratingValue) async {
final prefs = await SharedPreferences.getInstance();
await prefs.setString('feedbackText', _feedbackController.text);
await prefs.setInt('selected_emoji_index', emojiIndex);
await prefs.setDouble(ratingKey, ratingValue);
}
Future<void> _saveAllRatings() async {
final prefs = await SharedPreferences.getInstance();
await prefs.setDouble('ease_of_use_rating', _easeOfUseRating);
await prefs.setDouble('quality_rating', _qualityRating);
await prefs.setDouble('design_rating', _designRating);
await prefs.setDouble('redundancy_rating', _redundancyRating);
}
Future<void> _loadFeedbackText() async {
final prefs = await SharedPreferences.getInstance();
// Step 1: Load values from SharedPreferences into variables
final feedbackText = prefs.getString('feedbackText') ?? '';
final savedIndex = prefs.getInt('selected_emoji_index');
final easeOfUseRating = prefs.getDouble('ease_of_use_rating') ?? 0;
final qualityRating = prefs.getDouble('quality_rating') ?? 0;
final designRating = prefs.getDouble('design_rating') ?? 0;
final redundancyRating = prefs.getDouble('redundancy_rating') ?? 0;
// Step 2: Populate fields with values without immediately clearing storage
setState(() {
_feedbackController.text = feedbackText;
if (savedIndex != null) {
_selectedEmojiIndex = savedIndex;
_isSmileySelected = true; // Indicating the user selected an emoji
}
_easeOfUseRating = easeOfUseRating;
_qualityRating = qualityRating;
_designRating = designRating;
_redundancyRating = redundancyRating;
});
// Step 3: Clear storage after a slight delay
Future.delayed(Duration(milliseconds: 50), () async {
await prefs.remove('feedbackText');
await prefs.remove('selected_emoji_index');
await prefs.remove('ease_of_use_rating');
await prefs.remove('quality_rating');
await prefs.remove('design_rating');
await prefs.remove('redundancy_rating');
});
}
// Remove feedback text from local storage
Future<void> _removeFeedbackText() async {
final prefs = await SharedPreferences.getInstance();
await prefs.remove('feedbackText');
await prefs.remove('selected_emoji_index');
await prefs.remove('ease_of_use_rating');
await prefs.remove('quality_rating');
await prefs.remove('design_rating');
await prefs.remove('redundancy_rating');
}
void _handleTextChange() {
String currentText = _feedbackController.text;
// Enforce character limit and truncate excess on paste
if (currentText.length > _characterLimit) {
_feedbackController.text = currentText.substring(0, _characterLimit);
_feedbackController.selection = TextSelection.fromPosition(
TextPosition(offset: _feedbackController.text.length),
);
}
// Validate characters
if (!_allowedCharacters.hasMatch(currentText)) {
setState(() {
_hasError = true;
_errorMessage = "Invalid Characters";
});
} else {
setState(() {
_hasError = false;
_errorMessage = '';
});
}
}
@override
Widget build(BuildContext context) {
// final uri = Uri.parse(GoRouterState.of(context).uri.toString());
// final userName = uri.queryParameters['userName'] ?? 'Guest User';
ref.listen<AsyncValue<bool>>(connectivityProvider, (previous, hasInternet) {
if (hasInternet.value == false) {
context.push('/internetcheck');
}
});
// ref.listen<Locale?>(localeProvider, (previous, next) async {
// final localeCode = next?.languageCode ?? 'en'; // Default to 'en' if null
// await _userService.updateLanguage(localeCode);
// });
ref.listen<Locale?>(localeProvider, (previous, next) async {
final localeCode = next?.languageCode ?? 'en';
setState(() {
isLoading = true;
});
await _userService.updateLanguage(localeCode, ref);
final currentTheme = ref.read(themeProvider);
final isDark = currentTheme == ThemeMode.dark ||
(currentTheme == ThemeMode.system &&
MediaQuery.of(context).platformBrightness == Brightness.dark);
final themeString = isDark ? 'dark' : 'light';
fetchData(localeCode, themeString);
});
ref.listen<ThemeMode>(themeProvider, (previous, next) async {
final localeCode = ref.watch(localeProvider)?.languageCode ?? 'en';
// Default to 'en' if null
setState(() {
isLoading = true;
});
final currentTheme = ref.read(themeProvider);
final isDark = currentTheme == ThemeMode.dark ||
(currentTheme == ThemeMode.system &&
MediaQuery.of(context).platformBrightness == Brightness.dark);
final themeString = isDark ? 'dark' : 'light';
fetchData(localeCode, themeString);
});
final themeMode = ref.read(themeProvider);
final isDarkTheme = themeMode == ThemeMode.dark ||
(themeMode == ThemeMode.system &&
MediaQuery.of(context).platformBrightness == Brightness.dark);
final userName = GoRouterState.of(context).extra as String? ?? 'Guest User';
return PopScope(
canPop: false, // Allow back navigation only if not login screen
onPopInvokedWithResult: (didPop, result) {
if (didPop) return;
context.go('/myhomepage'); // Show exit confirmation dialog
},
child: BaseScaffold(
backgroundColor: isDarkTheme ? Colors.black : Colors.white,
appbarColor: isDarkTheme ? Colors.black : Colors.white,
// backgroundColor: isDarkTheme ? Color(0xFF333333) : Color(0xFF414042),
// appbarColor: isDarkTheme ? Color(0xFF333333) : Color(0xFF414042),
// title: Text("Feedback"),
title: Text(
AppLocalizations.of(context)!.feedback_title,
style: TextStyle(
fontSize: 24,
fontFamily: context.translate(
'Roboto',
'NotoKufi',
),
fontWeight: FontWeight.w500),
),
// appBar: AppBar(
// backgroundColor: const Color(0xFFf8f9ff),
// title: Text(AppLocalizations.of(context)!.feedback_title),
// actions: [
// Align(
// alignment: AlignmentDirectional.topEnd,
// child: LangToggle(
// onSaveFeedback: _saveFeedbackText,
// onLoadFeedback: _loadFeedbackText,
// selectedEmojiIndex: _selectedEmojiIndex ?? 0,
// easeOfUseRating: _easeOfUseRating,
// qualityRating: _qualityRating,
// designRating: _designRating,
// redundancyRating: _redundancyRating,
// ),
// ),
// ],
// ),
body: isLoading
? Container(
// color: Color(0x98FFFCE5), // Semi-transparent background
color: isDarkTheme ? Color(0xFF000000) : Colors.white,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Container(
margin: EdgeInsets.symmetric(
horizontal: 40), // Left & Right space
child: LinearProgressIndicator(
minHeight: 5, // Adjust thickness
backgroundColor:
Colors.grey[100], // Optional: Background color
valueColor: AlwaysStoppedAnimation<Color>(
Color(0xFFAA8E83)), // Loader color
),
),
],
),
)
: Padding(
padding: const EdgeInsets.only(
top: 8.0, bottom: 16.0, left: 23.0, right: 23.0),
child: SingleChildScrollView(
child: _isFeedbackSubmitted
? _buildThankYouMessage(userName, isDarkTheme)
: _isFeedbackFailed
? _buildFailureMessage(isDarkTheme)
: _buildFeedbackForm(isDarkTheme),
),
),
));
}
Widget _buildFeedbackForm(bool isDarkTheme) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Text(
context.translate(
'Do you have a suggestion or had any problem? Let us know.',
'هل لديك اقتراح أو واجهت أي مشكلة؟ أخبرنا',
),
style: TextStyle(
fontSize: 18,
color: isDarkTheme ? Colors.white : Color(0xFF898C81),
fontFamily: context.translate(
'Roboto',
'NotoKufi',
),
fontWeight: FontWeight.w700,
),
textAlign: TextAlign.center,
),
const SizedBox(height: 20),
Center(
child: FittedBox(
fit: BoxFit
.scaleDown, // This will make the text smaller to fit within the available space
child: Text(
context.translate('How was your experience with us today?',
'كيف كانت تجربتك معنا اليوم؟'),
style: TextStyle(
fontSize: 18,
fontFamily: context.translate(
'Roboto',
'NotoKufi',
),
fontWeight: FontWeight.w400,
color: isDarkTheme ? Colors.white : Color(0xff898C81),
// color: Color(0xff898C81),
),
),
),
),
const SizedBox(height: 15),
Container(
width: double.infinity,
decoration: BoxDecoration(
border: Border.all(
color:
_isSmileySelected ? Colors.transparent : Colors.transparent,
width: 1.5,
),
borderRadius: BorderRadius.circular(8),
),
padding: const EdgeInsets.all(0.0),
child: Column(
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: List.generate(_emojiOptions.length, (index) {
return _buildEmojiButton(
icon: _emojiOptions[index]["icon"],
// icon: _emojiOptions[index]["icon"],
label: _emojiOptions[index]["label"],
index: index,
isDarkTheme: isDarkTheme,
);
}),
),
if (!_isSmileySelected) // Show the error message if no smiley is selected
Padding(
padding: const EdgeInsets.only(top: 8.0),
child: Text(
context.translate(
'Please rate your experience before submitting your feedback.',
'يرجى تقييم تجربتك قبل تقديم ملاحظاتك.',
),
style: TextStyle(
color: Color(0xFFD83731),
fontSize: 14,
fontFamily: context.translate(
'Roboto',
'NotoKufi',
),
),
),
),
],
),
),
const SizedBox(height: 15),
FittedBox(
fit: BoxFit
.scaleDown, // This will make the text smaller to fit within the available space
child: Text(
context.translate('How good did we do in these aspects?',
'ما مدى جودة أدائنا في هذه الجوانب؟'),
style: TextStyle(
fontSize: 18,
fontFamily: context.translate(
'Roboto',
'NotoKufi',
),
fontWeight: FontWeight.w400,
// color: Color(0xFF898C81),
color: isDarkTheme ? Colors.white : Color(0xff898C81),
),
)),
const SizedBox(height: 15),
_buildRatingRow(context.translate('Ease of use', 'سهولة الاستخدام'),
_easeOfUseRating, (rating) {
setState(() {
_easeOfUseRating = rating;
});
}, isDarkTheme),
_buildRatingRow(
context.translate('Quality', 'الجودة'),
_qualityRating,
(rating) {
setState(() {
_qualityRating = rating;
});
},
isDarkTheme,
),
_buildRatingRow(
context.translate('Design', 'التصميم'),
_designRating,
(rating) {
setState(() {
_designRating = rating;
});
},
isDarkTheme,
),
_buildRatingRow(
context.translate('Redundant', 'مكرر'),
_redundancyRating,
(rating) {
setState(() {
_redundancyRating = rating;
});
},
isDarkTheme,
),
const SizedBox(height: 20),
TextField(
controller: _feedbackController,
minLines: 5, // Start with 5 lines
maxLines: null, // Allows the field to expand automatically
maxLength: _characterLimit,
style: TextStyle(
color: isDarkTheme ? Colors.white : Color(0xFF898C81),
fontFamily: context.translate(
'Roboto',
'NotoKufi',
),
),
decoration: InputDecoration(
hintText: context.translate(
'Tell us how we can improve', 'أخبرنا كيف يمكننا التحسين'),
hintStyle: TextStyle(
color: isDarkTheme ? Colors.white : Color(0xFF898C81),
fontFamily: context.translate(
'Roboto',
'NotoKufi',
),
),
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(10.0),
borderSide: BorderSide(color: Color(0xFFB68A34), width: 2),
),
// Border when the TextField is enabled but not focused
enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(10.0),
borderSide: BorderSide(color: Color(0xFFB68A34), width: 2),
),
// Border when there is an error
errorBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(10.0),
borderSide: BorderSide(color: Color(0xFFD83731), width: 2),
),
// Border when focused and there is an error
focusedErrorBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(10.0),
borderSide: BorderSide(color: Color(0xFFD83731), width: 2),
),
errorText:
_hasError ? _errorMessage : null, // Show error below field
errorStyle: TextStyle(
color: Color(0xFFD83731),
),
),
onChanged: (text) {
// Trigger re-validation and character limit on each change
_handleTextChange();
},
),
const SizedBox(height: 15),
Center(
child: ElevatedButton(
onPressed: _submitFeedback,
style: ButtonStyle(
shape: WidgetStatePropertyAll(
RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10),
),
),
padding: const WidgetStatePropertyAll(
EdgeInsets.symmetric(vertical: 5.5),
),
textStyle: WidgetStatePropertyAll(
TextStyle(
fontFamily: context.translate(
'Roboto',
'NotoKufi',
),
fontSize: 16,
fontWeight: FontWeight.w600,
),
),
backgroundColor: WidgetStatePropertyAll(Color(0xFFB68A34)),
// backgroundColor: WidgetStatePropertyAll(
// MyTheme.topicColor(IndicatorTopic.environment),
// ),
foregroundColor: const WidgetStatePropertyAll(
Colors.white,
),
),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
context.translate(
'Submit',
'إرسال',
),
style: TextStyle(
fontFamily: context.translate(
'Roboto',
'NotoKufi',
),
color: Colors.white,
),
),
6.horizontalSpace,
Icon(
Icons.chevron_right_outlined,
// color: Colors.white,
color: Colors.white,
),
],
),
),
),
],
);
}
Widget _buildThankYouMessage(String userName, bool isDarkTheme) {
double screenHeight = MediaQuery.of(context).size.height;
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
SizedBox(height: screenHeight / 5),
Container(
padding: EdgeInsets.all(20),
decoration: BoxDecoration(
color: Color(0xFFF9F9F9), // Circle background color
shape: BoxShape.circle, // Make it a circle
boxShadow: [
BoxShadow(
color: Color.fromRGBO(0, 0, 0, 0.2),
blurRadius: 5,
spreadRadius: 2,
offset: Offset(0, 3), // Shadow position
),
],
),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Image.asset(
FeedBackAssetPath.thumbfb,
width: 40,
height: 40,
)
// Icon(
// Icons.thumb,
// size: 80,
// color: Color(0xFF7DAFBC), // Icon color
// ),
// const SizedBox(height: 20),
],
),
),
// Icon(Icons.thumb_up, size: 80, color: Color(0xFF7DAFBC)),
const SizedBox(height: 20),
Text(
'${AppLocalizations.of(context)!.thankYou} $userName !',
style: TextStyle(
fontSize: 24,
fontWeight: FontWeight.bold,
fontFamily: context.translate(
'Roboto',
'NotoKufi',
),
),
),
const SizedBox(height: 15),
Text(
AppLocalizations.of(context)!.feedback_message,
style: TextStyle(
fontSize: 16,
color: isDarkTheme ? Colors.white : Color(0xFF898C81),
fontFamily: context.translate(
'Roboto',
'NotoKufi',
),
),
textAlign: TextAlign.center,
),
// const SizedBox(height: 30),
// ElevatedButton(
// onPressed: () => context.go('/myhomepage'),
// //context.go('/${context.language}/${BottomNavBarItem.home.routePath}'),
// child: const Text("Go to Home"),
// ),
],
),
);
}
// New widget for failure message
Widget _buildFailureMessage(bool isDarkTheme) {
double screenHeight = MediaQuery.of(context).size.height;
double screenWidth = MediaQuery.of(context).size.width;
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
SizedBox(height: screenHeight / 5),
Container(
padding: EdgeInsets.all(20),
decoration: BoxDecoration(
color: Color(0xFFF9F9F9), // Circle background color
shape: BoxShape.circle, // Make it a circle
boxShadow: [
BoxShadow(
color: Color.fromRGBO(0, 0, 0, 0.2),
blurRadius: 5,
spreadRadius: 2,
offset: Offset(0, 3), // Shadow position
),
],
),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Image.asset(
FeedBackAssetPath.errorfb,
width: 40,
height: 40,
),
])),
// Icon(Icons.error, size: 80, color: Colors.red),
const SizedBox(height: 20),
Text(
AppLocalizations.of(context)!.failed_to_sharefeedback,
style: TextStyle(
fontFamily: context.translate(
'Roboto',
'NotoKufi',
),
fontSize: 24,
fontWeight: FontWeight.bold,
color: isDarkTheme ? Colors.white : Color(0xFF414042)),
),
const SizedBox(height: 10),
Text(AppLocalizations.of(context)!.feedback_failed_msg,
style: TextStyle(
fontSize: 16,
color: isDarkTheme ? Colors.white : Color(0xFF898C81),
fontFamily: context.translate(
'Roboto',
'NotoKufi',
),
),
textAlign: TextAlign.center),
// const SizedBox(height: 30),
// ElevatedButton(
// onPressed: () {
// setState(() {
// _isFeedbackFailed = false; // Reset failure state
// });
// },
// child: const Text("Retry"),
// ),
],
),
);
}
Widget _buildEmojiButton({
// required IconData icon,
required Widget icon,
required String label,
required int index,
required bool isDarkTheme,
}) {
bool isSelected = _selectedEmojiIndex == index;
return GestureDetector(
onTap: () {
setState(() {
_selectedEmojiIndex = index;
_isSmileySelected = true; // Hide error when a smiley is selected
});
},
child: Container(
padding: EdgeInsets.symmetric(vertical: 8, horizontal: 8),
decoration: BoxDecoration(
border: Border.all(
color: isSelected ? Color(0xFF8E8E8E) : Colors.transparent,
width: 2,
),
borderRadius: BorderRadius.circular(12), // Rounded corners
),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
icon, // Assuming `icon` is an Icon widget
SizedBox(height: 3),
FittedBox(
fit: BoxFit
.scaleDown, // This will make the text smaller to fit within the available space
child: Text(
label,
style: TextStyle(
fontFamily: context.translate(
'Roboto',
'NotoKufi',
),
fontWeight: FontWeight.w400,
fontSize: 14,
color: isDarkTheme
? Colors.white
: Colors.black, // ❌ Wrong: black in dark theme?
// color: Colors.black,
// color: isSelected ? Colors.green : Colors.black,
),
),
),
],
),
),
);
// return Column(
// children: [
//
// IconButton(
// // icon: Icon(icon, size: 40),
// icon: icon,
// color: isSelected ? Colors.green : Colors.amber,
// onPressed: () {
// setState(() {
// _selectedEmojiIndex = index;
// _isSmileySelected = true; // Hide error when a smiley is selected
// });
// },
// ),
// Text(
// label,
// style: TextStyle(
// fontWeight: FontWeight.w400,
// color: isSelected ? Colors.green : Color(0xFF000000),
// ),
// ),
// ],
// );
}
Widget _buildRatingRow(
String label,
double rating,
Function(double) onRatingUpdate,
bool isDarkTheme,
) {
return Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(label,
style: TextStyle(
fontSize: 14,
fontFamily: context.translate(
'Roboto',
'NotoKufi',
),
fontWeight: FontWeight.w400,
// color: Color(0xFF898C81),
color: isDarkTheme ? Colors.white : Color(0xff898C81),
)),
RatingBar.builder(
initialRating: rating,
minRating: 0,
direction: Axis.horizontal,
allowHalfRating: true,
itemCount: 5,
itemSize: 28,
itemBuilder: (context, index) => Icon(
index < rating ? Icons.star_rounded : Icons.star_border_sharp,
color: Colors.amber,
size: 30,
),
// itemBuilder: (context, _) => const Icon(
// Icons.star,
// color: Colors.amber,
// ),
onRatingUpdate: onRatingUpdate,
unratedColor: Color(0xFF8E8E8E),
),
],
);
}
Future<void> _submitFeedback() async {
if (_selectedEmojiIndex == null) {
setState(() {
_isSmileySelected = false;
});
return;
} else {
setState(() {
_isSmileySelected = true;
});
}
if (_hasError) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(
AppLocalizations.of(context)!.formError,
style: TextStyle(
fontFamily: context.translate(
'Roboto',
'NotoKufi',
)),
// "Please correct the errors before submitting."
)),
);
return;
}
setState(() {
isLoading = true;
});
final feedbackData = {
"userId": userId,
"ease_of_use": _easeOfUseRating,
"quality": _qualityRating,
"design": _designRating,
"redundancy": _redundancyRating,
"emoji_rating": _emojiOptions[_selectedEmojiIndex!]
["label"], // Emoji rating
"feedback": _feedbackController.text,
};
try {
// final adminAuth = await _pb.admins
// .authWithPassword('pb@venbainfotech.com', 'pb@venbainfotech.com');
// final auth = ref.watch(authProvider);
// final adminAuth = await pb.admins.authWithPassword(
// auth.email,
// auth.password,
// );
//
// final adminToken = adminAuth.token;
final themeService = ThemeBaseService();
// final adminToken = await themeService.getAdminToken(
// email: auth.email,
// password: auth.password,
// );
final response = await _pb.collection('feedback').create(
body: feedbackData,
headers: {'APP_SIGNATURE': 'fcsc.gov.ae.X7pL9qZm2A'},
);
if (response != null) {
final createdTime = response.created;
// Send email
// await sendFeedbackEmail(feedbackData, createdTime);
await _removeFeedbackText();
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(
AppLocalizations.of(context)!.feedback_submitted,
style: TextStyle(
fontFamily: context.translate(
'Roboto',
'NotoKufi',
)),
),
),
);
setState(() {
_isFeedbackSubmitted = true;
_isFeedbackFailed = false;
_resetFeedbackForm();
isLoading = false;
});
}
} catch (e) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(
// "Failed to submit feedback: $e"
AppLocalizations.of(context)!.feedback_failed(e.toString()),
style: TextStyle(
fontFamily: context.translate(
'Roboto',
'NotoKufi',
)),
)),
);
setState(() {
_isFeedbackFailed = true; // Update state to show failure message
});
}
}
void _resetFeedbackForm() {
_easeOfUseRating = 0;
_qualityRating = 0;
_designRating = 0;
_redundancyRating = 0;
_selectedEmojiIndex = null;
_feedbackController.clear();
}
// Future<void> fetchEmailConfiguration() async {
// try {
// // Fetch data from the email_configuration collection
// final response =
// await _pb.collection('email_configuration').getFullList();
//
// // Filter the data where the label is "Feedback"
// final feedbackConfig = response.firstWhere(
// (item) =>
// item.data['label'] == 'Feedback', // Accessing the 'data' property
// );
//
// // Check if a match is found
// if (feedbackConfig != null) {
// configEmail = feedbackConfig.data['email']; // Access the 'email' value
// } else {
// print('No feedback configuration found.');
// }
// } catch (e) {
// print('Error fetching email configuration: $e');
// }
// }
//
// Future<void> sendFeedbackEmail(
// Map<String, dynamic> feedbackData, createdTime) async {
// String username = 'emailapikey'; // Your SMTP username (API key)
// String password =
// 'PHtE6r0MFu66jTQp8BAFsP7sH5TwNd4v/+02KwBH5ItACvAES01Tot4okDawqhoiB/FEHfaey4Nvteyf5ePQJG28YW9OCWqyqK3sx/VYSPOZsbq6x00auVwYd0zUVY7pe9ds0yLTvNraNA=='; // Your SMTP password
//
// final smtpServer = SmtpServer('smtp.zeptomail.in',
// port: 587,
// username: username,
// password: password,
// ssl: false, // Use TLS
// ignoreBadCertificate:
// true); // Set to true if you're testing with a self-signed certificate
//
// // Check if additional feedback was provided
// String additionalFeedback = feedbackData["feedback"]?.isNotEmpty == true
// ? feedbackData["feedback"]
// : "No additional feedback provided.";
//
// // Format separately for date and time
// // Parse the string to DateTime
// // Parse the input as UTC and convert to DateTime in UTC timezone
// DateTime feedbackDateTime = DateTime.parse(createdTime).toUtc();
//
// // Format date as DD-MM-YYYY in UTC
// final String formattedDate =
// DateFormat('dd-MM-yyyy').format(feedbackDateTime);
//
// // Format to the desired output: "dd.MM.yyyy HH:mm 'UTC'"
// final DateFormat formatter = DateFormat("dd.MM.yyyy HH:mm 'UTC'");
// String formattedDateTime = formatter.format(feedbackDateTime);
//
// String email = configEmail; // Direct assignment
// // Create the email message
// final message = Message()
// ..from = Address('bbone@venbait.in', 'FCSC')
// ..recipients.add(email) // Set the recipient email
// ..subject = 'UAE Stats Feedback'
// ..text = '''
// Dear [App Owner/Admin],
//
// You have received new feedback from a user through the mobile application.
//
// User Details:
//
// 1. Name: Guest
// 2. Date of Submission: $formattedDate
// 3. Time of Submission: $formattedDateTime
//
// Feedback:
//
// 1. How was your experience with us today? Rating: ${feedbackData["emoji_rating"]}
// 2. How did we perform in key areas?
// 1. Ease of Use: ${feedbackData["ease_of_use"]}
// 2. Quality: ${feedbackData["quality"]}
// 3. Design: ${feedbackData["design"]}
// 4. Redundancy: ${feedbackData["redundancy"]}
// 3. Additional Feedback:
// 1. $additionalFeedback
//
// Thank you,
// The FCSC App Team
// ''';
//
// try {
// // Send the email
// print("Message details:");
// print("From: ${message.from}");
// print("To: ${message.recipients}");
// print("Subject: ${message.subject}");
// print("Body: ${message.text}");
// final sendReport = await send(message, smtpServer);
// print('Message sent: ' + sendReport.toString());
// } catch (e) {
// print('Message not sent: $e');
// // Handle the error as needed
// }
// }
@override
void dispose() {
_feedbackController.dispose();
WidgetsBinding.instance.removeObserver(this);
super.dispose();
}
}
class LangToggle extends ConsumerWidget {
final Future<void> Function(
int emojiIndex, String ratingKey, double ratingValue) onSaveFeedback;
final Future<void> Function() onLoadFeedback;
final int selectedEmojiIndex;
final double easeOfUseRating;
final double qualityRating;
final double designRating;
final double redundancyRating;
const LangToggle({
required this.onSaveFeedback,
required this.onLoadFeedback,
required this.selectedEmojiIndex,
required this.easeOfUseRating,
required this.qualityRating,
required this.designRating,
required this.redundancyRating,
});
@override
Widget build(BuildContext context, WidgetRef ref) {
final isOn = context.language == LanguageLocale.enUS;
return MyToggle(
isOn: isOn,
onTap: () async {
await onSaveFeedback(
selectedEmojiIndex,
'ease_of_use_rating',
easeOfUseRating,
);
await onSaveFeedback(
selectedEmojiIndex,
'quality_rating',
qualityRating,
);
await onSaveFeedback(
selectedEmojiIndex,
'design_rating',
designRating,
);
await onSaveFeedback(
selectedEmojiIndex,
'redundancy_rating',
redundancyRating,
);
final languageAfter = isOn ? LanguageLocale.arAE : LanguageLocale.enUS;
final grs = GoRouterState.of(context);
grs.pathParameters['locale'] = languageAfter.toString();
context.go(grs.pathWithParameters);
ref.read(preferencesUseCaseProvider.notifier).updatePreferences(
(prefs) => prefs.copyWith(language: languageAfter),
);
await onLoadFeedback(); // Reload feedback text after toggling language
},
knobTextWhenOn: 'ع',
knobTextWhenOff: 'EN',
pathColorWhenOn: Color(0xFFD9D9D9),
pathColorWhenOff: Color(0xFFD9D9D9),
knobTextStyleWhenOn: TextStyle(
color: Color(0xFF010101),
fontWeight: FontWeight.bold,
),
knobTextStyleWhenOff: TextStyle(
color: Color(0xFF010101),
fontWeight: FontWeight.bold,
),
);
}
}