initial commit: push everything
This commit is contained in:
464
lib/views/Dashboard/profile/Profilepage.dart
Normal file
464
lib/views/Dashboard/profile/Profilepage.dart
Normal file
@@ -0,0 +1,464 @@
|
||||
import 'dart:io';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:flutter_screenutil/flutter_screenutil.dart'; // ⭐ REQUIRED
|
||||
import 'package:nearle/views/Dashboard/profile/informations/help_center.dart';
|
||||
import 'package:nearle/views/Dashboard/profile/informations/profile.dart';
|
||||
import 'package:nearle/views/Dashboard/profile/informations/saved_address.dart';
|
||||
import 'package:nearle/views/Dashboard/profile/informations/faq.dart';
|
||||
import 'package:nearle/views/Dashboard/profile/informations/notifications_page.dart';
|
||||
import 'package:nearle/views/Dashboard/profile/informations/support_ticket.dart';
|
||||
|
||||
import 'package:nearle/views/helpers/constants/Colorconstants.dart';
|
||||
import 'package:nearle/views/helpers/constants/Font_constant.dart';
|
||||
import 'package:nearle/views/onboardscreens/Sign_in.dart';
|
||||
import 'package:nearle/controllers/profile_controller.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
import 'package:nearle/views/Dashboard/profile/informations/order_alert_sound.dart';
|
||||
import 'package:nearle/controllers/rewards_controller.dart';
|
||||
import 'package:nearle/views/Dashboard/profile/rewards_card.dart';
|
||||
import 'package:nearle/views/Dashboard/profile/informations/rider_rewards_page.dart';
|
||||
import 'package:nearle/utils/mqtt_service.dart';
|
||||
|
||||
class ProfilePage extends StatefulWidget {
|
||||
const ProfilePage({super.key});
|
||||
|
||||
@override
|
||||
State<ProfilePage> createState() => _ProfilePageState();
|
||||
}
|
||||
|
||||
class _ProfilePageState extends State<ProfilePage> {
|
||||
late final ProfileController _profileController =
|
||||
Get.isRegistered<ProfileController>()
|
||||
? Get.find<ProfileController>()
|
||||
: Get.put(ProfileController(), permanent: true);
|
||||
|
||||
final RewardsController _rewardsController = Get.put(RewardsController());
|
||||
|
||||
String _name = '';
|
||||
String _email = '';
|
||||
String _contact = '';
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_loadProfilePrefs();
|
||||
ever(_profileController.userName, (_) => _assignFromController());
|
||||
ever(_profileController.userEmail, (_) => _assignFromController());
|
||||
ever(_profileController.userContact, (_) => _assignFromController());
|
||||
ever(_profileController.userAddress, (_) => _assignFromController());
|
||||
_profileController.loadFromPrefs();
|
||||
}
|
||||
|
||||
Future<void> _loadProfilePrefs() async {
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
setState(() {
|
||||
_name = prefs.getString('user_name') ?? '';
|
||||
_email = prefs.getString('user_email') ?? '';
|
||||
_contact = prefs.getString('contactno') ?? '';
|
||||
_contact = prefs.getString('contactno') ?? '';
|
||||
});
|
||||
|
||||
final userId = prefs.getInt('userid') ?? 0;
|
||||
if (userId > 0) {
|
||||
_rewardsController.fetchBonusSummary(userId);
|
||||
}
|
||||
}
|
||||
|
||||
void _assignFromController() {
|
||||
setState(() {
|
||||
if (_profileController.userName.value.trim().isNotEmpty) {
|
||||
_name = _profileController.userName.value.trim();
|
||||
}
|
||||
if (_profileController.userEmail.value.trim().isNotEmpty) {
|
||||
_email = _profileController.userEmail.value.trim();
|
||||
}
|
||||
if (_profileController.userContact.value.trim().isNotEmpty) {
|
||||
_contact = _profileController.userContact.value.trim();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return SafeArea(
|
||||
child: Scaffold(
|
||||
backgroundColor: Colors.grey.shade200,
|
||||
|
||||
appBar: AppBar(
|
||||
backgroundColor: Colors.grey.shade200,
|
||||
elevation: 0,
|
||||
toolbarHeight: 70.h, // ⭐ responsive
|
||||
title: Padding(
|
||||
padding: EdgeInsets.only(top: 12.h),
|
||||
child: Text(
|
||||
"PROFILE",
|
||||
style: TextStyle(
|
||||
fontSize: FontConstants.xxxLarge(context).sp,
|
||||
fontWeight: FontWeight.bold,
|
||||
fontFamily: FontConstants.fontFamily,
|
||||
color: Colors.black,
|
||||
),
|
||||
),
|
||||
),
|
||||
bottom: PreferredSize(
|
||||
preferredSize: Size.fromHeight(1.h),
|
||||
child: Divider(height: 1.h, color: Colors.grey),
|
||||
),
|
||||
),
|
||||
|
||||
body: SingleChildScrollView(
|
||||
physics: const ClampingScrollPhysics(),
|
||||
child: Padding(
|
||||
padding: EdgeInsets.symmetric(horizontal: 16.w),
|
||||
child: Padding(
|
||||
padding: EdgeInsets.only(bottom: 40.h),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
SizedBox(height: 20.h),
|
||||
|
||||
/// ⭐ PROFILE CARD RESPONSIVE
|
||||
Container(
|
||||
padding: EdgeInsets.all(16.r),
|
||||
decoration: BoxDecoration(
|
||||
color: ColorConstants.primaryColor,
|
||||
borderRadius: BorderRadius.circular(20.r),
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
Icon(
|
||||
Icons.person,
|
||||
color: Colors.white,
|
||||
size: 22.sp,
|
||||
),
|
||||
SizedBox(width: 6.w),
|
||||
Flexible(
|
||||
child: Text(
|
||||
_name.isNotEmpty ? _name : "—",
|
||||
style: TextStyle(
|
||||
color: Colors.white,
|
||||
fontSize: 18.sp,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
SizedBox(height: 12.h),
|
||||
|
||||
Row(
|
||||
children: [
|
||||
Icon(
|
||||
Icons.phone,
|
||||
color: Colors.white,
|
||||
size: 20.sp,
|
||||
),
|
||||
SizedBox(width: 6.w),
|
||||
Flexible(
|
||||
child: Text(
|
||||
_contact.isNotEmpty ? _contact : "—",
|
||||
style: TextStyle(
|
||||
color: Colors.white,
|
||||
fontSize: 16.sp,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
SizedBox(height: 12.h),
|
||||
|
||||
Row(
|
||||
children: [
|
||||
Icon(
|
||||
Icons.email,
|
||||
color: Colors.white,
|
||||
size: 20.sp,
|
||||
),
|
||||
SizedBox(width: 6.w),
|
||||
Flexible(
|
||||
child: Text(
|
||||
_email.isNotEmpty ? _email : "—",
|
||||
style: TextStyle(
|
||||
color: Colors.white,
|
||||
fontSize: 16.sp,
|
||||
),
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
/// ⭐ Circle Avatar Responsive
|
||||
Obx(() {
|
||||
final path = _profileController.imagePath.value;
|
||||
final hasImage =
|
||||
path.isNotEmpty && File(path).existsSync();
|
||||
|
||||
return CircleAvatar(
|
||||
radius: 40.r,
|
||||
backgroundColor: Colors.grey.shade300,
|
||||
backgroundImage: hasImage
|
||||
? FileImage(File(path))
|
||||
: null,
|
||||
child: !hasImage
|
||||
? Icon(
|
||||
Icons.person,
|
||||
color: Colors.white,
|
||||
size: 40.sp,
|
||||
)
|
||||
: null,
|
||||
);
|
||||
}),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
SizedBox(height: 20.h),
|
||||
|
||||
// ⭐ REWARDS SECTION
|
||||
RewardsCard(controller: _rewardsController),
|
||||
|
||||
SizedBox(height: 20.h),
|
||||
|
||||
_buildHeader("Your Information"),
|
||||
_buildBox([
|
||||
_buildInfoTile(Icons.person, "Profile"),
|
||||
Divider(),
|
||||
_buildInfoTile(Icons.location_on, "Saved Address"),
|
||||
Divider(),
|
||||
_buildInfoTile(Icons.card_giftcard, "Rewards"),
|
||||
Divider(),
|
||||
_buildInfoTile(Icons.notifications, "Notification"),
|
||||
]),
|
||||
|
||||
SizedBox(height: 20.h),
|
||||
|
||||
_buildHeader("Support"),
|
||||
_buildBox([
|
||||
_buildInfoTile(Icons.support_agent, "Help Centre"),
|
||||
Divider(),
|
||||
_buildInfoTile(Icons.local_activity, "Support tickets"),
|
||||
]),
|
||||
|
||||
SizedBox(height: 20.h),
|
||||
|
||||
_buildHeader("Other Information"),
|
||||
_buildBox([
|
||||
_buildInfoTile(Icons.translate, "Faq"),
|
||||
Divider(),
|
||||
// _buildInfoTile(Icons.sticky_note_2, "Terms & Conditions"),
|
||||
// Divider(),
|
||||
_buildInfoTile(
|
||||
Icons.notifications_active,
|
||||
"Order alert sound",
|
||||
),
|
||||
]),
|
||||
|
||||
SizedBox(height: 55.h),
|
||||
|
||||
/// ⭐ Logout Button Responsive
|
||||
SizedBox(
|
||||
height: 55.h,
|
||||
width: double.infinity,
|
||||
child: OutlinedButton(
|
||||
onPressed: () => _showLogoutDialog(context),
|
||||
style: OutlinedButton.styleFrom(
|
||||
backgroundColor: ColorConstants.secondaryColor,
|
||||
side: BorderSide(color: Colors.black, width: 0.2.w),
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(12.r),
|
||||
),
|
||||
),
|
||||
child: Text(
|
||||
"Logout",
|
||||
style: TextStyle(
|
||||
fontSize: 20.sp,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Colors.black,
|
||||
fontFamily: FontConstants.fontFamily,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
SizedBox(height: 15.h),
|
||||
|
||||
Center(
|
||||
child: Text(
|
||||
"App Version 1.2.19",
|
||||
style: TextStyle(
|
||||
fontSize: 16.sp,
|
||||
color: Colors.grey.shade600,
|
||||
fontFamily: FontConstants.fontFamily,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildHeader(String title) {
|
||||
return Padding(
|
||||
padding: EdgeInsets.all(8.r),
|
||||
child: Text(
|
||||
title,
|
||||
style: TextStyle(
|
||||
fontSize: 22.sp,
|
||||
fontWeight: FontWeight.bold,
|
||||
fontFamily: FontConstants.fontFamily,
|
||||
color: Colors.black,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildBox(List<Widget> children) {
|
||||
return Container(
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white,
|
||||
borderRadius: BorderRadius.circular(12.r),
|
||||
),
|
||||
child: Column(children: children),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildInfoTile(IconData icon, String title) {
|
||||
return ListTile(
|
||||
leading: Icon(icon, size: 26.sp),
|
||||
title: Text(
|
||||
title,
|
||||
style: TextStyle(
|
||||
fontSize: 19.sp,
|
||||
fontWeight: FontWeight.w500,
|
||||
fontFamily: FontConstants.fontFamily,
|
||||
),
|
||||
),
|
||||
trailing: Icon(Icons.arrow_forward_ios, size: 22.sp),
|
||||
onTap: () => _handleNavigation(title),
|
||||
);
|
||||
}
|
||||
|
||||
void _handleNavigation(String title) {
|
||||
switch (title) {
|
||||
case "Profile":
|
||||
Get.to(() => Profile());
|
||||
break;
|
||||
case "Saved Address":
|
||||
Get.to(() => const SavedAddressPage());
|
||||
break;
|
||||
case "Notification":
|
||||
Get.to(() => const NotificationsPage());
|
||||
break;
|
||||
case "Help Centre":
|
||||
Get.to(() => HelpCenter());
|
||||
break;
|
||||
case "Support tickets":
|
||||
Get.to(() => SupportTicket());
|
||||
break;
|
||||
case "Rewards":
|
||||
Get.to(() => const RiderRewardsPage());
|
||||
break;
|
||||
case "Faq":
|
||||
Get.to(() => const FaqPage());
|
||||
break;
|
||||
// case "Terms & Conditions":
|
||||
// Get.to(() => const TermsCondition());
|
||||
// break;
|
||||
case "Order alert sound":
|
||||
Get.to(() => const OrderAlertSoundPage());
|
||||
break;
|
||||
default:
|
||||
debugPrint("Tapped on $title — no page linked yet.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void _showLogoutDialog(BuildContext context) {
|
||||
showDialog(
|
||||
context: context,
|
||||
barrierDismissible: true,
|
||||
builder: (BuildContext context) {
|
||||
return Center(
|
||||
child: FittedBox(
|
||||
child: AlertDialog(
|
||||
backgroundColor: ColorConstants.secondaryColor,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(20.r),
|
||||
),
|
||||
title: Text(
|
||||
"Logout",
|
||||
style: TextStyle(
|
||||
fontSize: 22.sp,
|
||||
fontWeight: FontWeight.bold,
|
||||
fontFamily: FontConstants.fontFamily,
|
||||
),
|
||||
),
|
||||
content: Text(
|
||||
"Are you sure you want to logout?",
|
||||
style: TextStyle(
|
||||
fontSize: 19.sp,
|
||||
fontFamily: FontConstants.fontFamily,
|
||||
),
|
||||
),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () => Navigator.pop(context),
|
||||
child: Text(
|
||||
"No",
|
||||
style: TextStyle(
|
||||
fontSize: 19.sp,
|
||||
color: Colors.grey,
|
||||
fontFamily: FontConstants.fontFamily,
|
||||
),
|
||||
),
|
||||
),
|
||||
ElevatedButton(
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: const Color.fromARGB(255, 153, 121, 167),
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(12.r),
|
||||
),
|
||||
),
|
||||
onPressed: () async {
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
final userId =
|
||||
prefs.getInt('userid') ?? prefs.getInt('userId') ?? 0;
|
||||
if (userId != 0) {
|
||||
await prefs.remove('skipped_orders_cache_$userId');
|
||||
}
|
||||
NearleMqttService().disconnect();
|
||||
prefs.setBool('logged_out', true);
|
||||
Get.offAll(() => const SignIn());
|
||||
},
|
||||
child: Text(
|
||||
"Yes",
|
||||
style: TextStyle(
|
||||
fontSize: 19.sp,
|
||||
color: Colors.black,
|
||||
fontFamily: FontConstants.fontFamily,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
95
lib/views/Dashboard/profile/informations/faq.dart
Normal file
95
lib/views/Dashboard/profile/informations/faq.dart
Normal file
@@ -0,0 +1,95 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:nearle/views/helpers/constants/Colorconstants.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:webview_flutter/webview_flutter.dart';
|
||||
|
||||
class FaqController extends GetxController {
|
||||
WebViewController? webViewController;
|
||||
var isLoading = true.obs;
|
||||
|
||||
@override
|
||||
void onInit() {
|
||||
super.onInit();
|
||||
initializeWebView();
|
||||
}
|
||||
|
||||
void initializeWebView() {
|
||||
webViewController = WebViewController()
|
||||
..setJavaScriptMode(JavaScriptMode.unrestricted)
|
||||
..setBackgroundColor(const Color(0x00000000))
|
||||
..setNavigationDelegate(
|
||||
NavigationDelegate(
|
||||
onPageStarted: (url) {
|
||||
isLoading.value = true;
|
||||
print('Started loading: $url');
|
||||
},
|
||||
onPageFinished: (url) {
|
||||
isLoading.value = false;
|
||||
print('Finished loading: $url');
|
||||
},
|
||||
onWebResourceError: (error) {
|
||||
isLoading.value = false;
|
||||
print('WebView error: ${error.description}');
|
||||
},
|
||||
),
|
||||
);
|
||||
loadFaqUrl();
|
||||
}
|
||||
|
||||
Future<void> loadFaqUrl() async {
|
||||
if (webViewController != null) {
|
||||
try {
|
||||
await webViewController!.loadRequest(
|
||||
Uri.parse('https://nearle.in/faq'),
|
||||
);
|
||||
} catch (e) {
|
||||
print('Error loading URL: $e');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class FaqPage extends StatelessWidget {
|
||||
const FaqPage({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final controller = Get.put(FaqController());
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
backgroundColor: ColorConstants.primaryColor,
|
||||
centerTitle: true,
|
||||
toolbarHeight: 70,
|
||||
leading: IconButton(
|
||||
icon: const Icon(
|
||||
Icons.arrow_back_ios,
|
||||
color: Colors.white,
|
||||
), // :small_blue_diamond: white back arrow
|
||||
onPressed: () {
|
||||
Navigator.pop(context); // goes back to previous screen
|
||||
},
|
||||
),
|
||||
title: const Text(
|
||||
'FAQ',
|
||||
style: TextStyle(
|
||||
fontSize: 26, // :small_blue_diamond: larger font size
|
||||
color: Colors.white, // :small_blue_diamond: white text
|
||||
fontWeight: FontWeight.bold,
|
||||
letterSpacing: 1.2,
|
||||
),
|
||||
),
|
||||
elevation: 4,
|
||||
),
|
||||
body: Obx(() {
|
||||
final wvc = controller.webViewController;
|
||||
return Stack(
|
||||
children: [
|
||||
if (wvc != null) WebViewWidget(controller: wvc),
|
||||
if (controller.isLoading.value)
|
||||
const LinearProgressIndicator(minHeight: 2),
|
||||
],
|
||||
);
|
||||
}),
|
||||
);
|
||||
}
|
||||
}
|
||||
380
lib/views/Dashboard/profile/informations/help_center.dart
Normal file
380
lib/views/Dashboard/profile/informations/help_center.dart
Normal file
@@ -0,0 +1,380 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:nearle/views/helpers/constants/Colorconstants.dart';
|
||||
import 'package:nearle/views/helpers/constants/Font_constant.dart';
|
||||
class HelpCenter extends StatelessWidget {
|
||||
const HelpCenter({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return SafeArea(
|
||||
child: Scaffold(
|
||||
appBar: AppBar(
|
||||
backgroundColor: ColorConstants.primaryColor,
|
||||
centerTitle: true,
|
||||
toolbarHeight: 70,
|
||||
leading: IconButton(
|
||||
icon: const Icon(Icons.arrow_back_ios, color: Colors.white),
|
||||
onPressed: () {
|
||||
Navigator.pop(context);
|
||||
},
|
||||
),
|
||||
title: const Text(
|
||||
'Help Center',
|
||||
style: TextStyle(
|
||||
fontSize: 26,
|
||||
color: Colors.white,
|
||||
fontWeight: FontWeight.bold,
|
||||
letterSpacing: 1.2,
|
||||
),
|
||||
),
|
||||
elevation: 4,
|
||||
),
|
||||
body: SingleChildScrollView(
|
||||
padding: const EdgeInsets.fromLTRB(16, 16, 16, 24),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
// Header text
|
||||
Text(
|
||||
"We're here to help you with anything and \neverything on Nearle Xpress",
|
||||
style: TextStyle(
|
||||
fontSize: 20,
|
||||
fontWeight: FontWeight.w800,
|
||||
fontFamily: FontConstants.fontFamily
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Text(
|
||||
"We make sure your delivery experience is smooth and clear. Whether you’re on your first trip or your hundredth, we’ve got your back. Browse through frequently asked questions or reach out directly if you need further help.",
|
||||
style: TextStyle(fontSize: 18,color: Colors.grey.shade700, height: 1.4, fontFamily: FontConstants.fontFamily),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
|
||||
|
||||
TextField(
|
||||
decoration: InputDecoration(
|
||||
hintText: 'Search help',
|
||||
prefixIcon: const Icon(Icons.search),
|
||||
contentPadding: const EdgeInsets.symmetric(vertical: 0, horizontal: 12),
|
||||
filled: true,
|
||||
fillColor: Colors.white,
|
||||
border: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
borderSide: BorderSide(color: Colors.grey.shade300),
|
||||
),
|
||||
enabledBorder: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
borderSide: BorderSide(color: Colors.grey.shade300),
|
||||
),
|
||||
focusedBorder: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
borderSide: BorderSide(color: ColorConstants.primaryColor, width: 1.5),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
const SizedBox(height: 15),
|
||||
Text(
|
||||
'FAQ',
|
||||
style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold, fontFamily: FontConstants.fontFamily,color: ColorConstants.primaryColor),
|
||||
),
|
||||
Divider(),
|
||||
|
||||
|
||||
|
||||
_FaqTile(
|
||||
title: 'What is Nearle Xpress?',
|
||||
initiallyExpanded: true,
|
||||
child: const Text(
|
||||
'Nearle Xpress is a delivery app for riders who complete local deliveries for nearby stores and markets. It helps riders accept tasks, manage pickup and drop points, and update deliveries in real time.',
|
||||
style: TextStyle(fontSize: 16, color: Colors.black87, height: 1.4),
|
||||
),
|
||||
),
|
||||
_FaqTile(
|
||||
title: 'How do I accept a delivery task?',
|
||||
child: const Text(
|
||||
'You can accept tasks from the Home screen when a new order appears. Tap on the order to view details and then press Accept.',
|
||||
style: TextStyle(fontSize: 16, color: Colors.black87, height: 1.4),
|
||||
),
|
||||
),
|
||||
_FaqTile(
|
||||
title: 'How do I update the delivery status?',
|
||||
child: const Text(
|
||||
'Open the active task and use the status buttons to mark Pickup, On the way, and Delivered. Ensure accurate updates for better tracking.',
|
||||
style: TextStyle(fontSize: 16, color: Colors.black87, height: 1.4),
|
||||
),
|
||||
),
|
||||
_FaqTile(
|
||||
title: 'Can I view my past deliveries?',
|
||||
child: const Text(
|
||||
'Yes. Go to the History section from your dashboard to see completed deliveries and earnings.',
|
||||
style: TextStyle(fontSize: 16, color: Colors.black87, height: 1.4),
|
||||
),
|
||||
),
|
||||
_FaqTile(
|
||||
title: 'What if I face an issue during delivery?',
|
||||
child: const Text(
|
||||
'Use the Help Center to report an issue or contact support. Provide order details and a short description of the problem.',
|
||||
style: TextStyle(fontSize: 16, color: Colors.black87, height: 1.4),
|
||||
),
|
||||
),
|
||||
SizedBox(height: 10,),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Text("Still stuck? Help is a mail away!",style: TextStyle(fontSize: 18,fontFamily: FontConstants.fontFamily,fontWeight: FontWeight.bold,color: ColorConstants.primaryColor),),
|
||||
],
|
||||
)
|
||||
|
||||
],
|
||||
),
|
||||
),
|
||||
bottomNavigationBar: Padding(padding: EdgeInsets.all(16),
|
||||
child: SizedBox(
|
||||
height: 55,
|
||||
width: double.infinity,
|
||||
child: ElevatedButton(
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: ColorConstants.primaryColor,
|
||||
foregroundColor: ColorConstants.primaryColor,
|
||||
side: BorderSide(color: ColorConstants.primaryColor, width: 1.2),
|
||||
padding: const EdgeInsets.symmetric(vertical: 14),
|
||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(10)),
|
||||
),
|
||||
onPressed: () {
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (_) => const HelpCenterMessage(),
|
||||
),
|
||||
);
|
||||
},
|
||||
child: Text(
|
||||
'Send a message',
|
||||
style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold,fontFamily: FontConstants.fontFamily,color: Colors.white),
|
||||
),
|
||||
),
|
||||
),),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _FaqTile extends StatelessWidget {
|
||||
final String title;
|
||||
final Widget child;
|
||||
final bool initiallyExpanded;
|
||||
|
||||
const _FaqTile({
|
||||
required this.title,
|
||||
required this.child,
|
||||
this.initiallyExpanded = false,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Card(
|
||||
margin: const EdgeInsets.symmetric(vertical: 6),
|
||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
|
||||
child: Theme(
|
||||
data: Theme.of(context).copyWith(dividerColor: Colors.transparent),
|
||||
child: ExpansionTile(
|
||||
initiallyExpanded: initiallyExpanded,
|
||||
tilePadding: const EdgeInsets.symmetric(horizontal: 12, vertical: 2),
|
||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
|
||||
collapsedShape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
|
||||
title: Text(
|
||||
title,
|
||||
style: TextStyle(fontWeight: FontWeight.w600,fontSize: 18,fontFamily: FontConstants.fontFamily),
|
||||
),
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.fromLTRB(12, 0, 12, 12),
|
||||
child: child,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
// -------------------------------message page------------------------------------
|
||||
class HelpCenterMessage extends StatefulWidget {
|
||||
const HelpCenterMessage({super.key});
|
||||
|
||||
@override
|
||||
State<HelpCenterMessage> createState() => _HelpCenterMessageState();
|
||||
}
|
||||
|
||||
class _HelpCenterMessageState extends State<HelpCenterMessage> {
|
||||
final _subjectController = TextEditingController();
|
||||
final _messageController = TextEditingController();
|
||||
final _formKey = GlobalKey<FormState>();
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_subjectController.dispose();
|
||||
_messageController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return SafeArea(
|
||||
child: Scaffold(
|
||||
appBar: AppBar(
|
||||
backgroundColor: ColorConstants.primaryColor,
|
||||
centerTitle: true,
|
||||
toolbarHeight: 80,
|
||||
leading: IconButton(
|
||||
icon: const Icon(Icons.arrow_back_ios, color: Colors.white),
|
||||
onPressed: () => Navigator.pop(context),
|
||||
),
|
||||
title: const Text(
|
||||
'Help Centre',
|
||||
style: TextStyle(
|
||||
fontSize: 26,
|
||||
color: Colors.white,
|
||||
fontWeight: FontWeight.bold,
|
||||
letterSpacing: 1.2,
|
||||
),
|
||||
),
|
||||
elevation: 4,
|
||||
),
|
||||
body: SingleChildScrollView(
|
||||
padding: const EdgeInsets.fromLTRB(16, 16, 16, 24),
|
||||
child: Form(
|
||||
key: _formKey,
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
// Heading
|
||||
Text(
|
||||
'Send Us a Message',
|
||||
style: TextStyle(
|
||||
fontSize: 22,
|
||||
fontWeight: FontWeight.w800,
|
||||
fontFamily: FontConstants.fontFamily,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Text(
|
||||
"Not finding what you're looking for in the FAQs? Don't worry—we're here to help!",
|
||||
style: TextStyle(
|
||||
fontSize: 18,
|
||||
color: Colors.grey.shade700,
|
||||
height: 1.4,
|
||||
fontFamily: FontConstants.fontFamily,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
|
||||
// Subject label
|
||||
Text(
|
||||
'Subject',
|
||||
style: TextStyle(
|
||||
fontSize: 18,
|
||||
fontWeight: FontWeight.w600,
|
||||
fontFamily: FontConstants.fontFamily,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
TextFormField(
|
||||
controller: _subjectController,
|
||||
decoration: InputDecoration(
|
||||
hintText: 'Type Something',
|
||||
filled: true,
|
||||
fillColor: Colors.white,
|
||||
contentPadding: const EdgeInsets.symmetric(vertical: 12, horizontal: 12),
|
||||
border: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
borderSide: BorderSide(color: Colors.grey.shade300),
|
||||
),
|
||||
enabledBorder: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
borderSide: BorderSide(color: Colors.grey.shade300),
|
||||
),
|
||||
focusedBorder: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
borderSide: BorderSide(color: ColorConstants.primaryColor, width: 1.5),
|
||||
),
|
||||
),
|
||||
validator: (v) => (v == null || v.trim().isEmpty) ? 'Please enter a subject' : null,
|
||||
),
|
||||
|
||||
const SizedBox(height: 18),
|
||||
|
||||
// Message label
|
||||
Text(
|
||||
'Your Message',
|
||||
style: TextStyle(
|
||||
fontSize: 18,
|
||||
fontWeight: FontWeight.w600,
|
||||
fontFamily: FontConstants.fontFamily,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
TextFormField(
|
||||
controller: _messageController,
|
||||
minLines: 5,
|
||||
maxLines: 8,
|
||||
decoration: InputDecoration(
|
||||
hintText: 'Type Something',
|
||||
filled: true,
|
||||
fillColor: Colors.white,
|
||||
alignLabelWithHint: true,
|
||||
contentPadding: const EdgeInsets.symmetric(vertical: 12, horizontal: 12),
|
||||
border: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
borderSide: BorderSide(color: Colors.grey.shade300),
|
||||
),
|
||||
enabledBorder: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
borderSide: BorderSide(color: Colors.grey.shade300),
|
||||
),
|
||||
focusedBorder: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
borderSide: BorderSide(color: ColorConstants.primaryColor, width: 1.5),
|
||||
),
|
||||
),
|
||||
validator: (v) => (v == null || v.trim().isEmpty) ? 'Please enter your message' : null,
|
||||
),
|
||||
SizedBox(height: 30,),
|
||||
SizedBox(
|
||||
height: 55,
|
||||
width: double.infinity,
|
||||
child: ElevatedButton(
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: ColorConstants.primaryColor,
|
||||
foregroundColor: Colors.white,
|
||||
padding: const EdgeInsets.symmetric(vertical: 14),
|
||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(10)),
|
||||
),
|
||||
onPressed: () {
|
||||
if (_formKey.currentState?.validate() ?? false) {
|
||||
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(content: Text('Message sent')),
|
||||
);
|
||||
Navigator.pop(context);
|
||||
}
|
||||
},
|
||||
child: Text(
|
||||
'Send a message',
|
||||
style: TextStyle(
|
||||
fontSize: 20,
|
||||
fontWeight: FontWeight.bold,
|
||||
fontFamily: FontConstants.fontFamily,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
176
lib/views/Dashboard/profile/informations/notifications_page.dart
Normal file
176
lib/views/Dashboard/profile/informations/notifications_page.dart
Normal file
@@ -0,0 +1,176 @@
|
||||
import 'dart:convert';
|
||||
import 'dart:io';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:nearle/views/helpers/constants/Colorconstants.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
import 'package:nearle/views/helpers/constants/Font_constant.dart';
|
||||
|
||||
class NotificationsPage extends StatefulWidget {
|
||||
const NotificationsPage({super.key});
|
||||
|
||||
@override
|
||||
State<NotificationsPage> createState() => _NotificationsPageState();
|
||||
}
|
||||
|
||||
class _NotificationsPageState extends State<NotificationsPage> {
|
||||
List<Map<String, dynamic>> _items = const [];
|
||||
bool _loading = true;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_load();
|
||||
}
|
||||
|
||||
Future<void> _load() async {
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
final raw = prefs.getString('notifications_log');
|
||||
List<Map<String, dynamic>> parsed = [];
|
||||
if (raw != null && raw.isNotEmpty) {
|
||||
try {
|
||||
final list = jsonDecode(raw) as List<dynamic>;
|
||||
parsed = list.map((e) => (e as Map).map((k, v) => MapEntry(k.toString(), v))).toList();
|
||||
} catch (_) {}
|
||||
}
|
||||
if (!mounted) return;
|
||||
setState(() {
|
||||
_items = parsed;
|
||||
_loading = false;
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
backgroundColor: ColorConstants.primaryColor,
|
||||
centerTitle: true,
|
||||
toolbarHeight: 70, // increases AppBar height
|
||||
elevation: 4,
|
||||
leading: IconButton(
|
||||
icon: const Icon(Icons.arrow_back_ios, color: Colors.white), // white back arrow
|
||||
onPressed: () {
|
||||
Navigator.pop(context);
|
||||
},
|
||||
),
|
||||
title: Text(
|
||||
'Notifications',
|
||||
style: const TextStyle(
|
||||
fontSize: 26, // larger font size
|
||||
color: Colors.white, // white text
|
||||
fontWeight: FontWeight.bold,
|
||||
letterSpacing: 1.2,
|
||||
).copyWith(fontFamily: FontConstants.fontFamily), // keep your font
|
||||
),
|
||||
actions: [
|
||||
IconButton(
|
||||
icon: const Icon(Icons.delete_sweep, color: Colors.white), // white icon
|
||||
onPressed: () async {
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
await prefs.remove('notifications_log');
|
||||
if (!mounted) return;
|
||||
setState(() => _items = const []);
|
||||
// ignore: use_build_context_synchronously
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(content: Text('Notifications cleared')),
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
body: _loading
|
||||
? const Center(child: CircularProgressIndicator())
|
||||
: _items.isEmpty
|
||||
? Center(child: Text('No notifications yet',style: TextStyle(fontSize: 20,fontFamily: FontConstants.fontFamily),))
|
||||
: RefreshIndicator(
|
||||
onRefresh: _load,
|
||||
child: ListView.separated(
|
||||
padding: const EdgeInsets.all(12),
|
||||
itemCount: _items.length,
|
||||
separatorBuilder: (_, __) => const Divider(height: 1),
|
||||
itemBuilder: (context, index) {
|
||||
final it = _items[index];
|
||||
final title = (it['title'] ?? 'Nearle').toString();
|
||||
final body = (it['body'] ?? '').toString();
|
||||
final time = (it['time'] ?? '').toString();
|
||||
final imageUrl = (it['imageUrl'] ?? '').toString();
|
||||
final imagePath = (it['imagePath'] ?? '').toString();
|
||||
return Card(
|
||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
|
||||
elevation: 1.5,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 12),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
const Icon(Icons.notifications_active, color: Colors.purple),
|
||||
const SizedBox(width: 8),
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
title,
|
||||
style: TextStyle(
|
||||
fontFamily: FontConstants.fontFamily,
|
||||
fontWeight: FontWeight.w700,
|
||||
fontSize: 16,
|
||||
),
|
||||
),
|
||||
if (time.isNotEmpty)
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(top: 2),
|
||||
child: Text(
|
||||
time,
|
||||
style: const TextStyle(fontSize: 12, color: Colors.grey),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
if (body.isNotEmpty)
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(top: 8),
|
||||
child: Text(
|
||||
body,
|
||||
style: TextStyle(fontFamily: FontConstants.fontFamily, fontSize: 14),
|
||||
),
|
||||
),
|
||||
if (imagePath.isNotEmpty || imageUrl.isNotEmpty)
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(top: 10),
|
||||
child: ClipRRect(
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
child: imagePath.isNotEmpty
|
||||
? Image.file(
|
||||
File(imagePath),
|
||||
height: 170,
|
||||
width: double.infinity,
|
||||
fit: BoxFit.cover,
|
||||
)
|
||||
: Image.network(
|
||||
imageUrl,
|
||||
height: 170,
|
||||
width: double.infinity,
|
||||
fit: BoxFit.cover,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
206
lib/views/Dashboard/profile/informations/order_alert_sound.dart
Normal file
206
lib/views/Dashboard/profile/informations/order_alert_sound.dart
Normal file
@@ -0,0 +1,206 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
import 'package:audioplayers/audioplayers.dart';
|
||||
import 'package:nearle/views/helpers/constants/Colorconstants.dart';
|
||||
import 'package:nearle/views/helpers/constants/Font_constant.dart';
|
||||
|
||||
class OrderAlertSoundPage extends StatefulWidget {
|
||||
const OrderAlertSoundPage({super.key});
|
||||
|
||||
@override
|
||||
State<OrderAlertSoundPage> createState() => _OrderAlertSoundPageState();
|
||||
}
|
||||
|
||||
class _OrderAlertSoundPageState extends State<OrderAlertSoundPage> {
|
||||
static const String _prefsKey = 'order_alert_sound';
|
||||
static const String _defaultSound = 'assets/audio/alert-1.mp3';
|
||||
|
||||
final AudioPlayer _player = AudioPlayer();
|
||||
String _selected = _defaultSound;
|
||||
bool _loading = true;
|
||||
|
||||
// Available sounds from assets/audio/ folder
|
||||
final List<_SoundOption> _options = const [
|
||||
_SoundOption(
|
||||
label: 'Alert 1 (Default)',
|
||||
assetPath: 'assets/audio/alert-1.mp3',
|
||||
),
|
||||
_SoundOption(label: 'Alert 2', assetPath: 'assets/audio/alert-2.mp3'),
|
||||
_SoundOption(label: 'Alert 3', assetPath: 'assets/audio/alert-3.mp3'),
|
||||
_SoundOption(label: 'Alert 4', assetPath: 'assets/audio/alert-4.mp3'),
|
||||
_SoundOption(label: 'Alert 5', assetPath: 'assets/audio/alert-5.mp3'),
|
||||
_SoundOption(label: 'Alert 6', assetPath: 'assets/audio/alert-6.mp3'),
|
||||
_SoundOption(label: 'Alert 7', assetPath: 'assets/audio/alert-7.mp3'),
|
||||
_SoundOption(label: 'Alert 8', assetPath: 'assets/audio/alert-8.mp3'),
|
||||
_SoundOption(label: 'Alert 9', assetPath: 'assets/audio/alert-9.mp3'),
|
||||
_SoundOption(label: 'Alert 10', assetPath: 'assets/audio/alert-10.mp3'),
|
||||
];
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_loadSelection();
|
||||
}
|
||||
|
||||
Future<void> _loadSelection() async {
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
final saved = prefs.getString(_prefsKey);
|
||||
setState(() {
|
||||
_selected = (saved != null && saved.isNotEmpty) ? saved : _defaultSound;
|
||||
_loading = false;
|
||||
});
|
||||
}
|
||||
|
||||
Future<void> _saveSelection(BuildContext context, String assetPath) async {
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
await prefs.setString(_prefsKey, assetPath);
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: const Text('Order alert sound updated'),
|
||||
behavior: SnackBarBehavior.floating,
|
||||
margin: const EdgeInsets.all(16),
|
||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
|
||||
duration: const Duration(seconds: 2),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _preview(BuildContext context, String assetPath) async {
|
||||
try {
|
||||
await _player.stop();
|
||||
await _player.play(AssetSource(assetPath.replaceFirst('assets/', '')));
|
||||
// Note: AssetSource expects relative to assets/ root; hence replaceFirst
|
||||
} catch (_) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: Text('Preview unavailable for: $assetPath'),
|
||||
behavior: SnackBarBehavior.floating,
|
||||
margin: const EdgeInsets.all(16),
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
duration: const Duration(seconds: 2),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_player.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
backgroundColor: ColorConstants.primaryColor,
|
||||
centerTitle: true,
|
||||
toolbarHeight: 70, // :small_blue_diamond: increases app bar height
|
||||
leading: IconButton(
|
||||
icon: const Icon(
|
||||
Icons.arrow_back_ios,
|
||||
color: Colors.white,
|
||||
), // :small_blue_diamond: white back arrow
|
||||
onPressed: () {
|
||||
Navigator.pop(context); // goes back to previous screen
|
||||
},
|
||||
),
|
||||
title: const Text(
|
||||
'Orders alert Sound',
|
||||
style: TextStyle(
|
||||
fontSize: 26, // :small_blue_diamond: larger font size
|
||||
color: Colors.white, // :small_blue_diamond: white text
|
||||
fontWeight: FontWeight.bold,
|
||||
letterSpacing: 1.2,
|
||||
),
|
||||
),
|
||||
elevation: 4,
|
||||
),
|
||||
body: _loading
|
||||
? const Center(child: CircularProgressIndicator())
|
||||
: Column(
|
||||
children: [
|
||||
Container(
|
||||
width: double.infinity,
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 16,
|
||||
vertical: 10,
|
||||
),
|
||||
color: Colors.grey.shade200,
|
||||
child: Row(
|
||||
children: [
|
||||
const Icon(Icons.volume_up, size: 20),
|
||||
const SizedBox(width: 8),
|
||||
Expanded(
|
||||
child: Text(
|
||||
'Current: ${_options.firstWhereOrNull((o) => o.assetPath == _selected)?.label ?? 'Unknown'}',
|
||||
style: TextStyle(
|
||||
fontFamily: FontConstants.fontFamily,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
),
|
||||
const Text(
|
||||
'Tap a sound to select',
|
||||
style: TextStyle(fontSize: 12),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
const Divider(height: 1),
|
||||
Expanded(
|
||||
child: ListView.separated(
|
||||
itemCount: _options.length,
|
||||
separatorBuilder: (_, __) => const Divider(height: 1),
|
||||
itemBuilder: (context, index) {
|
||||
final opt = _options[index];
|
||||
final isSelected = _selected == opt.assetPath;
|
||||
return ListTile(
|
||||
title: Text(
|
||||
opt.label,
|
||||
style: TextStyle(
|
||||
fontFamily: FontConstants.fontFamily,
|
||||
),
|
||||
),
|
||||
leading: Radio<String>(
|
||||
value: opt.assetPath,
|
||||
groupValue: _selected,
|
||||
onChanged: (value) {
|
||||
if (value == null) return;
|
||||
setState(() => _selected = value);
|
||||
_saveSelection(context, value);
|
||||
},
|
||||
),
|
||||
trailing: IconButton(
|
||||
icon: const Icon(Icons.play_arrow),
|
||||
onPressed: () => _preview(context, opt.assetPath),
|
||||
),
|
||||
onTap: () {
|
||||
setState(() => _selected = opt.assetPath);
|
||||
_saveSelection(context, opt.assetPath);
|
||||
},
|
||||
subtitle: isSelected
|
||||
? const Text(
|
||||
'Selected',
|
||||
style: TextStyle(fontSize: 12),
|
||||
)
|
||||
: null,
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _SoundOption {
|
||||
final String label;
|
||||
final String assetPath;
|
||||
const _SoundOption({required this.label, required this.assetPath});
|
||||
}
|
||||
280
lib/views/Dashboard/profile/informations/profile.dart
Normal file
280
lib/views/Dashboard/profile/informations/profile.dart
Normal file
@@ -0,0 +1,280 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:nearle/controllers/profile_controller.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
|
||||
import 'package:nearle/views/helpers/constants/Font_constant.dart';
|
||||
|
||||
class Profile extends StatefulWidget {
|
||||
const Profile({super.key});
|
||||
|
||||
@override
|
||||
State<Profile> createState() => _ProfileState();
|
||||
}
|
||||
|
||||
class _ProfileState extends State<Profile> {
|
||||
String _name = '';
|
||||
String _email = '';
|
||||
String _contact = '';
|
||||
String _address = '';
|
||||
final List<Worker> _workers = [];
|
||||
late final ProfileController _profileController =
|
||||
Get.isRegistered<ProfileController>()
|
||||
? Get.find<ProfileController>()
|
||||
: Get.put(ProfileController(), permanent: true);
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_loadProfile();
|
||||
// Keep in sync with controller
|
||||
_workers.addAll([
|
||||
ever(_profileController.userName, (_) => _assignFromController()),
|
||||
ever(_profileController.userEmail, (_) => _assignFromController()),
|
||||
ever(_profileController.userContact, (_) => _assignFromController()),
|
||||
ever(_profileController.userAddress, (_) => _assignFromController()),
|
||||
]);
|
||||
_profileController.loadFromPrefs();
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
for (final worker in _workers) {
|
||||
worker.dispose();
|
||||
}
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
Future<void> _loadProfile() async {
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
setState(() {
|
||||
_name = prefs.getString('user_name') ?? '';
|
||||
_email = prefs.getString('user_email') ?? '';
|
||||
_contact = prefs.getString('contactno') ?? '';
|
||||
_address = prefs.getString('user_address') ?? '';
|
||||
});
|
||||
debugPrint('[PROFILE_DETAILS] Loaded - Name: "$_name", Email: "$_email", Contact: "$_contact"');
|
||||
}
|
||||
|
||||
void _assignFromController() {
|
||||
setState(() {
|
||||
if (_profileController.userName.value.trim().isNotEmpty) {
|
||||
_name = _profileController.userName.value.trim();
|
||||
}
|
||||
if (_profileController.userEmail.value.trim().isNotEmpty) {
|
||||
_email = _profileController.userEmail.value.trim();
|
||||
}
|
||||
if (_profileController.userContact.value.trim().isNotEmpty) {
|
||||
_contact = _profileController.userContact.value.trim();
|
||||
}
|
||||
if (_profileController.userAddress.value.trim().isNotEmpty) {
|
||||
_address = _profileController.userAddress.value.trim();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final size = MediaQuery.of(context).size;
|
||||
final width = size.width;
|
||||
// ignore: unused_local_variable
|
||||
final height = size.height;
|
||||
return Scaffold(
|
||||
backgroundColor: Colors.grey.shade200,
|
||||
body: SafeArea(
|
||||
child: SingleChildScrollView(
|
||||
padding: EdgeInsets.all(width * 0.04),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
// Profile image
|
||||
Center(
|
||||
child: Stack(
|
||||
children: [
|
||||
// White border circle
|
||||
Container(
|
||||
padding: const EdgeInsets.all(4), // border thickness
|
||||
decoration: BoxDecoration(
|
||||
shape: BoxShape.circle,
|
||||
color: Colors.white, // white border
|
||||
),
|
||||
child: CircleAvatar(
|
||||
radius: 60,
|
||||
backgroundColor: Colors.grey.shade400,
|
||||
child: const Icon(
|
||||
Icons.person,
|
||||
size: 60,
|
||||
color: Colors.white,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
const SizedBox(height: 40),
|
||||
|
||||
// Name
|
||||
_buildLabel("Enter name", required: true),
|
||||
_buildTextField(
|
||||
hintText: _name.isNotEmpty ? _name : "EX: Vijayan",
|
||||
value: _name.isNotEmpty ? _name : null,
|
||||
readOnly: true,
|
||||
disabled: true,
|
||||
),
|
||||
|
||||
const SizedBox(height: 16),
|
||||
|
||||
// Contact No
|
||||
_buildLabel("Contact no", required: true),
|
||||
_buildTextField(
|
||||
hintText: _contact.isNotEmpty
|
||||
? "+91 $_contact"
|
||||
: "EX: +91 8838304677",
|
||||
value: _contact.isNotEmpty ? "+91 $_contact" : null,
|
||||
keyboardType: TextInputType.phone,
|
||||
readOnly: true,
|
||||
disabled: true,
|
||||
),
|
||||
|
||||
const SizedBox(height: 16),
|
||||
|
||||
// Email Id
|
||||
_buildLabel("Email Id"),
|
||||
_buildTextField(
|
||||
hintText: _email.isNotEmpty ? _email : "EX: gmail@gmail.com",
|
||||
value: _email.isNotEmpty ? _email : null,
|
||||
keyboardType: TextInputType.emailAddress,
|
||||
readOnly: true,
|
||||
disabled: true,
|
||||
),
|
||||
|
||||
const SizedBox(height: 16),
|
||||
|
||||
// Location
|
||||
_buildLabel("Address"),
|
||||
_buildTextField(hintText: _address.isNotEmpty ? _address : " EX: R.s puram", value: _address.isNotEmpty ? _address : null, readOnly: true, disabled: true),
|
||||
|
||||
const SizedBox(height: 40),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
bottomNavigationBar: SafeArea(
|
||||
child: Padding(
|
||||
padding: EdgeInsets.all(width * 0.04),
|
||||
child: SizedBox(
|
||||
width: double.infinity,
|
||||
height: 55,
|
||||
child: ElevatedButton(
|
||||
onPressed: _handleBackNavigation,
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: const Color(0xFF5C1D8D), // Purple button color
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
),
|
||||
child: Text(
|
||||
"Back",
|
||||
style: TextStyle(
|
||||
fontSize: 21,
|
||||
color: Colors.white,
|
||||
fontWeight: FontWeight.bold,
|
||||
fontFamily: FontConstants.fontFamily,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _handleBackNavigation() async {
|
||||
final navigator = Navigator.of(context);
|
||||
if (navigator.canPop()) {
|
||||
navigator.pop();
|
||||
return;
|
||||
}
|
||||
final rootNavigator = Get.key.currentState;
|
||||
if (rootNavigator != null && rootNavigator.canPop()) {
|
||||
rootNavigator.pop();
|
||||
return;
|
||||
}
|
||||
if (Get.isOverlaysOpen) {
|
||||
Get.back(closeOverlays: true);
|
||||
return;
|
||||
}
|
||||
Get.back();
|
||||
}
|
||||
|
||||
// Text label widget
|
||||
Widget _buildLabel(String text, {bool required = false}) {
|
||||
return Align(
|
||||
alignment: Alignment.centerLeft,
|
||||
child: RichText(
|
||||
text: TextSpan(
|
||||
text: text,
|
||||
style: TextStyle(
|
||||
fontSize: 20,
|
||||
color: Colors.black,
|
||||
fontWeight: FontWeight.bold,
|
||||
fontFamily: FontConstants.fontFamily,
|
||||
),
|
||||
children: required
|
||||
? const [
|
||||
TextSpan(
|
||||
text: " *",
|
||||
style: TextStyle(color: Colors.red),
|
||||
),
|
||||
]
|
||||
: [],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
// Reusable TextField
|
||||
Widget _buildTextField({
|
||||
required String hintText,
|
||||
String? value,
|
||||
TextInputType keyboardType = TextInputType.text,
|
||||
required bool readOnly,
|
||||
bool disabled = false,
|
||||
}) {
|
||||
return Container(
|
||||
margin: const EdgeInsets.only(top: 6),
|
||||
child: SizedBox(
|
||||
height: 55,
|
||||
width: 350,
|
||||
child: TextFormField(
|
||||
keyboardType: keyboardType,
|
||||
readOnly: readOnly,
|
||||
enabled: !disabled,
|
||||
enableInteractiveSelection: false,
|
||||
initialValue: value,
|
||||
decoration: InputDecoration(
|
||||
hintText: hintText,
|
||||
filled: true,
|
||||
fillColor: Colors.white,
|
||||
contentPadding: const EdgeInsets.symmetric(
|
||||
horizontal: 16,
|
||||
vertical: 14,
|
||||
),
|
||||
border: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
borderSide: BorderSide(color: Colors.grey.shade300),
|
||||
),
|
||||
enabledBorder: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
borderSide: BorderSide(color: Colors.grey.shade300),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
||||
import 'package:nearle/views/helpers/constants/Font_constant.dart';
|
||||
import 'package:nearle/controllers/rewards_controller.dart';
|
||||
import 'package:nearle/views/Dashboard/profile/rewards_card.dart';
|
||||
|
||||
class RiderRewardsPage extends StatelessWidget {
|
||||
const RiderRewardsPage({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final RewardsController rewardsController = Get.isRegistered<RewardsController>()
|
||||
? Get.find<RewardsController>()
|
||||
: Get.put(RewardsController());
|
||||
|
||||
return Scaffold(
|
||||
backgroundColor: Colors.grey.shade100,
|
||||
appBar: AppBar(
|
||||
title: Text(
|
||||
"REWARDS",
|
||||
style: TextStyle(
|
||||
color: Colors.black,
|
||||
fontSize: FontConstants.xxxLarge(context).sp,
|
||||
fontWeight: FontWeight.bold,
|
||||
fontFamily: FontConstants.fontFamily,
|
||||
),
|
||||
),
|
||||
backgroundColor: Colors.grey.shade100,
|
||||
elevation: 0,
|
||||
leading: IconButton(
|
||||
icon: const Icon(Icons.arrow_back, color: Colors.black),
|
||||
onPressed: () => Navigator.pop(context),
|
||||
),
|
||||
),
|
||||
body: SafeArea(
|
||||
child: SingleChildScrollView(
|
||||
physics: const BouncingScrollPhysics(),
|
||||
padding: EdgeInsets.symmetric(horizontal: 16.w, vertical: 10.h),
|
||||
child: RewardsCard(
|
||||
controller: rewardsController,
|
||||
showFullDetails: true,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
169
lib/views/Dashboard/profile/informations/saved_address.dart
Normal file
169
lib/views/Dashboard/profile/informations/saved_address.dart
Normal file
@@ -0,0 +1,169 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
import 'package:nearle/views/helpers/constants/Font_constant.dart';
|
||||
import 'package:nearle/views/helpers/constants/Colorconstants.dart';
|
||||
|
||||
class SavedAddressPage extends StatefulWidget {
|
||||
const SavedAddressPage({super.key});
|
||||
|
||||
@override
|
||||
State<SavedAddressPage> createState() => _SavedAddressPageState();
|
||||
}
|
||||
|
||||
class _SavedAddressPageState extends State<SavedAddressPage> {
|
||||
final TextEditingController _addressController = TextEditingController();
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_loadAddress();
|
||||
}
|
||||
|
||||
Future<void> _loadAddress() async {
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
final address = (prefs.getString('user_address') ?? '').trim();
|
||||
_addressController.text = address;
|
||||
setState(() {});
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_addressController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final size = MediaQuery.of(context).size;
|
||||
final addressText = _addressController.text.trim();
|
||||
|
||||
return Scaffold(
|
||||
backgroundColor: const Color(0xFFF8F9FB),
|
||||
appBar: AppBar(
|
||||
backgroundColor: ColorConstants.primaryColor,
|
||||
centerTitle: true,
|
||||
toolbarHeight: 70,
|
||||
elevation: 3,
|
||||
leading: IconButton(
|
||||
icon: const Icon(Icons.arrow_back_ios_new_rounded, color: Colors.white),
|
||||
onPressed: () => Navigator.pop(context),
|
||||
),
|
||||
title: const Text(
|
||||
'Saved Address',
|
||||
style: TextStyle(
|
||||
fontSize: 24,
|
||||
color: Colors.white,
|
||||
fontWeight: FontWeight.bold,
|
||||
letterSpacing: 1.1,
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
body: SafeArea(
|
||||
child: Padding(
|
||||
padding: EdgeInsets.symmetric(horizontal: size.width * 0.05, vertical: 20),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
// 🏠 Header Section
|
||||
Container(
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white,
|
||||
borderRadius: BorderRadius.circular(14),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: Colors.grey.withOpacity(0.15),
|
||||
spreadRadius: 1,
|
||||
blurRadius: 8,
|
||||
offset: const Offset(0, 3),
|
||||
),
|
||||
],
|
||||
),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Container(
|
||||
decoration: BoxDecoration(
|
||||
color: ColorConstants.primaryColor.withOpacity(0.1),
|
||||
shape: BoxShape.circle,
|
||||
),
|
||||
padding: const EdgeInsets.all(10),
|
||||
child: Icon(
|
||||
Icons.location_on_rounded,
|
||||
color: ColorConstants.primaryColor,
|
||||
size: 26,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
'Current Address',
|
||||
style: TextStyle(
|
||||
fontSize: 17,
|
||||
fontWeight: FontWeight.w700,
|
||||
fontFamily: FontConstants.fontFamily,
|
||||
color: Colors.black87,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Text(
|
||||
addressText.isNotEmpty ? addressText : 'No address saved yet.',
|
||||
style: TextStyle(
|
||||
fontSize: 15,
|
||||
fontFamily: FontConstants.fontFamily,
|
||||
color: Colors.grey.shade700,
|
||||
height: 1.4,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
const SizedBox(height: 28),
|
||||
|
||||
// ✨ Info Section
|
||||
Container(
|
||||
padding: const EdgeInsets.all(16),
|
||||
decoration: BoxDecoration(
|
||||
color: ColorConstants.primaryColor.withOpacity(0.05),
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
Icon(Icons.info_outline_rounded,
|
||||
color: ColorConstants.primaryColor, size: 24),
|
||||
const SizedBox(width: 12),
|
||||
Expanded(
|
||||
child: Text(
|
||||
'Your saved address is used for deliveries, pickups, and nearby service accuracy.',
|
||||
style: TextStyle(
|
||||
fontSize: 14,
|
||||
color: Colors.grey.shade800,
|
||||
fontFamily: FontConstants.fontFamily,
|
||||
height: 1.4,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
const Spacer(),
|
||||
|
||||
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
503
lib/views/Dashboard/profile/informations/support_ticket.dart
Normal file
503
lib/views/Dashboard/profile/informations/support_ticket.dart
Normal file
@@ -0,0 +1,503 @@
|
||||
import 'dart:io';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:image_picker/image_picker.dart';
|
||||
import 'package:nearle/controllers/support_ticket.dart';
|
||||
import 'package:nearle/views/helpers/constants/Colorconstants.dart';
|
||||
import 'package:nearle/views/helpers/constants/Font_constant.dart';
|
||||
|
||||
class SupportTicket extends StatefulWidget {
|
||||
const SupportTicket({super.key});
|
||||
|
||||
@override
|
||||
State<SupportTicket> createState() => _SupportTicketState();
|
||||
}
|
||||
|
||||
class _SupportTicketState extends State<SupportTicket>
|
||||
with SingleTickerProviderStateMixin {
|
||||
late final TabController _tabController;
|
||||
|
||||
// Form
|
||||
final _formKey = GlobalKey<FormState>();
|
||||
final _subjectCtrl = TextEditingController();
|
||||
final _messageCtrl = TextEditingController();
|
||||
String _category = 'Account';
|
||||
String _priority = 'Medium';
|
||||
int _attachmentCount = 0;
|
||||
|
||||
// Image
|
||||
final ImagePicker _picker = ImagePicker();
|
||||
final List<XFile> _attachments = [];
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_tabController = TabController(length: 2, vsync: this);
|
||||
_tabController.addListener(() => setState(() {}));
|
||||
Get.put(SupportTicketController());
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_subjectCtrl.dispose();
|
||||
_messageCtrl.dispose();
|
||||
_tabController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return SafeArea(
|
||||
child: Scaffold(
|
||||
appBar: AppBar(
|
||||
backgroundColor: ColorConstants.primaryColor,
|
||||
centerTitle: true,
|
||||
toolbarHeight: 70,
|
||||
leading: IconButton(
|
||||
icon: const Icon(Icons.arrow_back_ios, color: Colors.white),
|
||||
onPressed: () => Navigator.pop(context),
|
||||
),
|
||||
title: Text(
|
||||
'Support Ticket',
|
||||
style: TextStyle(
|
||||
fontSize: 26,
|
||||
color: Colors.white,
|
||||
fontWeight: FontWeight.bold,
|
||||
letterSpacing: 1.2,
|
||||
fontFamily: FontConstants.fontFamily,
|
||||
),
|
||||
),
|
||||
elevation: 4,
|
||||
),
|
||||
body: Column(
|
||||
children: [
|
||||
Container(
|
||||
color: Colors.white,
|
||||
child: TabBar(
|
||||
controller: _tabController,
|
||||
indicatorColor: ColorConstants.primaryColor,
|
||||
labelColor: ColorConstants.primaryColor,
|
||||
unselectedLabelColor: Colors.grey,
|
||||
labelStyle: TextStyle(
|
||||
fontFamily: FontConstants.fontFamily,
|
||||
fontWeight: FontWeight.bold,
|
||||
fontSize: 16,
|
||||
),
|
||||
tabs: [
|
||||
Tab(
|
||||
child: Text(
|
||||
'Create Tickets',
|
||||
style: TextStyle(fontFamily: FontConstants.fontFamily, fontSize: 20),
|
||||
),
|
||||
),
|
||||
Tab(
|
||||
child: Text(
|
||||
'My Tickets',
|
||||
style: TextStyle(fontFamily: FontConstants.fontFamily, fontSize: 20),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: TabBarView(
|
||||
controller: _tabController,
|
||||
children: [_buildCreateForm(), _buildTicketsList()],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
bottomNavigationBar: _tabController.index == 0
|
||||
? Padding(
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: SizedBox(
|
||||
height: 55,
|
||||
width: double.infinity,
|
||||
child: ElevatedButton(
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: ColorConstants.primaryColor,
|
||||
foregroundColor: Colors.white,
|
||||
padding: const EdgeInsets.symmetric(vertical: 14),
|
||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(10)),
|
||||
),
|
||||
onPressed: _submitTicket,
|
||||
child: Text(
|
||||
'Submit Ticket',
|
||||
style: TextStyle(
|
||||
fontSize: 20,
|
||||
fontWeight: FontWeight.bold,
|
||||
fontFamily: FontConstants.fontFamily,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
: null,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
// ===============================================
|
||||
// CREATE FORM
|
||||
// ===============================================
|
||||
Widget _buildCreateForm() {
|
||||
final controller = Get.find<SupportTicketController>();
|
||||
|
||||
return SingleChildScrollView(
|
||||
padding: const EdgeInsets.fromLTRB(16, 16, 16, 24),
|
||||
child: Form(
|
||||
key: _formKey,
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
'Create a new support ticket',
|
||||
style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold, fontFamily: FontConstants.fontFamily),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Text(
|
||||
'Tell us what went wrong. We\'ll get back to you as soon as possible.',
|
||||
style: TextStyle(fontSize: 18, color: Colors.grey.shade700, height: 1.4, fontFamily: FontConstants.fontFamily),
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
|
||||
// Category
|
||||
Text('Category', style: _labelStyle()),
|
||||
const SizedBox(height: 8),
|
||||
DropdownButtonFormField<String>(
|
||||
// ignore: deprecated_member_use
|
||||
value: _category,
|
||||
items: ['Account', 'Orders', 'Payments', 'App issue', 'Other']
|
||||
.map((e) => DropdownMenuItem(value: e, child: Text(e, style: TextStyle(fontFamily: FontConstants.fontFamily))))
|
||||
.toList(),
|
||||
onChanged: (v) => setState(() => _category = v ?? _category),
|
||||
decoration: _inputDecoration(),
|
||||
),
|
||||
|
||||
const SizedBox(height: 16),
|
||||
|
||||
// Priority
|
||||
Text('Priority', style: _labelStyle()),
|
||||
const SizedBox(height: 8),
|
||||
Wrap(
|
||||
spacing: 8,
|
||||
children: ['Low', 'Medium', 'High'].map((p) {
|
||||
final selected = _priority == p;
|
||||
return ChoiceChip(
|
||||
label: Text(p, style: TextStyle(fontFamily: FontConstants.fontFamily, fontWeight: FontWeight.w600, fontSize: 16)),
|
||||
selected: selected,
|
||||
selectedColor: ColorConstants.primaryColor.withOpacity(0.15),
|
||||
labelStyle: TextStyle(
|
||||
color: selected ? ColorConstants.primaryColor : Colors.black87,
|
||||
fontWeight: FontWeight.w600,
|
||||
fontFamily: FontConstants.fontFamily,
|
||||
),
|
||||
onSelected: (_) => setState(() => _priority = p),
|
||||
);
|
||||
}).toList(),
|
||||
),
|
||||
|
||||
const SizedBox(height: 16),
|
||||
|
||||
// Subject
|
||||
Text('Subject', style: _labelStyle()),
|
||||
const SizedBox(height: 8),
|
||||
TextFormField(
|
||||
controller: _subjectCtrl,
|
||||
decoration: _inputDecoration(hint: 'Type Something'),
|
||||
style: TextStyle(fontFamily: FontConstants.fontFamily),
|
||||
validator: (v) => (v == null || v.trim().isEmpty) ? 'Please enter a subject' : null,
|
||||
),
|
||||
|
||||
const SizedBox(height: 16),
|
||||
|
||||
// Message
|
||||
Text('Describe the issue', style: _labelStyle()),
|
||||
const SizedBox(height: 8),
|
||||
TextFormField(
|
||||
controller: _messageCtrl,
|
||||
minLines: 5,
|
||||
maxLines: 8,
|
||||
decoration: _inputDecoration(hint: 'Type Something'),
|
||||
style: TextStyle(fontFamily: FontConstants.fontFamily),
|
||||
validator: (v) => (v == null || v.trim().isEmpty) ? 'Please enter your message' : null,
|
||||
),
|
||||
|
||||
const SizedBox(height: 16),
|
||||
|
||||
// Attachments
|
||||
Row(
|
||||
children: [
|
||||
OutlinedButton.icon(
|
||||
onPressed: _addAttachment,
|
||||
icon: const Icon(Icons.attach_file),
|
||||
label: Text('Add screenshot', style: TextStyle(fontFamily: FontConstants.fontFamily)),
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
if (_attachmentCount > 0)
|
||||
Text('$_attachmentCount attached', style: TextStyle(fontWeight: FontWeight.w600, fontFamily: FontConstants.fontFamily)),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
if (_attachments.isNotEmpty)
|
||||
Wrap(
|
||||
spacing: 8,
|
||||
runSpacing: 8,
|
||||
children: _attachments.asMap().entries.map((entry) {
|
||||
final idx = entry.key;
|
||||
final file = entry.value;
|
||||
return Stack(
|
||||
clipBehavior: Clip.none,
|
||||
children: [
|
||||
ClipRRect(
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
child: Image.file(File(file.path), width: 80, height: 80, fit: BoxFit.cover),
|
||||
),
|
||||
Positioned(
|
||||
top: -8,
|
||||
right: -8,
|
||||
child: InkWell(
|
||||
onTap: () => setState(() {
|
||||
_attachments.removeAt(idx);
|
||||
_attachmentCount = _attachments.length;
|
||||
}),
|
||||
child: Container(
|
||||
width: 22,
|
||||
height: 22,
|
||||
decoration: BoxDecoration(color: Colors.black.withOpacity(0.6), shape: BoxShape.circle),
|
||||
child: const Icon(Icons.close, size: 16, color: Colors.white),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}).toList(),
|
||||
),
|
||||
|
||||
// Submit loading
|
||||
Obx(() => controller.isSubmitting.value
|
||||
? const Padding(
|
||||
padding: EdgeInsets.symmetric(vertical: 16),
|
||||
child: Center(child: CircularProgressIndicator()),
|
||||
)
|
||||
: const SizedBox.shrink()),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
// ===============================================
|
||||
// MY TICKETS LIST
|
||||
// ===============================================
|
||||
Widget _buildTicketsList() {
|
||||
final controller = Get.find<SupportTicketController>();
|
||||
|
||||
return Obx(() {
|
||||
if (controller.isLoading.value) {
|
||||
return const Center(child: CircularProgressIndicator());
|
||||
}
|
||||
|
||||
if (controller.errorMessage.value.isNotEmpty) {
|
||||
return Center(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(24),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
const Icon(Icons.error_outline, size: 48, color: Colors.red),
|
||||
const SizedBox(height: 12),
|
||||
Text('Failed to load tickets', style: TextStyle(fontFamily: FontConstants.fontFamily, fontWeight: FontWeight.w600)),
|
||||
const SizedBox(height: 8),
|
||||
Text(controller.errorMessage.value, textAlign: TextAlign.center, style: TextStyle(color: Colors.grey.shade600, fontFamily: FontConstants.fontFamily)),
|
||||
const SizedBox(height: 16),
|
||||
ElevatedButton(onPressed: controller.fetchTickets, child: const Text('Retry')),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
if (controller.tickets.isEmpty) {
|
||||
return _buildEmptyState();
|
||||
}
|
||||
|
||||
return ListView.separated(
|
||||
padding: const EdgeInsets.fromLTRB(16, 16, 16, 24),
|
||||
itemCount: controller.tickets.length,
|
||||
separatorBuilder: (_, __) => const SizedBox(height: 10),
|
||||
itemBuilder: (context, i) {
|
||||
final t = controller.tickets[i];
|
||||
final statusColor = _getPriorityColor(t.priority);
|
||||
|
||||
return Card(
|
||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
|
||||
elevation: 2,
|
||||
child: ListTile(
|
||||
contentPadding: const EdgeInsets.all(12),
|
||||
title: Text(t.subject, style: TextStyle(fontWeight: FontWeight.w700, fontFamily: FontConstants.fontFamily)),
|
||||
subtitle: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
const SizedBox(height: 4),
|
||||
Text('Category: ${t.category} • Priority: ${t.priority}', style: TextStyle(fontFamily: FontConstants.fontFamily)),
|
||||
const SizedBox(height: 4),
|
||||
Text('Created: ${_formatDate(t.created)}', style: TextStyle(fontFamily: FontConstants.fontFamily)),
|
||||
],
|
||||
),
|
||||
trailing: Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 6),
|
||||
decoration: BoxDecoration(color: statusColor.withOpacity(0.15), borderRadius: BorderRadius.circular(20)),
|
||||
child: Text(t.priority, style: TextStyle(color: statusColor, fontWeight: FontWeight.bold, fontFamily: FontConstants.fontFamily)),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
Widget _buildEmptyState() {
|
||||
return Center(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(24.0),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
const Icon(Icons.support_agent, size: 48, color: Colors.grey),
|
||||
const SizedBox(height: 12),
|
||||
Text('No tickets yet', style: TextStyle(fontSize: 18, fontWeight: FontWeight.w700, fontFamily: FontConstants.fontFamily)),
|
||||
const SizedBox(height: 8),
|
||||
Text('Create your first ticket from the Create tab.', style: TextStyle(color: Colors.grey.shade700, fontFamily: FontConstants.fontFamily)),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
// ===============================================
|
||||
// HELPERS
|
||||
// ===============================================
|
||||
TextStyle _labelStyle() => TextStyle(fontSize: 20, fontWeight: FontWeight.bold, fontFamily: FontConstants.fontFamily);
|
||||
|
||||
InputDecoration _inputDecoration({String? hint}) {
|
||||
return InputDecoration(
|
||||
hintText: hint,
|
||||
filled: true,
|
||||
fillColor: Colors.white,
|
||||
contentPadding: const EdgeInsets.symmetric(vertical: 12, horizontal: 12),
|
||||
border: OutlineInputBorder(borderRadius: BorderRadius.circular(10), borderSide: BorderSide(color: Colors.grey.shade300)),
|
||||
enabledBorder: OutlineInputBorder(borderRadius: BorderRadius.circular(10), borderSide: BorderSide(color: Colors.grey.shade300)),
|
||||
focusedBorder: OutlineInputBorder(borderRadius: BorderRadius.circular(10), borderSide: BorderSide(color: ColorConstants.primaryColor, width: 1.5)),
|
||||
hintStyle: TextStyle(fontFamily: FontConstants.fontFamily),
|
||||
);
|
||||
}
|
||||
|
||||
Color _getPriorityColor(String priority) {
|
||||
return switch (priority.toLowerCase()) {
|
||||
'high' => Colors.red,
|
||||
'medium' => Colors.orange,
|
||||
'low' => Colors.green,
|
||||
_ => Colors.grey,
|
||||
};
|
||||
}
|
||||
|
||||
String _formatDate(DateTime date) {
|
||||
return '${date.day}/${date.month}/${date.year} ${date.hour}:${date.minute.toString().padLeft(2, '0')}';
|
||||
}
|
||||
|
||||
// ===============================================
|
||||
// IMAGE PICKER
|
||||
// ===============================================
|
||||
Future<void> _addAttachment() async {
|
||||
final source = await showModalBottomSheet<ImageSource>(
|
||||
context: context,
|
||||
shape: const RoundedRectangleBorder(borderRadius: BorderRadius.vertical(top: Radius.circular(16))),
|
||||
builder: (ctx) => SafeArea(
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
ListTile(
|
||||
leading: const Icon(Icons.photo_library),
|
||||
title: Text('Gallery', style: TextStyle(fontFamily: FontConstants.fontFamily)),
|
||||
onTap: () => Navigator.pop(ctx, ImageSource.gallery),
|
||||
),
|
||||
ListTile(
|
||||
leading: const Icon(Icons.camera_alt),
|
||||
title: Text('Camera', style: TextStyle(fontFamily: FontConstants.fontFamily)),
|
||||
onTap: () => Navigator.pop(ctx, ImageSource.camera),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
if (source == null) return;
|
||||
|
||||
try {
|
||||
if (source == ImageSource.gallery) {
|
||||
final multi = await _picker.pickMultiImage(imageQuality: 85);
|
||||
if (multi.isNotEmpty) {
|
||||
setState(() => _attachments.addAll(multi));
|
||||
} else {
|
||||
final one = await _picker.pickImage(source: ImageSource.gallery, imageQuality: 85);
|
||||
if (one != null) setState(() => _attachments.add(one));
|
||||
}
|
||||
} else {
|
||||
final captured = await _picker.pickImage(source: ImageSource.camera, imageQuality: 85);
|
||||
if (captured != null) setState(() => _attachments.add(captured));
|
||||
}
|
||||
} catch (_) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(content: Text('Failed to pick image', style: TextStyle(fontFamily: FontConstants.fontFamily))),
|
||||
);
|
||||
}
|
||||
|
||||
setState(() => _attachmentCount = _attachments.length);
|
||||
}
|
||||
|
||||
// ===============================================
|
||||
// SUBMIT TICKET
|
||||
// ===============================================
|
||||
Future<void> _submitTicket() async {
|
||||
if (!(_formKey.currentState?.validate() ?? false)) return;
|
||||
|
||||
final controller = Get.find<SupportTicketController>();
|
||||
final success = await controller.createTicket(
|
||||
userid: 1242,
|
||||
category: _category,
|
||||
priority: _priority,
|
||||
subject: _subjectCtrl.text.trim(),
|
||||
issue: _messageCtrl.text.trim(),
|
||||
attachments: _attachments.isEmpty ? null : _attachments,
|
||||
);
|
||||
|
||||
if (success) {
|
||||
_subjectCtrl.clear();
|
||||
_messageCtrl.clear();
|
||||
_attachments.clear();
|
||||
_attachmentCount = 0;
|
||||
setState(() {});
|
||||
|
||||
_tabController.animateTo(1);
|
||||
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (_) => AlertDialog(
|
||||
title: const Text('Ticket Submitted!'),
|
||||
content: const Text('Your ticket has been created and saved. Our team will get back to you soon.'),
|
||||
actions: [
|
||||
TextButton(onPressed: () => Navigator.pop(context), child: const Text('OK')),
|
||||
],
|
||||
),
|
||||
);
|
||||
} else {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: Text('Failed to submit ticket: ${controller.errorMessage.value}'),
|
||||
backgroundColor: Colors.red,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
100
lib/views/Dashboard/profile/informations/terms_condition.dart
Normal file
100
lib/views/Dashboard/profile/informations/terms_condition.dart
Normal file
@@ -0,0 +1,100 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:nearle/views/helpers/constants/Colorconstants.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:webview_flutter/webview_flutter.dart';
|
||||
|
||||
// ===== Controller =====
|
||||
class TermsController extends GetxController {
|
||||
WebViewController? webViewController;
|
||||
var isLoading = true.obs;
|
||||
|
||||
@override
|
||||
void onInit() {
|
||||
super.onInit();
|
||||
initializeWebView();
|
||||
}
|
||||
|
||||
void initializeWebView() {
|
||||
webViewController = WebViewController()
|
||||
..setJavaScriptMode(JavaScriptMode.unrestricted)
|
||||
..setBackgroundColor(const Color(0x00000000))
|
||||
..setNavigationDelegate(
|
||||
NavigationDelegate(
|
||||
onPageStarted: (url) {
|
||||
isLoading.value = true;
|
||||
print('Started loading: $url');
|
||||
},
|
||||
onPageFinished: (url) {
|
||||
isLoading.value = false;
|
||||
print('Finished loading: $url');
|
||||
},
|
||||
onWebResourceError: (error) {
|
||||
isLoading.value = false;
|
||||
print('WebView error: ${error.description}');
|
||||
},
|
||||
),
|
||||
);
|
||||
loadTermsUrl();
|
||||
}
|
||||
|
||||
Future<void> loadTermsUrl() async {
|
||||
if (webViewController != null) {
|
||||
try {
|
||||
await webViewController!.loadRequest(
|
||||
Uri.parse('https://nearle.in/terms'),
|
||||
);
|
||||
} catch (e) {
|
||||
print('Error loading URL: $e');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ===== Page =====
|
||||
class TermsCondition extends StatelessWidget {
|
||||
const TermsCondition({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final controller = Get.put(TermsController());
|
||||
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
backgroundColor: ColorConstants.primaryColor,
|
||||
centerTitle: true,
|
||||
toolbarHeight: 70,
|
||||
leading: IconButton(
|
||||
icon: const Icon(
|
||||
Icons.arrow_back_ios,
|
||||
color: Colors.white,
|
||||
),
|
||||
onPressed: () {
|
||||
Navigator.pop(context);
|
||||
},
|
||||
),
|
||||
title: const Text(
|
||||
'Terms & Conditions',
|
||||
style: TextStyle(
|
||||
fontSize: 26,
|
||||
color: Colors.white,
|
||||
fontWeight: FontWeight.bold,
|
||||
letterSpacing: 1.2,
|
||||
),
|
||||
),
|
||||
elevation: 4,
|
||||
),
|
||||
body: SafeArea(
|
||||
child: Obx(() {
|
||||
final wvc = controller.webViewController;
|
||||
return Stack(
|
||||
children: [
|
||||
if (wvc != null) WebViewWidget(controller: wvc),
|
||||
if (controller.isLoading.value)
|
||||
const LinearProgressIndicator(minHeight: 2),
|
||||
],
|
||||
);
|
||||
}),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
474
lib/views/Dashboard/profile/rewards_card.dart
Normal file
474
lib/views/Dashboard/profile/rewards_card.dart
Normal file
@@ -0,0 +1,474 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:nearle/controllers/rewards_controller.dart';
|
||||
import 'package:nearle/views/helpers/constants/Font_constant.dart';
|
||||
import 'package:nearle/views/Dashboard/profile/informations/rider_rewards_page.dart';
|
||||
|
||||
class RewardsCard extends StatelessWidget {
|
||||
final RewardsController controller;
|
||||
final bool showFullDetails;
|
||||
|
||||
const RewardsCard({
|
||||
super.key,
|
||||
required this.controller,
|
||||
this.showFullDetails = false,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
// 1. Original Rewards Card (The Gradient Card)
|
||||
GestureDetector(
|
||||
onTap: () {
|
||||
if (!showFullDetails) {
|
||||
Get.to(() => const RiderRewardsPage());
|
||||
}
|
||||
},
|
||||
child: Container(
|
||||
width: double.infinity,
|
||||
padding: EdgeInsets.all(16.r),
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(20.r),
|
||||
gradient: const LinearGradient(
|
||||
colors: [
|
||||
Color(0xFF2C3E50), // Dark blue/grey
|
||||
Color(0xFF4CA1AF), // Tealish
|
||||
],
|
||||
begin: Alignment.topLeft,
|
||||
end: Alignment.bottomRight,
|
||||
),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: Colors.black.withOpacity(0.1),
|
||||
blurRadius: 10,
|
||||
offset: const Offset(0, 5),
|
||||
),
|
||||
],
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
"Nearle Rewards",
|
||||
style: TextStyle(
|
||||
fontSize: 22.sp,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Colors.white,
|
||||
fontFamily: FontConstants.fontFamily,
|
||||
),
|
||||
),
|
||||
SizedBox(height: 4.h),
|
||||
Text(
|
||||
"Ride more to get more rewards 🚴",
|
||||
style: TextStyle(
|
||||
fontSize: 14.sp,
|
||||
color: Colors.white70,
|
||||
fontFamily: FontConstants.fontFamily,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
Container(
|
||||
padding: EdgeInsets.symmetric(horizontal: 12.w, vertical: 6.h),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white.withOpacity(0.2),
|
||||
borderRadius: BorderRadius.circular(20.r),
|
||||
border: Border.all(color: Colors.white30, width: 1),
|
||||
),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Icon(
|
||||
Icons.stars_rounded, // Coin-like icon
|
||||
color: Colors.amberAccent,
|
||||
size: 24.sp,
|
||||
),
|
||||
SizedBox(width: 8.w),
|
||||
Obx(() {
|
||||
return Text(
|
||||
controller.isLoading.value
|
||||
? "..."
|
||||
: "${controller.totalPoints.value}",
|
||||
style: TextStyle(
|
||||
fontSize: 24.sp,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Colors.amberAccent,
|
||||
fontFamily: FontConstants.fontFamily,
|
||||
),
|
||||
);
|
||||
}),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
SizedBox(height: 16.h),
|
||||
Container(
|
||||
padding: EdgeInsets.all(12.r),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white.withOpacity(0.1),
|
||||
borderRadius: BorderRadius.circular(12.r),
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
"Keep riding correctly to get more points!",
|
||||
style: TextStyle(
|
||||
fontSize: 16.sp,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: Colors.white,
|
||||
fontFamily: FontConstants.fontFamily,
|
||||
),
|
||||
),
|
||||
SizedBox(height: 4.h),
|
||||
Text(
|
||||
"Earn 100 points to unlock new rewards",
|
||||
style: TextStyle(
|
||||
fontSize: 14.sp,
|
||||
color: Colors.white70,
|
||||
fontFamily: FontConstants.fontFamily,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
Icon(
|
||||
Icons.emoji_events,
|
||||
color: Colors.amber,
|
||||
size: 32.sp,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
if (showFullDetails) ...[
|
||||
SizedBox(height: 24.h),
|
||||
|
||||
// 2. Surprise Gift Section
|
||||
_buildSurpriseGiftCard(),
|
||||
|
||||
SizedBox(height: 24.h),
|
||||
|
||||
// 3. The 4 Cards Section
|
||||
Text(
|
||||
"Redeem Your Points",
|
||||
style: TextStyle(
|
||||
fontSize: 22.sp,
|
||||
fontWeight: FontWeight.bold,
|
||||
fontFamily: FontConstants.fontFamily,
|
||||
color: Colors.black87,
|
||||
),
|
||||
),
|
||||
SizedBox(height: 16.h),
|
||||
|
||||
// Card 1: Data Recharge
|
||||
_buildRewardOptionCard(
|
||||
title: "Data Recharge",
|
||||
subtitle: "Get free data for 1 month",
|
||||
points: "300 Points",
|
||||
icon: Icons.wifi,
|
||||
color1: const Color(0xFF11998e),
|
||||
color2: const Color(0xFF38ef7d),
|
||||
),
|
||||
SizedBox(height: 16.h),
|
||||
|
||||
// Card 2: Bonus Fuel
|
||||
_buildRewardOptionCard(
|
||||
title: "Bonus Fuel",
|
||||
subtitle: "Fuel support for your vehicle",
|
||||
points: "600 Points",
|
||||
icon: Icons.local_gas_station,
|
||||
color1: const Color(0xFFFF5F6D),
|
||||
color2: const Color(0xFFFFC371),
|
||||
),
|
||||
SizedBox(height: 16.h),
|
||||
|
||||
// Card 3: Gadgets
|
||||
_buildRewardOptionCard(
|
||||
title: "Gadgets Support",
|
||||
subtitle: "Powerbank or New Mobile support",
|
||||
points: "1000 - 1500 Points",
|
||||
icon: Icons.devices_other,
|
||||
color1: const Color(0xFF2193b0),
|
||||
color2: const Color(0xFF6dd5ed),
|
||||
),
|
||||
SizedBox(height: 16.h),
|
||||
|
||||
// Card 4: Vehicle Support
|
||||
_buildRewardOptionCard(
|
||||
title: "Vehicle Support",
|
||||
subtitle: "New vehicle or 50% loan support",
|
||||
points: "2000 Bonus Points",
|
||||
description: "Earn 2000 bonus without any loses in bonus point",
|
||||
icon: Icons.motorcycle,
|
||||
color1: const Color(0xFF8E2DE2),
|
||||
color2: const Color(0xFF4A00E0),
|
||||
isPremium: true,
|
||||
),
|
||||
|
||||
SizedBox(height: 30.h),
|
||||
|
||||
// 4. Bottom Warning / Info Section
|
||||
Container(
|
||||
padding: EdgeInsets.all(16.r),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.red.shade50,
|
||||
borderRadius: BorderRadius.circular(12.r),
|
||||
border: Border.all(color: Colors.red.shade200),
|
||||
),
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Icon(Icons.info_outline, color: Colors.red.shade700, size: 24.sp),
|
||||
SizedBox(width: 12.w),
|
||||
Expanded(
|
||||
child: Text(
|
||||
"Note: If you miss any deliveries or if requirements are not met for each delivery, negative bonus points will affect your board.",
|
||||
style: TextStyle(
|
||||
fontSize: 15.sp,
|
||||
color: Colors.red.shade900,
|
||||
fontFamily: FontConstants.fontFamily,
|
||||
height: 1.4,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildSurpriseGiftCard() {
|
||||
return Container(
|
||||
width: double.infinity,
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white,
|
||||
borderRadius: BorderRadius.circular(16.r),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: Colors.purple.withOpacity(0.1),
|
||||
blurRadius: 15,
|
||||
offset: const Offset(0, 5),
|
||||
),
|
||||
],
|
||||
),
|
||||
child: Stack(
|
||||
children: [
|
||||
Positioned(
|
||||
right: -20,
|
||||
top: -20,
|
||||
child: Icon(
|
||||
Icons.card_giftcard,
|
||||
size: 100.sp,
|
||||
color: Colors.purple.withOpacity(0.05),
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: EdgeInsets.all(20.r),
|
||||
child: Row(
|
||||
children: [
|
||||
Container(
|
||||
padding: EdgeInsets.all(12.r),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.purple.shade50,
|
||||
borderRadius: BorderRadius.circular(12.r),
|
||||
),
|
||||
child: Icon(Icons.card_giftcard, color: Colors.purple, size: 30.sp),
|
||||
),
|
||||
SizedBox(width: 16.w),
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
"Monthly Surprise Gift",
|
||||
style: TextStyle(
|
||||
fontSize: 19.sp,
|
||||
fontWeight: FontWeight.bold,
|
||||
fontFamily: FontConstants.fontFamily,
|
||||
color: Colors.black87,
|
||||
),
|
||||
),
|
||||
SizedBox(height: 4.h),
|
||||
Text(
|
||||
"If you didn't skip any orders in 1 month!",
|
||||
style: TextStyle(
|
||||
fontSize: 14.sp,
|
||||
color: Colors.black54,
|
||||
fontFamily: FontConstants.fontFamily,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildRewardOptionCard({
|
||||
required String title,
|
||||
required String subtitle,
|
||||
required String points,
|
||||
required IconData icon,
|
||||
required Color color1,
|
||||
required Color color2,
|
||||
String? description,
|
||||
bool isPremium = false,
|
||||
}) {
|
||||
return Container(
|
||||
width: double.infinity,
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(20.r),
|
||||
color: Colors.white,
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: Colors.black.withOpacity(0.05),
|
||||
blurRadius: 10,
|
||||
offset: const Offset(0, 4),
|
||||
),
|
||||
],
|
||||
),
|
||||
child: ClipRRect(
|
||||
borderRadius: BorderRadius.circular(20.r),
|
||||
child: Stack(
|
||||
children: [
|
||||
// Decorative Background Circle
|
||||
Positioned(
|
||||
right: -30,
|
||||
top: -30,
|
||||
child: Container(
|
||||
width: 120.w,
|
||||
height: 120.w,
|
||||
decoration: BoxDecoration(
|
||||
shape: BoxShape.circle,
|
||||
gradient: LinearGradient(
|
||||
colors: [color1.withOpacity(0.2), color2.withOpacity(0.0)],
|
||||
begin: Alignment.bottomLeft,
|
||||
end: Alignment.topRight,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
Padding(
|
||||
padding: EdgeInsets.all(20.r),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Container(
|
||||
padding: EdgeInsets.all(10.r),
|
||||
decoration: BoxDecoration(
|
||||
gradient: LinearGradient(
|
||||
colors: [color1, color2],
|
||||
begin: Alignment.topLeft,
|
||||
end: Alignment.bottomRight,
|
||||
),
|
||||
shape: BoxShape.circle,
|
||||
),
|
||||
child: Icon(icon, color: Colors.white, size: 24.sp),
|
||||
),
|
||||
Container(
|
||||
padding: EdgeInsets.symmetric(horizontal: 12.w, vertical: 6.h),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.amber.shade50,
|
||||
borderRadius: BorderRadius.circular(20.r),
|
||||
border: Border.all(color: Colors.amber.shade200),
|
||||
),
|
||||
child: FittedBox(
|
||||
fit: BoxFit.scaleDown,
|
||||
child: Text(
|
||||
points,
|
||||
style: TextStyle(
|
||||
fontSize: 14.sp,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Colors.amber.shade900,
|
||||
fontFamily: FontConstants.fontFamily,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
SizedBox(height: 16.h),
|
||||
Text(
|
||||
title,
|
||||
style: TextStyle(
|
||||
fontSize: 20.sp,
|
||||
fontWeight: FontWeight.bold,
|
||||
fontFamily: FontConstants.fontFamily,
|
||||
color: Colors.black87,
|
||||
),
|
||||
),
|
||||
SizedBox(height: 4.h),
|
||||
Text(
|
||||
subtitle,
|
||||
style: TextStyle(
|
||||
fontSize: 15.sp,
|
||||
color: Colors.black54,
|
||||
fontFamily: FontConstants.fontFamily,
|
||||
),
|
||||
),
|
||||
if (description != null) ...[
|
||||
SizedBox(height: 12.h),
|
||||
Container(
|
||||
padding: EdgeInsets.all(10.r),
|
||||
width: double.infinity,
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.grey.shade50,
|
||||
borderRadius: BorderRadius.circular(8.r),
|
||||
border: Border.all(color: Colors.grey.shade200),
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
Icon(Icons.star_outline, size: 16.sp, color: Colors.blueGrey),
|
||||
SizedBox(width: 6.w),
|
||||
Expanded(
|
||||
child: Text(
|
||||
description,
|
||||
style: TextStyle(
|
||||
fontSize: 14.sp,
|
||||
color: Colors.black87,
|
||||
fontStyle: FontStyle.italic,
|
||||
fontFamily: FontConstants.fontFamily,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
)
|
||||
],
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user