Files
2026-05-26 18:01:57 +05:30

748 lines
29 KiB
Dart

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<Authentication> fetchedLocations = [];
var categories = <Category>[].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<void> 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 = <String>[].obs;
// List<Tenants> getAllTenants = [];
// Grid items
var gridItems = <Map<String, String>>[].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<void> _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<void> _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<String, dynamic> 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<void> _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<Placemark> 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<void> _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<String, dynamic>) {
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<String, dynamic>) {
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<String, String> item) {
gridItems.add(item);
}
}