import 'package:flutter/material.dart'; import 'package:get/get.dart'; import 'package:google_maps_flutter/google_maps_flutter.dart'; import 'package:google_places_flutter/google_places_flutter.dart'; import 'package:google_places_flutter/model/prediction.dart'; import 'package:nearledaily/constants/color_constants.dart'; import 'package:nearledaily/view/cart/cart_view.dart'; import 'package:shared_preferences/shared_preferences.dart'; import 'package:geolocator/geolocator.dart'; import 'package:geocoding/geocoding.dart'; import 'package:flutter/material.dart'; import 'package:get/get.dart'; import '../../../modules/authentication/auth.dart'; import '../../constants/font_constants.dart'; import '../../domain/provider/authentication/location.dart'; import '../../main.dart'; import '../../widgets/text_widget.dart'; class LocationPage extends StatefulWidget { const LocationPage({super.key}); @override State createState() => _LocationPageState(); } class _LocationPageState extends State with RouteAware { final CustomerLocationProvider locationProvider = CustomerLocationProvider(); List fetchedLocations = []; bool isLoading = true; String? newAddress; String? newLat; String? newLong; int? selectedLocationId; Authentication? selectedLocation; String searchQuery = ""; @override void initState() { super.initState(); _fetchLocations(); } @override void didPopNext() { _fetchLocations(); super.didPopNext(); } @override void didChangeDependencies() { super.didChangeDependencies(); routeObserver.subscribe(this, ModalRoute.of(context)!); } @override void dispose() { routeObserver.unsubscribe(this); super.dispose(); } Future _fetchLocations() async { SharedPreferences prefs = await SharedPreferences.getInstance(); final id = prefs.getInt('customerId'); setState(() => isLoading = true); try { final locations = await locationProvider.fetchCustomerLocations(id!); setState(() { fetchedLocations = locations; }); } catch (e) { print('Error fetching locations: $e'); } finally { setState(() => isLoading = false); } } Future _addNewAddress() async { await Get.to(() => const MapPickerPage())?.then((result) async { if (result == true) { print("Refreshing locations now ✅"); await _fetchLocations(); } }); } Widget _badge({ required IconData icon, required String label, required bool isSelected, }) { const primaryColor = Color(0xFF662582); return ConstrainedBox( constraints: const BoxConstraints(maxWidth: 220), // ✅ prevents overflow child: Container( padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 3), decoration: BoxDecoration( color: isSelected ? const Color(0xFFF3E8FA) : Colors.grey.shade100, borderRadius: BorderRadius.circular(20), ), child: Row( mainAxisSize: MainAxisSize.min, children: [ Icon( icon, size: 10, color: isSelected ? primaryColor : Colors.grey.shade500, ), const SizedBox(width: 4), Flexible( // ✅ allows text to shrink and ellipsis child: Text( label, maxLines: 1, overflow: TextOverflow.ellipsis, style: TextStyle( fontSize: 11, fontFamily: FontConstants.fontFamily, color: isSelected ? primaryColor : Colors.grey.shade500, ), ), ), ], ), ), ); } Widget _addressCard({ required String address, required String doorNo, required String landmark, required VoidCallback onTap, required bool isSelected, bool isAddNew = false, }) { const primaryColor = Color(0xFF662582); if (isAddNew) { return GestureDetector( onTap: onTap, behavior: HitTestBehavior.opaque, child: Container( margin: const EdgeInsets.symmetric(vertical: 5), padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 14), decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(14), border: Border.all( color: primaryColor.withOpacity(0.35), width: 1, ), ), child: Row( children: [ Container( width: 34, height: 34, decoration: const BoxDecoration( color: Color(0xFFF3E8FA), shape: BoxShape.circle, ), child: const Icon( Icons.add_location_alt_rounded, size: 17, color: primaryColor, ), ), const SizedBox(width: 12), ReusableTextWidget( text: "Add new address", fontSize: 13, fontWeight: FontWeight.w500, fontFamily: FontConstants.fontFamily, color: primaryColor, maxLines: 1, overflow: TextOverflow.ellipsis, ), ], ), ), ); } return GestureDetector( onTap: onTap, behavior: HitTestBehavior.opaque, child: AnimatedContainer( duration: const Duration(milliseconds: 200), curve: Curves.easeInOut, margin: const EdgeInsets.symmetric(vertical: 5), padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 14), decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(14), border: Border.all( color: isSelected ? primaryColor : Colors.grey.withOpacity(0.25), width: isSelected ? 1.5 : 0.5, ), ), child: Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ // Icon circle AnimatedContainer( duration: const Duration(milliseconds: 200), width: 34, height: 34, decoration: BoxDecoration( color: isSelected ? const Color(0xFFF3E8FA) : Colors.grey.shade100, shape: BoxShape.circle, ), child: Icon( Icons.location_on_rounded, size: 17, color: isSelected ? primaryColor : Colors.grey.shade500, ), ), const SizedBox(width: 12), // Address + badges — Expanded so it never overflows Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ // Main address bold (first 2 parts) ReusableTextWidget( text: address.split(',').take(2).join(',').trim(), fontSize: 13, fontWeight: FontWeight.w500, fontFamily: FontConstants.fontFamily, color: Colors.black.withOpacity(0.87), maxLines: 1, overflow: TextOverflow.ellipsis, ), const SizedBox(height: 2), // Rest of address muted ReusableTextWidget( text: address.split(',').skip(2).join(',').trim(), fontSize: 12, fontWeight: FontWeight.w400, fontFamily: FontConstants.fontFamily, color: Colors.grey.shade500, maxLines: 1, overflow: TextOverflow.ellipsis, ), const SizedBox(height: 8), // Badges — each individually constrained if (doorNo.isNotEmpty || landmark.isNotEmpty) Wrap( spacing: 6, runSpacing: 4, children: [ if (doorNo.isNotEmpty) _badge( icon: Icons.door_front_door_outlined, label: "Door: $doorNo", isSelected: isSelected, ), if (landmark.isNotEmpty) _badge( icon: Icons.near_me_outlined, label: "Near: $landmark", isSelected: false, ), ], ), ], ), ), const SizedBox(width: 10), // Radio indicator AnimatedContainer( duration: const Duration(milliseconds: 200), width: 18, height: 18, margin: const EdgeInsets.only(top: 2), decoration: BoxDecoration( shape: BoxShape.circle, border: Border.all( color: isSelected ? primaryColor : Colors.grey.withOpacity(0.4), width: 1.5, ), ), child: AnimatedScale( duration: const Duration(milliseconds: 200), scale: isSelected ? 1 : 0, child: Center( child: Container( width: 8, height: 8, decoration: const BoxDecoration( color: primaryColor, shape: BoxShape.circle, ), ), ), ), ), ], ), ), ); } List _buildAddressList() { List list = []; // 1️⃣ Add API fetched addresses for (var loc in fetchedLocations) { final addressText = loc.address ?? ''; if (addressText.toLowerCase().contains(searchQuery.toLowerCase())) { list.add(_addressCard( address: addressText, doorNo: loc.doorno ?? '', landmark: loc.landmark ?? '', isSelected: selectedLocationId == loc.locationid, onTap: () { setState(() { selectedLocationId = loc.locationid; selectedLocation = loc; }); }, )); } } // 2️⃣ Add new address (default, unchanged) if (newAddress != null && newAddress!.toLowerCase().contains(searchQuery.toLowerCase())) { list.add(_addressCard( address: newAddress!, doorNo: '', landmark: '', isSelected: selectedLocationId == -1, onTap: () { setState(() { selectedLocationId = -1; selectedLocation = Authentication( locationid: 0, customerid: "0", address: newAddress ?? "", suburb: "", city: "", state: "", landmark: "", doorno: "", postcode: "", latitude: newLat ?? "", longitude: newLong ?? "", ); }); }, )); } // 3️⃣ Always show "Add New Address" option list.add(_addressCard( address: "Add new address", doorNo: '', landmark: '', isSelected: false, isAddNew: true, onTap: _addNewAddress, )); return list; } void _showPaymentBottomSheet() { if (selectedLocation != null) { print("Selected Location Details:"); print("locationid: ${selectedLocation!.locationid}"); print("customerid: ${selectedLocation!.customerid}"); print("address: ${selectedLocation!.address}"); print("suburb: ${selectedLocation!.suburb}"); print("city: ${selectedLocation!.city}"); print("state: ${selectedLocation!.state}"); print("landmark: ${selectedLocation!.landmark}"); print("doorno: ${selectedLocation!.doorno}"); print("postcode: ${selectedLocation!.postcode}"); print("latitude: ${selectedLocation!.latitude}"); print("longitude: ${selectedLocation!.longitude}"); Navigator.pop(context, selectedLocation); } } @override Widget build(BuildContext context) { return SafeArea( top: false, bottom: true, child: Scaffold( backgroundColor: Colors.white, appBar: AppBar( backgroundColor: Colors.white, elevation: 1, leadingWidth: double.infinity, centerTitle: false, leading: Row( children: [ IconButton( icon: const Icon(Icons.arrow_back, color: Colors.black), onPressed: () => Navigator.of(context).pop(), ), ReusableTextWidget( text: "Select Location", color: Colors.black, fontFamily: FontConstants.fontFamily, fontSize: 20, fontWeight: FontWeight.bold, overflow: TextOverflow.ellipsis, maxLines: 1, ), ], ), actions: [ IconButton( icon: const Icon(Icons.add_location_alt, color: Color(0xFF662582)), tooltip: "Add New Location", onPressed: _addNewAddress, ), ], ), body: Padding( padding: const EdgeInsets.all(16.0), child: Column( children: [ TextField( onChanged: (val) { setState(() => searchQuery = val); }, decoration: InputDecoration( hintText: "Search Address", prefixIcon: const Icon(Icons.search), filled: true, fillColor: Colors.grey.shade100, border: OutlineInputBorder( borderRadius: BorderRadius.circular(12), borderSide: BorderSide.none, ), ), ), const SizedBox(height: 16), Expanded( child: isLoading ? const Center(child: CircularProgressIndicator()) : ListView( children: _buildAddressList(), ), ), ], ), ), bottomNavigationBar: Padding( padding: const EdgeInsets.all(16.0), child: ElevatedButton( onPressed: selectedLocationId == null ? null : _showPaymentBottomSheet, style: ElevatedButton.styleFrom( backgroundColor: const Color(0xFF662582), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(8), ), padding: const EdgeInsets.symmetric(vertical: 16), ), child: ReusableTextWidget( text: "Confirm Address", color: Colors.white, fontFamily: FontConstants.fontFamily, fontSize: 16, overflow: TextOverflow.ellipsis, maxLines: 1, ), ), ), ), ); } } class MapPickerPage extends StatefulWidget { const MapPickerPage({super.key}); @override State createState() => _MapPickerPageState(); } class _MapPickerPageState extends State { LatLng? selectedLatLng; String? selectedAddress; GoogleMapController? mapController; LatLng currentLatLng = const LatLng(11.0168, 76.9558); // default Coimbatore static const String googleApiKey = "AIzaSyBhkGfnq27sN0wV5y_S-M2KojpFTk_by-Q"; @override void initState() { super.initState(); _checkPermissionAndGetLocation(); } // Search function Future _checkPermissionAndGetLocation() async { bool serviceEnabled = await Geolocator.isLocationServiceEnabled(); if (!serviceEnabled) { Get.snackbar("Location Disabled", "Please enable location services"); return; } LocationPermission permission = await Geolocator.checkPermission(); if (permission == LocationPermission.denied) { permission = await Geolocator.requestPermission(); } if (permission == LocationPermission.deniedForever) { Get.snackbar("Permission Denied", "Location permission is permanently denied, please enable it in settings"); return; } if (permission == LocationPermission.whileInUse || permission == LocationPermission.always) { await _goToCurrentLocation(); } } Future _getAddressFromLatLng(LatLng latLng) async { setState(() { selectedAddress = "Loading address..."; }); try { List placemarks = await placemarkFromCoordinates(latLng.latitude, latLng.longitude); if (placemarks.isNotEmpty) { final place = placemarks.first; setState(() { selectedAddress = "${place.name}, ${place.locality}, ${place.administrativeArea}, ${place.postalCode}"; }); } else { setState(() { selectedAddress = "Unknown location"; }); } } catch (e) { setState(() { selectedAddress = "Failed to get address"; }); } } Future _goToCurrentLocation() async { try { Position position = await Geolocator.getCurrentPosition( desiredAccuracy: LocationAccuracy.high); LatLng latLng = LatLng(position.latitude, position.longitude); setState(() { selectedLatLng = latLng; }); mapController?.animateCamera(CameraUpdate.newLatLngZoom(latLng, 16)); await _getAddressFromLatLng(latLng); } catch (e) { // Get.snackbar(); } } @override Widget build(BuildContext context) { return SafeArea( top: false, child: Scaffold( backgroundColor: Colors.white, appBar: AppBar( backgroundColor: Colors.white, title: const Text("Pick Location"), actions: [ IconButton( onPressed: _goToCurrentLocation, icon: Container( decoration: BoxDecoration( color: Colors.white, shape: BoxShape.circle, boxShadow: [ BoxShadow( color: Colors.black26, blurRadius: 4, offset: Offset(0, 2), ), ], ), child: const Padding( padding: EdgeInsets.all(8.0), child: Icon(Icons.my_location, color: Colors.black), ), ), ), ], ), body: Stack( children: [ GoogleMap( initialCameraPosition: CameraPosition(target: currentLatLng, zoom: 14), onMapCreated: (controller) => mapController = controller, onTap: (latLng) async { setState(() { selectedLatLng = latLng; }); await _getAddressFromLatLng(latLng); }, markers: selectedLatLng != null ? { Marker( markerId: const MarkerId("picked"), position: selectedLatLng!) } : {}, myLocationEnabled: true, myLocationButtonEnabled: false, ), // Floating button for current location // Address card if (selectedAddress != null) Positioned( bottom: 80, left: 16, right: 16, child: Card( child: Padding( padding: const EdgeInsets.all(12), child: Text( selectedAddress!, style: const TextStyle(fontSize: 14), ), ), ), ), ], ), bottomNavigationBar: Padding( padding: const EdgeInsets.all(16), child: ElevatedButton( onPressed: selectedLatLng == null ? null : () async { String address = selectedAddress ?? ""; String suburb = ""; String city = ""; String state = ""; String postcode = ""; try { List placemarks = await placemarkFromCoordinates( selectedLatLng!.latitude, selectedLatLng!.longitude); if (placemarks.isNotEmpty) { final place = placemarks.first; suburb = place.subLocality ?? ""; city = place.locality ?? ""; state = place.administrativeArea ?? ""; postcode = place.postalCode ?? ""; final result = await Get.to(() => AddressDetailsPage( address: address, suburb: suburb, city: city, state: state, postcode: postcode, latitude: selectedLatLng!.latitude.toString(), longitude: selectedLatLng!.longitude.toString(), )); if (result == true) { Get.back(result: true); } } } catch (e) { print("Error parsing placemark: $e"); } }, style: ElevatedButton.styleFrom( backgroundColor: ColorConstants.primaryColor, padding: const EdgeInsets.symmetric(vertical: 16)), child: const Text( "Confirm Location", style: TextStyle(color: Colors.white), ), ), ), ), ); } } class AddressDetailsPage extends StatefulWidget { final String address; final String? suburb; final String? city; final String? state; final String? postcode; final String? latitude; final String? longitude; const AddressDetailsPage({ super.key, required this.address, this.suburb, this.city, this.state, this.postcode, this.latitude, this.longitude, }); @override State createState() => _AddressDetailsPageState(); } class _AddressDetailsPageState extends State { final _formKey = GlobalKey(); late TextEditingController addressController; late TextEditingController doorController; late TextEditingController landmarkController; bool isLoading = false; final CustomerLocationProvider provider = CustomerLocationProvider(); @override void initState() { super.initState(); addressController = TextEditingController(text: widget.address); doorController = TextEditingController(); landmarkController = TextEditingController(); } @override void dispose() { addressController.dispose(); doorController.dispose(); landmarkController.dispose(); super.dispose(); } void submitAddress() async { if (!_formKey.currentState!.validate()) return; setState(() => isLoading = true); final SharedPreferences prefs = await SharedPreferences.getInstance(); final id = prefs.getInt('customerId'); final success = await provider.createCustomerLocation( customerId: id!, // Replace with your dynamic customer ID address: addressController.text, doorNo: doorController.text, landmark: landmarkController.text, suburb: widget.suburb ?? "", city: widget.city ?? "", state: widget.state ?? "", postcode: widget.postcode ?? "", latitude: widget.latitude ?? "", longitude: widget.longitude ?? "", defaultAddress: "Yes", primaryAddress: 1, status: 1, ); setState(() => isLoading = false); Get.until((route) => route.settings.name == '/LocationPage'); if (success == true) { print("API Success ✅"); Get.snackbar("Success", "Address submitted successfully"); await Future.delayed(const Duration(milliseconds: 800)); Get.back(result: true); } else { print("API failed ❌"); Get.snackbar("Error", "Failed to submit address"); } } @override Widget build(BuildContext context) { return SafeArea( child: Scaffold( backgroundColor: Colors.grey[200], appBar: AppBar(title: const Text("Edit Address"),backgroundColor: Colors.grey[200],), body: Padding( padding: const EdgeInsets.all(16), child: Form( key: _formKey, child: ListView( children: [ _buildTextField("Address", addressController), const SizedBox(height: 12), _buildTextField("Door Number", doorController), const SizedBox(height: 12), _buildTextField("Landmark", landmarkController), const SizedBox(height: 20), ElevatedButton( style: ElevatedButton.styleFrom( backgroundColor: const Color(0xFF662582), // Purple color padding: const EdgeInsets.symmetric(vertical: 14), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(12), ), minimumSize: const Size(double.infinity, 50), // full width ), onPressed: isLoading ? null : submitAddress, child: isLoading ? const CircularProgressIndicator(color: Colors.white) : const Text( "Submit Address", style: TextStyle(color: Colors.white, fontSize: 16), ), ) ], ), ), ), ), ); } Widget _buildTextField(String label, TextEditingController controller) { return TextFormField( controller: controller, decoration: InputDecoration( labelText: label, border: const OutlineInputBorder(), ), validator: (value) => value == null || value.isEmpty ? "Enter $label" : null, ); } }