// Fixed All Services Tab Content Widget - No Loading States with Search Functionality // ignore_for_file: unused_result import 'dart:math'; import 'package:bookmywages/consts_widgets/app_colors.dart'; import 'package:bookmywages/consts_widgets/user_flow_drawer.dart'; import 'package:bookmywages/model/Categories_model.dart'; import 'package:bookmywages/model/cancel_booking.dart'; import 'package:bookmywages/model/most_popular_model.dart'; import 'package:bookmywages/routers/consts_router.dart'; import 'package:bookmywages/view/user_main_screens/history_screen/Service_Booking.dart'; import 'package:bookmywages/view/user_main_screens/main_contoller.dart'; import 'package:bookmywages/viewmodel/api_controller.dart'; import 'package:bookmywages/viewmodel/consts_api.dart'; import 'package:cached_network_image/cached_network_image.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 '../../../consts_widgets/app_assets.dart'; class AllServicesTabContent extends ConsumerWidget { final String? searchQuery; const AllServicesTabContent({super.key, this.searchQuery}); @override Widget build(BuildContext context, WidgetRef ref) { // Safe access to provider data with error handling final popularServicesAsync = ref.watch(mostPopularProvider("0")); // Handle different states safely final allServices = popularServicesAsync.when( data: (services) => services ?? [], loading: () => [], error: (error, stack) { // Log error but don't show it to user, just return empty list debugPrint('Error loading popular services: $error'); return []; }, ); // Filter services based on search query final services = searchQuery == null || searchQuery!.isEmpty ? allServices : allServices.where((service) { final serviceName = (service.serviceName ?? '').toLowerCase(); final query = searchQuery!.toLowerCase(); return serviceName.contains(query); }).toList(); // Always show content, never loading if (services.isEmpty) { return Padding( padding: const EdgeInsets.all(8.0), child: Center( child: Text( searchQuery != null && searchQuery!.isNotEmpty ? "No services found for '$searchQuery'" : "No services available", ), ), ); } final int itemCount = services.length > 4 ? 4 : services.length; final int rowsNeeded = (itemCount + 1) ~/ 2; final double gridHeight = rowsNeeded * 250.0; return Padding( padding: const EdgeInsets.all(8.0), child: SizedBox( height: gridHeight, child: GridView.builder( shrinkWrap: true, physics: const NeverScrollableScrollPhysics(), itemCount: itemCount, gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount( crossAxisCount: 2, crossAxisSpacing: 10, mainAxisSpacing: 10, childAspectRatio: 0.7, ), itemBuilder: (context, index) { final service = services[index]; return ServiceCard(service: service); }, ), ), ); } } class HomeScreen extends ConsumerStatefulWidget { const HomeScreen({super.key}); @override ConsumerState createState() => _HomeScreenState(); } class _HomeScreenState extends ConsumerState with WidgetsBindingObserver, TickerProviderStateMixin { final CarouselSliderController _controller = CarouselSliderController(); final ScrollController _scrollController = ScrollController(); final TextEditingController _searchController = TextEditingController(); int? selectedIndex; late TabController _tabController; List tabTitles = []; List items = []; bool _isFirstLoad = true; bool _tabsInitialized = false; String _searchQuery = ''; // Filter variables String? selectedCategory; String? selectedSubcategory; String? selectedType; String? selectedSubcategoryId; String? selectedTypeValue; List categories = []; List subcategories = []; String? selectedCategoryId; int defaultTabIndex = 0; @override void initState() { super.initState(); WidgetsBinding.instance.addObserver(this); _isFirstLoad = false; // Add search listener _searchController.addListener(() { if (mounted) { setState(() { _searchQuery = _searchController.text; }); } }); } @override void didChangeDependencies() { super.didChangeDependencies(); // Removed problematic refresh calls } @override void didChangeAppLifecycleState(AppLifecycleState state) { // Removed problematic refresh calls } final GlobalKey _scaffoldKey = GlobalKey(); void _initializeTabController(List categories) { if (categories.isEmpty || _tabsInitialized) return; try { tabTitles = ["All"] + categories.map((cat) => cat.name as String).toList(); selectedCategoryId = "0"; _tabController = TabController( length: categories.length + 1, vsync: this, initialIndex: defaultTabIndex, ); _tabController.addListener(() { if (mounted) { setState(() {}); } }); _tabsInitialized = true; } catch (e) { debugPrint('Error initializing tab controller: $e'); } } @override void dispose() { if (_tabsInitialized) { _tabController.dispose(); } _searchController.dispose(); WidgetsBinding.instance.removeObserver(this); _scrollController.dispose(); super.dispose(); } @override Widget build(BuildContext context) { // Safe access to all providers with proper error handling final bannerAsyncValue = ref.watch(bannerListProvider); final categoryAsync = ref.watch(categoryListProvider); final bookingAsyncValue = ref.watch(userbookinghistorydetailsProvider); final expiredPlan = ref.watch(expiredPlanProvider); final profileData = ref.watch(profilegetuserProvider); final indexController = InheritedIndexController.of(context); final screenSize = MediaQuery.of(context).size; final double verticalSpacing = screenSize.height * 0.03; // Safe data extraction with error handling final banners = bannerAsyncValue.when( data: (data) => data ?? [], loading: () => [], error: (_, __) => [], ); final categoriesData = categoryAsync.when( data: (data) => data ?? [], loading: () => [], error: (_, __) => [], ); final bookings = bookingAsyncValue.when( data: (data) => data ?? [], loading: () => [], error: (_, __) => [], ); final plan = expiredPlan.when( data: (data) => data, loading: () => null, error: (_, __) => null, ); final profiles = profileData.when( data: (data) => data ?? [], loading: () => [], error: (_, __) => [], ); return Scaffold( resizeToAvoidBottomInset: false, backgroundColor: AppColors.secondprimary, key: _scaffoldKey, drawer: DrawerMenu( userName: profiles.isNotEmpty ? (profiles.first.name ?? "User") : "User", userImage: profiles.isNotEmpty ? (profiles.first.profilePic1 ?? "") : "", ), body: SafeArea( child: SingleChildScrollView( controller: _scrollController, child: Column( crossAxisAlignment: CrossAxisAlignment.start, mainAxisSize: MainAxisSize.min, children: [ // Header Section Padding( padding: const EdgeInsets.only(left: 16, right: 24), child: Row( mainAxisAlignment: MainAxisAlignment.start, children: [ GestureDetector( onTap: () { // Safe scaffold access without geometry calls if (mounted) { try { final scaffoldState = _scaffoldKey.currentState; if (scaffoldState != null && !scaffoldState.isDrawerOpen) { scaffoldState.openDrawer(); } } catch (e) { debugPrint('Error opening drawer: $e'); } } }, child: Image.asset(AppAssets.menu, height: 40), ), Padding( padding: const EdgeInsets.only(left: 10), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ const Text( 'Welcome', style: TextStyle( fontFamily: 'Gilroy-Bold', fontWeight: FontWeight.w700, fontSize: 20, height: 1.0, letterSpacing: 0.2372, color: Colors.black, ), ), SizedBox(height: 5), Text( '👋 ${profiles.isNotEmpty ? (profiles.first.name ?? "User") : "User"}', style: const TextStyle( fontFamily: 'Gilroy-Medium', fontWeight: FontWeight.w400, fontSize: 13.48, height: 1.5, letterSpacing: 0.2696, color: Colors.black, ), ), ], ), ), const Spacer(), Container( width: 40, height: 40, decoration: BoxDecoration( color: AppColors.lightGrey, shape: BoxShape.circle, ), child: IconButton( icon: Icon( Icons.notifications_none, color: Colors.black, ), onPressed: () {}, ), ), ], ), ), SizedBox(height: 20), // Banner Section - Always show content Padding( padding: const EdgeInsets.symmetric( horizontal: 16, vertical: 5, ), child: banners.isNotEmpty ? CarouselSlider( options: CarouselOptions( height: 180, autoPlay: false, enlargeCenterPage: true, viewportFraction: 1.0, autoPlayCurve: Curves.fastOutSlowIn, enableInfiniteScroll: true, ), carouselController: _controller, items: banners.map((banner) { return Builder( builder: (BuildContext context) { return Container( height: 180, width: double.infinity, decoration: BoxDecoration( borderRadius: BorderRadius.circular(15), image: DecorationImage( image: CachedNetworkImageProvider( banner.documentUrl ?? '', ), fit: BoxFit.cover, onError: (exception, stackTrace) { debugPrint( 'Banner image error: $exception', ); }, ), ), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ // Left Arrow Transform.translate( offset: const Offset(-15, 0), child: Padding( padding: const EdgeInsets.only( left: 8.0, ), child: GestureDetector( onTap: () => _controller.previousPage(), child: Container( width: 30, height: 30, decoration: BoxDecoration( color: Colors.white, shape: BoxShape.circle, boxShadow: [ BoxShadow( color: Colors.black .withOpacity(0.2), blurRadius: 5, offset: Offset(0, 3), ), ], ), child: Center( child: Image.asset( AppAssets.arrowbutton, width: 50, height: 50, color: AppColors.thridprimary, ), ), ), ), ), ), // Right Arrow Transform.translate( offset: const Offset(15, 0), child: Padding( padding: const EdgeInsets.only( right: 8.0, ), child: GestureDetector( onTap: () => _controller.nextPage(), child: Container( width: 30, height: 30, decoration: BoxDecoration( color: Colors.white, shape: BoxShape.circle, boxShadow: [ BoxShadow( color: Colors.black .withOpacity(0.2), blurRadius: 5, offset: Offset(0, 3), ), ], ), child: Center( child: Transform.rotate( angle: 3.14, child: Image.asset( AppAssets.arrowbutton, width: 50, height: 50, color: AppColors.thridprimary, ), ), ), ), ), ), ), ], ), ); }, ); }).toList(), ) : Container( height: 180, decoration: BoxDecoration( borderRadius: BorderRadius.circular(15), color: Colors.grey[300], ), child: Center(child: Text('No banners available')), ), ), SizedBox(height: 20), // Search Section with Enhanced Search Functionality Padding( padding: const EdgeInsets.all(16.0), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Expanded( flex: 5, child: Container( width: 323.63, height: 60.3, decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(15.08), border: Border.all( color: const Color(0xFFAEAEAE), width: 1.01, ), ), child: TextFormField( controller: _searchController, decoration: InputDecoration( contentPadding: const EdgeInsets.symmetric( horizontal: 16, vertical: 20, ), border: InputBorder.none, hintText: 'Search your service', hintStyle: TextStyle(color: Colors.grey[600]), prefixIcon: Icon( Icons.search_sharp, color: Colors.grey[600], ), suffixIcon: _searchQuery.isNotEmpty ? IconButton( icon: Icon( Icons.clear, color: Colors.grey[600], ), onPressed: () { _searchController.clear(); if (mounted) { setState(() { _searchQuery = ''; }); } }, ) : null, ), style: const TextStyle( fontSize: 16, color: Colors.black, ), onChanged: (value) { if (mounted) { setState(() { _searchQuery = value; }); } }, ), ), ), SizedBox(width: 5), Expanded( flex: 1, child: Container( width: 62.31, height: 60.30, decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(15.08), border: Border.all( color: const Color(0xFFAEAEAE), width: 1.01, ), ), child: Center( child: IconButton( onPressed: () { // Add small delay to avoid scaffold geometry issues Future.delayed(Duration(milliseconds: 50), () { if (mounted) { _showCommanFilterBottomSheet(context); } }); }, icon: Image.asset( AppAssets.filtericon, height: 25, width: 25, color: const Color(0xff797777), ), ), ), ), ), ], ), ), SizedBox(height: 20), // Free/Paid Service Buttons Padding( padding: const EdgeInsets.symmetric(horizontal: 16), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Expanded( child: GestureDetector( onTap: () { Get.toNamed( '${RouterConts.listservice}/0', // category id = 0 arguments: { 'service': 'free', // service type = 1 (manual/paid) 'subcategoryId': null, // or specific subcategory id if needed }, ); }, child: Container( width: 181, height: 60, padding: EdgeInsets.all(8), decoration: BoxDecoration( color: Colors.white, border: Border.all( color: Color(0xFFC7C7C7), width: 1, ), borderRadius: BorderRadius.circular(15), ), child: Row( mainAxisAlignment: MainAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center, children: [ Image.asset( AppAssets.free, width: 46, height: 46, ), SizedBox(width: 10), Text( "Free", style: TextStyle( fontFamily: 'Gilroy-Bold', fontWeight: FontWeight.w700, fontSize: 22, height: 1.7, letterSpacing: 0.22, color: Color(0xFF524F4F), ), ), ], ), ), ), ), SizedBox(width: 16), Expanded( child: GestureDetector( onTap: () { Get.toNamed( '${RouterConts.listservice}/0', // category id = 0 arguments: { 'service': '1', // service type = 1 (manual/paid) 'subcategoryId': null, // or specific subcategory id if needed }, ); }, child: Container( width: 181, height: 60, padding: EdgeInsets.all(8), decoration: BoxDecoration( color: Colors.white, border: Border.all( color: Color(0xFFC7C7C7), width: 1, ), borderRadius: BorderRadius.circular(15), ), child: Row( mainAxisAlignment: MainAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center, children: [ Image.asset( AppAssets.paid, width: 46, height: 46, ), SizedBox(width: 10), Text( "Paid", style: TextStyle( fontFamily: 'Gilroy-Bold', fontWeight: FontWeight.w700, fontSize: 22, height: 1.7, letterSpacing: 0.22, color: Color(0xFF524F4F), ), ), ], ), ), ), ), ], ), ), SizedBox(height: verticalSpacing), // Categories Section Padding( padding: const EdgeInsets.symmetric(horizontal: 16), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( 'Categories', textAlign: TextAlign.center, style: TextStyle( fontFamily: 'Gilroy-Bold', fontWeight: FontWeight.w700, fontSize: 18, height: 27.59 / 24, letterSpacing: 1, color: AppColors.thridprimary, ), ), GestureDetector( onTap: () { Get.offAllNamed(RouterConts.categorypage, arguments: 2); }, child: Text( 'View more', textAlign: TextAlign.center, style: TextStyle( fontFamily: 'Gilroy-Bold', fontWeight: FontWeight.w700, fontSize: 16, height: 25.82 / 16, letterSpacing: 1, color: AppColors.thridprimary, ), ), ), ], ), ), // Categories Grid - Always show content Padding( padding: const EdgeInsets.symmetric( horizontal: 16, vertical: 20, ), child: categoriesData.isNotEmpty ? GridView.count( shrinkWrap: true, physics: NeverScrollableScrollPhysics(), crossAxisCount: 3, crossAxisSpacing: 10, mainAxisSpacing: 20, childAspectRatio: 0.9, children: List.generate( categoriesData.length > 6 ? 6 : categoriesData.length, (index) { var item = categoriesData[index]; bool isSelected = selectedIndex == index; Widget imageWidget; try { if (item.name.toLowerCase() == 'plumbing') { imageWidget = Image.network( item.getIconUrl(), height: 50, width: 50, errorBuilder: (context, error, stackTrace) => Icon(Icons.error, size: 50), ); } else if (item.name.toLowerCase().contains( 'electrical', )) { imageWidget = Image.network( item.getIconUrl(), height: 50, width: 50, errorBuilder: (context, error, stackTrace) => Icon(Icons.error, size: 50), ); } else { imageWidget = Image.network( item.getImageUrl(), height: 50, width: 50, errorBuilder: (context, error, stackTrace) => Image.network( item.iconUrl ?? '', height: 50, width: 50, errorBuilder: (context, error, stackTrace) => Icon(Icons.image, size: 50), ), ); } } catch (e) { imageWidget = Icon(Icons.category, size: 50); } return GestureDetector( onTap: () { if (mounted) { setState(() { selectedIndex = index; }); } Future.delayed(Duration(milliseconds: 300), () { Get.toNamed( RouterConts.listservice, arguments: { 'id': item.id, // Pass ID in arguments 'subcategoryId': null, 'service': '0', }, ); }); }, child: Container( decoration: BoxDecoration( color: Color(0xFFE3E3E3), borderRadius: BorderRadius.circular(15), boxShadow: [ BoxShadow( color: Colors.black.withOpacity(0.25), offset: Offset(2, 1), blurRadius: 3.5, ), ], ), child: Padding( padding: EdgeInsets.all(isSelected ? 8 : 0), child: AnimatedContainer( duration: Duration(milliseconds: 300), padding: EdgeInsets.all( isSelected ? 8 : 16, ), decoration: BoxDecoration( color: isSelected ? Color(0xFF52CC40) : Color(0xffFAFAFA), borderRadius: BorderRadius.circular(15), ), child: AnimatedRotation( turns: isSelected ? -10 / 360 : 0, duration: Duration(milliseconds: 300), child: Column( mainAxisAlignment: MainAxisAlignment.center, mainAxisSize: MainAxisSize.min, children: [ imageWidget, Padding( padding: const EdgeInsets.only( top: 6, ), child: Text( item.name ?? 'Category', textAlign: TextAlign.center, maxLines: 1, overflow: TextOverflow.ellipsis, style: TextStyle( fontFamily: 'Gilroy-Medium', fontWeight: FontWeight.w900, fontSize: 10, color: Colors.black, ), ), ), ], ), ), ), ), ), ); }, ), ) : SizedBox( height: 200, child: Center(child: Text('No categories available')), ), ), SizedBox(height: 20), // Expired Plan Section - Show only if plan exists if (plan != null && plan.endDate != null && plan.planName != null) 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: [ Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ 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.offAllNamed( RouterConts.history, arguments: { 'historyTab': 2, // Enquiry list tab }, ); } catch (e) { debugPrint('Navigation error: $e'); } }, child: Text( 'View More', style: TextStyle( fontFamily: 'Gilroy-Medium', fontWeight: FontWeight.w400, fontSize: 15, color: Color(0xFF534E4E), ), ), ), ], ), const SizedBox(height: 10), Row( children: [ Image.asset( AppAssets.subscription, width: 60, height: 60, ), const SizedBox(width: 16), Flexible( child: Text.rich( TextSpan( children: [ 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: _formatDate(plan.endDate), 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: [ 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), ), ), 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: [ ElevatedButton( onPressed: () { Get.offAllNamed( RouterConts.packageList, arguments: 1, ); }, 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), ), ), ], ), ], ), ), ), ), SizedBox(height: 20), // Your Booking Section - Always show content Padding( padding: const EdgeInsets.symmetric(horizontal: 16), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ // Only show header if there are bookings if (bookings.isNotEmpty) ...[ Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( 'Your Booking', style: TextStyle( fontSize: 18, fontWeight: FontWeight.bold, ), ), GestureDetector( onTap: () { Get.offAllNamed( RouterConts.history, arguments: { 'historyTab': 0, // Enquiry list tab }, ); }, child: Text( 'View more', style: TextStyle( fontSize: 16, fontWeight: FontWeight.bold, ), ), ), ], ), const SizedBox(height: 40), ], // Booking list - Always show without loading ListView.builder( itemCount: bookings.isEmpty ? 0 : min(3, bookings.length), shrinkWrap: true, physics: const NeverScrollableScrollPhysics(), itemBuilder: (context, index) { final booking = bookings[index]; return _buildBookingCard(context, booking, ref); }, ), ], ), ), SizedBox(height: 20), // Most Popular Service Section Padding( padding: const EdgeInsets.symmetric(horizontal: 16), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( 'Most popular service', textAlign: TextAlign.center, style: TextStyle( fontFamily: 'Gilroy-Bold', fontWeight: FontWeight.w700, fontSize: 18, height: 27.59 / 24, letterSpacing: 1, color: AppColors.thridprimary, ), ), GestureDetector( onTap: () { Get.toNamed(RouterConts.mostpopluarserviceviewall); }, child: Text( 'View more', textAlign: TextAlign.center, style: TextStyle( fontFamily: 'Gilroy-Bold', fontWeight: FontWeight.w700, fontSize: 16, height: 25.82 / 16, letterSpacing: 1, color: AppColors.thridprimary, ), ), ), ], ), ), SizedBox(height: 30), // Dynamic TabBar and TabBarView - Always show content with search functionality categoriesData.isNotEmpty ? Builder( builder: (context) { // Initialize tab controller if not already done if (!_tabsInitialized && categoriesData.isNotEmpty) { // Initialize immediately without post-frame callback _initializeTabController(categoriesData); } if (!_tabsInitialized) { return Center(child: Text("Loading categories...")); } return Column( mainAxisSize: MainAxisSize.min, children: [ Material( color: Colors.transparent, child: TabBar( tabAlignment: TabAlignment.start, controller: _tabController, isScrollable: true, indicatorColor: Colors.transparent, labelColor: Colors.white, unselectedLabelColor: Colors.black, dividerColor: Colors.transparent, onTap: (index) { if (mounted) { setState(() { _tabController.animateTo(index); if (index == 0) { selectedCategoryId = "0"; } else { selectedCategoryId = categoriesData[index - 1].id .toString(); } }); } }, tabs: List.generate(categoriesData.length + 1, ( index, ) { bool isSelected = _tabController.index == index; return Tab( child: Container( height: 48, decoration: BoxDecoration( color: isSelected ? Color(0xFF0066FF) : Colors.white, border: Border.all( color: isSelected ? Color(0xFF0066FF) : Color(0xFFB7B7B7), width: isSelected ? 0 : 1, ), borderRadius: BorderRadius.circular(38), ), padding: const EdgeInsets.symmetric( vertical: 10, horizontal: 40, ), child: Center( child: Text( index == 0 ? "All" : categoriesData[index - 1] .name ?? "Category", style: TextStyle( color: isSelected ? Colors.white : Colors.black, fontWeight: FontWeight.w600, ), ), ), ), ); }), ), ), const SizedBox(height: 20), IndexedStack( index: _tabController.index, sizing: StackFit.loose, children: List.generate( categoriesData.length + 1, (index) => index == 0 ? AllServicesTabContent( searchQuery: _searchQuery, ) : ServiceTabContent( category: categoriesData[index - 1], searchQuery: _searchQuery, ), ), ), ], ); }, ) : Center(child: Text('No categories available')), SizedBox(height: 10), ], ), ), ), ); } // Helper method to format date String _formatDate(dynamic dateString) { try { final date = DateTime.parse(dateString.toString()); return DateFormat('MMMM dd').format(date); } catch (_) { return dateString?.toString() ?? 'Not available'; } } // Helper method to build booking card Widget _buildBookingCard( BuildContext context, dynamic booking, WidgetRef ref, ) { return Container( margin: const EdgeInsets.only(bottom: 10), decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(20), border: Border.all(color: AppColors.lightGrey), ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ // Top Row: ID and "View order" Padding( padding: const EdgeInsets.only(right: 12, left: 12, top: 12), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( 'ID : ${booking.id ?? 'N/A'}', style: const TextStyle( fontWeight: FontWeight.bold, fontSize: 16, ), ), GestureDetector( onTap: () { Get.toNamed( RouterConts.detailserivce, arguments: booking.serviceId, ); }, child: Text( 'View order', style: TextStyle( color: Colors.blue.shade600, fontWeight: FontWeight.w500, ), ), ), ], ), ), const Divider(), // Image, Company, and Status Padding( padding: const EdgeInsets.symmetric(horizontal: 12), child: Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ Stack( children: [ ClipRRect( borderRadius: BorderRadius.circular(12), child: (booking.images1 != null && booking.images1!.isNotEmpty) ? Image.network( booking.images1![0], width: 100, height: 110, fit: BoxFit.cover, errorBuilder: (context, error, stackTrace) { return Container( width: 100, height: 110, color: Colors.grey[300], child: const Icon(Icons.error, size: 50), ); }, ) : Container( width: 100, height: 110, color: Colors.grey[300], child: const Icon( Icons.image_not_supported, size: 50, ), ), ), 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: [ 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), Flexible( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( booking.vendorName ?? 'Vendor', style: const TextStyle( fontFamily: 'Gilroy-Bold', fontWeight: FontWeight.w700, fontSize: 16.11, height: 14.5 / 16.11, letterSpacing: 0.01 * 16.11, ), ), const SizedBox(height: 4), Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Expanded( child: Text( booking.serviceName ?? 'Service', style: const TextStyle( fontFamily: 'Gilroy-Medium', fontWeight: FontWeight.w500, fontSize: 13.91, height: 1.3, letterSpacing: 0.01 * 13.91, color: Color(0xFF5A5A5A), ), ), ), const SizedBox(width: 10), Expanded( child: Container( width: booking.status == 3 ? 70 : booking.status == 1 ? 85 : 77.84, height: booking.status == 3 ? 25 : booking.status == 1 ? 25 : 27.96, decoration: BoxDecoration( color: booking.status == 3 ? const Color(0xFFFFEEEE) : booking.status == 1 ? const Color(0xFFE6F7E6) : const Color(0xFFDAE9FF), borderRadius: BorderRadius.circular(6.05), ), child: Center( child: Text( booking.status == 3 ? 'Cancel' : booking.status == 1 ? '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(0xFF2E8B57) : const Color(0xFF0066FF), letterSpacing: 1.0, height: 0.98, ), ), ), ), ), ], ), const SizedBox(height: 6), Container( padding: const EdgeInsets.all(8), decoration: BoxDecoration( border: Border.all(color: AppColors.lightGrey), color: Colors.white, borderRadius: BorderRadius.circular(12), ), child: Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ CircleAvatar( radius: 15, backgroundImage: booking.profilePic != null && booking.profilePic!.isNotEmpty ? NetworkImage(booking.profilePic.toString()) : null, child: booking.profilePic == null || booking.profilePic!.isEmpty ? const Icon(Icons.person, size: 20) : null, ), const SizedBox(width: 10), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ Expanded( child: Text( booking.vendorName ?? 'no data', style: const TextStyle( fontFamily: 'Gilroy-Medium', fontWeight: FontWeight.w600, fontSize: 11, height: 10.65 / 11.84, letterSpacing: 0, color: Color(0xFF353434), ), overflow: TextOverflow.visible, ), ), const Icon( Icons.star, color: Colors.orange, size: 15, ), const SizedBox(width: 4), Text( '${booking.isRated ?? '4.5'}', textAlign: TextAlign.center, style: const TextStyle( fontFamily: 'SF UI Display', fontWeight: FontWeight.w800, fontSize: 10, height: 12.5 / 8.82, letterSpacing: -0.31, ), ), ], ), const SizedBox(height: 7), Text( booking.categoryName ?? 'Category', style: const TextStyle( fontFamily: 'Gilroy-Regular', fontWeight: FontWeight.w400, fontSize: 11.84, height: 10.65 / 11.84, letterSpacing: 0, color: Color(0xFF717171), ), overflow: TextOverflow.ellipsis, ), ], ), ), ], ), ), ], ), ), ], ), ), const SizedBox(height: 16), // Date / Time / Hours Padding( padding: const EdgeInsets.symmetric(horizontal: 12), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ 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 ?? 'April 23, 2024', style: const TextStyle( fontFamily: 'Gilroy-Medium', fontWeight: FontWeight.w400, fontSize: 14.45, height: 13 / 14.45, letterSpacing: 0, ), ), ], ), Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ 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.serviceTime ?? '12:00 PM', style: const TextStyle( fontFamily: 'Gilroy-Medium', fontWeight: FontWeight.w400, fontSize: 14.45, height: 13 / 14.45, letterSpacing: 0, ), ), ], ), Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ const Text( 'Working hours :', style: TextStyle( fontFamily: 'Gilroy-Bold', fontWeight: FontWeight.w700, fontSize: 14.45, height: 13 / 14.45, letterSpacing: 0, ), ), const SizedBox(height: 10), Text( booking.workingHours ?? '8 hours', style: const TextStyle( fontFamily: 'Gilroy-Medium', fontWeight: FontWeight.w400, fontSize: 14.45, height: 13 / 14.45, letterSpacing: 0, ), ), ], ), ], ), ), const SizedBox(height: 20), // Buttons Section if (booking.status != 3) Padding( padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 10), child: Row( mainAxisAlignment: booking.status == 1 ? MainAxisAlignment.end : MainAxisAlignment.spaceBetween, children: [ if (booking.status != 1) Expanded( child: SizedBox( height: 42, child: ElevatedButton( onPressed: () { showDialog( context: context, barrierDismissible: false, builder: (context) => Dialog( shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(20), ), child: Container( width: double.maxFinite, decoration: BoxDecoration( color: AppColors.secondprimary, borderRadius: BorderRadius.circular(16), ), child: BookingModificationDialog( id: booking.id.toString(), serviceId: booking.serviceId.toString(), ), ), ), ); }, style: ElevatedButton.styleFrom( backgroundColor: const Color(0xFF0066FF), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(20), ), ), child: const FittedBox( child: Text( "Modification", style: TextStyle( fontFamily: 'Gilroy-Bold', fontWeight: FontWeight.w800, fontSize: 14.45, height: 13 / 14.45, letterSpacing: 0.289, color: Colors.white, ), ), ), ), ), ), if (booking.status != 1) const SizedBox(width: 8), GestureDetector( onTap: () async { final data = CancelBookingRequest( id: booking.id.toString(), serviceId: booking.serviceId.toString(), type: booking.type == 0 ? "0" : booking.type.toString(), ); try { await ref.read(cancelbookingProvider(data).future); if (mounted) { Fluttertoast.showToast( msg: 'Booking cancelled successfully', toastLength: Toast.LENGTH_SHORT, gravity: ToastGravity.BOTTOM, backgroundColor: Colors.green, textColor: Colors.white, ); ref.invalidate(userbookinghistorydetailsProvider); } } catch (e) { if (mounted) { Fluttertoast.showToast( msg: 'Failed to cancel booking: $e', toastLength: Toast.LENGTH_LONG, gravity: ToastGravity.BOTTOM, backgroundColor: Colors.red, textColor: Colors.white, ); } } }, child: Container( padding: const EdgeInsets.symmetric( horizontal: 12, vertical: 8, ), decoration: BoxDecoration( color: Colors.red.shade50, borderRadius: BorderRadius.circular(20), ), child: const Text( 'Cancel Booking', style: TextStyle( color: Colors.red, fontWeight: FontWeight.bold, ), ), ), ), ], ), ), const SizedBox(height: 20), ], ), ); } // Fixed Filter Bottom Sheet Implementation void _showCommanFilterBottomSheet(BuildContext context) { String? localSelectedCategory = selectedCategory; String? localSelectedSubcategory = selectedSubcategory; String? localSelectedType = selectedType; List localSubcategories = List.from(subcategories); List localCategories = List.from(categories); // Direct call without post-frame callback to avoid scaffold geometry issues if (localCategories.isEmpty) { final categoryRepo = ref.read(categoryRepositoryProvider); categoryRepo .fetchCategories(ConstsApi.catgories) .then((loadedCategories) { if (mounted) { localCategories = loadedCategories; _displayBottomSheet( context, localCategories, localSubcategories, localSelectedCategory, localSelectedSubcategory, localSelectedType, ); } }) .catchError((e) { debugPrint('Error loading categories for filter: $e'); if (mounted) { _displayBottomSheet( context, [], localSubcategories, localSelectedCategory, localSelectedSubcategory, localSelectedType, ); } }); } else { _displayBottomSheet( context, localCategories, localSubcategories, localSelectedCategory, localSelectedSubcategory, localSelectedType, ); } } void _displayBottomSheet( BuildContext context, List localCategories, List localSubcategories, String? localSelectedCategory, String? localSelectedSubcategory, String? localSelectedType, ) { if (!mounted) return; showModalBottomSheet( context: context, isScrollControlled: true, backgroundColor: Colors.transparent, builder: (context) { return StatefulBuilder( builder: (context, setModalState) { Future loadSubcategoriesForModal(String categoryId) async { setModalState(() { localSubcategories = []; }); final subcategoryRepo = ref.read(subcategoryRepositoryProvider); try { final newSubcategories = await subcategoryRepo .fetchSubcategories(ConstsApi.subcat, categoryId); if (context.mounted) { setModalState(() { localSubcategories = newSubcategories; }); } } catch (e) { debugPrint('Error loading subcategories: $e'); if (context.mounted) { setModalState(() { localSubcategories = []; }); } } } return Container( height: MediaQuery.of(context).size.height * 0.6, margin: const EdgeInsets.only(top: 100), decoration: BoxDecoration( color: Colors.white, borderRadius: const BorderRadius.only( topLeft: Radius.circular(40), topRight: Radius.circular(40), ), border: Border.all(color: const Color(0xFF858181), width: 1), ), child: SafeArea( child: Padding( padding: const EdgeInsets.symmetric( horizontal: 20.0, vertical: 20, ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ // Header Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ const Text( "Filter", style: TextStyle( fontFamily: 'Gilroy', fontWeight: FontWeight.w900, fontSize: 25, height: 1.0, ), ), IconButton( onPressed: () => Navigator.pop(context), icon: Image.asset( AppAssets.filtericon, height: 25, width: 25, color: const Color(0xff797777), ), ), ], ), const SizedBox(height: 20), // Category Dropdown Container( width: double.infinity, decoration: BoxDecoration( color: const Color(0xFFFDFDFD), borderRadius: BorderRadius.circular(12), border: Border.all( color: const Color(0xFFD3D3D3), width: 1, ), ), child: DropdownButtonHideUnderline( child: ButtonTheme( alignedDropdown: true, child: DropdownButton( hint: Text("Select Category"), value: localSelectedCategory, isExpanded: true, icon: Icon(Icons.arrow_drop_down), iconSize: 24, elevation: 16, items: localCategories.isEmpty ? [ DropdownMenuItem( value: null, child: Text("No categories available"), ), ] : localCategories.map((category) { return DropdownMenuItem( value: category.id.toString(), child: Text( category.name ?? 'Category', ), ); }).toList(), onChanged: localCategories.isEmpty ? null : (value) { if (value != null) { setModalState(() { localSelectedCategory = value; localSelectedSubcategory = null; }); loadSubcategoriesForModal(value); } }, ), ), ), ), const SizedBox(height: 20), // Subcategory Dropdown Container( width: double.infinity, decoration: BoxDecoration( color: const Color(0xFFFDFDFD), borderRadius: BorderRadius.circular(12), border: Border.all( color: const Color(0xFFD3D3D3), width: 1, ), ), child: DropdownButtonHideUnderline( child: ButtonTheme( alignedDropdown: true, child: DropdownButton( hint: Text("Select Subcategory"), value: localSelectedSubcategory, isExpanded: true, icon: Icon(Icons.arrow_drop_down), iconSize: 24, elevation: 16, items: localSubcategories.isEmpty ? [ DropdownMenuItem( value: null, child: localSelectedCategory == null ? Text("Select a category first") : Text( "No subcategories available", ), ), ] : localSubcategories.map((subcategory) { return DropdownMenuItem( value: subcategory.id.toString(), child: Text( subcategory.name ?? 'Subcategory', ), ); }).toList(), onChanged: localSubcategories.isEmpty ? null : (value) { setModalState(() { localSelectedSubcategory = value; }); }, ), ), ), ), const SizedBox(height: 20), // Type Dropdown Container( width: double.infinity, decoration: BoxDecoration( color: const Color(0xFFFDFDFD), borderRadius: BorderRadius.circular(12), border: Border.all( color: const Color(0xFFD3D3D3), width: 1, ), ), child: DropdownButtonHideUnderline( child: ButtonTheme( alignedDropdown: true, child: DropdownButton( hint: Text("Select Type"), value: localSelectedType, isExpanded: true, icon: Icon(Icons.arrow_drop_down), iconSize: 24, elevation: 16, items: ["Free", "Paid"].map((type) { return DropdownMenuItem( value: type, child: Text(type), ); }).toList(), onChanged: (value) { setModalState(() { localSelectedType = value; }); }, ), ), ), ), SizedBox(height: 50), // Save Button SizedBox( width: double.infinity, height: 50, child: ElevatedButton( style: ElevatedButton.styleFrom( backgroundColor: const Color(0xFF000000), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(12), ), ), onPressed: () { final updatedCategory = localSelectedCategory ?? "0"; final updatedSubcategory = localSelectedSubcategory; final updatedType = localSelectedType; String? updatedTypeValue; if (localSelectedType != null) { updatedTypeValue = localSelectedType == "Paid" ? '1' : 'free'; } if (mounted) { setState(() { selectedCategory = updatedCategory; selectedSubcategory = updatedSubcategory; selectedType = updatedType; selectedSubcategoryId = updatedSubcategory; selectedTypeValue = updatedTypeValue; if (selectedCategory == "0") { selectedIndex = 0; } categories = List.from(localCategories); subcategories = List.from(localSubcategories); }); } debugPrint( "Filter applied - Category: $selectedCategory, Subcategory: $selectedSubcategoryId, Type: $selectedTypeValue", ); Navigator.pop(context); // Pass everything in arguments instead of query params Get.toNamed( RouterConts.listservice, arguments: { 'id': selectedCategory, // Move id to arguments 'subcategoryId': selectedSubcategoryId, 'service': selectedTypeValue, 'sourceTab': 0, // or whatever tab you want to maintain }, ); }, child: const Text( 'Save', style: TextStyle( color: Colors.white, fontFamily: 'Gilroy', fontSize: 16, fontWeight: FontWeight.w600, ), ), ), ), ], ), ), ), ); }, ); }, ); } } class ServiceTabContent extends ConsumerWidget { final dynamic category; final String? searchQuery; const ServiceTabContent({ super.key, required this.category, this.searchQuery, }); @override Widget build(BuildContext context, WidgetRef ref) { // Safe access to provider data with error handling final popularServicesAsync = ref.watch( mostPopularProvider(category.id.toString()), ); // Handle different states safely final allServices = popularServicesAsync.when( data: (services) => services ?? [], loading: () => [], error: (error, stack) { debugPrint( 'Error loading services for category ${category.id}: $error', ); return []; }, ); // Filter services based on search query final services = searchQuery == null || searchQuery!.isEmpty ? allServices : allServices.where((service) { final serviceName = (service.serviceName ?? '').toLowerCase(); final query = searchQuery!.toLowerCase(); return serviceName.contains(query); }).toList(); // Always show content, never loading if (services.isEmpty) { return Padding( padding: const EdgeInsets.all(8.0), child: Center( child: Text( searchQuery != null && searchQuery!.isNotEmpty ? "No services found for '$searchQuery' in this category" : "No services available for this category", ), ), ); } final int itemCount = services.length > 4 ? 4 : services.length; final int rowsNeeded = (itemCount + 1) ~/ 2; final double gridHeight = rowsNeeded * 250.0; return Padding( padding: const EdgeInsets.all(16.0), child: SizedBox( height: gridHeight, child: GridView.builder( shrinkWrap: true, physics: const NeverScrollableScrollPhysics(), itemCount: itemCount, gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount( crossAxisCount: 2, crossAxisSpacing: 10, mainAxisSpacing: 10, childAspectRatio: 0.7, ), itemBuilder: (context, index) { final service = services[index]; return ServiceCard(service: service); }, ), ), ); } } // Service Card Widget - Enhanced with better error handling class ServiceCard extends StatelessWidget { final MostPopularModel service; const ServiceCard({super.key, required this.service}); @override Widget build(BuildContext context) { return Container( width: 189, decoration: BoxDecoration( color: const Color(0xFFFCFAFA), borderRadius: BorderRadius.circular(20), border: Border.all(color: const Color(0xFFE3E3E3), width: 1), ), child: Column( crossAxisAlignment: CrossAxisAlignment.center, mainAxisSize: MainAxisSize.min, children: [ // Service image ClipRRect( borderRadius: const BorderRadius.vertical(top: Radius.circular(19)), child: SizedBox( height: 120, width: double.infinity, child: (service.images1 != null && service.images1!.isNotEmpty && _isValidCompleteUrl(service.images1![0])) ? Image.network( service.images1!.first, width: double.infinity, height: 120, fit: BoxFit.cover, errorBuilder: (context, error, stackTrace) { return Image.asset( AppAssets.cleaning, width: double.infinity, height: 120, fit: BoxFit.cover, ); }, ) : Image.asset( AppAssets.cleaning, width: double.infinity, height: 120, fit: BoxFit.cover, ), ), ), // Price tag Align( alignment: Alignment.centerRight, child: Container( height: 30, width: 80, transform: Matrix4.translationValues(-10, -15, 0), decoration: BoxDecoration( color: Colors.blue, borderRadius: BorderRadius.circular(20), border: Border.all(color: Colors.white, width: 1.5), ), child: Center( child: FittedBox( fit: BoxFit.scaleDown, child: Padding( padding: const EdgeInsets.symmetric(horizontal: 4), child: Text( 'Rs.${service.amount ?? "N/A"}', style: const TextStyle( fontFamily: 'Gilroy-Bold', fontWeight: FontWeight.w400, fontSize: 16.86, height: 1.0, letterSpacing: 0.02, color: Color(0xFFFCFAFA), ), ), ), ), ), ), ), // Service details Padding( padding: const EdgeInsets.fromLTRB(8, 0, 8, 8), child: Column( children: [ Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Expanded( flex: 2, child: Text( service.serviceName ?? 'Service', style: const TextStyle( fontFamily: 'Gilroy-Medium', fontWeight: FontWeight.w700, fontSize: 15.02, height: 1.0, letterSpacing: 0.0, ), maxLines: 1, overflow: TextOverflow.ellipsis, ), ), Expanded( flex: 1, child: Row( mainAxisSize: MainAxisSize.min, children: [ const Icon( Icons.star, color: Colors.orange, size: 18, ), const SizedBox(width: 2), Flexible( child: Text( service.averageReview?.toString() ?? '0.0', textAlign: TextAlign.center, style: const TextStyle( fontFamily: 'SF UI Display', fontWeight: FontWeight.w600, fontSize: 14, height: 1.0, letterSpacing: -0.5, ), overflow: TextOverflow.ellipsis, ), ), ], ), ), ], ), const SizedBox(height: 10), Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Flexible( flex: 2, child: Container( width: 110, padding: const EdgeInsets.all(4), decoration: BoxDecoration( border: Border.all(color: AppColors.lightGrey), color: Colors.white, borderRadius: BorderRadius.circular(12), ), child: Row( mainAxisSize: MainAxisSize.min, children: [ service.profilePic1 != null && service.profilePic1!.isNotEmpty ? CircleAvatar( radius: 15, backgroundImage: NetworkImage( service.profilePic1!, ), onBackgroundImageError: (exception, stackTrace) { debugPrint( 'Profile image error: $exception', ); }, ) : const CircleAvatar( radius: 15, backgroundImage: AssetImage( AppAssets.login, ), ), const SizedBox(width: 4), Flexible( child: Column( crossAxisAlignment: CrossAxisAlignment.start, mainAxisSize: MainAxisSize.min, children: [ Text( service.vendorName ?? 'Vendor', style: const TextStyle( fontFamily: 'Gilroy-Medium', fontWeight: FontWeight.w400, fontSize: 11.75, height: 1.0, letterSpacing: 0.0, color: Color(0xFF353434), ), maxLines: 1, overflow: TextOverflow.ellipsis, ), SizedBox(height: 5), Text( service.serviceName ?? 'Service', style: const TextStyle( fontFamily: 'Gilroy-Regular', fontWeight: FontWeight.w400, fontSize: 11.75, height: 1.0, letterSpacing: 0.0, color: Color(0xFF717171), ), maxLines: 1, overflow: TextOverflow.ellipsis, ), ], ), ), ], ), ), ), const SizedBox(width: 4), GestureDetector( onTap: () { Get.toNamed( RouterConts.detailserivce, arguments: service.id, ); }, child: Container( padding: const EdgeInsets.all(8), decoration: BoxDecoration( color: AppColors.primary, borderRadius: BorderRadius.circular(12), ), child: const Icon( Icons.arrow_forward_ios_rounded, size: 16, color: Colors.white, ), ), ), ], ), ], ), ), ], ), ); } bool _isValidCompleteUrl(String url) { try { final uri = Uri.parse(url); return uri.hasScheme && (uri.scheme == 'http' || uri.scheme == 'https') && uri.host.isNotEmpty; } catch (e) { return false; } } }