1248 lines
41 KiB
Dart
Executable File
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,
|
|
),
|
|
);
|
|
}
|
|
}
|