Registration page added
This commit is contained in:
commit
4efcd311ba
3
devtools_options.yaml
Normal file
3
devtools_options.yaml
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
description: This file stores settings for Dart & Flutter DevTools.
|
||||||
|
documentation: https://docs.flutter.dev/tools/devtools/extensions#configure-extension-enablement-states
|
||||||
|
extensions:
|
||||||
@ -1,6 +1,12 @@
|
|||||||
|
import 'dart:convert';
|
||||||
|
import 'dart:io';
|
||||||
|
|
||||||
import 'package:flutter/gestures.dart';
|
import 'package:flutter/gestures.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:http/http.dart' as http;
|
||||||
|
import 'package:image_picker/image_picker.dart';
|
||||||
import 'package:intl/intl.dart';
|
import 'package:intl/intl.dart';
|
||||||
|
import 'package:pocketbase/pocketbase.dart';
|
||||||
|
|
||||||
class ProfileScreen extends StatefulWidget {
|
class ProfileScreen extends StatefulWidget {
|
||||||
@override
|
@override
|
||||||
@ -8,9 +14,13 @@ class ProfileScreen extends StatefulWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class _ProfileScreenState extends State<ProfileScreen> {
|
class _ProfileScreenState extends State<ProfileScreen> {
|
||||||
|
final _pb = PocketBase('https://pb.venbait.in');
|
||||||
|
// final _pb = PocketBase('http://127.0.0.1:8090');
|
||||||
final _formKey = GlobalKey<FormState>();
|
final _formKey = GlobalKey<FormState>();
|
||||||
final TextEditingController _fullNameController = TextEditingController();
|
final TextEditingController _fullNameController = TextEditingController();
|
||||||
final TextEditingController _dateController = TextEditingController();
|
final TextEditingController _dateController = TextEditingController();
|
||||||
|
final TextEditingController _usernameController = TextEditingController();
|
||||||
|
final TextEditingController _emailController = TextEditingController();
|
||||||
// To keep track of the selected date
|
// To keep track of the selected date
|
||||||
DateTime? _selectedDate;
|
DateTime? _selectedDate;
|
||||||
|
|
||||||
@ -24,15 +34,72 @@ class _ProfileScreenState extends State<ProfileScreen> {
|
|||||||
];
|
];
|
||||||
String? _selectedCountry;
|
String? _selectedCountry;
|
||||||
bool _termsAccepted = false;
|
bool _termsAccepted = false;
|
||||||
|
final _picker = ImagePicker();
|
||||||
|
File? _profileImage;
|
||||||
|
|
||||||
// Regular expression to validate Full Name (no special characters)
|
// Regular expression to validate Full Name (no special characters)
|
||||||
final RegExp _nameRegExp = RegExp(r"^[a-zA-Z\s]+$");
|
final RegExp _nameRegExp = RegExp(r"^[a-zA-Z\s]+$");
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
_fetchUserData(); // Call the function to fetch user data
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
_fullNameController.dispose();
|
||||||
|
_dateController.dispose();
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
void _fetchUserData() async {
|
||||||
|
try {
|
||||||
|
final userId = 'tsgehyvgy6owypj';
|
||||||
|
final adminAuth = await _pb.admins
|
||||||
|
.authWithPassword('pb@venbainfotech.com', 'pb@venbainfotech.com');
|
||||||
|
final adminToken = adminAuth.token;
|
||||||
|
final response = await http.get(
|
||||||
|
Uri.parse(
|
||||||
|
'https://pb.venbait.in/api/collections/users/records/$userId'),
|
||||||
|
headers: {
|
||||||
|
'Authorization': 'Bearer $adminToken', // Add token to header
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
if (response.statusCode == 200) {
|
||||||
|
final data = jsonDecode(response.body);
|
||||||
|
setState(() {
|
||||||
|
_usernameController.text = data['username'];
|
||||||
|
_emailController.text = data['email'];
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
print('Failed to fetch user data: ${response.body}');
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
print('Failed to fetch user data: $error');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void _pickImage() async {
|
||||||
|
final XFile? pickedFile =
|
||||||
|
await _picker.pickImage(source: ImageSource.gallery);
|
||||||
|
if (pickedFile != null) {
|
||||||
|
setState(() {
|
||||||
|
_profileImage = File(pickedFile.path);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Function to open the date picker
|
// Function to open the date picker
|
||||||
Future<void> _pickDate() async {
|
Future<void> _pickDate() async {
|
||||||
final DateTime today = DateTime.now();
|
final DateTime today = DateTime.now();
|
||||||
final DateTime initialDate = _selectedDate ?? today.subtract(const Duration(days: 365 * 18)); // Default to 18 years ago
|
final DateTime initialDate = _selectedDate ??
|
||||||
final DateTime firstDate = today.subtract(const Duration(days: 365 * 100)); // Allow picking dates back to 100 years ago
|
today.subtract(
|
||||||
|
const Duration(days: 365 * 18)); // Default to 18 years ago
|
||||||
|
final DateTime firstDate = today.subtract(const Duration(
|
||||||
|
days: 365 * 100)); // Allow picking dates back to 100 years ago
|
||||||
final DateTime lastDate = today; // Allow picking dates up to today
|
final DateTime lastDate = today; // Allow picking dates up to today
|
||||||
|
|
||||||
// Updated Date format to DD/MM/YYYY
|
// Updated Date format to DD/MM/YYYY
|
||||||
@ -82,6 +149,97 @@ class _ProfileScreenState extends State<ProfileScreen> {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void showConfirmationDialog(BuildContext context) async {
|
||||||
|
final result = await showDialog<bool>(
|
||||||
|
context: context,
|
||||||
|
builder: (context) => const ConfirmationDialog(),
|
||||||
|
);
|
||||||
|
if (result == true) {
|
||||||
|
if (_formKey.currentState?.validate() ?? false) {
|
||||||
|
try {
|
||||||
|
String userID = 'tsgehyvgy6owypj';
|
||||||
|
// Retrieve data from text fields and other inputs
|
||||||
|
String fullName = _fullNameController.text;
|
||||||
|
// String username = 'users55538';
|
||||||
|
// String email = 'surendarsuri30@gmail.com';
|
||||||
|
String dateOfBirth = _dateController.text; // in DD/MM/YYYY format
|
||||||
|
String countryRegion = _selectedCountry ?? '';
|
||||||
|
bool termsAccepted = _termsAccepted;
|
||||||
|
|
||||||
|
// Parse the date from dd/MM/yyyy format and convert to yyyy-MM-dd
|
||||||
|
// Parse the date from dd/MM/yyyy format and format to yyyy-MM-dd
|
||||||
|
DateTime dob = DateFormat('dd/MM/yyyy').parse(dateOfBirth);
|
||||||
|
String formattedDob = DateFormat('yyyy-MM-dd').format(dob);
|
||||||
|
|
||||||
|
// Prepare form data
|
||||||
|
final request = http.MultipartRequest(
|
||||||
|
'PATCH',
|
||||||
|
Uri.parse(
|
||||||
|
'https://pb.venbait.in/api/collections/users/records/$userID'), // replace with actual user ID
|
||||||
|
);
|
||||||
|
|
||||||
|
// Set the fields for the user profile
|
||||||
|
request.fields['full_name'] = fullName;
|
||||||
|
// request.fields['username'] = username;
|
||||||
|
// request.fields['email'] = email;
|
||||||
|
request.fields['dob'] = formattedDob; // ensure correct format
|
||||||
|
request.fields['country_region'] = countryRegion;
|
||||||
|
request.fields['terms_accepted'] = termsAccepted.toString();
|
||||||
|
|
||||||
|
// Add image file if selected
|
||||||
|
if (_profileImage != null) {
|
||||||
|
request.files.add(await http.MultipartFile.fromPath(
|
||||||
|
'avatar', _profileImage!.path));
|
||||||
|
}
|
||||||
|
final adminAuth = await _pb.admins
|
||||||
|
.authWithPassword('pb@venbainfotech.com', 'pb@venbainfotech.com');
|
||||||
|
final adminToken = adminAuth.token;
|
||||||
|
// Add PocketBase auth headers if needed (for authenticated requests)
|
||||||
|
request.headers['Authorization'] = 'Bearer ${adminToken}';
|
||||||
|
|
||||||
|
print(request);
|
||||||
|
|
||||||
|
// Send the request
|
||||||
|
final response = await request.send();
|
||||||
|
|
||||||
|
if (response.statusCode == 200) {
|
||||||
|
print(response.statusCode);
|
||||||
|
_resetFormFields();
|
||||||
|
Navigator.pushNamed(context, 'home');
|
||||||
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
|
SnackBar(content: Text("Profile updated successfully!")));
|
||||||
|
} else {
|
||||||
|
// Log the response body for better debugging
|
||||||
|
final responseBody = await response.stream.bytesToString();
|
||||||
|
ScaffoldMessenger.of(context).showSnackBar(SnackBar(
|
||||||
|
content: Text(
|
||||||
|
"Failed to update profile: ${response.reasonPhrase}, Body: $responseBody")));
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
|
SnackBar(content: Text("Failed to update profile: $error")));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void _resetFormFields() {
|
||||||
|
print('reset');
|
||||||
|
setState(() {
|
||||||
|
// Reset all text controllers
|
||||||
|
_fullNameController.clear();
|
||||||
|
_dateController.clear();
|
||||||
|
_selectedCountry = null;
|
||||||
|
_termsAccepted = false;
|
||||||
|
|
||||||
|
// Reset profile image
|
||||||
|
_profileImage = null;
|
||||||
|
|
||||||
|
// Reset form validation state
|
||||||
|
_formKey.currentState?.reset();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
@ -121,18 +279,22 @@ class _ProfileScreenState extends State<ProfileScreen> {
|
|||||||
children: [
|
children: [
|
||||||
CircleAvatar(
|
CircleAvatar(
|
||||||
radius: 50,
|
radius: 50,
|
||||||
backgroundImage:
|
backgroundImage: _profileImage != null
|
||||||
AssetImage("assets/edit_profile/profile.png"),
|
? FileImage(_profileImage!)
|
||||||
// Replace with actual image URL
|
: AssetImage("assets/edit_profile/profile.png")
|
||||||
|
as ImageProvider,
|
||||||
child: Align(
|
child: Align(
|
||||||
alignment: Alignment.bottomRight,
|
alignment: Alignment.bottomRight,
|
||||||
child: CircleAvatar(
|
child: GestureDetector(
|
||||||
radius: 15,
|
onTap: _pickImage, // Call `_pickImage` on tap
|
||||||
backgroundColor: Colors.white,
|
child: CircleAvatar(
|
||||||
child: Icon(
|
radius: 15,
|
||||||
Icons.camera_alt,
|
backgroundColor: Colors.white,
|
||||||
size: 15,
|
child: Icon(
|
||||||
color: Colors.grey,
|
Icons.camera_alt,
|
||||||
|
size: 15,
|
||||||
|
color: Colors.grey,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@ -141,46 +303,56 @@ class _ProfileScreenState extends State<ProfileScreen> {
|
|||||||
Row(
|
Row(
|
||||||
mainAxisAlignment: MainAxisAlignment.start,
|
mainAxisAlignment: MainAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
Text("User Name",
|
Text(
|
||||||
style: TextStyle(fontSize: 16, fontWeight: FontWeight.w500),
|
"User Name",
|
||||||
|
style:
|
||||||
|
TextStyle(fontSize: 16, fontWeight: FontWeight.w500),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
SizedBox(height: 10),
|
SizedBox(height: 10),
|
||||||
TextFormField(
|
TextFormField(
|
||||||
|
controller: _usernameController,
|
||||||
decoration: InputDecoration(
|
decoration: InputDecoration(
|
||||||
hintText: 'Mohammad Hassan',
|
hintText: 'Mohammad Hassan',
|
||||||
hintStyle: TextStyle(color: Colors.grey),
|
hintStyle: TextStyle(color: Colors.grey),
|
||||||
border: OutlineInputBorder(
|
border: OutlineInputBorder(
|
||||||
borderRadius: BorderRadius.circular(8),
|
borderRadius: BorderRadius.circular(8),
|
||||||
),enabled: false,
|
),
|
||||||
|
enabled: false,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
SizedBox(height: 10),
|
SizedBox(height: 10),
|
||||||
Row(
|
Row(
|
||||||
mainAxisAlignment: MainAxisAlignment.start,
|
mainAxisAlignment: MainAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
Text("E-mail",
|
Text(
|
||||||
style: TextStyle(fontSize: 16, fontWeight: FontWeight.w500),
|
"E-mail",
|
||||||
|
style:
|
||||||
|
TextStyle(fontSize: 16, fontWeight: FontWeight.w500),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
SizedBox(height: 10),
|
SizedBox(height: 10),
|
||||||
TextFormField(
|
TextFormField(
|
||||||
|
controller: _emailController,
|
||||||
decoration: InputDecoration(
|
decoration: InputDecoration(
|
||||||
hintText: 'mohammad.hassan@fcsc.gov.ae',
|
hintText: 'mohammad.hassan@fcsc.gov.ae',
|
||||||
hintStyle: TextStyle(color: Colors.grey),
|
hintStyle: TextStyle(color: Colors.grey),
|
||||||
border: OutlineInputBorder(
|
border: OutlineInputBorder(
|
||||||
borderRadius: BorderRadius.circular(8),
|
borderRadius: BorderRadius.circular(8),
|
||||||
),enabled: false,
|
),
|
||||||
|
enabled: false,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
SizedBox(height: 10),
|
SizedBox(height: 10),
|
||||||
Row(
|
Row(
|
||||||
mainAxisAlignment: MainAxisAlignment.start,
|
mainAxisAlignment: MainAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
Text("Full Name",
|
Text(
|
||||||
style: TextStyle(fontSize: 16, fontWeight: FontWeight.w500),
|
"Full Name",
|
||||||
|
style:
|
||||||
|
TextStyle(fontSize: 16, fontWeight: FontWeight.w500),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
@ -210,13 +382,14 @@ class _ProfileScreenState extends State<ProfileScreen> {
|
|||||||
Row(
|
Row(
|
||||||
mainAxisAlignment: MainAxisAlignment.start,
|
mainAxisAlignment: MainAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
Text("Date of Birth*",
|
Text(
|
||||||
style: TextStyle(fontSize: 16, fontWeight: FontWeight.w500),
|
"Date of Birth*",
|
||||||
|
style:
|
||||||
|
TextStyle(fontSize: 16, fontWeight: FontWeight.w500),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
SizedBox(height: 10),
|
SizedBox(height: 10),
|
||||||
|
|
||||||
TextFormField(
|
TextFormField(
|
||||||
controller: _dateController,
|
controller: _dateController,
|
||||||
decoration: InputDecoration(
|
decoration: InputDecoration(
|
||||||
@ -226,18 +399,18 @@ class _ProfileScreenState extends State<ProfileScreen> {
|
|||||||
hintText: 'Select your Date of Birth',
|
hintText: 'Select your Date of Birth',
|
||||||
suffixIcon: const Icon(Icons.arrow_drop_down_sharp),
|
suffixIcon: const Icon(Icons.arrow_drop_down_sharp),
|
||||||
),
|
),
|
||||||
|
|
||||||
readOnly: true,
|
readOnly: true,
|
||||||
onTap: _pickDate,
|
onTap: _pickDate,
|
||||||
validator: _validateDob,
|
validator: _validateDob,
|
||||||
),
|
),
|
||||||
|
|
||||||
SizedBox(height: 10),
|
SizedBox(height: 10),
|
||||||
Row(
|
Row(
|
||||||
mainAxisAlignment: MainAxisAlignment.start,
|
mainAxisAlignment: MainAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
Text("Country/Region*",
|
Text(
|
||||||
style: TextStyle(fontSize: 16, fontWeight: FontWeight.w500),
|
"Country/Region*",
|
||||||
|
style:
|
||||||
|
TextStyle(fontSize: 16, fontWeight: FontWeight.w500),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
@ -252,9 +425,9 @@ class _ProfileScreenState extends State<ProfileScreen> {
|
|||||||
),
|
),
|
||||||
items: _countries
|
items: _countries
|
||||||
.map((item) => DropdownMenuItem<String>(
|
.map((item) => DropdownMenuItem<String>(
|
||||||
value: item,
|
value: item,
|
||||||
child: Text(item),
|
child: Text(item),
|
||||||
))
|
))
|
||||||
.toList(),
|
.toList(),
|
||||||
onChanged: (String? newValue) {
|
onChanged: (String? newValue) {
|
||||||
setState(() {
|
setState(() {
|
||||||
@ -275,7 +448,9 @@ class _ProfileScreenState extends State<ProfileScreen> {
|
|||||||
mainAxisAlignment: MainAxisAlignment.start,
|
mainAxisAlignment: MainAxisAlignment.start,
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
SizedBox(height: 10,),
|
SizedBox(
|
||||||
|
height: 10,
|
||||||
|
),
|
||||||
Text.rich(
|
Text.rich(
|
||||||
TextSpan(
|
TextSpan(
|
||||||
text: 'I agree to the ',
|
text: 'I agree to the ',
|
||||||
@ -324,14 +499,22 @@ class _ProfileScreenState extends State<ProfileScreen> {
|
|||||||
],
|
],
|
||||||
),
|
),
|
||||||
SizedBox(height: 20),
|
SizedBox(height: 20),
|
||||||
|
Center(
|
||||||
Center(child: Text("Change Password",style: TextStyle(color: Colors.blue,decoration: TextDecoration.underline),),),
|
child: Text(
|
||||||
|
"Change Password",
|
||||||
|
style: TextStyle(
|
||||||
|
color: Colors.blue,
|
||||||
|
decoration: TextDecoration.underline),
|
||||||
|
),
|
||||||
|
),
|
||||||
SizedBox(height: 20),
|
SizedBox(height: 20),
|
||||||
ElevatedButton.icon(
|
ElevatedButton.icon(
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
if (_formKey.currentState?.validate() ?? false) {
|
if (_formKey.currentState?.validate() ?? false) {
|
||||||
|
_formKey.currentState?.save();
|
||||||
if (_termsAccepted) {
|
if (_termsAccepted) {
|
||||||
_formKey.currentState?.save();
|
_formKey.currentState?.save();
|
||||||
|
// _confirmSaveProfile();
|
||||||
showConfirmationDialog(context);
|
showConfirmationDialog(context);
|
||||||
} else {
|
} else {
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
@ -366,33 +549,55 @@ class _ProfileScreenState extends State<ProfileScreen> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//Alert Dialog box
|
|
||||||
|
|
||||||
class ConfirmationDialog extends StatelessWidget {
|
class ConfirmationDialog extends StatelessWidget {
|
||||||
const ConfirmationDialog({Key? key}) : super(key: key);
|
const ConfirmationDialog({Key? key}) : super(key: key);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return AlertDialog(
|
return AlertDialog(
|
||||||
content: Column(
|
content: Column(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
children: [
|
||||||
children: const [
|
|
||||||
Text(
|
Text(
|
||||||
'Are you sure you want to save this page?',
|
"Are you sure you want to save this page?",
|
||||||
style: TextStyle(
|
style: const TextStyle(
|
||||||
fontSize: 14,
|
fontSize: 14,
|
||||||
color: Colors.black54,
|
color: Color(0xFF898C81),
|
||||||
|
fontFamily: 'Roboto',
|
||||||
|
fontWeight: FontWeight.w400,
|
||||||
),
|
),
|
||||||
|
textAlign: TextAlign.center,
|
||||||
),
|
),
|
||||||
SizedBox(height: 8),
|
SizedBox(height: 8),
|
||||||
Text(
|
Text.rich(
|
||||||
'Once saved, you will not be able to change your Name and Date of birth.',
|
TextSpan(
|
||||||
style: TextStyle(
|
text: "Once saved, you will not be able to change your ",
|
||||||
fontSize: 14,
|
style: TextStyle(
|
||||||
color: Colors.black54,
|
fontSize: 14,
|
||||||
|
color: Color(0xFF898C81),
|
||||||
|
fontFamily: 'Roboto',
|
||||||
|
fontWeight: FontWeight.w400,
|
||||||
|
),
|
||||||
|
children: [
|
||||||
|
TextSpan(
|
||||||
|
text: "name",
|
||||||
|
style:
|
||||||
|
TextStyle(fontWeight: FontWeight.w700), // Bold for "name"
|
||||||
|
),
|
||||||
|
TextSpan(
|
||||||
|
text: " or ",
|
||||||
|
),
|
||||||
|
TextSpan(
|
||||||
|
text: "date of birth",
|
||||||
|
style: TextStyle(
|
||||||
|
fontWeight: FontWeight.w700), // Bold for "date of birth"
|
||||||
|
),
|
||||||
|
TextSpan(
|
||||||
|
text: ".",
|
||||||
|
),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
),
|
textAlign: TextAlign.center,
|
||||||
|
)
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
shape: RoundedRectangleBorder(
|
shape: RoundedRectangleBorder(
|
||||||
@ -408,15 +613,18 @@ class ConfirmationDialog extends StatelessWidget {
|
|||||||
children: [
|
children: [
|
||||||
OutlinedButton(
|
OutlinedButton(
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
Navigator.of(context).pop(true);
|
Navigator.of(context).pop(false);
|
||||||
},
|
},
|
||||||
style: OutlinedButton.styleFrom(
|
style: OutlinedButton.styleFrom(
|
||||||
side: BorderSide(color: Color(0xFF92722A)),
|
side: BorderSide(color: Color(0xFF92722A)),
|
||||||
shape: RoundedRectangleBorder(
|
shape: RoundedRectangleBorder(
|
||||||
borderRadius: BorderRadius.all(Radius.circular(7)),
|
borderRadius: BorderRadius.all(Radius.circular(7)),
|
||||||
),// Set the border color here
|
), // Set the border color here
|
||||||
|
),
|
||||||
|
child: Text(
|
||||||
|
"Cancel",
|
||||||
|
style: TextStyle(color: Color(0xFF92722A)),
|
||||||
),
|
),
|
||||||
child: Text("Cancel",style: TextStyle(color: Color(0xFF92722A)),),
|
|
||||||
),
|
),
|
||||||
ElevatedButton(
|
ElevatedButton(
|
||||||
onPressed: () => Navigator.of(context).pop(true),
|
onPressed: () => Navigator.of(context).pop(true),
|
||||||
@ -437,31 +645,10 @@ class ConfirmationDialog extends StatelessWidget {
|
|||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
SizedBox(height: 5,)
|
SizedBox(
|
||||||
|
height: 5,
|
||||||
|
)
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Usage example:
|
|
||||||
void showConfirmationDialog(BuildContext context) async {
|
|
||||||
final result = await showDialog<bool>(
|
|
||||||
context: context,
|
|
||||||
builder: (context) => const ConfirmationDialog(),
|
|
||||||
);
|
|
||||||
|
|
||||||
if (result == true) {
|
|
||||||
// Add your save logic here
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -1,98 +1,22 @@
|
|||||||
// import 'package:flutter/material.dart';
|
import 'package:external_repos/external_repos.dart';
|
||||||
// import 'package:flutter_hooks/flutter_hooks.dart';
|
|
||||||
// import 'package:hooks_riverpod/hooks_riverpod.dart';
|
|
||||||
// import 'package:webview_flutter/webview_flutter.dart';
|
|
||||||
|
|
||||||
// import 'package:uae_stat/domain/use_cases/language.dart';
|
|
||||||
// import 'package:uae_stat/presentation/components/themed_app_bar.dart';
|
|
||||||
|
|
||||||
// class FeedbackRoute extends HookConsumerWidget {
|
|
||||||
// const FeedbackRoute({super.key});
|
|
||||||
|
|
||||||
// static const enUrl =
|
|
||||||
// 'https://fcsc.gov.ae/en-us/Pages/e-Participation/Contact-Us.aspx';
|
|
||||||
// static const arUrl =
|
|
||||||
// 'https://fcsc.gov.ae/ar-ae/Pages/e-Participation/Contact-Us.aspx';
|
|
||||||
|
|
||||||
// @override
|
|
||||||
// Widget build(BuildContext context, WidgetRef ref) {
|
|
||||||
// final didLoadSuccessfully = useState<bool?>(true);
|
|
||||||
// final url = context.translate(enUrl, arUrl);
|
|
||||||
// final ctl = WebViewController()
|
|
||||||
// ..setJavaScriptMode(JavaScriptMode.unrestricted)
|
|
||||||
// ..setBackgroundColor(Colors.transparent)
|
|
||||||
// ..setNavigationDelegate(
|
|
||||||
// NavigationDelegate(
|
|
||||||
// // onProgress: (int progress) {},
|
|
||||||
// onPageStarted: (String url) => didLoadSuccessfully.value = null,
|
|
||||||
// onPageFinished: (String url) => didLoadSuccessfully.value = true,
|
|
||||||
// onWebResourceError: (WebResourceError error) =>
|
|
||||||
// didLoadSuccessfully.value = null,
|
|
||||||
// onNavigationRequest: (NavigationRequest request) {
|
|
||||||
// if (!request.url.startsWith('https://fcsc.gov.ae/')) {
|
|
||||||
// return NavigationDecision.prevent;
|
|
||||||
// }
|
|
||||||
// return NavigationDecision.navigate;
|
|
||||||
// },
|
|
||||||
// ),
|
|
||||||
// )
|
|
||||||
// ..loadRequest(
|
|
||||||
// Uri.parse(
|
|
||||||
// url,
|
|
||||||
// ),
|
|
||||||
// );
|
|
||||||
// final bodyContent = Stack(
|
|
||||||
// children: [
|
|
||||||
// Center(
|
|
||||||
// child: didLoadSuccessfully.value == null
|
|
||||||
// ? const CircularProgressIndicator()
|
|
||||||
// : didLoadSuccessfully.value!
|
|
||||||
// ? const SizedBox.shrink()
|
|
||||||
// : const Text('An error occurred.'),
|
|
||||||
// ),
|
|
||||||
// WebViewWidget(controller: ctl),
|
|
||||||
// ],
|
|
||||||
// );
|
|
||||||
// final body = Column(
|
|
||||||
// children: [
|
|
||||||
// ThemedAppBar(
|
|
||||||
// titleText: context.translate(
|
|
||||||
// 'Feedback',
|
|
||||||
// 'تعليق',
|
|
||||||
// ),
|
|
||||||
// ),
|
|
||||||
// Expanded(
|
|
||||||
// child: bodyContent,
|
|
||||||
// ),
|
|
||||||
// ],
|
|
||||||
// );
|
|
||||||
// return ColoredBox(
|
|
||||||
// color: Colors.white,
|
|
||||||
// child: SafeArea(
|
|
||||||
// bottom: false,
|
|
||||||
// child: body,
|
|
||||||
// ),
|
|
||||||
// );
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
import 'package:excel/excel.dart';
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_rating_bar/flutter_rating_bar.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:intl/intl.dart';
|
||||||
|
import 'package:pocketbase/pocketbase.dart';
|
||||||
|
import 'package:shared_preferences/shared_preferences.dart';
|
||||||
|
import 'package:uae_stat/domain/use_cases/language.dart';
|
||||||
|
import 'package:uae_stat/infrastructure/services/packages/go_router.dart';
|
||||||
import 'package:uae_stat/presentation/components/my_drawer.dart';
|
import 'package:uae_stat/presentation/components/my_drawer.dart';
|
||||||
|
import 'package:mailer/mailer.dart';
|
||||||
// class FeedbackPage extends StatelessWidget {
|
import 'package:mailer/smtp_server.dart';
|
||||||
// const FeedbackPage({super.key});
|
import 'package:uae_stat/presentation/components/space.dart';
|
||||||
|
import '../../../config/my_theme.dart';
|
||||||
// @override
|
import '../../../domain/use_cases/preferences_use_case.dart';
|
||||||
// Widget build(BuildContext context) {
|
import '../../components/lang_toggle.dart';
|
||||||
// return const Scaffold(
|
import '../../components/my_bottom_nav_bar.dart';
|
||||||
// body: Center(
|
import '../../components/my_toggle.dart';
|
||||||
// child: Text('Feedback'),
|
|
||||||
// ),
|
|
||||||
// );
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
class FeedbackForm extends StatefulWidget {
|
class FeedbackForm extends StatefulWidget {
|
||||||
const FeedbackForm({super.key});
|
const FeedbackForm({super.key});
|
||||||
@ -102,111 +26,438 @@ class FeedbackForm extends StatefulWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class _FeedbackFormState extends State<FeedbackForm> {
|
class _FeedbackFormState extends State<FeedbackForm> {
|
||||||
|
final _pb =
|
||||||
|
PocketBase('https://pb.venbait.in'); // Initialize PocketBase client
|
||||||
|
// final _pb = PocketBase('http://127.0.0.1:8090'); // Initialize PocketBase client
|
||||||
|
|
||||||
|
final TextEditingController _feedbackController = TextEditingController();
|
||||||
|
|
||||||
double _easeOfUseRating = 0;
|
double _easeOfUseRating = 0;
|
||||||
double _qualityRating = 0;
|
double _qualityRating = 0;
|
||||||
double _designRating = 0;
|
double _designRating = 0;
|
||||||
double _redundancyRating = 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;
|
||||||
|
|
||||||
|
List<Map<String, dynamic>> get _emojiOptions => [
|
||||||
|
{
|
||||||
|
"icon": Icons.sentiment_very_dissatisfied,
|
||||||
|
"label": context.translate("Terrible", "رهيب"), // Translate here
|
||||||
|
"value": 1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon": Icons.sentiment_dissatisfied,
|
||||||
|
"label": context.translate("Bad", "سيء"), // Translate here
|
||||||
|
"value": 2,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon": Icons.sentiment_neutral,
|
||||||
|
"label": context.translate("Okay", "تمام"), // Translate here
|
||||||
|
"value": 3,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon": Icons.sentiment_satisfied,
|
||||||
|
"label": context.translate("Good", "جيد"), // Translate here
|
||||||
|
"value": 4,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon": Icons.sentiment_very_satisfied,
|
||||||
|
"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 = '';
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
_loadFeedbackText();
|
||||||
|
_feedbackController.addListener(_handleTextChange);
|
||||||
|
fetchEmailConfiguration();
|
||||||
|
}
|
||||||
|
|
||||||
|
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();
|
||||||
|
final feedbackText = prefs.getString('feedbackText') ?? '';
|
||||||
|
setState(() {
|
||||||
|
_feedbackController.text = feedbackText;
|
||||||
|
});
|
||||||
|
|
||||||
|
final savedIndex = prefs.getInt('selected_emoji_index');
|
||||||
|
if (savedIndex != null) {
|
||||||
|
setState(() {
|
||||||
|
_selectedEmojiIndex = savedIndex;
|
||||||
|
_isSmileySelected = true; // Indicating the user selected an emoji
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
setState(() {
|
||||||
|
_easeOfUseRating = prefs.getDouble('ease_of_use_rating') ?? 0;
|
||||||
|
_qualityRating = prefs.getDouble('quality_rating') ?? 0;
|
||||||
|
_designRating = prefs.getDouble('design_rating') ?? 0;
|
||||||
|
_redundancyRating = prefs.getDouble('redundancy_rating') ?? 0;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
drawer: const MyDrawer(),
|
drawer: const MyDrawer(),
|
||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
title: const Text('Feedback Form'),
|
backgroundColor: const Color(0xFFf8f9ff),
|
||||||
|
title: Text(context.translate('Feedback Form', 'نموذج الملاحظات')),
|
||||||
|
actions: [
|
||||||
|
Align(
|
||||||
|
alignment: AlignmentDirectional.topEnd,
|
||||||
|
child: LangToggle(
|
||||||
|
onSaveFeedback: _saveFeedbackText,
|
||||||
|
onLoadFeedback: _loadFeedbackText,
|
||||||
|
selectedEmojiIndex: _selectedEmojiIndex ?? 0,
|
||||||
|
easeOfUseRating: _easeOfUseRating,
|
||||||
|
qualityRating: _qualityRating,
|
||||||
|
designRating: _designRating,
|
||||||
|
redundancyRating: _redundancyRating,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
body: Padding(
|
body: Padding(
|
||||||
padding: const EdgeInsets.all(16.0),
|
padding: const EdgeInsets.all(16.0),
|
||||||
child: SingleChildScrollView(
|
child: SingleChildScrollView(
|
||||||
child: Column(
|
child: _isFeedbackSubmitted
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
? _buildThankYouMessage()
|
||||||
children: <Widget>[
|
: _isFeedbackFailed
|
||||||
const Text(
|
? _buildFailureMessage()
|
||||||
'Do you have a suggestion or had any problem? Let us know.',
|
: _buildFeedbackForm(),
|
||||||
style: TextStyle(fontSize: 18),
|
|
||||||
),
|
|
||||||
const SizedBox(height: 20),
|
|
||||||
const Text(
|
|
||||||
'How was your experience with us today?',
|
|
||||||
style: TextStyle(fontSize: 16),
|
|
||||||
),
|
|
||||||
const SizedBox(height: 10),
|
|
||||||
Row(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
|
||||||
children: [
|
|
||||||
_buildEmojiButton(
|
|
||||||
Icons.sentiment_very_dissatisfied,
|
|
||||||
'Terrible',
|
|
||||||
),
|
|
||||||
_buildEmojiButton(Icons.sentiment_dissatisfied, 'Bad'),
|
|
||||||
_buildEmojiButton(Icons.sentiment_neutral, 'Okay'),
|
|
||||||
_buildEmojiButton(Icons.sentiment_satisfied, 'Good'),
|
|
||||||
_buildEmojiButton(Icons.sentiment_very_satisfied, 'Amazing'),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
const SizedBox(height: 30),
|
|
||||||
const Text(
|
|
||||||
'How good did we do in these aspects?',
|
|
||||||
style: TextStyle(fontSize: 16),
|
|
||||||
),
|
|
||||||
const SizedBox(height: 20),
|
|
||||||
_buildRatingRow('Ease of use', _easeOfUseRating, (rating) {
|
|
||||||
setState(() {
|
|
||||||
_easeOfUseRating = rating;
|
|
||||||
});
|
|
||||||
}),
|
|
||||||
_buildRatingRow('Quality', _qualityRating, (rating) {
|
|
||||||
setState(() {
|
|
||||||
_qualityRating = rating;
|
|
||||||
});
|
|
||||||
}),
|
|
||||||
_buildRatingRow('Design', _designRating, (rating) {
|
|
||||||
setState(() {
|
|
||||||
_designRating = rating;
|
|
||||||
});
|
|
||||||
}),
|
|
||||||
_buildRatingRow('Redundant', _redundancyRating, (rating) {
|
|
||||||
setState(() {
|
|
||||||
_redundancyRating = rating;
|
|
||||||
});
|
|
||||||
}),
|
|
||||||
const SizedBox(height: 20),
|
|
||||||
const TextField(
|
|
||||||
decoration: InputDecoration(
|
|
||||||
hintText: 'Tell us how we can improve',
|
|
||||||
border: OutlineInputBorder(
|
|
||||||
borderSide: BorderSide(color: Colors.blueAccent)),
|
|
||||||
),
|
|
||||||
maxLines: 5,
|
|
||||||
),
|
|
||||||
const SizedBox(height: 20),
|
|
||||||
Center(
|
|
||||||
child: ElevatedButton(
|
|
||||||
onPressed: () {
|
|
||||||
// Handle the submit action
|
|
||||||
},
|
|
||||||
child: const Text('Submit'),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildEmojiButton(IconData icon, String label) {
|
Widget _buildFeedbackForm() {
|
||||||
|
return Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: <Widget>[
|
||||||
|
Text(
|
||||||
|
context.translate(
|
||||||
|
'Do you have a suggestion or had any problem? Let us know.',
|
||||||
|
'هل لديك اقتراح أو واجهت أي مشكلة؟ اسمحوا لنا أن نعرف.',
|
||||||
|
),
|
||||||
|
style: const TextStyle(
|
||||||
|
fontSize: 18,
|
||||||
|
color: Color(0xFF898C81),
|
||||||
|
fontFamily: 'Roboto',
|
||||||
|
fontWeight: FontWeight.w700,
|
||||||
|
),
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
),
|
||||||
|
const SizedBox(height: 20),
|
||||||
|
Text(
|
||||||
|
context.translate('How was your experience with us today?',
|
||||||
|
'كيف كانت تجربتك معنا اليوم؟'),
|
||||||
|
style: const TextStyle(
|
||||||
|
fontSize: 18,
|
||||||
|
fontFamily: 'Roboto',
|
||||||
|
fontWeight: FontWeight.w400,
|
||||||
|
color: Color(0xFF898C81),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 10),
|
||||||
|
Container(
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
border: Border.all(
|
||||||
|
color: _isSmileySelected ? Colors.transparent : Colors.red,
|
||||||
|
width: 1.5,
|
||||||
|
),
|
||||||
|
borderRadius: BorderRadius.circular(8),
|
||||||
|
),
|
||||||
|
padding: const EdgeInsets.all(8),
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
||||||
|
children: List.generate(_emojiOptions.length, (index) {
|
||||||
|
return _buildEmojiButton(
|
||||||
|
icon: _emojiOptions[index]["icon"],
|
||||||
|
label: _emojiOptions[index]["label"],
|
||||||
|
index: index,
|
||||||
|
);
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
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: Colors.red, fontSize: 14),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 30),
|
||||||
|
Text(
|
||||||
|
context.translate('How good did we do in these aspects?',
|
||||||
|
'ما مدى جودة ما قمنا به في هذه الجوانب؟'),
|
||||||
|
style: const TextStyle(
|
||||||
|
fontSize: 18,
|
||||||
|
fontFamily: 'Roboto',
|
||||||
|
fontWeight: FontWeight.w400,
|
||||||
|
color: Color(0xFF898C81),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 20),
|
||||||
|
_buildRatingRow(
|
||||||
|
context.translate('Ease of use', 'سهولة الاستخدام'),
|
||||||
|
_easeOfUseRating,
|
||||||
|
(rating) {
|
||||||
|
setState(() {
|
||||||
|
_easeOfUseRating = rating;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
),
|
||||||
|
_buildRatingRow(
|
||||||
|
context.translate('Quality', 'جودة'),
|
||||||
|
_qualityRating,
|
||||||
|
(rating) {
|
||||||
|
setState(() {
|
||||||
|
_qualityRating = rating;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
),
|
||||||
|
_buildRatingRow(
|
||||||
|
context.translate('Design', 'تصميم'),
|
||||||
|
_designRating,
|
||||||
|
(rating) {
|
||||||
|
setState(() {
|
||||||
|
_designRating = rating;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
),
|
||||||
|
_buildRatingRow(
|
||||||
|
context.translate('Redundant', 'متكرر'),
|
||||||
|
_redundancyRating,
|
||||||
|
(rating) {
|
||||||
|
setState(() {
|
||||||
|
_redundancyRating = rating;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
),
|
||||||
|
const SizedBox(height: 20),
|
||||||
|
TextField(
|
||||||
|
controller: _feedbackController,
|
||||||
|
minLines: 5, // Start with 5 lines
|
||||||
|
maxLines: null, // Allows the field to expand automatically
|
||||||
|
maxLength: _characterLimit,
|
||||||
|
decoration: InputDecoration(
|
||||||
|
hintText: context.translate(
|
||||||
|
'Tell us how we can improve', 'أخبرنا كيف يمكننا تحسين'),
|
||||||
|
border: OutlineInputBorder(
|
||||||
|
borderSide: BorderSide(
|
||||||
|
color: _hasError ? Colors.red : Color(0xFF7296BE),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
errorText:
|
||||||
|
_hasError ? _errorMessage : null, // Show error below field
|
||||||
|
),
|
||||||
|
onChanged: (text) {
|
||||||
|
// Trigger re-validation and character limit on each change
|
||||||
|
_handleTextChange();
|
||||||
|
},
|
||||||
|
),
|
||||||
|
const SizedBox(height: 20),
|
||||||
|
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(
|
||||||
|
MyTheme.topicColor(IndicatorTopic.environment),
|
||||||
|
),
|
||||||
|
foregroundColor: const WidgetStatePropertyAll(
|
||||||
|
Colors.white,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
child: Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
context.translate(
|
||||||
|
'Submit',
|
||||||
|
'يُقدِّم',
|
||||||
|
),
|
||||||
|
),
|
||||||
|
6.horizontalSpace,
|
||||||
|
const Icon(Icons.chevron_right_outlined),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildThankYouMessage() {
|
||||||
|
return Center(
|
||||||
|
child: Column(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
Icon(Icons.thumb_up, size: 80, color: Color(0xFF7DAFBC)),
|
||||||
|
const SizedBox(height: 20),
|
||||||
|
Text(
|
||||||
|
"Thank You",
|
||||||
|
style: const TextStyle(fontSize: 24, fontWeight: FontWeight.bold),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 10),
|
||||||
|
const Text(
|
||||||
|
"For your valuable feedback, we truly appreciate your input!",
|
||||||
|
style: TextStyle(fontSize: 16),
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
),
|
||||||
|
const SizedBox(height: 30),
|
||||||
|
ElevatedButton(
|
||||||
|
onPressed: () => context
|
||||||
|
.go('/${context.language}/${BottomNavBarItem.home.routePath}'),
|
||||||
|
child: const Text("Go to Home"),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// New widget for failure message
|
||||||
|
Widget _buildFailureMessage() {
|
||||||
|
return Center(
|
||||||
|
child: Column(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
Icon(Icons.error, size: 80, color: Colors.red),
|
||||||
|
const SizedBox(height: 20),
|
||||||
|
const Text("Failed to Share Feedback",
|
||||||
|
style: TextStyle(fontSize: 24, fontWeight: FontWeight.bold)),
|
||||||
|
const SizedBox(height: 10),
|
||||||
|
const Text("Your feedback could not be submitted. Please try again.",
|
||||||
|
style: TextStyle(fontSize: 16), 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 String label,
|
||||||
|
required int index,
|
||||||
|
}) {
|
||||||
|
bool isSelected = _selectedEmojiIndex == index;
|
||||||
|
|
||||||
return Column(
|
return Column(
|
||||||
children: [
|
children: [
|
||||||
IconButton(
|
IconButton(
|
||||||
icon: Icon(icon, size: 40),
|
icon: Icon(icon, size: 40),
|
||||||
color: Colors.amber,
|
color: isSelected ? Colors.green : Colors.amber,
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
// Handle the emoji rating
|
setState(() {
|
||||||
|
_selectedEmojiIndex = index;
|
||||||
|
_isSmileySelected = true; // Hide error when a smiley is selected
|
||||||
|
});
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
Text(
|
Text(
|
||||||
label,
|
label,
|
||||||
style: TextStyle(fontWeight: FontWeight.bold),
|
style: TextStyle(
|
||||||
|
fontWeight: FontWeight.w400,
|
||||||
|
color: isSelected ? Colors.green : Color(0xFF000000),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
@ -220,7 +471,13 @@ class _FeedbackFormState extends State<FeedbackForm> {
|
|||||||
return Row(
|
return Row(
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
children: [
|
children: [
|
||||||
Text(label, style: const TextStyle(fontSize: 16)),
|
Text(label,
|
||||||
|
style: const TextStyle(
|
||||||
|
fontSize: 14,
|
||||||
|
fontFamily: 'Roboto',
|
||||||
|
fontWeight: FontWeight.w400,
|
||||||
|
color: Color(0xFF898C81),
|
||||||
|
)),
|
||||||
RatingBar.builder(
|
RatingBar.builder(
|
||||||
initialRating: rating,
|
initialRating: rating,
|
||||||
minRating: 1,
|
minRating: 1,
|
||||||
@ -233,8 +490,259 @@ class _FeedbackFormState extends State<FeedbackForm> {
|
|||||||
color: Colors.amber,
|
color: Colors.amber,
|
||||||
),
|
),
|
||||||
onRatingUpdate: onRatingUpdate,
|
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(
|
||||||
|
const SnackBar(
|
||||||
|
content: Text("Please correct the errors before submitting.")),
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
final feedbackData = {
|
||||||
|
"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 adminToken = adminAuth.token;
|
||||||
|
final response = await _pb
|
||||||
|
.collection('feedback')
|
||||||
|
.create(body: feedbackData, headers: {'Authorization': adminToken});
|
||||||
|
if (response != null) {
|
||||||
|
final createdTime = response.created;
|
||||||
|
// Send email
|
||||||
|
|
||||||
|
await sendFeedbackEmail(feedbackData, createdTime);
|
||||||
|
await _removeFeedbackText();
|
||||||
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
|
const SnackBar(content: Text("Feedback submitted successfully!")),
|
||||||
|
);
|
||||||
|
setState(() {
|
||||||
|
_isFeedbackSubmitted = true;
|
||||||
|
_isFeedbackFailed = false;
|
||||||
|
_resetFeedbackForm();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
|
SnackBar(content: Text("Failed to submit feedback: $e")),
|
||||||
|
);
|
||||||
|
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);
|
||||||
|
|
||||||
|
// Assuming the server timezone as UTC, you can append as needed
|
||||||
|
String submissionTime = "$formattedDateTime <Server_Timezone>";
|
||||||
|
|
||||||
|
print("Formatted Date (UTC): $formattedDate");
|
||||||
|
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: $submissionTime
|
||||||
|
|
||||||
|
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();
|
||||||
|
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: Colors.grey.shade300,
|
||||||
|
pathColorWhenOff: Colors.grey.shade300,
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -5,6 +5,7 @@
|
|||||||
import FlutterMacOS
|
import FlutterMacOS
|
||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
|
import file_selector_macos
|
||||||
import flutter_secure_storage_macos
|
import flutter_secure_storage_macos
|
||||||
import path_provider_foundation
|
import path_provider_foundation
|
||||||
import share_plus
|
import share_plus
|
||||||
@ -13,6 +14,7 @@ import video_player_avfoundation
|
|||||||
import webview_flutter_wkwebview
|
import webview_flutter_wkwebview
|
||||||
|
|
||||||
func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
|
func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
|
||||||
|
FileSelectorPlugin.register(with: registry.registrar(forPlugin: "FileSelectorPlugin"))
|
||||||
FlutterSecureStoragePlugin.register(with: registry.registrar(forPlugin: "FlutterSecureStoragePlugin"))
|
FlutterSecureStoragePlugin.register(with: registry.registrar(forPlugin: "FlutterSecureStoragePlugin"))
|
||||||
PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin"))
|
PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin"))
|
||||||
SharePlusMacosPlugin.register(with: registry.registrar(forPlugin: "SharePlusMacosPlugin"))
|
SharePlusMacosPlugin.register(with: registry.registrar(forPlugin: "SharePlusMacosPlugin"))
|
||||||
|
|||||||
124
pubspec.lock
124
pubspec.lock
@ -341,6 +341,38 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "7.0.1"
|
version: "7.0.1"
|
||||||
|
file_selector_linux:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: file_selector_linux
|
||||||
|
sha256: b2b91daf8a68ecfa4a01b778a6f52edef9b14ecd506e771488ea0f2e0784198b
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "0.9.3+1"
|
||||||
|
file_selector_macos:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: file_selector_macos
|
||||||
|
sha256: "271ab9986df0c135d45c3cdb6bd0faa5db6f4976d3e4b437cf7d0f258d941bfc"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "0.9.4+2"
|
||||||
|
file_selector_platform_interface:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: file_selector_platform_interface
|
||||||
|
sha256: a3994c26f10378a039faa11de174d7b78eb8f79e4dd0af2a451410c1a5c3f66b
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.6.2"
|
||||||
|
file_selector_windows:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: file_selector_windows
|
||||||
|
sha256: "8f5d2f6590d51ecd9179ba39c64f722edc15226cc93dcc8698466ad36a4a85a4"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "0.9.3+3"
|
||||||
fixnum:
|
fixnum:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -407,6 +439,14 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.4.2"
|
version: "2.4.2"
|
||||||
|
flutter_plugin_android_lifecycle:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: flutter_plugin_android_lifecycle
|
||||||
|
sha256: "9b78450b89f059e96c9ebb355fa6b3df1d6b330436e0b885fb49594c41721398"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.0.23"
|
||||||
flutter_rating_bar:
|
flutter_rating_bar:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
@ -601,6 +641,70 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "4.3.0"
|
version: "4.3.0"
|
||||||
|
image_picker:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: image_picker
|
||||||
|
sha256: "021834d9c0c3de46bf0fe40341fa07168407f694d9b2bb18d532dc1261867f7a"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.1.2"
|
||||||
|
image_picker_android:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: image_picker_android
|
||||||
|
sha256: "8faba09ba361d4b246dc0a17cb4289b3324c2b9f6db7b3d457ee69106a86bd32"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "0.8.12+17"
|
||||||
|
image_picker_for_web:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: image_picker_for_web
|
||||||
|
sha256: "717eb042ab08c40767684327be06a5d8dbb341fe791d514e4b92c7bbe1b7bb83"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "3.0.6"
|
||||||
|
image_picker_ios:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: image_picker_ios
|
||||||
|
sha256: "4f0568120c6fcc0aaa04511cb9f9f4d29fc3d0139884b1d06be88dcec7641d6b"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "0.8.12+1"
|
||||||
|
image_picker_linux:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: image_picker_linux
|
||||||
|
sha256: "4ed1d9bb36f7cd60aa6e6cd479779cc56a4cb4e4de8f49d487b1aaad831300fa"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "0.2.1+1"
|
||||||
|
image_picker_macos:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: image_picker_macos
|
||||||
|
sha256: "3f5ad1e8112a9a6111c46d0b57a7be2286a9a07fc6e1976fdf5be2bd31d4ff62"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "0.2.1+1"
|
||||||
|
image_picker_platform_interface:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: image_picker_platform_interface
|
||||||
|
sha256: "9ec26d410ff46f483c5519c29c02ef0e02e13a543f882b152d4bfd2f06802f80"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.10.0"
|
||||||
|
image_picker_windows:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: image_picker_windows
|
||||||
|
sha256: "6ad07afc4eb1bc25f3a01084d28520496c4a3bb0cb13685435838167c9dcedeb"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "0.2.1+1"
|
||||||
injectable:
|
injectable:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
@ -713,6 +817,14 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.1.2-main.4"
|
version: "0.1.2-main.4"
|
||||||
|
mailer:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: mailer
|
||||||
|
sha256: "21fde1497c79f402cb5fa7c50abd58927d360139e492546c941ee10767684fac"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "6.2.0"
|
||||||
marquee:
|
marquee:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
@ -749,10 +861,10 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: mime
|
name: mime
|
||||||
sha256: "41a20518f0cb1256669420fdba0cd90d21561e560ac240f26ef8322e45bb7ed6"
|
sha256: "801fd0b26f14a4a58ccb09d5892c3fbdeff209594300a542492cf13fba9d247a"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.0.0"
|
version: "1.0.6"
|
||||||
nested:
|
nested:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -964,10 +1076,10 @@ packages:
|
|||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: share_plus
|
name: share_plus
|
||||||
sha256: "3af2cda1752e5c24f2fc04b6083b40f013ffe84fb90472f30c6499a9213d5442"
|
sha256: "9c9bafd4060728d7cdb2464c341743adbd79d327cb067ec7afb64583540b47c8"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "10.1.1"
|
version: "10.1.2"
|
||||||
share_plus_platform_interface:
|
share_plus_platform_interface:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -980,10 +1092,10 @@ packages:
|
|||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: shared_preferences
|
name: shared_preferences
|
||||||
sha256: "746e5369a43170c25816cc472ee016d3a66bc13fcf430c0bc41ad7b4b2922051"
|
sha256: "95f9997ca1fb9799d494d0cb2a780fd7be075818d59f00c43832ed112b158a82"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.3.2"
|
version: "2.3.3"
|
||||||
shared_preferences_android:
|
shared_preferences_android:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|||||||
@ -52,6 +52,8 @@ dependencies:
|
|||||||
shared_preferences: ^2.2.3
|
shared_preferences: ^2.2.3
|
||||||
flutter_rating_bar: ^4.0.1
|
flutter_rating_bar: ^4.0.1
|
||||||
provider: ^6.1.2
|
provider: ^6.1.2
|
||||||
|
mailer: ^6.2.0
|
||||||
|
image_picker: ^1.1.2
|
||||||
|
|
||||||
dependency_overrides:
|
dependency_overrides:
|
||||||
fading_edge_scrollview: ^4.1.1
|
fading_edge_scrollview: ^4.1.1
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user