router update
This commit is contained in:
parent
3ab1010409
commit
c86f9663ed
@ -207,7 +207,7 @@
|
|||||||
// ),
|
// ),
|
||||||
// ),
|
// ),
|
||||||
// ];
|
// ];
|
||||||
//
|
// //
|
||||||
// ShellRoute get inAppRoutes => ShellRoute(
|
// ShellRoute get inAppRoutes => ShellRoute(
|
||||||
// builder: (context, state, child) => LoggedInHomeScaffold(
|
// builder: (context, state, child) => LoggedInHomeScaffold(
|
||||||
// body: child,
|
// body: child,
|
||||||
@ -218,7 +218,7 @@
|
|||||||
// ],
|
// ],
|
||||||
// );
|
// );
|
||||||
// }
|
// }
|
||||||
//
|
// //
|
||||||
// String? _pathCache;
|
// String? _pathCache;
|
||||||
//
|
//
|
||||||
// @riverpod
|
// @riverpod
|
||||||
@ -292,9 +292,9 @@
|
|||||||
// return router;
|
// return router;
|
||||||
// }
|
// }
|
||||||
|
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:go_router/go_router.dart';
|
import 'package:go_router/go_router.dart';
|
||||||
|
import 'package:uae_stat/presentation/routes/drawer_routes/custom_drawer_routes.dart';
|
||||||
|
|
||||||
import '../domain/use_cases/preferences_use_case.dart';
|
import '../domain/use_cases/preferences_use_case.dart';
|
||||||
import '../presentation/Screens/changepassword.dart';
|
import '../presentation/Screens/changepassword.dart';
|
||||||
@ -303,47 +303,59 @@ import '../presentation/Screens/otp_verification.dart';
|
|||||||
import '../presentation/Screens/profilepage.dart';
|
import '../presentation/Screens/profilepage.dart';
|
||||||
import '../presentation/routes/auth_routes/login_route.dart';
|
import '../presentation/routes/auth_routes/login_route.dart';
|
||||||
import '../presentation/routes/bottom_bar_routes/tab_routes/home_route.dart';
|
import '../presentation/routes/bottom_bar_routes/tab_routes/home_route.dart';
|
||||||
|
import '../presentation/routes/drawer_routes/Drawer Items/feedback.dart';
|
||||||
|
import '../presentation/routes/drawer_routes/Drawer Items/manage_users.dart';
|
||||||
|
|
||||||
final GoRouter router = GoRouter(
|
final GoRouter router = GoRouter(
|
||||||
routes: [
|
routes: [
|
||||||
GoRoute(
|
GoRoute(
|
||||||
path: '/',
|
path: '/',
|
||||||
builder: (context, state) => LoginRoute(),
|
//builder: (context, state) => LoginRoute(),
|
||||||
|
builder: (context, state) => MyHomePage(),
|
||||||
),
|
),
|
||||||
GoRoute(
|
|
||||||
path: '/profile',
|
|
||||||
builder: (context, state) => ProfileScreen(userId: '',),
|
|
||||||
),
|
|
||||||
// GoRoute(
|
|
||||||
// path: '/home',
|
|
||||||
// builder: (context, state) => HomeRoute(),
|
|
||||||
// ),
|
|
||||||
|
|
||||||
GoRoute(
|
GoRoute(
|
||||||
path: '/myhomepage',
|
path: '/myhomepage',
|
||||||
builder: (context, state) => MyHomePage(),
|
builder: (context, state) => MyHomePage(),
|
||||||
),
|
),
|
||||||
GoRoute(
|
|
||||||
path: '/changepw',
|
|
||||||
builder: (context, state) => Changepassword(),
|
|
||||||
),
|
|
||||||
GoRoute(
|
GoRoute(
|
||||||
path: '/mailverification',
|
path: '/mailverification',
|
||||||
builder: (context, state) => EmailVerificationScreen(email: '', userId: '', otp: '', otpId: '', sendVerificationCode: (String ) { },),
|
builder: (context, state) => EmailVerificationScreen(
|
||||||
|
email: '',
|
||||||
|
userId: '',
|
||||||
|
otp: '',
|
||||||
|
otpId: '',
|
||||||
|
sendVerificationCode: (String) {},
|
||||||
|
),
|
||||||
),
|
),
|
||||||
GoRoute(
|
GoRoute(
|
||||||
path: '/changepass',
|
path: '/changepassword',
|
||||||
builder: (context, state) => Changepassword(),
|
builder: (context, state) => Changepassword(
|
||||||
|
userId: '',
|
||||||
|
),
|
||||||
),
|
),
|
||||||
GoRoute(
|
GoRoute(
|
||||||
path: '/confirmpasswd',
|
path: '/confirmpasswd',
|
||||||
builder: (context, state) => ConfirmPassword(email: '', userId: '',),
|
builder: (context, state) => ConfirmPassword(
|
||||||
|
email: '',
|
||||||
|
userId: '',
|
||||||
|
),
|
||||||
),
|
),
|
||||||
|
|
||||||
|
// Drawer Routers
|
||||||
|
GoRoute(
|
||||||
|
path: '/feedback',
|
||||||
|
builder: (context, state) => FeedbackForm(),
|
||||||
|
),
|
||||||
|
GoRoute(
|
||||||
|
path: '/profile',
|
||||||
|
builder: (context, state) => ProfileScreen(
|
||||||
|
userId: '',
|
||||||
|
),
|
||||||
|
),
|
||||||
|
GoRoute(
|
||||||
|
path: '/manageuser',
|
||||||
|
builder: (context, state) => ManageUserRouter(),
|
||||||
|
),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@ -27,15 +27,28 @@ class MainApp extends StatelessWidget {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return MaterialApp(
|
return MaterialApp.router(
|
||||||
home: RegisterScreen(),
|
|
||||||
//home: ProfileScreen(),
|
|
||||||
debugShowCheckedModeBanner: false,
|
debugShowCheckedModeBanner: false,
|
||||||
|
routerConfig: router, // Use the GoRouter instance from app_router.dart
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// class MainApp extends StatelessWidget {
|
||||||
|
// const MainApp({super.key});
|
||||||
|
//
|
||||||
|
// @override
|
||||||
|
// Widget build(BuildContext context) {
|
||||||
|
// return MaterialApp(
|
||||||
|
// home: RegisterScreen(),
|
||||||
|
// //home: ProfileScreen(),
|
||||||
|
// debugShowCheckedModeBanner: false,
|
||||||
|
// );
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
|
||||||
// class MainApp extends ConsumerWidget {
|
// class MainApp extends ConsumerWidget {
|
||||||
// const MainApp({super.key});
|
// const MainApp({super.key});
|
||||||
//
|
//
|
||||||
|
|||||||
@ -71,7 +71,7 @@ class _ConfirmPasswordState extends State<ConfirmPassword> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Update password
|
// Update password
|
||||||
await pb.collection('users').update(
|
await _pb.collection('users').update(
|
||||||
userId, // User ID
|
userId, // User ID
|
||||||
body: {
|
body: {
|
||||||
'password': newPassword,
|
'password': newPassword,
|
||||||
|
|||||||
@ -4,6 +4,7 @@ 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:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
|
import 'package:go_router/go_router.dart';
|
||||||
import 'package:http/http.dart' as http;
|
import 'package:http/http.dart' as http;
|
||||||
import 'package:image_picker/image_picker.dart';
|
import 'package:image_picker/image_picker.dart';
|
||||||
import 'package:intl/intl.dart';
|
import 'package:intl/intl.dart';
|
||||||
@ -515,12 +516,15 @@ class _ProfileScreenState extends State<ProfileScreen> {
|
|||||||
Center(
|
Center(
|
||||||
child: GestureDetector(
|
child: GestureDetector(
|
||||||
onTap: () {
|
onTap: () {
|
||||||
Navigator.push(
|
context.go('/confirmpasswd');
|
||||||
context,
|
|
||||||
MaterialPageRoute(
|
|
||||||
builder: (context) => Changepassword()),
|
|
||||||
);
|
|
||||||
},
|
},
|
||||||
|
// {
|
||||||
|
// Navigator.push(
|
||||||
|
// context,
|
||||||
|
// MaterialPageRoute(
|
||||||
|
// builder: (context) => Changepassword(userId: '',)),
|
||||||
|
// );
|
||||||
|
// },
|
||||||
child: Text(
|
child: Text(
|
||||||
"Change Password",
|
"Change Password",
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
@ -532,35 +536,38 @@ class _ProfileScreenState extends State<ProfileScreen> {
|
|||||||
SizedBox(height: 20),
|
SizedBox(height: 20),
|
||||||
ElevatedButton.icon(
|
ElevatedButton.icon(
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
if ((_formKey.currentState?.validate() ?? false) &&
|
context.go('/myhomepage');
|
||||||
(isChecked)) {
|
},
|
||||||
_formKey.currentState?.save();
|
// {
|
||||||
showConfirmationDialog(context);
|
// if ((_formKey.currentState?.validate() ?? false) &&
|
||||||
} else {
|
// (isChecked)) {
|
||||||
setState(() {
|
|
||||||
showError =
|
|
||||||
!isChecked; // Show error if the checkbox is not checked
|
|
||||||
});
|
|
||||||
}
|
|
||||||
// if ((_formKey.currentState?.validate() ?? false) && (isChecked)) {
|
|
||||||
// _formKey.currentState?.save();
|
// _formKey.currentState?.save();
|
||||||
// showConfirmationDialog(context);
|
// showConfirmationDialog(context);
|
||||||
// // if (isChecked) {
|
// } else {
|
||||||
// // _formKey.currentState?.save();
|
|
||||||
// // // _confirmSaveProfile();
|
|
||||||
// // showConfirmationDialog(context);
|
|
||||||
// // }
|
|
||||||
// else {
|
|
||||||
// setState(() {
|
// setState(() {
|
||||||
// showError = !isChecked; // Show error if the checkbox is not checked
|
// showError =
|
||||||
|
// !isChecked; // Show error if the checkbox is not checked
|
||||||
// });
|
// });
|
||||||
|
|
||||||
// ScaffoldMessenger.of(context).showSnackBar(
|
|
||||||
// SnackBar(content: Text('Please accept the terms and conditions.',style: TextStyle(color: Colors.white),)),
|
|
||||||
// );
|
|
||||||
// }
|
// }
|
||||||
//}
|
// // if ((_formKey.currentState?.validate() ?? false) && (isChecked)) {
|
||||||
},
|
// // _formKey.currentState?.save();
|
||||||
|
// // showConfirmationDialog(context);
|
||||||
|
// // // if (isChecked) {
|
||||||
|
// // // _formKey.currentState?.save();
|
||||||
|
// // // // _confirmSaveProfile();
|
||||||
|
// // // showConfirmationDialog(context);
|
||||||
|
// // // }
|
||||||
|
// // else {
|
||||||
|
// // setState(() {
|
||||||
|
// // showError = !isChecked; // Show error if the checkbox is not checked
|
||||||
|
// // });
|
||||||
|
//
|
||||||
|
// // ScaffoldMessenger.of(context).showSnackBar(
|
||||||
|
// // SnackBar(content: Text('Please accept the terms and conditions.',style: TextStyle(color: Colors.white),)),
|
||||||
|
// // );
|
||||||
|
// //}
|
||||||
|
// //}
|
||||||
|
// },
|
||||||
icon: Icon(
|
icon: Icon(
|
||||||
Icons.save,
|
Icons.save,
|
||||||
color: Colors.white,
|
color: Colors.white,
|
||||||
|
|||||||
@ -157,97 +157,105 @@ class LoginRoute extends HookConsumerWidget {
|
|||||||
final loginBtn = SizedBox(
|
final loginBtn = SizedBox(
|
||||||
width: double.infinity,
|
width: double.infinity,
|
||||||
child: ElevatedButton(
|
child: ElevatedButton(
|
||||||
onPressed: () async {
|
onPressed: () {
|
||||||
final isValid = formKey.currentState!.validate();
|
context.go('/profile');
|
||||||
if (!isValid) return;
|
|
||||||
final session = await context.loaderWithErrorDialog(
|
|
||||||
() => ref
|
|
||||||
.read(
|
|
||||||
authUseCaseProvider.notifier,
|
|
||||||
)
|
|
||||||
.login(
|
|
||||||
emailCtl.text,
|
|
||||||
pwCtl.text,
|
|
||||||
),
|
|
||||||
errorDialogBuilder: (
|
|
||||||
error, [
|
|
||||||
StackTrace? stackTrace,
|
|
||||||
]) {
|
|
||||||
if (error == LoginError.invalidEmailPw) {
|
|
||||||
return context.simpleDialog(
|
|
||||||
title: context.translate(
|
|
||||||
'Incorrect credentials',
|
|
||||||
'أوراق غير صحيحة',
|
|
||||||
),
|
|
||||||
content: context.translate(
|
|
||||||
'Your email or password is invalid. Please try again.',
|
|
||||||
'بريدك الإلكتروني أو كلمة المرور غير صالحة. حاول مرة اخرى.',
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
if (error == LoginError.emailAddressNotVerified) {
|
|
||||||
return context.simpleDialog(
|
|
||||||
title: context.translate(
|
|
||||||
'Verification Error',
|
|
||||||
'خطأ التحقق',
|
|
||||||
),
|
|
||||||
content: context.translate(
|
|
||||||
'${emailCtl.text} is not a verified email address. Please check your email for a verification link.',
|
|
||||||
'${emailCtl.text} ليس عنوان بريد إلكتروني تم التحقق منه. يرجى التحقق من بريدك الإلكتروني للحصول على رابط التحقق.',
|
|
||||||
),
|
|
||||||
extraAction: ElevatedButton(
|
|
||||||
onPressed: () async {
|
|
||||||
Navigator.of(
|
|
||||||
context,
|
|
||||||
rootNavigator: true,
|
|
||||||
).pop();
|
|
||||||
await context.loaderWithErrorDialog(
|
|
||||||
() => ref
|
|
||||||
.read(authUseCaseProvider.notifier)
|
|
||||||
.requestVerificationEmail(emailCtl.text),
|
|
||||||
);
|
|
||||||
if (!context.mounted) return;
|
|
||||||
context.simpleDialog(
|
|
||||||
title: 'Email Re-sent',
|
|
||||||
content:
|
|
||||||
'We\'ve sent you the verification email at ${emailCtl.text} again.',
|
|
||||||
);
|
|
||||||
},
|
|
||||||
child: Text(
|
|
||||||
context.translate(
|
|
||||||
'I did not receive an email',
|
|
||||||
'لم أتلق بريدًا إلكترونيًا',
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return context.simpleDialog();
|
|
||||||
},
|
|
||||||
);
|
|
||||||
if (!context.mounted || session == null) return;
|
|
||||||
// Extract userId from the session
|
|
||||||
final userId = session.id;
|
|
||||||
|
|
||||||
final bool isUpdate = await profileStatus(userId);
|
|
||||||
|
|
||||||
print('Is profile completed: $isUpdate');
|
|
||||||
|
|
||||||
if (isUpdate) {
|
|
||||||
Navigator.pushReplacement(
|
|
||||||
context,
|
|
||||||
MaterialPageRoute(builder: (context) => DemoHome()),
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
Navigator.pushReplacement(
|
|
||||||
context,
|
|
||||||
MaterialPageRoute(
|
|
||||||
builder: (context) => ProfileScreen(userId: userId)),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// context.go('/${context.language}/${BottomNavBarItem.home.routePath}');
|
|
||||||
},
|
},
|
||||||
|
// async {
|
||||||
|
// final isValid = formKey.currentState!.validate();
|
||||||
|
// if (!isValid) return;
|
||||||
|
// final session = await context.loaderWithErrorDialog(
|
||||||
|
// () => ref
|
||||||
|
// .read(
|
||||||
|
// authUseCaseProvider.notifier,
|
||||||
|
// )
|
||||||
|
// .login(
|
||||||
|
// emailCtl.text,
|
||||||
|
// pwCtl.text,
|
||||||
|
// ),
|
||||||
|
// errorDialogBuilder: (
|
||||||
|
// error, [
|
||||||
|
// StackTrace? stackTrace,
|
||||||
|
// ]) {
|
||||||
|
// if (error == LoginError.invalidEmailPw) {
|
||||||
|
// return context.simpleDialog(
|
||||||
|
// title: context.translate(
|
||||||
|
// 'Incorrect credentials',
|
||||||
|
// 'أوراق غير صحيحة',
|
||||||
|
// ),
|
||||||
|
// content: context.translate(
|
||||||
|
// 'Your email or password is invalid. Please try again.',
|
||||||
|
// 'بريدك الإلكتروني أو كلمة المرور غير صالحة. حاول مرة اخرى.',
|
||||||
|
// ),
|
||||||
|
// );
|
||||||
|
// }
|
||||||
|
// if (error == LoginError.emailAddressNotVerified) {
|
||||||
|
// return context.simpleDialog(
|
||||||
|
// title: context.translate(
|
||||||
|
// 'Verification Error',
|
||||||
|
// 'خطأ التحقق',
|
||||||
|
// ),
|
||||||
|
// content: context.translate(
|
||||||
|
// '${emailCtl.text} is not a verified email address. Please check your email for a verification link.',
|
||||||
|
// '${emailCtl.text} ليس عنوان بريد إلكتروني تم التحقق منه. يرجى التحقق من بريدك الإلكتروني للحصول على رابط التحقق.',
|
||||||
|
// ),
|
||||||
|
// extraAction: ElevatedButton(
|
||||||
|
// onPressed: () {
|
||||||
|
// context.go('/profile');
|
||||||
|
// },
|
||||||
|
// // async {
|
||||||
|
// // Navigator.of(
|
||||||
|
// // context,
|
||||||
|
// // rootNavigator: true,
|
||||||
|
// // ).pop();
|
||||||
|
// // await context.loaderWithErrorDialog(
|
||||||
|
// // () => ref
|
||||||
|
// // .read(authUseCaseProvider.notifier)
|
||||||
|
// // .requestVerificationEmail(emailCtl.text),
|
||||||
|
// // );
|
||||||
|
// // if (!context.mounted) return;
|
||||||
|
// // context.simpleDialog(
|
||||||
|
// // title: 'Email Re-sent',
|
||||||
|
// // content:
|
||||||
|
// // 'We\'ve sent you the verification email at ${emailCtl.text} again.',
|
||||||
|
// // );
|
||||||
|
// // },
|
||||||
|
// child: Text(
|
||||||
|
// context.translate(
|
||||||
|
// 'I did not receive an email',
|
||||||
|
// 'لم أتلق بريدًا إلكترونيًا',
|
||||||
|
// ),
|
||||||
|
// ),
|
||||||
|
// ),
|
||||||
|
// );
|
||||||
|
// }
|
||||||
|
// return context.simpleDialog();
|
||||||
|
// },
|
||||||
|
// );
|
||||||
|
// // if (!context.mounted || session == null) return;
|
||||||
|
// // Extract userId from the session
|
||||||
|
// // final userId = session.id;
|
||||||
|
// //
|
||||||
|
// // final bool isUpdate = await profileStatus(userId);
|
||||||
|
// //
|
||||||
|
// // print('Is profile completed: $isUpdate');
|
||||||
|
//
|
||||||
|
// // if (isUpdate) {
|
||||||
|
// // Navigator.pushReplacement(
|
||||||
|
// // context,
|
||||||
|
// // MaterialPageRoute(builder: (context) => DemoHome()),
|
||||||
|
// // );
|
||||||
|
// // }
|
||||||
|
// // else {
|
||||||
|
//
|
||||||
|
// // Navigator.pushReplacement(
|
||||||
|
// // context,
|
||||||
|
// // MaterialPageRoute(
|
||||||
|
// // builder: (context) => ProfileScreen(userId: userId)),
|
||||||
|
// // );
|
||||||
|
// //}
|
||||||
|
//
|
||||||
|
// // context.go('/${context.language}/${BottomNavBarItem.home.routePath}');
|
||||||
|
// },
|
||||||
style: ButtonStyle(
|
style: ButtonStyle(
|
||||||
shape: WidgetStatePropertyAll(
|
shape: WidgetStatePropertyAll(
|
||||||
RoundedRectangleBorder(
|
RoundedRectangleBorder(
|
||||||
|
|||||||
@ -1,164 +1,167 @@
|
|||||||
import 'dart:math';
|
// import 'dart:math';
|
||||||
|
//
|
||||||
import 'package:external_repos/external_repos.dart';
|
// import 'package:external_repos/external_repos.dart';
|
||||||
|
// import 'package:flutter/material.dart';
|
||||||
|
// import 'package:flutter_hooks/flutter_hooks.dart';
|
||||||
|
// import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
|
// import 'package:uae_stat/config/my_theme.dart';
|
||||||
|
// import 'package:uae_stat/domain/use_cases/language.dart';
|
||||||
|
// import 'package:uae_stat/presentation/components/indicators/indicator_view.dart';
|
||||||
|
// import 'package:uae_stat/presentation/components/my_bottom_nav_bar.dart';
|
||||||
|
// import 'package:uae_stat/presentation/components/my_drawer.dart';
|
||||||
|
// import 'package:uae_stat/presentation/components/space.dart';
|
||||||
|
// import 'package:uae_stat/presentation/components/themed_app_bar.dart';
|
||||||
|
// import 'package:uae_stat/presentation/routes/drawer_routes/custom_drawer_routes.dart';
|
||||||
|
//
|
||||||
|
// bool _wasHomeNeverBuiltThisSession = true;
|
||||||
|
//
|
||||||
|
// class HomeRoute extends HookConsumerWidget {
|
||||||
|
// const HomeRoute({super.key});
|
||||||
|
//
|
||||||
|
// @override
|
||||||
|
// Widget build(BuildContext context, WidgetRef ref) {
|
||||||
|
// final bodyContent = Column(
|
||||||
|
// children: IndicatorTopic.values.map(
|
||||||
|
// (topic) {
|
||||||
|
// final scaledHeight = context.height * pow(50 / 880, 1.1);
|
||||||
|
// final titleText = Text(
|
||||||
|
// context.translate(
|
||||||
|
// topic.nameEN,
|
||||||
|
// topic.nameAR,
|
||||||
|
// ),
|
||||||
|
// textAlign: TextAlign.center,
|
||||||
|
// style: TextStyle(
|
||||||
|
// fontFamily: context.translate(
|
||||||
|
// 'Roboto',
|
||||||
|
// 'NotoKufi',
|
||||||
|
// ),
|
||||||
|
// color: Colors.white,
|
||||||
|
// fontSize: 20,
|
||||||
|
// fontWeight: FontWeight.w700,
|
||||||
|
// ),
|
||||||
|
// );
|
||||||
|
// final title = Container(
|
||||||
|
// height: context.height > 880 ? null : scaledHeight,
|
||||||
|
// width: context.width,
|
||||||
|
// margin: const EdgeInsets.symmetric(
|
||||||
|
// horizontal: 24,
|
||||||
|
// ),
|
||||||
|
// padding: const EdgeInsets.symmetric(
|
||||||
|
// vertical: 5,
|
||||||
|
// ),
|
||||||
|
// decoration: BoxDecoration(
|
||||||
|
// color: MyTheme.topicColor(topic),
|
||||||
|
// borderRadius: BorderRadius.circular(100),
|
||||||
|
// ),
|
||||||
|
// child: context.height > 880
|
||||||
|
// ? titleText
|
||||||
|
// : FittedBox(
|
||||||
|
// fit: BoxFit.scaleDown,
|
||||||
|
// child: titleText,
|
||||||
|
// ),
|
||||||
|
// );
|
||||||
|
// final ids = IndicatorEnum.values.where(
|
||||||
|
// (id) => id.topic == topic,
|
||||||
|
// );
|
||||||
|
// const horizontalGap = 16.0;
|
||||||
|
// const verticalGap = 9.0;
|
||||||
|
// final boxWidth = (context.width - (3 * horizontalGap)) / 2;
|
||||||
|
// final indicatorBoxes = ids
|
||||||
|
// .map(
|
||||||
|
// (id) => SizedBox(
|
||||||
|
// width: boxWidth,
|
||||||
|
// height: double.infinity,
|
||||||
|
// child: IndicatorView(id),
|
||||||
|
// ),
|
||||||
|
// )
|
||||||
|
// .toList();
|
||||||
|
// final scrollCtl = useScrollController();
|
||||||
|
// if (_wasHomeNeverBuiltThisSession) {
|
||||||
|
// WidgetsBinding.instance.addPostFrameCallback(
|
||||||
|
// (_) async {
|
||||||
|
// final duration = kThemeAnimationDuration * 0.75;
|
||||||
|
// const distance = -30.0;
|
||||||
|
// scrollCtl.animateTo(
|
||||||
|
// distance,
|
||||||
|
// duration: duration,
|
||||||
|
// curve: Curves.fastOutSlowIn,
|
||||||
|
// );
|
||||||
|
// await Future.delayed(duration * 2.5);
|
||||||
|
// scrollCtl.animateTo(
|
||||||
|
// distance,
|
||||||
|
// duration: duration,
|
||||||
|
// curve: Curves.fastOutSlowIn,
|
||||||
|
// );
|
||||||
|
// _wasHomeNeverBuiltThisSession = false;
|
||||||
|
// },
|
||||||
|
// );
|
||||||
|
// }
|
||||||
|
// final content = SingleChildScrollView(
|
||||||
|
// controller: scrollCtl,
|
||||||
|
// padding: const EdgeInsets.symmetric(
|
||||||
|
// vertical: verticalGap,
|
||||||
|
// horizontal: horizontalGap,
|
||||||
|
// ),
|
||||||
|
// scrollDirection: Axis.horizontal,
|
||||||
|
// child: Column(
|
||||||
|
// children: [
|
||||||
|
// Expanded(
|
||||||
|
// child: Row(
|
||||||
|
// children: [
|
||||||
|
// indicatorBoxes[0],
|
||||||
|
// horizontalGap.horizontalSpace,
|
||||||
|
// indicatorBoxes[1],
|
||||||
|
// horizontalGap.horizontalSpace,
|
||||||
|
// indicatorBoxes[2],
|
||||||
|
// ],
|
||||||
|
// ),
|
||||||
|
// ),
|
||||||
|
// verticalGap.verticalSpace,
|
||||||
|
// Expanded(
|
||||||
|
// child: Row(
|
||||||
|
// children: [
|
||||||
|
// indicatorBoxes[3],
|
||||||
|
// horizontalGap.horizontalSpace,
|
||||||
|
// indicatorBoxes[4],
|
||||||
|
// horizontalGap.horizontalSpace,
|
||||||
|
// indicatorBoxes.length == 6
|
||||||
|
// ? indicatorBoxes[5]
|
||||||
|
// : boxWidth.horizontalSpace,
|
||||||
|
// ],
|
||||||
|
// ),
|
||||||
|
// ),
|
||||||
|
// ],
|
||||||
|
// ),
|
||||||
|
// );
|
||||||
|
// return Expanded(
|
||||||
|
// child: Column(
|
||||||
|
// children: [
|
||||||
|
// title,
|
||||||
|
// Expanded(
|
||||||
|
// child: content,
|
||||||
|
// ),
|
||||||
|
// ],
|
||||||
|
// ),
|
||||||
|
// );
|
||||||
|
// },
|
||||||
|
// ).toList(),
|
||||||
|
// );
|
||||||
|
// return SafeArea(
|
||||||
|
// bottom: false,
|
||||||
|
// child: Column(
|
||||||
|
// crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||||
|
// children: [
|
||||||
|
// const ThemedAppBar(),
|
||||||
|
// Expanded(
|
||||||
|
// child: bodyContent,
|
||||||
|
// ),
|
||||||
|
// ],
|
||||||
|
// ),
|
||||||
|
// );
|
||||||
|
// }
|
||||||
|
// }
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
import '../../drawer_routes/custom_drawer_routes.dart';
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
|
||||||
import 'package:uae_stat/config/my_theme.dart';
|
|
||||||
import 'package:uae_stat/domain/use_cases/language.dart';
|
|
||||||
import 'package:uae_stat/presentation/components/indicators/indicator_view.dart';
|
|
||||||
import 'package:uae_stat/presentation/components/my_bottom_nav_bar.dart';
|
|
||||||
import 'package:uae_stat/presentation/components/my_drawer.dart';
|
|
||||||
import 'package:uae_stat/presentation/components/space.dart';
|
|
||||||
import 'package:uae_stat/presentation/components/themed_app_bar.dart';
|
|
||||||
|
|
||||||
bool _wasHomeNeverBuiltThisSession = true;
|
|
||||||
|
|
||||||
class HomeRoute extends HookConsumerWidget {
|
|
||||||
const HomeRoute({super.key});
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context, WidgetRef ref) {
|
|
||||||
final bodyContent = Column(
|
|
||||||
children: IndicatorTopic.values.map(
|
|
||||||
(topic) {
|
|
||||||
final scaledHeight = context.height * pow(50 / 880, 1.1);
|
|
||||||
final titleText = Text(
|
|
||||||
context.translate(
|
|
||||||
topic.nameEN,
|
|
||||||
topic.nameAR,
|
|
||||||
),
|
|
||||||
textAlign: TextAlign.center,
|
|
||||||
style: TextStyle(
|
|
||||||
fontFamily: context.translate(
|
|
||||||
'Roboto',
|
|
||||||
'NotoKufi',
|
|
||||||
),
|
|
||||||
color: Colors.white,
|
|
||||||
fontSize: 20,
|
|
||||||
fontWeight: FontWeight.w700,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
final title = Container(
|
|
||||||
height: context.height > 880 ? null : scaledHeight,
|
|
||||||
width: context.width,
|
|
||||||
margin: const EdgeInsets.symmetric(
|
|
||||||
horizontal: 24,
|
|
||||||
),
|
|
||||||
padding: const EdgeInsets.symmetric(
|
|
||||||
vertical: 5,
|
|
||||||
),
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: MyTheme.topicColor(topic),
|
|
||||||
borderRadius: BorderRadius.circular(100),
|
|
||||||
),
|
|
||||||
child: context.height > 880
|
|
||||||
? titleText
|
|
||||||
: FittedBox(
|
|
||||||
fit: BoxFit.scaleDown,
|
|
||||||
child: titleText,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
final ids = IndicatorEnum.values.where(
|
|
||||||
(id) => id.topic == topic,
|
|
||||||
);
|
|
||||||
const horizontalGap = 16.0;
|
|
||||||
const verticalGap = 9.0;
|
|
||||||
final boxWidth = (context.width - (3 * horizontalGap)) / 2;
|
|
||||||
final indicatorBoxes = ids
|
|
||||||
.map(
|
|
||||||
(id) => SizedBox(
|
|
||||||
width: boxWidth,
|
|
||||||
height: double.infinity,
|
|
||||||
child: IndicatorView(id),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
.toList();
|
|
||||||
final scrollCtl = useScrollController();
|
|
||||||
if (_wasHomeNeverBuiltThisSession) {
|
|
||||||
WidgetsBinding.instance.addPostFrameCallback(
|
|
||||||
(_) async {
|
|
||||||
final duration = kThemeAnimationDuration * 0.75;
|
|
||||||
const distance = -30.0;
|
|
||||||
scrollCtl.animateTo(
|
|
||||||
distance,
|
|
||||||
duration: duration,
|
|
||||||
curve: Curves.fastOutSlowIn,
|
|
||||||
);
|
|
||||||
await Future.delayed(duration * 2.5);
|
|
||||||
scrollCtl.animateTo(
|
|
||||||
distance,
|
|
||||||
duration: duration,
|
|
||||||
curve: Curves.fastOutSlowIn,
|
|
||||||
);
|
|
||||||
_wasHomeNeverBuiltThisSession = false;
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
final content = SingleChildScrollView(
|
|
||||||
controller: scrollCtl,
|
|
||||||
padding: const EdgeInsets.symmetric(
|
|
||||||
vertical: verticalGap,
|
|
||||||
horizontal: horizontalGap,
|
|
||||||
),
|
|
||||||
scrollDirection: Axis.horizontal,
|
|
||||||
child: Column(
|
|
||||||
children: [
|
|
||||||
Expanded(
|
|
||||||
child: Row(
|
|
||||||
children: [
|
|
||||||
indicatorBoxes[0],
|
|
||||||
horizontalGap.horizontalSpace,
|
|
||||||
indicatorBoxes[1],
|
|
||||||
horizontalGap.horizontalSpace,
|
|
||||||
indicatorBoxes[2],
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
verticalGap.verticalSpace,
|
|
||||||
Expanded(
|
|
||||||
child: Row(
|
|
||||||
children: [
|
|
||||||
indicatorBoxes[3],
|
|
||||||
horizontalGap.horizontalSpace,
|
|
||||||
indicatorBoxes[4],
|
|
||||||
horizontalGap.horizontalSpace,
|
|
||||||
indicatorBoxes.length == 6
|
|
||||||
? indicatorBoxes[5]
|
|
||||||
: boxWidth.horizontalSpace,
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
return Expanded(
|
|
||||||
child: Column(
|
|
||||||
children: [
|
|
||||||
title,
|
|
||||||
Expanded(
|
|
||||||
child: content,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
).toList(),
|
|
||||||
);
|
|
||||||
return SafeArea(
|
|
||||||
bottom: false,
|
|
||||||
child: Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
|
||||||
children: [
|
|
||||||
const ThemedAppBar(),
|
|
||||||
Expanded(
|
|
||||||
child: bodyContent,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class MyHomePage extends StatefulWidget {
|
class MyHomePage extends StatefulWidget {
|
||||||
const MyHomePage({super.key});
|
const MyHomePage({super.key});
|
||||||
@ -170,10 +173,8 @@ class MyHomePage extends StatefulWidget {
|
|||||||
class _MyHomePageState extends State<MyHomePage> {
|
class _MyHomePageState extends State<MyHomePage> {
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Scaffold(
|
return BaseScaffold(
|
||||||
//drawer: MyDrawer(),
|
body: Center(child : Text("Home Page"))
|
||||||
//bottomNavigationBar: MyBottomNavBar(),
|
|
||||||
body: HomeRoute(),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
775
lib/presentation/routes/drawer_routes/Drawer Items/feedback.dart
Normal file
775
lib/presentation/routes/drawer_routes/Drawer Items/feedback.dart
Normal file
@ -0,0 +1,775 @@
|
|||||||
|
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: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:mailer/mailer.dart';
|
||||||
|
import 'package:mailer/smtp_server.dart';
|
||||||
|
import 'package:uae_stat/presentation/components/space.dart';
|
||||||
|
import 'package:uae_stat/presentation/routes/drawer_routes/custom_drawer_routes.dart';
|
||||||
|
import '../../../../config/my_theme.dart';
|
||||||
|
import '../../../../domain/use_cases/preferences_use_case.dart';
|
||||||
|
import '../../../components/my_toggle.dart';
|
||||||
|
|
||||||
|
|
||||||
|
class FeedbackForm extends StatefulWidget {
|
||||||
|
const FeedbackForm({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
_FeedbackFormState createState() => _FeedbackFormState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _FeedbackFormState extends State<FeedbackForm>
|
||||||
|
with WidgetsBindingObserver {
|
||||||
|
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 _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;
|
||||||
|
|
||||||
|
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();
|
||||||
|
WidgetsBinding.instance.addObserver(this);
|
||||||
|
_loadFeedbackText();
|
||||||
|
_feedbackController.addListener(_handleTextChange);
|
||||||
|
fetchEmailConfiguration();
|
||||||
|
}
|
||||||
|
|
||||||
|
@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);
|
||||||
|
print('one save');
|
||||||
|
}
|
||||||
|
|
||||||
|
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) {
|
||||||
|
return BaseScaffold(
|
||||||
|
// appBar: AppBar(
|
||||||
|
// 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(
|
||||||
|
padding: const EdgeInsets.all(16.0),
|
||||||
|
child: SingleChildScrollView(
|
||||||
|
child: _isFeedbackSubmitted
|
||||||
|
? _buildThankYouMessage()
|
||||||
|
: _isFeedbackFailed
|
||||||
|
? _buildFailureMessage()
|
||||||
|
: _buildFeedbackForm(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
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('/myhomepage'),
|
||||||
|
//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(
|
||||||
|
children: [
|
||||||
|
IconButton(
|
||||||
|
icon: Icon(icon, size: 40),
|
||||||
|
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,
|
||||||
|
) {
|
||||||
|
return Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: [
|
||||||
|
Text(label,
|
||||||
|
style: const TextStyle(
|
||||||
|
fontSize: 14,
|
||||||
|
fontFamily: 'Roboto',
|
||||||
|
fontWeight: FontWeight.w400,
|
||||||
|
color: Color(0xFF898C81),
|
||||||
|
)),
|
||||||
|
RatingBar.builder(
|
||||||
|
initialRating: rating,
|
||||||
|
minRating: 1,
|
||||||
|
direction: Axis.horizontal,
|
||||||
|
allowHalfRating: true,
|
||||||
|
itemCount: 5,
|
||||||
|
itemSize: 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(
|
||||||
|
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);
|
||||||
|
|
||||||
|
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: Colors.grey.shade300,
|
||||||
|
pathColorWhenOff: Colors.grey.shade300,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,254 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:uae_stat/presentation/components/my_bottom_nav_bar.dart';
|
||||||
|
import 'package:uae_stat/presentation/components/my_drawer.dart';
|
||||||
|
import 'package:uae_stat/presentation/routes/drawer_routes/custom_drawer_routes.dart';
|
||||||
|
|
||||||
|
class ManageUserRouter extends StatefulWidget {
|
||||||
|
@override
|
||||||
|
State<ManageUserRouter> createState() => _ManageUserRouterState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _ManageUserRouterState extends State<ManageUserRouter> {
|
||||||
|
// Sample data for the table
|
||||||
|
final List<User> userData = List.generate(
|
||||||
|
5,
|
||||||
|
(index) => User(
|
||||||
|
userName: 'Conan Keller ${index + 1}',
|
||||||
|
emailId: 'ConanKeller${index + 1}@gmail.com',
|
||||||
|
registrationDate: '18/07/2024',
|
||||||
|
status: 'Pending',
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
// Sorting state
|
||||||
|
int _sortColumnIndex = 0;
|
||||||
|
bool _isAscending = true;
|
||||||
|
|
||||||
|
// Method to sort the data based on a column
|
||||||
|
void _sort<T>(Comparable<T> Function(User user) getField, int columnIndex,
|
||||||
|
bool ascending) {
|
||||||
|
setState(() {
|
||||||
|
_sortColumnIndex = columnIndex;
|
||||||
|
_isAscending = ascending;
|
||||||
|
userData.sort((a, b) {
|
||||||
|
final aValue = getField(a);
|
||||||
|
final bValue = getField(b);
|
||||||
|
return ascending
|
||||||
|
? Comparable.compare(aValue, bValue)
|
||||||
|
: Comparable.compare(bValue, aValue);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Define dropdown items
|
||||||
|
final List<String> statusOptions = ['Approved', 'Denied', 'Pending'];
|
||||||
|
|
||||||
|
// Method to get status color
|
||||||
|
Color getStatusColor(String status) {
|
||||||
|
switch (status) {
|
||||||
|
case 'Approved':
|
||||||
|
return Colors.green;
|
||||||
|
case 'Denied':
|
||||||
|
return Colors.red;
|
||||||
|
case 'Pending':
|
||||||
|
return Colors.blue;
|
||||||
|
default:
|
||||||
|
return Colors.black;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//Method to show a confirmation dialog when status is changed
|
||||||
|
Future<void> _showConfirmationDialog(User user, String newStatus) async {
|
||||||
|
double myheight = MediaQuery.of(context).size.height;
|
||||||
|
return showDialog<void>(
|
||||||
|
context: context,
|
||||||
|
barrierDismissible: false, // User must tap button to dismiss dialog
|
||||||
|
builder: (BuildContext context) {
|
||||||
|
return AlertDialog(
|
||||||
|
shape: RoundedRectangleBorder(
|
||||||
|
borderRadius: BorderRadius.circular(5.0), // Rounded corners
|
||||||
|
),
|
||||||
|
contentPadding: EdgeInsets.zero, // Ensure no padding issues with close icon
|
||||||
|
content: Stack(
|
||||||
|
children: [
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.all(16.0),
|
||||||
|
child: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
SizedBox(height: myheight/30,),
|
||||||
|
Text(
|
||||||
|
'Are you sure you want to change \n the status to $newStatus?',
|
||||||
|
style: TextStyle(fontSize: 15),
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Positioned(
|
||||||
|
right: 0,
|
||||||
|
top: 0,
|
||||||
|
child: IconButton(
|
||||||
|
icon: Icon(Icons.close),
|
||||||
|
onPressed: () {
|
||||||
|
Navigator.of(context).pop(); // Close the dialog
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
actions: <Widget>[
|
||||||
|
Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceAround,
|
||||||
|
children: [
|
||||||
|
TextButton(
|
||||||
|
onPressed: () {
|
||||||
|
Navigator.of(context)
|
||||||
|
.pop(); // Close dialog without changing status
|
||||||
|
},
|
||||||
|
style: TextButton.styleFrom(
|
||||||
|
shape: RoundedRectangleBorder(
|
||||||
|
borderRadius: BorderRadius.circular(
|
||||||
|
10.0), // Adjust the radius as needed
|
||||||
|
),
|
||||||
|
side: BorderSide(
|
||||||
|
color: Colors.blue, // Set the outline color
|
||||||
|
width: 2.0, // Set the border width
|
||||||
|
),
|
||||||
|
),
|
||||||
|
child: Text('No'),
|
||||||
|
),
|
||||||
|
TextButton(
|
||||||
|
onPressed: () {
|
||||||
|
setState(() {
|
||||||
|
user.status = newStatus; // Change status
|
||||||
|
});
|
||||||
|
Navigator.of(context).pop();
|
||||||
|
},
|
||||||
|
style: TextButton.styleFrom(
|
||||||
|
backgroundColor: Colors.blue, // Set background color
|
||||||
|
foregroundColor: Colors.white,
|
||||||
|
shape: RoundedRectangleBorder(
|
||||||
|
borderRadius:
|
||||||
|
BorderRadius.circular(10.0), // Set text color
|
||||||
|
)),
|
||||||
|
child: Text(
|
||||||
|
'Yes',
|
||||||
|
style: TextStyle(color: Colors.white),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
],
|
||||||
|
);
|
||||||
|
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
double myheight = MediaQuery.of(context).size.height;
|
||||||
|
return BaseScaffold(
|
||||||
|
body: SingleChildScrollView(
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.only(left: 10.0, right: 10.0, top: 3.0),
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
TextField(
|
||||||
|
decoration: InputDecoration(
|
||||||
|
prefixIcon: const Icon(Icons.search),
|
||||||
|
hintText: 'Search',
|
||||||
|
border: OutlineInputBorder(
|
||||||
|
borderRadius: BorderRadius.all(Radius.circular(20)),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
SizedBox(height: myheight / 40),
|
||||||
|
SingleChildScrollView(
|
||||||
|
scrollDirection: Axis.horizontal,
|
||||||
|
child: DataTable(
|
||||||
|
sortColumnIndex: _sortColumnIndex,
|
||||||
|
sortAscending: _isAscending,
|
||||||
|
columns: [
|
||||||
|
DataColumn(
|
||||||
|
label: const Text('User Name'),
|
||||||
|
onSort: (columnIndex, ascending) => _sort(
|
||||||
|
(user) => user.userName, columnIndex, ascending),
|
||||||
|
numeric: false,
|
||||||
|
),
|
||||||
|
DataColumn(
|
||||||
|
label: const Text('Email ID'),
|
||||||
|
onSort: (columnIndex, ascending) =>
|
||||||
|
_sort((user) => user.emailId, columnIndex, ascending),
|
||||||
|
numeric: false,
|
||||||
|
),
|
||||||
|
DataColumn(
|
||||||
|
label: const Text('Registration Date'),
|
||||||
|
onSort: (columnIndex, ascending) => _sort(
|
||||||
|
(user) => user.registrationDate,
|
||||||
|
columnIndex,
|
||||||
|
ascending),
|
||||||
|
numeric: false,
|
||||||
|
),
|
||||||
|
DataColumn(
|
||||||
|
label: const Text('Status'),
|
||||||
|
onSort: (columnIndex, ascending) =>
|
||||||
|
_sort((user) => user.status, columnIndex, ascending),
|
||||||
|
numeric: false,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
rows: userData.map((user) {
|
||||||
|
return DataRow(
|
||||||
|
cells: [
|
||||||
|
DataCell(Text(user.userName)),
|
||||||
|
DataCell(Text(user.emailId)),
|
||||||
|
DataCell(Text(user.registrationDate)),
|
||||||
|
DataCell(
|
||||||
|
DropdownButton<String>(
|
||||||
|
value: user.status,
|
||||||
|
items: statusOptions.map((status) {
|
||||||
|
return DropdownMenuItem<String>(
|
||||||
|
value: status,
|
||||||
|
child: Text(
|
||||||
|
status,
|
||||||
|
style:
|
||||||
|
TextStyle(color: getStatusColor(status)),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}).toList(),
|
||||||
|
onChanged: (String? newStatus) {
|
||||||
|
if (newStatus != null) {
|
||||||
|
_showConfirmationDialog(user,
|
||||||
|
newStatus); // Show dialog for confirmation
|
||||||
|
}
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}).toList(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// User data model
|
||||||
|
class User {
|
||||||
|
final String userName;
|
||||||
|
final String emailId;
|
||||||
|
final String registrationDate;
|
||||||
|
String status;
|
||||||
|
|
||||||
|
User({
|
||||||
|
required this.userName,
|
||||||
|
required this.emailId,
|
||||||
|
required this.registrationDate,
|
||||||
|
this.status = 'Pending',
|
||||||
|
});
|
||||||
|
}
|
||||||
108
lib/presentation/routes/drawer_routes/custom_drawer_routes.dart
Normal file
108
lib/presentation/routes/drawer_routes/custom_drawer_routes.dart
Normal file
@ -0,0 +1,108 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:go_router/go_router.dart';
|
||||||
|
|
||||||
|
|
||||||
|
class BaseScaffold extends StatelessWidget {
|
||||||
|
final Widget body;
|
||||||
|
|
||||||
|
const BaseScaffold({required this.body});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
// Get the current route to highlight the active item
|
||||||
|
String currentRoute = GoRouterState.of(context).matchedLocation;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
return Scaffold(
|
||||||
|
appBar: AppBar(title: Text(""),),
|
||||||
|
drawer: Drawer(
|
||||||
|
child: ListView(
|
||||||
|
children: [
|
||||||
|
DrawerHeader(
|
||||||
|
decoration: BoxDecoration(color: Colors.blue),
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||||
|
children: [
|
||||||
|
Row(children: [
|
||||||
|
|
||||||
|
],)
|
||||||
|
Divider(),
|
||||||
|
ListTile()
|
||||||
|
],
|
||||||
|
),
|
||||||
|
//child: Text('Drawer Header', style: TextStyle(color: Colors.white, fontSize: 24)),
|
||||||
|
),
|
||||||
|
ListTile(
|
||||||
|
leading: Icon(Icons.person),
|
||||||
|
title: Text('Profile'),
|
||||||
|
onTap: () => context.go('/profile'),
|
||||||
|
),
|
||||||
|
ListTile(
|
||||||
|
leading: Icon(Icons.note),
|
||||||
|
title: Text('Feedback'),
|
||||||
|
onTap: () => context.go('/feedback'),
|
||||||
|
),
|
||||||
|
ListTile(
|
||||||
|
leading : Icon(Icons.percent),
|
||||||
|
title: Text('Manage User'),
|
||||||
|
onTap: () => context.go('/manageuser'),
|
||||||
|
),
|
||||||
|
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
bottomNavigationBar: BottomNavigationBar(
|
||||||
|
currentIndex: _getSelectedIndex(currentRoute),
|
||||||
|
onTap: (index) => _onItemTapped(context, index),
|
||||||
|
items: [
|
||||||
|
BottomNavigationBarItem(icon: Icon(Icons.home,color: Colors.black,), label: 'Home',),
|
||||||
|
BottomNavigationBarItem(icon: Icon(Icons.info,color: Colors.black,), label: 'About',),
|
||||||
|
BottomNavigationBarItem(icon: Icon(Icons.settings,color: Colors.black,), label: 'Settings'),
|
||||||
|
BottomNavigationBarItem(icon: Icon(Icons.person,color: Colors.black,), label: 'Profile'),
|
||||||
|
],
|
||||||
|
selectedItemColor: Colors.blue,
|
||||||
|
unselectedItemColor: Colors.grey,
|
||||||
|
showUnselectedLabels: true,
|
||||||
|
),
|
||||||
|
body: body,
|
||||||
|
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
//Map the current route to the selected index
|
||||||
|
int _getSelectedIndex(String route) {
|
||||||
|
switch (route) {
|
||||||
|
case '/':
|
||||||
|
return 0;
|
||||||
|
case '/about':
|
||||||
|
return 1;
|
||||||
|
case '/settings':
|
||||||
|
return 2;
|
||||||
|
case '/profile':
|
||||||
|
return 3;
|
||||||
|
default:
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//Handle navigation when an item is tapped
|
||||||
|
void _onItemTapped(BuildContext context, int index) {
|
||||||
|
switch (index) {
|
||||||
|
case 0:
|
||||||
|
context.go('/');
|
||||||
|
break;
|
||||||
|
case 1:
|
||||||
|
context.go('/about');
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
context.go('/settings');
|
||||||
|
break;
|
||||||
|
case 3:
|
||||||
|
context.go('/profile');
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
Loading…
Reference in New Issue
Block a user