import 'dart:convert'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:geocoding/geocoding.dart'; import 'package:geolocator/geolocator.dart'; import 'package:get/get.dart'; import 'package:http/http.dart' as http; import 'package:lottie/lottie.dart'; import 'package:nearledaily/constants/color_constants.dart'; import 'package:shared_preferences/shared_preferences.dart'; import '../../Helper/Logger.dart'; import '../../constants/font_constants.dart'; import '../../data/tenant/get_tenant_res.dart'; import '../../domain/provider/authentication/location.dart'; import '../../domain/provider/tenant/get_tenant_pro.dart'; import '../../domain/repository/authentication/auth_repository.dart'; import '../../domain/repository/tenant/get_tenant_repo.dart'; import '../../modules/authentication/auth.dart'; import '../../modules/tenant/category.dart'; import '../../modules/tenant/get_tenant.dart'; import '../../view/authentication/costomer_create_view.dart'; import '../../widgets/text_widget.dart'; import '../tenant_controller /tenant_list.dart'; class DashboardController extends GetxController { // Loading state var isLoading = true.obs; List fetchedLocations = []; var categories = [].obs; var selectedIndex = 0.obs; var show = true.obs; void fetchCategories() async { try { isLoading(true); final url = Uri.parse( 'https://fiesta.nearle.app/live/api/v1/mob/utils/getappcategories'); final response = await http.get(url); if (response.statusCode == 200) { final data = jsonDecode(response.body); categories.value = (data['details'] as List) .map((e) => Category.fromJson(e)) .toList(); print(response.body); print('gtot'); } } catch (e) { print("Error: $e"); } finally { isLoading(false); } } void selectCategory(int index) { selectedIndex.value = index; } Future checkMainFlag() async { SharedPreferences prefs = await SharedPreferences.getInstance(); bool firstTime = prefs.getBool("firstTime") ?? true; if (firstTime) { show.value = true; // Next time this becomes false prefs.setBool("firstTime", false); } else { show.value = false; } } void location(){ _showLocationBottomSheet(); } // Carousel images var carouselImages = [].obs; // List getAllTenants = []; // Grid items var gridItems = >[].obs; var currentAddress = ''.obs; final CustomerLocationProvider locationProvider = CustomerLocationProvider(); final TenantController tenantController = Get.put(TenantController()); @override void onInit() { // loadTenants(); _getAndUpdateCurrentLocation(); _fetchLocations(); checkMainFlag(); print("๐Ÿš€ DashboardController onInit() called"); // getTenantCustomers(); // Simulate data loading (e.g., from an API) Future.delayed(const Duration(seconds: 2), () { // Load carousel images carouselImages.addAll([ 'assets/Banner_1.png', 'assets/Banner_2.png', ]); // Set loading to false after data is loaded isLoading.value = false; }); fetchCategories(); super.onInit(); } Future _fetchLocations() async { SharedPreferences prefs = await SharedPreferences.getInstance(); final id = prefs.getInt('customerId'); try { final locations = await locationProvider.fetchCustomerLocations(id!); fetchedLocations = locations; print(locations); } catch (e) { print('Error fetching locations: $e'); } finally { } } void loadTenants() async { isLoading.value = true; SharedPreferences prefs = await SharedPreferences.getInstance(); int? id = prefs.getInt('customerId'); try { final response = await CustomerTenantsProvider().getCustomerTenants(id!, 1); if (response != null && response.status == true && response.details != null) { populateGridFromTenants(response); } else { gridItems.clear(); } } catch (e) { print("โ›” Error fetching tenants: $e"); gridItems.clear(); } finally { isLoading.value = false; } } void populateGridFromTenants(CustomerTenantsResponse response) { final tenants = response.details ?? []; gridItems.clear(); for (var tenant in tenants) { gridItems.add({ 'tenantid': (tenant.tenantid ?? 0).toString(), 'title': tenant.tenantname ?? 'No Name', 'address': tenant.address ?? '', 'licenseno': tenant.licenseno ?? '', 'primaryemail': tenant.primaryemail ?? '', 'primarycontact': tenant.primarycontact ?? '', 'pickuplocationid': (tenant.pickuplocationid ?? 0).toString(), 'applocationid': (tenant.applocationid ?? 0).toString(), 'suburb': tenant.suburb ?? '', 'city': tenant.city ?? '', 'latitude': tenant.latitude ?? '', 'longitude': tenant.longitude ?? '', 'postcode': tenant.postcode ?? '', 'tenantimage': tenant.tenantimage != null && tenant.tenantimage!.isNotEmpty ? tenant.tenantimage! : 'https://via.placeholder.com/150', 'locationid': (tenant.locationid ?? 0).toString(), 'locationname': tenant.locationname ?? '', 'subcategoryid': (tenant.subcategoryid ?? 0).toString(), 'categoryid': (tenant.categoryid ?? 0).toString(), 'registrationno': tenant.registrationno ?? '', }); } } Future _updateProfile({ required String address, required double latitude, required double longitude, required String city, required String state, required String suburb, }) async { SharedPreferences prefs = await SharedPreferences.getInstance(); int? id = prefs.getInt('customerId'); prefs.setDouble('lat', latitude); prefs.setDouble('long', longitude); String? name = prefs.getString('customerFirstname'); String? contactNo = prefs.getString('contactno'); String? fcm = prefs.getString('fcmToken'); if (id == null) { // Get.snackbar("Error", "Customer ID not found"); return; } final repo = LoginRepository(); Map data = { "customerid": id, "configid": 2, "address": address.toString(), "suburb": suburb.toString(), "city": city.toString(), "state": state.toString(), "latitude": latitude.toString(), "longitude": longitude.toString(), 'customertoken': fcm }; print("Request Data: $data"); // ๐Ÿงพ Print all data in readable format print("๐Ÿš€ Sending Update Profile Request:"); print("=================================="); print("๐Ÿ†” Customer ID: $id"); print("๐Ÿ‘ค Name: $name"); print("๐Ÿ“ž Contact: $contactNo"); print("๐Ÿ“ Address: $address"); print("๐Ÿ™๏ธ City: $city"); print("๐ŸŒ† State: $state"); print("๐Ÿ˜๏ธ Suburb: $suburb"); print("๐Ÿงญ Latitude: $latitude"); print("๐Ÿงญ Longitude: $longitude"); print("๐Ÿงญ fcm: $fcm"); print("=================================="); try { final response = await repo.updateProfile(data); print("Server Response: $response"); if (response != null && response['status'] == true) { tenantController.loadTenants(); // Get.snackbar("Success", "Location updated"); } else { // Get.snackbar("Error", "Something went wrong"); } } catch (e) { print("Update Profile Error: $e"); // Get.snackbar("Error", "Something went wrong"); } } Future _getAndUpdateCurrentLocation() async { bool serviceEnabled; LocationPermission permission; // Check if location service is enabled serviceEnabled = await Geolocator.isLocationServiceEnabled(); if (!serviceEnabled) { // Show bottom sheet to add new address _showLocationBottomSheet(); return; } // Check location permission permission = await Geolocator.checkPermission(); if (permission == LocationPermission.denied) { permission = await Geolocator.requestPermission(); if (permission == LocationPermission.denied) { print("โŒ Location permissions are denied."); return; } } if (permission == LocationPermission.deniedForever) { print("โŒ Location permissions are permanently denied."); return; } // Get current position Position position = await Geolocator.getCurrentPosition( desiredAccuracy: LocationAccuracy.high, ); // Reverse geocode List placemarks = await placemarkFromCoordinates(position.latitude, position.longitude); if (placemarks.isNotEmpty) { Placemark place = placemarks.first; String fullAddress = "${place.name}, ${place.subLocality}, ${place.locality}, ${place.administrativeArea}, ${place.country}, ${place.postalCode}"; String city = place.locality ?? ''; String state = place.administrativeArea ?? ''; String suburb = place.subLocality ?? ''; // Auto update profile await _updateProfile( address: fullAddress, latitude: position.latitude, longitude: position.longitude, city: city, state: state, suburb: suburb, ); } else { print("โš ๏ธ No address found for this location."); } } Future _showLocationBottomSheet() async { await _fetchLocations(); if (fetchedLocations.isEmpty) return; await Future.delayed(Duration.zero); Get.bottomSheet( StatefulBuilder( builder: (context, setState) { String? selectedAddress; return SafeArea( child: Container( padding: const EdgeInsets.only(bottom: 16), height: MediaQuery.of(context).size.height * 0.75, decoration: const BoxDecoration( color: Colors.white, borderRadius: BorderRadius.vertical(top: Radius.circular(24)), ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ // Drag Handle Center( child: Container( margin: const EdgeInsets.only(top: 10, bottom: 16), width: 40, height: 4, decoration: BoxDecoration( color: ColorConstants.primaryColor, borderRadius: BorderRadius.circular(10), ), ), ), // โ”€โ”€ Header Row โ”€โ”€ Padding( padding: const EdgeInsets.symmetric(horizontal: 16), child: Row( crossAxisAlignment: CrossAxisAlignment.center, children: [ // Title + subtitle Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ ReusableTextWidget( text: 'Location & Address', color: Colors.black, fontFamily: FontConstants.fontFamily, fontSize: 20, fontWeight: FontWeight.bold, ), const SizedBox(height: 4), ReusableTextWidget( text: 'Allow location access for faster delivery', color: Colors.grey.shade600, fontFamily: FontConstants.fontFamily, fontSize: 13, fontWeight: FontWeight.w400, ), ], ), ), // Map pin illustration box Container( width: 64, height: 64, decoration: BoxDecoration( color: const Color(0xFFEDE7F6), borderRadius: BorderRadius.circular(16), ), child: Stack( alignment: Alignment.center, children: [ const Icon(Icons.location_on, color: Color(0xFF662582), size: 32), Positioned( top: 8, left: 8, child: Icon(Icons.auto_awesome, color: Color(0xFF662582), size: 12), ), Positioned( top: 12, left: 14, child: Icon(Icons.auto_awesome, color: Color(0xFF662582), size: 8), ), ], ), ), ], ), ), const SizedBox(height: 16), // โ”€โ”€ Turn on Location Card โ”€โ”€ Padding( padding: const EdgeInsets.symmetric(horizontal: 16), child: Container( padding: const EdgeInsets.symmetric( horizontal: 16, vertical: 14), decoration: BoxDecoration( color: const Color(0xFFF0EAFB), borderRadius: BorderRadius.circular(16), ), child: Row( children: [ // Crosshair icon circle Container( width: 44, height: 44, decoration: BoxDecoration( color: Colors.white, shape: BoxShape.circle, boxShadow: [ BoxShadow( color: Colors.black.withOpacity(0.08), blurRadius: 6, offset: const Offset(0, 2), ), ], ), child: const Icon(Icons.my_location, color: Color(0xFF662582), size: 22), ), const SizedBox(width: 12), // Text Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ ReusableTextWidget( text: 'Turn on location', color: Colors.black, fontFamily: FontConstants.fontFamily, fontSize: 15, fontWeight: FontWeight.bold, ), const SizedBox(height: 2), ReusableTextWidget( text: 'Detect your location automatically', color: Colors.grey.shade600, fontFamily: FontConstants.fontFamily, fontSize: 12, fontWeight: FontWeight.w400, ), ], ), ), const SizedBox(width: 8), // Enable button ElevatedButton.icon( onPressed: () async { final result = await Get.to(() => const MapPickerPage1()); if (result != null && result is Map) { await _updateProfile( address: result['address'] ?? '', latitude: double.tryParse( result['latitude'] ?? '0') ?? 0.0, longitude: double.tryParse( result['longitude'] ?? '0') ?? 0.0, city: result['city'] ?? '', state: result['state'] ?? '', suburb: result['suburb'] ?? '', ); Navigator.pop(context, result); } }, icon: const Icon(Icons.near_me, color: Colors.white, size: 16), label: ReusableTextWidget( text: 'Enable', color: Colors.white, fontFamily: FontConstants.fontFamily, fontSize: 14, fontWeight: FontWeight.bold, ), style: ElevatedButton.styleFrom( backgroundColor: ColorConstants.primaryColor, elevation: 0, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(12), ), padding: const EdgeInsets.symmetric( horizontal: 14, vertical: 10), ), ), ], ), ), ), const SizedBox(height: 20), // โ”€โ”€ Saved Addresses Header โ”€โ”€ Padding( padding: const EdgeInsets.symmetric(horizontal: 16), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ ReusableTextWidget( text: 'Saved Addresses', color: Colors.black, fontFamily: FontConstants.fontFamily, fontSize: 16, fontWeight: FontWeight.bold, ), GestureDetector( onTap: () async { final result = await Get.to(() => const MapPickerPage1()); if (result != null && result is Map) { await _updateProfile( address: result['address'] ?? '', latitude: double.tryParse( result['latitude'] ?? '0') ?? 0.0, longitude: double.tryParse( result['longitude'] ?? '0') ?? 0.0, city: result['city'] ?? '', state: result['state'] ?? '', suburb: result['suburb'] ?? '', ); Navigator.pop(context, result); } }, child: Row( children: [ Icon(Icons.add_circle_outline, color: ColorConstants.primaryColor, size: 18), const SizedBox(width: 4), ReusableTextWidget( text: 'Add New', color: ColorConstants.primaryColor, fontFamily: FontConstants.fontFamily, fontSize: 14, fontWeight: FontWeight.w600, ), ], ), ), ], ), ), const SizedBox(height: 10), // โ”€โ”€ Address List โ”€โ”€ Expanded( child: fetchedLocations.isNotEmpty ? ListView.builder( padding: const EdgeInsets.symmetric(horizontal: 16), itemCount: fetchedLocations.length, itemBuilder: (context, index) { final loc = fetchedLocations[index]; final addr = loc.address ?? ''; final isSelected = selectedAddress == addr; return GestureDetector( onTap: () async { setState(() => selectedAddress = addr); await _updateProfile( address: loc.address ?? '', latitude: double.tryParse( loc.latitude ?? '0') ?? 0.0, longitude: double.tryParse( loc.longitude ?? '0') ?? 0.0, city: loc.city ?? '', state: loc.state ?? '', suburb: loc.suburb ?? '', ); Navigator.pop(context, loc); }, child: Container( margin: const EdgeInsets.only(bottom: 10), padding: const EdgeInsets.symmetric( horizontal: 12, vertical: 14), decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(14), border: Border.all( color: isSelected ? ColorConstants.primaryColor : Colors.grey.shade200, width: isSelected ? 1.5 : 1, ), boxShadow: [ BoxShadow( color: Colors.black.withOpacity(0.04), blurRadius: 6, offset: const Offset(0, 2), ), ], ), child: Row( crossAxisAlignment: CrossAxisAlignment.center, children: [ // Purple circle icon Container( width: 42, height: 42, decoration: BoxDecoration( color: const Color(0xFFEDE7F6), shape: BoxShape.circle, ), child: const Icon( Icons.location_on, color: Color(0xFF662582), size: 22, ), ), const SizedBox(width: 12), // Address text Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ ReusableTextWidget( text: loc.suburb?.isNotEmpty == true ? loc.suburb! : "Address ${index + 1}", color: Colors.black87, fontFamily: FontConstants.fontFamily, fontSize: 14, fontWeight: FontWeight.bold, ), const SizedBox(height: 3), ReusableTextWidget( text: addr, color: Colors.grey.shade600, fontFamily: FontConstants.fontFamily, fontSize: 12, fontWeight: FontWeight.w400, ), ], ), ), const SizedBox(width: 8), // Current badge or chevron if (isSelected) Container( padding: const EdgeInsets.symmetric( horizontal: 10, vertical: 4), decoration: BoxDecoration( color: const Color(0xFFE8F5E9), borderRadius: BorderRadius.circular(20), ), child: Row( mainAxisSize: MainAxisSize.min, children: [ Container( width: 8, height: 8, decoration: const BoxDecoration( color: Colors.green, shape: BoxShape.circle, ), ), const SizedBox(width: 4), ReusableTextWidget( text: 'Current', color: Colors.green.shade700, fontFamily: FontConstants.fontFamily, fontSize: 12, fontWeight: FontWeight.w600, ), ], ), ) else Icon(Icons.chevron_right, color: Colors.grey.shade400, size: 22), ], ), ), ); }, ) : const Center( child: Text( "No saved addresses found.", style: TextStyle( fontSize: 13, fontWeight: FontWeight.w500), ), ), ), const SizedBox(height: 12), ], ), ), ); }, ), isScrollControlled: true, ); } // Add method to update grid dynamically if needed void addGridItem(Map item) { gridItems.add(item); } }