bookmywages/lib/view/vendor_main_screens/vendor_homepage.dart
2025-10-16 11:21:52 +05:30

1999 lines
93 KiB
Dart

import 'dart:ui';
import 'package:bookmywages/consts_widgets/app_assets.dart';
import 'package:bookmywages/consts_widgets/app_colors.dart';
import 'package:bookmywages/consts_widgets/vendor_flow_drawer.dart';
import 'package:bookmywages/model/cancel_booking.dart';
import 'package:bookmywages/model/vendor_model/vendor_booking_status.dart';
import 'package:bookmywages/routers/consts_router.dart';
import 'package:bookmywages/routers/router.dart';
import 'package:bookmywages/view/user_main_screens/main_contoller.dart';
import 'package:bookmywages/view/user_main_screens/notification_page.dart';
import 'package:bookmywages/view/vendor_main_screens/vendor_maincontoller.dart';
import 'package:bookmywages/viewmodel/api_controller.dart';
import 'package:carousel_slider/carousel_slider.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:fluttertoast/fluttertoast.dart';
import 'package:get/get.dart';
import 'package:intl/intl.dart';
import 'package:shared_preferences/shared_preferences.dart'; // For date formatting
class VendorHomepage extends ConsumerStatefulWidget {
const VendorHomepage({super.key});
@override
ConsumerState<VendorHomepage> createState() => _VendorHomepageState();
}
class _VendorHomepageState extends ConsumerState<VendorHomepage>
with AutomaticKeepAliveClientMixin, WidgetsBindingObserver {
final GlobalKey<ScaffoldState> _scaffoldKey = GlobalKey<ScaffoldState>();
final CarouselSliderController _controller = CarouselSliderController();
int? expandedIndex;
@override
bool get wantKeepAlive => true;
@override
void initState() {
super.initState();
WidgetsBinding.instance.addObserver(this);
Future.microtask(() async {
final prefs = await SharedPreferences.getInstance();
final vendorId = prefs.getString('vendor_id') ?? '';
await updateNotificationCount(ref, type: 2, userId: vendorId);
});
}
@override
void dispose() {
WidgetsBinding.instance.removeObserver(this);
super.dispose();
}
@override
void didChangeAppLifecycleState(AppLifecycleState state) {
super.didChangeAppLifecycleState(state);
if (state == AppLifecycleState.resumed && mounted) {
_refreshData();
}
}
void _refreshData() {
if (!mounted) return;
try {
ref.invalidate(bannerListProvider);
ref.invalidate(vendorbookingdetailsProvider);
ref.invalidate(vendorserviceProvider);
ref.invalidate(enquriylistProvider);
ref.invalidate(vendorexpiredPlanProvider);
} catch (e) {
// Handle refresh error silently
}
}
// Helper function to get status color and text
Map<String, dynamic> getStatusInfo(int status) {
switch (status) {
case 0:
return {
'text': 'Pending',
'color': Colors.orange,
'bgColor': Colors.orange.shade100,
};
case 1:
return {
'text': 'Scheduled',
'color': Colors.green,
'bgColor': Colors.green.shade100,
};
case 2:
return {
'text': 'Completed',
'color': Colors.blue,
'bgColor': Colors.blue.shade100,
};
case 3:
return {
'text': 'Canceled',
'color': Colors.red,
'bgColor': Colors.red.shade100,
};
default:
return {
'text': 'pending',
'color': Colors.blue,
'bgColor': Colors.blue.shade100,
};
}
}
// Format date from API response
String formatDate(String dateStr) {
try {
final date = DateTime.parse(dateStr);
return DateFormat('MMMM dd, yyyy').format(date);
} catch (e) {
return dateStr;
}
}
String _formatDate(String dateString) {
try {
return DateFormat('dd/MM/yyyy').format(DateTime.parse(dateString));
} catch (e) {
return dateString;
}
}
Widget _infoText(String label, String value) {
return Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Text(
label,
style: const TextStyle(
fontFamily: 'Gilroy-Bold',
fontWeight: FontWeight.w700,
fontSize: 13,
height: 1.239,
),
),
Expanded(
child: Text(
value,
style: const TextStyle(
fontFamily: 'Gilroy-Medium',
fontWeight: FontWeight.w400,
fontSize: 13,
height: 1.239,
color: Color(0xFF373636),
),
overflow: TextOverflow.ellipsis,
maxLines: 2,
),
),
],
);
}
Widget _buildBannerSection(AsyncValue bannerAsyncValue) {
return bannerAsyncValue.when(
data: (banners) {
if (banners == null || banners.isEmpty) {
return Container(
height: 180,
margin: const EdgeInsets.only(left: 16, top: 40, right: 16),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(15),
color: Colors.grey[200],
),
child: const Center(
child: Icon(Icons.image, color: Colors.grey, size: 50),
),
);
}
final imageUrls = banners.map((b) => b.documentUrl).toList();
return Padding(
padding: const EdgeInsets.only(left: 16, top: 40, right: 16),
child: CarouselSlider(
options: CarouselOptions(
height: 180,
autoPlay: true,
enlargeCenterPage: true,
viewportFraction: 1.0,
autoPlayCurve: Curves.fastOutSlowIn,
enableInfiniteScroll: true,
),
carouselController: _controller,
items: imageUrls.map<Widget>((url) {
return Builder(
builder: (BuildContext context) {
return Container(
height: 180,
width: double.infinity,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(15),
image: DecorationImage(
image: NetworkImage(url),
fit: BoxFit.cover,
onError: (exception, stackTrace) {
// Handle image loading error silently
},
),
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
// Left arrow
Transform.translate(
offset: const Offset(-15, 0),
child: Padding(
padding: const EdgeInsets.only(left: 8.0),
child: GestureDetector(
onTap: () {
try {
_controller.previousPage();
} catch (e) {
// Handle carousel error silently
}
},
child: Container(
width: 30,
height: 30,
decoration: BoxDecoration(
color: Colors.white,
shape: BoxShape.circle,
boxShadow: <BoxShadow>[
BoxShadow(
color: Colors.black.withOpacity(0.2),
blurRadius: 5,
offset: const Offset(0, 3),
),
],
),
child: Center(
child: Image.asset(
AppAssets.arrowbutton,
width: 50,
height: 50,
color: AppColors.thridprimary,
errorBuilder: (context, error, stackTrace) {
return const Icon(
Icons.arrow_back,
size: 20,
);
},
),
),
),
),
),
),
// Right arrow
Transform.translate(
offset: const Offset(15, 0),
child: Padding(
padding: const EdgeInsets.only(right: 8.0),
child: GestureDetector(
onTap: () {
try {
_controller.nextPage();
} catch (e) {
// Handle carousel error silently
}
},
child: Container(
width: 30,
height: 30,
decoration: BoxDecoration(
color: Colors.white,
shape: BoxShape.circle,
boxShadow: <BoxShadow>[
BoxShadow(
color: Colors.black.withOpacity(0.2),
blurRadius: 5,
offset: const Offset(0, 3),
),
],
),
child: Center(
child: Transform.rotate(
angle: 3.14,
child: Image.asset(
AppAssets.arrowbutton,
width: 50,
height: 50,
color: AppColors.thridprimary,
errorBuilder:
(context, error, stackTrace) {
return const Icon(
Icons.arrow_forward,
size: 20,
);
},
),
),
),
),
),
),
),
],
),
);
},
);
}).toList(),
),
);
},
loading: () => Container(
height: 180,
margin: const EdgeInsets.only(left: 16, top: 40, right: 16),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(15),
color: Colors.grey[200],
),
child: const Center(
child: Icon(Icons.image, color: Colors.grey, size: 50),
),
),
error: (err, stack) => Container(
height: 180,
margin: const EdgeInsets.only(left: 16, top: 40, right: 16),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(15),
color: Colors.grey[200],
),
child: const Center(
child: Icon(Icons.image, color: Colors.grey, size: 50),
),
),
);
}
Widget _buildExpiredPlanSection(AsyncValue expiredPlan) {
return expiredPlan.when(
data: (plan) {
if (plan == null || plan.endDate == null || plan.planName == null) {
return const SizedBox();
}
String formattedEndDate = '';
try {
final date = DateTime.parse(plan.endDate.toString());
formattedEndDate = DateFormat('MMMM dd').format(date);
} catch (_) {
formattedEndDate = plan.endDate?.toString() ?? 'Not available';
}
final indexController = InheritedVendorIndexController.of(context);
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 16),
child: Container(
decoration: BoxDecoration(
color: AppColors.lightBlue,
borderRadius: BorderRadius.circular(10),
),
child: Padding(
padding: const EdgeInsets.all(12),
child: Column(
children: <Widget>[
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
Text(
'Subscription plan',
textAlign: TextAlign.center,
style: TextStyle(
fontFamily: 'Gilroy-Bold',
fontWeight: FontWeight.w700,
fontSize: 24,
height: 27.59 / 24,
letterSpacing: 1.0,
color: Color(0xFF9C34C2),
),
),
GestureDetector(
onTap: () async {
try {
// Get the index controller reference
Get.offAllNamed(
RouterConts.vendorhistory,
arguments: {
'historyTab': 2, // Enquiry list tab
},
);
} catch (e) {
// Handle navigation error silently
debugPrint('Navigation error: $e');
}
},
child: Text(
'View more',
style: TextStyle(
fontFamily: 'Gilroy-Medium',
fontWeight: FontWeight.w400,
fontSize: 15,
height: 11.17 / 15,
letterSpacing: 0.0,
color: Color(0xFF534E4E),
),
),
),
],
),
const SizedBox(height: 10),
Row(
children: <Widget>[
Image.asset(
AppAssets.subscription,
width: 60,
height: 60,
errorBuilder: (context, error, stackTrace) {
return Container(
width: 60,
height: 60,
color: Colors.grey[200],
child: const Icon(Icons.subscriptions),
);
},
),
const SizedBox(width: 16),
Flexible(
child: Text.rich(
TextSpan(
children: <TextSpan>[
TextSpan(
text: 'Your ',
style: TextStyle(
fontFamily: 'Gilroy-Medium',
color: Color(0xFF585454),
fontWeight: FontWeight.w400,
fontSize: 18,
height: 1.86,
letterSpacing: 0.1957,
),
),
TextSpan(
text: plan.planName ?? 'Subscription',
style: TextStyle(
fontFamily: 'Gilroy-Medium',
color: Color(0xFFFF0000),
fontWeight: FontWeight.w700,
fontSize: 18,
height: 1.86,
letterSpacing: 0.1957,
),
),
TextSpan(
text: ' subscription plan was expired on ',
style: TextStyle(
fontFamily: 'Gilroy-Medium',
color: Color(0xFF585454),
fontWeight: FontWeight.w400,
fontSize: 18,
height: 1.86,
letterSpacing: 0.1957,
),
),
TextSpan(
text: formattedEndDate,
style: TextStyle(
fontFamily: 'Gilroy-Medium',
color: Color(0xFFFF0000),
fontWeight: FontWeight.w700,
fontSize: 18,
letterSpacing: 0.1957,
),
),
],
),
),
),
],
),
const SizedBox(height: 15),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
Flexible(
child: Text(
'Start Date: ${plan.createdDate?.split(' ').first ?? 'Not available'}',
style: TextStyle(
fontFamily: 'Gilroy-Medium',
fontWeight: FontWeight.w400,
fontSize: 14,
height: 13 / 14,
letterSpacing: 0.14,
color: Color(0xFF4F4F4F),
),
),
),
Flexible(
child: Text(
'End Date: ${plan.endDate ?? 'Not available'}',
style: TextStyle(
fontFamily: 'Gilroy-Medium',
fontWeight: FontWeight.w400,
fontSize: 14,
height: 13 / 14,
letterSpacing: 0.14,
color: Color(0xFF4F4F4F),
),
),
),
],
),
const SizedBox(height: 10),
Row(
mainAxisAlignment: MainAxisAlignment.end,
children: <Widget>[
ElevatedButton(
onPressed: () {
try {
Get.offAllNamed(
RouterConts.vendorpackage,
arguments: 1,
);
} catch (e) {
// Handle navigation error silently
}
},
style: ElevatedButton.styleFrom(
backgroundColor: AppColors.primary,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(16),
),
padding: const EdgeInsets.symmetric(
horizontal: 24,
vertical: 12,
),
),
child: const Text(
'Renewal',
style: TextStyle(color: Colors.white),
),
),
],
),
],
),
),
),
);
},
loading: () => const SizedBox(),
error: (e, _) => const SizedBox(),
);
}
Widget _buildBookingsSection(AsyncValue bookingsAsyncValue) {
return bookingsAsyncValue.when(
data: (bookings) {
if (bookings == null || bookings.isEmpty) {
return const SizedBox.shrink();
}
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 16),
child: Column(
children: <Widget>[
// Bookings header
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
const Text(
'Your Bookings',
style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
),
GestureDetector(
onTap: () async {
try {
Get.offAllNamed(
RouterConts.vendorhistory,
arguments: {
'historyTab': 0, // Enquiry list tab
},
);
} catch (e) {
// Handle navigation error silently
debugPrint('Navigation error: $e');
}
},
child: const Text(
'View more',
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold,
color: AppColors.thridprimary,
),
),
),
],
),
const SizedBox(height: 16),
// Bookings list
ListView.builder(
itemCount: bookings.length,
shrinkWrap: true,
physics: const ClampingScrollPhysics(),
itemBuilder: (context, index) {
final booking = bookings[index];
return Container(
margin: const EdgeInsets.only(bottom: 16),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(20),
border: Border.all(color: AppColors.lightGrey),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
// ID and View order
Padding(
padding: const EdgeInsets.only(
right: 12,
left: 12,
top: 12,
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
Text(
'ID : ${booking.id ?? 'N/A'}',
style: const TextStyle(
fontWeight: FontWeight.bold,
fontSize: 16,
),
),
],
),
),
const Divider(),
// Image, Company, and Status
Padding(
padding: const EdgeInsets.symmetric(horizontal: 12),
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Stack(
children: <Widget>[
ClipRRect(
borderRadius: BorderRadius.circular(12),
child:
(booking.images1 != null &&
booking.images1!.isNotEmpty)
? Image.network(
booking.images1!,
width: 100,
height: 110,
fit: BoxFit.cover,
errorBuilder:
(context, error, stackTrace) {
return Image.asset(
AppAssets.cleaning,
width: 100,
height: 110,
fit: BoxFit.cover,
);
},
)
: Image.asset(
AppAssets.cleaning,
width: 100,
height: 110,
fit: BoxFit.cover,
),
),
Positioned(
top: 6,
left: 6,
child: Container(
padding: const EdgeInsets.symmetric(
horizontal: 6,
vertical: 2,
),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(8),
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Container(
width: 6,
height: 6,
decoration: const BoxDecoration(
color: Colors.green,
shape: BoxShape.circle,
),
),
const SizedBox(width: 4),
const Text(
'Live',
style: TextStyle(
fontSize: 12,
color: Colors.green,
fontWeight: FontWeight.bold,
),
),
],
),
),
),
],
),
const SizedBox(width: 12),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Text(
booking.name ?? 'No data',
style: const TextStyle(
fontFamily: 'Gilroy-Bold',
fontWeight: FontWeight.w700,
fontSize: 16.11,
height: 14.5 / 16.11,
letterSpacing: 0.01 * 16.11,
),
overflow: TextOverflow.ellipsis,
),
const SizedBox(height: 4),
Row(
children: <Widget>[
Expanded(
flex: 3,
child: Text(
booking.serviceName ?? '',
style: const TextStyle(
fontFamily: 'Gilroy-Medium',
fontWeight: FontWeight.w500,
fontSize: 13.91,
height: 1.3,
letterSpacing: 0.01 * 13.91,
color: Color(0xFF5A5A5A),
),
overflow: TextOverflow.ellipsis,
),
),
const SizedBox(width: 8),
Expanded(
flex: 2,
child: Container(
height: 25,
decoration: BoxDecoration(
color: booking.status == 3
? const Color(0xFFFFEEEE)
: booking.status == 1
? const Color(0xFFDAE9FF)
: const Color(0xFFE6F7E6),
borderRadius:
BorderRadius.circular(6.05),
),
child: Center(
child: Text(
booking.status == 3
? 'Cancel'
: booking.status == 1
? 'completed'
: booking.status == 4
? 'Scheduled'
: 'pending',
style: TextStyle(
fontFamily: 'Gilroy-Bold',
fontSize: 10.92,
fontWeight: FontWeight.w400,
color: booking.status == 3
? const Color(0xFFFF0000)
: booking.status == 1
? const Color(0xFF0066FF)
: const Color(0xFF2E8B57),
letterSpacing: 1.0,
height: 0.98,
),
overflow: TextOverflow.ellipsis,
),
),
),
),
],
),
const SizedBox(height: 6),
Row(
children: <Widget>[
Expanded(
child: Column(
crossAxisAlignment:
CrossAxisAlignment.start,
children: <Widget>[
const Text(
'Date :',
style: TextStyle(
fontFamily: 'Gilroy-Bold',
fontWeight: FontWeight.w700,
fontSize: 14.45,
height: 13 / 14.45,
letterSpacing: 0,
),
),
const SizedBox(height: 10),
Text(
booking.serviceDate != null
? _formatDate(
booking.serviceDate!,
)
: 'No date',
style: const TextStyle(
fontFamily: 'Gilroy-Medium',
fontWeight: FontWeight.w400,
fontSize: 14.45,
height: 13 / 14.45,
letterSpacing: 0,
),
overflow: TextOverflow.ellipsis,
),
],
),
),
const SizedBox(width: 8),
Expanded(
child: Column(
crossAxisAlignment:
CrossAxisAlignment.start,
children: <Widget>[
const Text(
'Time :',
style: TextStyle(
fontFamily: 'Gilroy-Bold',
fontWeight: FontWeight.w700,
fontSize: 14.45,
height: 13 / 14.45,
letterSpacing: 0,
),
),
const SizedBox(height: 10),
Text(
booking.serviceDate != null
? DateFormat(
'h:mm a',
).format(
DateTime.parse(
booking.serviceDate!,
),
)
: 'No time',
style: const TextStyle(
fontFamily: 'Gilroy-Medium',
fontWeight: FontWeight.w400,
fontSize: 14.45,
height: 13 / 14.45,
letterSpacing: 0,
),
overflow: TextOverflow.ellipsis,
),
],
),
),
],
),
],
),
),
],
),
),
const SizedBox(height: 16),
/// Contact information
Padding(
padding: const EdgeInsets.symmetric(horizontal: 12),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Row(
children: <Widget>[
const Text(
"Mobile number : ",
style: TextStyle(
fontFamily: 'Gilroy-Bold',
fontWeight: FontWeight.w700,
fontSize: 14.45,
height: 13 / 14.45,
letterSpacing: 0,
),
),
const SizedBox(width: 7),
Expanded(
child: Text(
booking.mobileNumber?.toString() ?? 'N/A',
style: const TextStyle(
fontFamily: 'Gilroy-Medium',
fontWeight: FontWeight.w400,
fontSize: 14.45,
height: 13 / 14.45,
letterSpacing: 0,
),
overflow: TextOverflow.ellipsis,
),
),
],
),
const SizedBox(height: 13),
Row(
children: <Widget>[
const Text(
"E-mail ID :",
style: TextStyle(
fontFamily: 'Gilroy-Bold',
fontWeight: FontWeight.w700,
fontSize: 14.45,
height: 13 / 14.45,
letterSpacing: 0,
),
),
const SizedBox(width: 7),
Expanded(
child: Text(
booking.email?.toString() ?? 'N/A',
style: const TextStyle(
fontFamily: 'Gilroy-Medium',
fontWeight: FontWeight.w400,
fontSize: 14.45,
height: 13 / 14.45,
letterSpacing: 0,
),
overflow: TextOverflow.ellipsis,
),
),
],
),
const SizedBox(height: 13),
Row(
children: <Widget>[
const Text(
"Address : ",
style: TextStyle(
fontFamily: 'Gilroy-Bold',
fontWeight: FontWeight.w700,
fontSize: 14.45,
height: 13 / 14.45,
letterSpacing: 0,
),
),
const SizedBox(width: 7),
Expanded(
child: Text(
booking.address?.toString() ?? 'N/A',
style: const TextStyle(
fontFamily: 'Gilroy-Medium',
fontWeight: FontWeight.w400,
fontSize: 14.45,
height: 13 / 14.45,
letterSpacing: 0,
),
overflow: TextOverflow.ellipsis,
maxLines: 2,
),
),
],
),
const SizedBox(height: 13),
const Text(
"Message : ",
style: TextStyle(
fontFamily: 'Gilroy-Bold',
fontWeight: FontWeight.w700,
fontSize: 14.45,
height: 13 / 14.45,
letterSpacing: 0,
),
),
const SizedBox(height: 13),
Text(
booking.message?.toString() ?? 'N/A',
style: const TextStyle(
fontFamily: 'Gilroy-Medium',
fontWeight: FontWeight.w400,
fontSize: 14.45,
height: 13 / 14.45,
letterSpacing: 0,
),
maxLines: 3,
overflow: TextOverflow.ellipsis,
),
],
),
),
const SizedBox(height: 20),
// Action buttons based on booking status
if (booking.status == 0)
Padding(
padding: const EdgeInsets.all(8.0),
child: Row(
children: <Widget>[
Expanded(
child: ElevatedButton(
onPressed: () async {
try {
final data = VendorBookingStatus(
id: booking.id.toString(),
status: "4",
);
await ref.read(
changebookingProvider(data).future,
);
if (mounted) {
Fluttertoast.showToast(
msg:
'Booking confirmed successfully',
backgroundColor: Colors.green,
textColor: Colors.white,
toastLength: Toast.LENGTH_SHORT,
gravity: ToastGravity.BOTTOM,
);
_refreshData();
}
} catch (e) {
if (mounted) {
Fluttertoast.showToast(
msg: 'Failed to confirm booking',
backgroundColor: Colors.red,
textColor: Colors.white,
toastLength: Toast.LENGTH_LONG,
gravity: ToastGravity.BOTTOM,
);
}
}
},
style: ElevatedButton.styleFrom(
backgroundColor: const Color(0xFF0066FF),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(20),
),
),
child: const Text(
"Confirmed",
style: TextStyle(
fontFamily: 'Gilroy-Bold',
fontWeight: FontWeight.w800,
fontSize: 14.45,
height: 13 / 14.45,
letterSpacing: 0.289,
color: Colors.white,
),
),
),
),
const SizedBox(width: 12),
Expanded(
child: GestureDetector(
onTap: () async {
try {
final data = CancelBookingRequest(
id: booking.id.toString(),
serviceId:
booking.serviceId?.toString() ??
"0",
type: booking.type?.toString() ?? "0",
);
await ref.read(
cancelbookingProvider(data).future,
);
if (mounted) {
Fluttertoast.showToast(
msg:
'Booking cancelled successfully',
backgroundColor: Colors.green,
textColor: Colors.white,
toastLength: Toast.LENGTH_SHORT,
gravity: ToastGravity.BOTTOM,
);
_refreshData();
}
} catch (e) {
if (mounted) {
Fluttertoast.showToast(
msg: 'Failed to cancel booking',
backgroundColor: Colors.red,
textColor: Colors.white,
toastLength: Toast.LENGTH_LONG,
gravity: ToastGravity.BOTTOM,
);
}
}
},
child: Container(
height: 42,
decoration: BoxDecoration(
border: Border.all(
color: const Color(0xff534E4E),
),
borderRadius: BorderRadius.circular(20),
),
child: const Center(
child: Text(
'Cancel Booking',
style: TextStyle(
fontFamily: 'Gilroy-Bold',
fontWeight: FontWeight.w400,
fontSize: 14.45,
height: 13 / 14.45,
letterSpacing: 0.02,
color: Color(0xff534E4E),
),
),
),
),
),
),
],
),
)
else if (booking.status == 4)
Padding(
padding: const EdgeInsets.all(8.0),
child: Row(
children: <Widget>[
Expanded(
child: ElevatedButton(
onPressed: null,
style: ElevatedButton.styleFrom(
backgroundColor: const Color(0xFF0066FF),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(20),
),
),
child: const Text(
"pending",
style: TextStyle(
fontFamily: 'Gilroy-Bold',
fontWeight: FontWeight.w800,
fontSize: 14.45,
height: 13 / 14.45,
letterSpacing: 0.289,
color: Colors.white,
),
),
),
),
const SizedBox(width: 12),
Expanded(
child: GestureDetector(
onTap: () async {
try {
final data = VendorBookingStatus(
id: booking.id.toString(),
status: "1",
);
await ref.read(
changebookingProvider(data).future,
);
if (mounted) {
Fluttertoast.showToast(
msg:
'Booking completed successfully',
backgroundColor: Colors.green,
textColor: Colors.white,
toastLength: Toast.LENGTH_SHORT,
gravity: ToastGravity.BOTTOM,
);
_refreshData();
}
} catch (e) {
if (mounted) {
Fluttertoast.showToast(
msg: 'Failed to complete booking',
backgroundColor: Colors.red,
textColor: Colors.white,
toastLength: Toast.LENGTH_LONG,
gravity: ToastGravity.BOTTOM,
);
}
}
},
child: Container(
height: 42,
decoration: BoxDecoration(
border: Border.all(
color: const Color(0xff534E4E),
),
borderRadius: BorderRadius.circular(20),
),
child: const Center(
child: Text(
'complete',
style: TextStyle(
fontFamily: 'Gilroy-Bold',
fontWeight: FontWeight.w400,
fontSize: 14.45,
height: 13 / 14.45,
letterSpacing: 0.02,
color: Color(0xff534E4E),
),
),
),
),
),
),
],
),
),
const SizedBox(height: 10),
],
),
);
},
),
],
),
);
},
loading: () => const SizedBox.shrink(),
error: (err, stack) => const SizedBox.shrink(),
);
}
Widget _buildEnquirySection(AsyncValue enquiryListAsync) {
return enquiryListAsync.when(
data: (enquiries) {
if (enquiries == null || enquiries.isEmpty) {
return const SizedBox.shrink();
}
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 16),
child: Column(
children: <Widget>[
// User Enquiry Header
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
const Text(
'User Enquiry',
style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
),
GestureDetector(
onTap: () async {
try {
Get.offAllNamed(
RouterConts.vendorhistory,
arguments: {
'historyTab': 4, // Enquiry list tab
},
);
} catch (e) {
// Handle navigation error silently
debugPrint('Navigation error: $e');
}
},
child: const Text(
'View more',
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold,
color: AppColors.thridprimary,
),
),
),
],
),
const SizedBox(height: 16),
// Enquiry List
ListView.builder(
itemCount: enquiries.length,
shrinkWrap: true,
physics: const ClampingScrollPhysics(),
itemBuilder: (context, index) {
final enquiry = enquiries[index];
final isExpanded = expandedIndex == index;
return SizedBox(
width: double.infinity,
child: Column(
children: <Widget>[
Container(
margin: const EdgeInsets.symmetric(
vertical: 10,
horizontal: 16,
),
decoration: BoxDecoration(
color: AppColors.secondprimary,
borderRadius: BorderRadius.circular(11),
border: Border.all(
color: const Color(0xFFE8E8E8),
width: 1,
),
boxShadow: const <BoxShadow>[
BoxShadow(
color: Color(0x66AEAEAE),
offset: Offset(0, 2),
blurRadius: 4,
),
],
),
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
children: <Widget>[
Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
ClipRRect(
borderRadius: BorderRadius.circular(10),
child: (enquiry.images1?.isEmpty ?? true)
? Container(
width: 89,
height: 64,
color: Colors.grey[300],
child: Icon(
Icons.image,
color: Colors.grey[600],
),
)
: Image.network(
enquiry.images1!.first,
width: 89,
height: 64,
fit: BoxFit.cover,
errorBuilder:
(
context,
error,
stackTrace,
) => Container(
width: 89,
height: 64,
color: Colors.grey[300],
child: Icon(
Icons.broken_image,
color: Colors.grey[600],
),
),
),
),
const SizedBox(width: 10),
Expanded(
child: Column(
crossAxisAlignment:
CrossAxisAlignment.start,
children: <Widget>[
Text(
enquiry.vendorName ?? 'vendorName',
style: const TextStyle(
fontFamily: 'Gilroy-Bold',
fontWeight: FontWeight.w700,
fontSize: 16,
height: 18.14 / 16,
letterSpacing: 0.16,
color: Colors.black,
),
overflow: TextOverflow.ellipsis,
),
const SizedBox(height: 12),
GestureDetector(
onTap: () {
if (mounted) {
setState(() {
expandedIndex = isExpanded
? null
: index;
});
}
},
child: Row(
children: <Widget>[
Expanded(
child: Text(
enquiry.serviceName ?? '',
textAlign: TextAlign.left,
maxLines: 2,
overflow:
TextOverflow.ellipsis,
style: const TextStyle(
fontFamily:
'Gilroy-Medium',
fontWeight:
FontWeight.w400,
fontSize: 16.11,
height: 17.3 / 16.11,
letterSpacing: 0.1611,
color: Colors.black,
),
),
),
const SizedBox(width: 4),
Row(
mainAxisSize:
MainAxisSize.min,
children: <Widget>[
Text(
isExpanded
? "View Less"
: "View More",
style: const TextStyle(
fontWeight:
FontWeight.w600,
fontSize: 13.15,
height: 5.71 / 13.15,
color: Color(
0xFFFF3D00,
),
),
),
const SizedBox(width: 5),
Icon(
isExpanded
? Icons
.keyboard_arrow_up
: Icons
.keyboard_arrow_down,
color: const Color(
0xffFF3D00,
),
),
],
),
],
),
),
],
),
),
],
),
],
),
),
),
// Animated expansion
AnimatedContainer(
duration: const Duration(milliseconds: 300),
curve: Curves.easeInOut,
height: isExpanded ? null : 0,
child: isExpanded
? Container(
width: double.infinity,
margin: const EdgeInsets.symmetric(
horizontal: 16,
),
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(16.43),
border: Border.all(
color: const Color(0xFFE8E8E8),
width: 0.82,
),
boxShadow: const <BoxShadow>[
BoxShadow(
color: Color(0xA9A9A940),
blurRadius: 3.29,
offset: Offset(0, 0.82),
),
],
),
child: Column(
crossAxisAlignment:
CrossAxisAlignment.start,
children: <Widget>[
Row(
mainAxisAlignment:
MainAxisAlignment.spaceBetween,
children: <Widget>[
Expanded(
child: Text.rich(
TextSpan(
children: <TextSpan>[
const TextSpan(
text: "Name : ",
style: TextStyle(
fontFamily: 'Gilroy-Bold',
fontWeight:
FontWeight.w700,
fontSize: 13,
height: 1.239,
),
),
TextSpan(
text:
enquiry.name ??
'No Name',
style: const TextStyle(
fontFamily:
'Gilroy-Medium',
fontWeight:
FontWeight.w400,
fontSize: 13,
height: 1.239,
color: Color(0xFF373636),
),
),
],
),
),
),
IconButton(
icon: const Icon(
Icons.delete,
color: AppColors.red,
size: 20,
),
onPressed: () async {
final shouldDelete =
await showDialog<bool>(
context: context,
builder: (context) =>
AlertDialog(
title: const Text(
'Delete Enquiry',
),
content: const Text(
'Are you sure you want to delete this enquiry?',
),
actions: <Widget>[
TextButton(
onPressed: () =>
Navigator.of(
context,
).pop(false),
child: const Text(
'Cancel',
),
),
TextButton(
onPressed: () =>
Navigator.of(
context,
).pop(true),
child: const Text(
'Delete',
),
),
],
),
);
if (shouldDelete == true &&
mounted) {
try {
final success = await ref
.read(
enquriydeleteProvider(
enquiry.id.toString(),
).future,
);
if (success && mounted) {
setState(() {
expandedIndex = null;
});
await Future.delayed(
const Duration(
milliseconds: 300,
),
);
_refreshData();
Fluttertoast.showToast(
msg:
'Enquiry deleted successfully',
toastLength:
Toast.LENGTH_SHORT,
gravity:
ToastGravity.BOTTOM,
);
} else if (mounted) {
Fluttertoast.showToast(
msg:
'Failed to delete enquiry',
toastLength:
Toast.LENGTH_SHORT,
gravity:
ToastGravity.BOTTOM,
);
}
} catch (e) {
if (mounted) {
Fluttertoast.showToast(
msg:
'Error deleting enquiry',
toastLength:
Toast.LENGTH_SHORT,
gravity:
ToastGravity.BOTTOM,
);
}
}
}
},
),
],
),
const SizedBox(height: 8),
_infoText(
"Mobile number : ",
enquiry.mobile ?? '',
),
const SizedBox(height: 13),
_infoText(
"E-mail Id : ",
enquiry.email ?? '',
),
const SizedBox(height: 13),
_infoText(
"Message : ",
enquiry.message ?? '',
),
],
),
)
: const SizedBox.shrink(),
),
],
),
);
},
),
],
),
);
},
loading: () => const SizedBox.shrink(),
error: (error, stack) => const SizedBox.shrink(),
);
}
Widget _buildServicesSection(AsyncValue vendorServiceAsyncValue) {
return vendorServiceAsyncValue.when(
data: (services) {
if (services == null || services.isEmpty) {
return const SizedBox.shrink();
}
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
const Text(
'Your Service',
style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
),
GestureDetector(
onTap: () async {
try {
Get.offAllNamed(
RouterConts.vendorhistory,
arguments: {
'historyTab': 1, // Enquiry list tab
},
);
} catch (e) {
// Handle navigation error silently
debugPrint('Navigation error: $e');
}
},
child: const Text(
'View more',
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold,
color: AppColors.thridprimary,
),
),
),
],
),
const SizedBox(height: 12),
SizedBox(
height: 220,
child: ListView.builder(
itemCount: services.length,
scrollDirection: Axis.horizontal,
itemBuilder: (context, index) {
final service = services[index];
return Container(
width: 162,
margin: const EdgeInsets.only(right: 12),
child: GestureDetector(
onTap: () {
try {
Get.toNamed(
RouterConts.vendorserviceupload,
arguments: service,
);
} catch (e) {
// Handle navigation error silently
print(
'Navigation error: $e',
); // Optional: for debugging
}
},
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
SizedBox(
height: 203,
child: Stack(
children: <Widget>[
ClipRRect(
borderRadius: BorderRadius.circular(20),
child:
(service.images1?.isNotEmpty ?? false)
? Image.network(
service.images1!.first,
width: double.infinity,
height: double.infinity,
fit: BoxFit.cover,
errorBuilder:
(
context,
error,
stackTrace,
) => Container(
color: Colors.grey[200],
child: const Center(
child: Icon(
Icons.image_not_supported,
size: 50,
color: Colors.grey,
),
),
),
)
: Container(
color: Colors.grey[200],
child: const Center(
child: Icon(
Icons.image_not_supported,
size: 50,
color: Colors.grey,
),
),
),
),
Positioned(
bottom: 0,
left: 0,
right: 0,
child: ClipRRect(
borderRadius: const BorderRadius.vertical(
bottom: Radius.circular(20),
),
child: BackdropFilter(
filter: ImageFilter.blur(
sigmaX: 5,
sigmaY: 5,
),
child: Container(
padding: const EdgeInsets.all(12),
color: Colors.black.withOpacity(0.3),
child: Center(
child: Text(
service.serviceName ?? 'Service',
style: const TextStyle(
color: Colors.white,
fontFamily: 'Gilroy-Bold',
fontWeight: FontWeight.w700,
fontSize: 15,
height: 24.43 / 18,
letterSpacing: 0.89,
),
textAlign: TextAlign.center,
maxLines: 2,
overflow: TextOverflow.ellipsis,
),
),
),
),
),
),
],
),
),
],
),
),
);
},
),
),
],
),
);
},
loading: () => const SizedBox.shrink(),
error: (err, stack) => const SizedBox.shrink(),
);
}
@override
Widget build(BuildContext context) {
super.build(context);
final bannerAsyncValue = ref.watch(bannerListProvider);
final bookingsAsyncValue = ref.watch(vendorbookingdetailsProvider);
final profileData = ref.watch(profilegetvendorProvider);
final vendorServiceAsyncValue = ref.watch(vendorserviceProvider);
final expiredPlan = ref.watch(vendorexpiredPlanProvider);
final enquiryListAsync = ref.watch(enquriylistProvider);
final profiles = profileData.value ?? [];
return Scaffold(
key: _scaffoldKey,
drawer: DrawerMenuVendor(
userName: profiles.isNotEmpty
? (profiles.first.name ?? "Vendor")
: "Vendor",
userImage: profiles.isNotEmpty
? (profiles.first.profilePic1 ?? "")
: "",
),
backgroundColor: Colors.white,
body: SafeArea(
child: SingleChildScrollView(
child: Column(
children: <Widget>[
// Header section
Padding(
padding: const EdgeInsets.only(left: 16, right: 24, top: 12),
child: Row(
mainAxisAlignment: MainAxisAlignment.start,
children: <Widget>[
GestureDetector(
onTap: () {
try {
_scaffoldKey.currentState?.openDrawer();
} catch (e) {
// Handle drawer error silently
}
},
child: Image.asset(
AppAssets.menu,
height: 40,
errorBuilder: (context, error, stackTrace) {
return const Icon(Icons.menu, size: 40);
},
),
),
Padding(
padding: const EdgeInsets.only(left: 10),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Text(
'Welcome',
style: TextStyle(
fontFamily: 'Gilroy-Bold',
fontWeight: FontWeight.w700,
fontSize: 20,
height: 1.0,
letterSpacing: 0.2372,
color: Colors.black,
),
),
SizedBox(height: 7),
Text(
'👋 ${profiles.isNotEmpty ? (profiles.first.name) : "vendor"}',
style: TextStyle(
fontFamily: 'Gilroy-Medium',
fontWeight: FontWeight.w400,
fontSize: 13.48,
height: 1.0,
letterSpacing: 0.2696,
color: Colors.black,
),
),
],
),
),
const Spacer(),
Consumer(
builder: (context, ref, _) {
final count = ref.watch(notificationCountProvider);
return Stack(
children: [
Container(
width: 40,
height: 40,
decoration: BoxDecoration(
color: AppColors.lightGrey,
shape: BoxShape.circle,
),
child: IconButton(
icon: const Icon(
Icons.notifications_none,
color: Colors.black,
),
onPressed: () async {
final prefs =
await SharedPreferences.getInstance();
final vendorId =
prefs.getString('vendor_id') ?? '';
// Reset badge when opened
ref
.read(
notificationCountProvider.notifier,
)
.state =
0;
Get.to(
() => VendorController(
child: NotificationPage(
type: 2,
id: vendorId,
),
),
);
},
),
),
if (count > 0)
Positioned(
right: 4,
top: 4,
child: Container(
padding: const EdgeInsets.all(4),
decoration: const BoxDecoration(
color: Colors.red,
shape: BoxShape.circle,
),
constraints: const BoxConstraints(
minWidth: 20,
minHeight: 20,
),
child: Text(
'$count',
style: const TextStyle(
color: Colors.white,
fontSize: 12,
),
textAlign: TextAlign.center,
),
),
),
],
);
},
),
],
),
),
// Banner Carousel
_buildBannerSection(bannerAsyncValue),
const SizedBox(height: 30),
// Expired Plan Section
_buildExpiredPlanSection(expiredPlan),
const SizedBox(height: 30),
// Bookings Section
_buildBookingsSection(bookingsAsyncValue),
// Enquiry Section
_buildEnquirySection(enquiryListAsync),
const SizedBox(height: 16),
// Services Section
_buildServicesSection(vendorServiceAsyncValue),
const SizedBox(height: 20),
],
),
),
),
);
}
}