import 'dart:convert'; import 'dart:io'; import 'dart:math'; import 'package:flutter/material.dart'; import 'package:get/get.dart' hide Response; import 'package:http/http.dart' as http; import 'package:minio/io.dart'; import 'package:minio/minio.dart'; import 'package:shared_preferences/shared_preferences.dart'; import 'package:shimmer/shimmer.dart'; import 'package:image_picker/image_picker.dart'; import '../../constants/color_constants.dart'; import '../../constants/font_constants.dart'; import '../../controllers/account_controller/profile.dart'; import '../../domain/repository/authentication/auth_repository.dart'; import '../../modules/authentication/auth.dart'; import '../../modules/authentication/getbyid.dart'; import '../../widgets/text_widget.dart'; class EditProfilePage extends StatefulWidget { const EditProfilePage({super.key}); @override State createState() => _EditProfilePageState(); } class _EditProfilePageState extends State { CustomerFullView? profile; bool isLoading = true; File? pickedImage; final AccountController accountController = Get.find(); String Name = ''; String Adress = ''; String Profile = ''; String Number = ''; Future _pickImage() async { final pickedFile = await ImagePicker().pickImage(source: ImageSource.gallery); if (pickedFile != null) { setState(() { pickedImage = File(pickedFile.path); }); } } // Controllers for editable fields final TextEditingController _nameController = TextEditingController(); final TextEditingController _contactController = TextEditingController(); final TextEditingController _dobController = TextEditingController(); final TextEditingController _genderController = TextEditingController(); final TextEditingController _addressController = TextEditingController(); @override void initState() { super.initState(); _loadProfile(); } Future _loadProfile() async { SharedPreferences prefs = await SharedPreferences.getInstance(); int? id = prefs.getInt('customerId'); if (id == null) { Get.snackbar("Error", "Customer ID not found"); return; } setState(() => isLoading = true); final repo = LoginRepository(); final fetchedProfile = await repo.fetchProfile(id.toString()); if (fetchedProfile != null) { _nameController.text = fetchedProfile.firstname ?? ''; _contactController.text = fetchedProfile.contactno ?? ''; _dobController.text = fetchedProfile.dob != null ? fetchedProfile.dob!.toIso8601String() : ''; _genderController.text = fetchedProfile.gender ?? ''; _addressController.text = fetchedProfile.address ?? ''; Name = fetchedProfile.firstname ?? ''; Profile = fetchedProfile.profileimage ?? ''; Number = fetchedProfile.contactno ?? ''; Adress = fetchedProfile.address ?? ''; } setState(() { profile = fetchedProfile; isLoading = false; }); } Future uploadImageAndSave( File selectedImage, int customerId) async { try { var rng = Random(); const String region = "sgp1"; const String accessKey = "DO00NQER7N2FRYZAB2HR"; const String secretKey = "nMDewX25IBEu1FM5dakK+v28/WbW3TzBAwq913+dxP0"; const String bucketName = "nearle"; const String folderName = "deals"; String fileName = 'profile-${rng.nextInt(1000)}-$customerId.jpg'; String endpointUrl = "https://$bucketName.$region.digitaloceanspaces.com/$folderName/$fileName"; // Initialize Minio client final minio = Minio( endPoint: '$region.digitaloceanspaces.com', accessKey: accessKey, secretKey: secretKey, region: region, useSSL: true, ); // Upload file await minio.fPutObject( bucketName, '$folderName/$fileName', selectedImage.path, metadata: { 'Content-Type': 'image/jpeg', 'x-amz-acl': 'public-read', // Set ACL to public-read if needed }, ); print("File uploaded successfully: $endpointUrl"); return endpointUrl; } catch (e) { Get.snackbar("Error", "Image upload failed: $e"); print("Upload error: $e"); return null; } } Future> fetchAddressDetails(String address) async { final url = Uri.parse( 'https://nominatim.openstreetmap.org/search' '?q=${Uri.encodeComponent(address)}' '&format=json' '&addressdetails=1', ); final response = await http.get( url, headers: {'User-Agent': 'FlutterApp'}, ); if (response.statusCode == 200) { final data = jsonDecode(response.body); if (data.isNotEmpty) { final item = data[0]; final addr = item['address'] ?? {}; return { "suburb": addr['suburb'] ?? addr['neighbourhood'] ?? '', "city": addr['city'] ?? addr['town'] ?? addr['village'] ?? '', "state": addr['state'] ?? '', "postcode": addr['postcode'] ?? '', "landmark": addr['road'] ?? addr['attraction'] ?? '', "latitude": item['lat'] ?? '', "longitude": item['lon'] ?? '', }; } } /// fallback (never null) return { "suburb": '', "city": '', "state": '', "postcode": '', "landmark": '', "latitude": '', "longitude": '', }; } Future _updateProfile() async { if (profile == null) { Get.snackbar("Error", "Profile data not loaded"); return; } SharedPreferences prefs = await SharedPreferences.getInstance(); int? customerId = prefs.getInt('customerId'); if (customerId == null) { Get.snackbar("Error", "Customer ID not found"); return; } setState(() => isLoading = true); String? uploadedFileUrl; /// Upload image if selected if (pickedImage != null) { uploadedFileUrl = await uploadImageAndSave(pickedImage!, customerId); } /// 🌍 AUTO-FETCH ADDRESS DETAILS final addressDetails = await fetchAddressDetails(_addressController.text.trim()); final data = { "customerid": customerId, "configid": profile!.configid ?? 1, "firstname": _nameController.text.trim(), "applocationid": profile!.applocationid ?? 91, "contactno": _contactController.text.trim(), "address": _addressController.text.trim(), "gender": _genderController.text.trim(), "dob": _dobController.text.trim(), "profileimage": uploadedFileUrl ?? profile!.profileimage, // ✅ AUTO FILLED "doorno": "", "suburb": addressDetails['suburb'], "city": addressDetails['city'], "state": addressDetails['state'], "postcode": addressDetails['postcode'], "landmark": addressDetails['landmark'], "latitude": addressDetails['latitude'], "longitude": addressDetails['longitude'], }; print("PROFILE UPDATE REQUEST => $data"); try { final repo = LoginRepository(); final response = await repo.updateProfile(data); setState(() => isLoading = false); if (response != null && response['status'] == true) { Get.snackbar("Success", response['message'] ?? "Profile updated"); Navigator.pop(context, true); } else { Get.snackbar( "Error", response?['message'] ?? "Profile update failed", ); } } catch (e) { setState(() => isLoading = false); Get.snackbar("Error", e.toString()); } } List predictions = []; // Replace with your API key final String googleApiKey = "AIzaSyBhkGfnq27sN0wV5y_S-M2KojpFTk_by-Q"; Future searchPlace(String input) async { if (input.isEmpty) { setState(() { predictions = []; }); return; } String url = 'https://maps.googleapis.com/maps/api/place/autocomplete/json?input=$input&types=geocode&components=country:in&key=$googleApiKey'; final response = await http.get(Uri.parse(url)); if (response.statusCode == 200) { final data = json.decode(response.body); setState(() { predictions = data['predictions']; }); } } @override Widget build(BuildContext context) { final Size screenSize = MediaQuery.of(context).size; return SafeArea( top: false, bottom: true, child: Scaffold( backgroundColor: const Color(0xFFFAFAFA), appBar: AppBar( backgroundColor: Colors.white, elevation: 0, shadowColor: Colors.black.withOpacity(0.05), leading: IconButton( icon: Container( padding: const EdgeInsets.all(8), decoration: BoxDecoration( color: Colors.grey.shade100, borderRadius: BorderRadius.circular(10), ), child: const Icon(Icons.arrow_back_ios_new, color: Colors.black87, size: 18), ), onPressed: () => Navigator.of(context).pop(), ), title: const ReusableTextWidget( text: "Edit Profile", color: Colors.black87, fontFamily: FontConstants.fontFamily, fontSize: 20, fontWeight: FontWeight.w700, ), centerTitle: true, ), body: isLoading ? _buildShimmer(screenSize) : SingleChildScrollView( padding: EdgeInsets.symmetric( horizontal: screenSize.width * 0.05, vertical: screenSize.height * 0.02, ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ // Profile Image Section Center( child: Column( children: [ Stack( alignment: Alignment.bottomRight, children: [ Container( decoration: BoxDecoration( shape: BoxShape.circle, gradient: LinearGradient( colors: [ ColorConstants.primaryColor .withOpacity(0.1), ColorConstants.primaryColor .withOpacity(0.05), ], begin: Alignment.topLeft, end: Alignment.bottomRight, ), boxShadow: [ BoxShadow( color: ColorConstants.primaryColor .withOpacity(0.15), blurRadius: 20, offset: const Offset(0, 10), ), ], ), child: Container( margin: const EdgeInsets.all(4), decoration: const BoxDecoration( shape: BoxShape.circle, color: Colors.white, ), child: CircleAvatar( radius: screenSize.height * 0.09, backgroundColor: Colors.grey.shade100, backgroundImage: pickedImage != null ? FileImage(pickedImage!) as ImageProvider : (profile?.profileimage != null && profile!.profileimage!.isNotEmpty ? NetworkImage( profile!.profileimage!) : null), child: (pickedImage == null && (profile?.profileimage == null || profile!.profileimage!.isEmpty)) ? Icon( Icons.person_outline, size: 60, color: Colors.grey.shade400, ) : null, ), ), ), GestureDetector( onTap: _pickImage, child: Container( padding: const EdgeInsets.all(10), decoration: BoxDecoration( color: ColorConstants.primaryColor, shape: BoxShape.circle, border: Border.all( color: Colors.white, width: 3, ), boxShadow: [ BoxShadow( color: ColorConstants.primaryColor .withOpacity(0.3), blurRadius: 8, offset: const Offset(0, 4), ), ], ), child: const Icon( Icons.camera_alt_rounded, color: Colors.white, size: 20, ), ), ), ], ), SizedBox(height: screenSize.height * 0.015), Text( "Tap to change profile photo", style: TextStyle( color: Colors.grey.shade600, fontSize: 13, fontFamily: FontConstants.fontFamily, ), ), ], ), ), SizedBox(height: screenSize.height * 0.04), // Form Section Container( padding: const EdgeInsets.all(20), decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(16), boxShadow: [ BoxShadow( color: Colors.black.withOpacity(0.04), blurRadius: 15, offset: const Offset(0, 4), ), ], ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ _buildLabel("Full Name"), const SizedBox(height: 8), _buildEditableField( _nameController, Icons.person_outline_rounded, "Enter your name", ), SizedBox(height: screenSize.height * 0.025), _buildLabel("Contact Number"), const SizedBox(height: 8), _buildEditableField( _contactController, Icons.phone_outlined, "Enter your phone number", ), SizedBox(height: screenSize.height * 0.025), _buildLabel("Address"), const SizedBox(height: 8), Container( decoration: BoxDecoration( color: const Color(0xFFF5F6FA), borderRadius: BorderRadius.circular(12), border: Border.all( color: Colors.grey.shade200, width: 1, ), ), child: TextFormField( controller: _addressController, style: const TextStyle( fontSize: 15, fontFamily: FontConstants.fontFamily, ), decoration: InputDecoration( hintText: "Search location...", hintStyle: TextStyle( color: Colors.grey.shade400, fontSize: 15, ), filled: true, fillColor: Colors.transparent, contentPadding: const EdgeInsets.symmetric( vertical: 16, horizontal: 16, ), prefixIcon: Icon( Icons.location_on_outlined, color: ColorConstants.primaryColor, size: 22, ), suffixIcon: _addressController.text.isNotEmpty ? IconButton( icon: Icon( Icons.close_rounded, color: Colors.grey.shade400, ), onPressed: () { _addressController.clear(); setState(() { predictions = []; }); }, ) : null, border: InputBorder.none, enabledBorder: InputBorder.none, focusedBorder: OutlineInputBorder( borderRadius: BorderRadius.circular(12), borderSide: BorderSide( color: ColorConstants.primaryColor, width: 1.5, ), ), ), onChanged: (value) { searchPlace(value); setState(() {}); }, ), ), const SizedBox(height: 8), // Display suggestions if (predictions.isNotEmpty) Container( constraints: BoxConstraints( maxHeight: screenSize.height * 0.3, ), decoration: BoxDecoration( color: Colors.white, border: Border.all( color: Colors.grey.shade200, ), borderRadius: BorderRadius.circular(12), boxShadow: [ BoxShadow( color: Colors.black.withOpacity(0.08), blurRadius: 10, offset: const Offset(0, 4), ), ], ), child: ListView.separated( shrinkWrap: true, padding: const EdgeInsets.symmetric(vertical: 8), itemCount: predictions.length, separatorBuilder: (context, index) => Divider( height: 1, color: Colors.grey.shade100, ), itemBuilder: (context, index) { final prediction = predictions[index]; return ListTile( dense: true, leading: Container( padding: const EdgeInsets.all(8), decoration: BoxDecoration( color: ColorConstants.primaryColor .withOpacity(0.1), borderRadius: BorderRadius.circular(8), ), child: Icon( Icons.location_on, color: ColorConstants.primaryColor, size: 18, ), ), title: Text( prediction['description'], style: const TextStyle( fontSize: 14, fontFamily: FontConstants.fontFamily, ), ), onTap: () { _addressController.text = prediction['description']; setState(() { predictions = []; }); FocusScope.of(context).unfocus(); }, ); }, ), ), ], ), ), SizedBox(height: screenSize.height * 0.1), ], ), ), // Bottom Button bottomNavigationBar: Container( padding: EdgeInsets.symmetric( horizontal: screenSize.width * 0.05, vertical: screenSize.height * 0.02, ), decoration: BoxDecoration( color: Colors.white, boxShadow: [ BoxShadow( color: Colors.black.withOpacity(0.05), blurRadius: 10, offset: const Offset(0, -5), ), ], ), child: Container( height: screenSize.height * 0.065, decoration: BoxDecoration( gradient: LinearGradient( colors: [ ColorConstants.primaryColor, ColorConstants.primaryColor.withOpacity(0.8), ], begin: Alignment.centerLeft, end: Alignment.centerRight, ), borderRadius: BorderRadius.circular(14), boxShadow: [ BoxShadow( color: ColorConstants.primaryColor.withOpacity(0.3), blurRadius: 15, offset: const Offset(0, 8), ), ], ), child: ElevatedButton( style: ElevatedButton.styleFrom( backgroundColor: Colors.transparent, shadowColor: Colors.transparent, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(14), ), ), onPressed: _updateProfile, child: const ReusableTextWidget( text: "Update Profile", color: Colors.white, fontFamily: FontConstants.fontFamily, fontSize: 16, fontWeight: FontWeight.w600, ), ), ), ), ), ); } /// Label Widget _buildLabel(String text) { return ReusableTextWidget( text: text, color: const Color(0xFF2D3142), fontFamily: FontConstants.fontFamily, fontSize: 14, fontWeight: FontWeight.w600, ); } /// Editable Text Field Widget _buildEditableField( TextEditingController controller, IconData icon, String hint, ) { return Container( decoration: BoxDecoration( color: const Color(0xFFF5F6FA), borderRadius: BorderRadius.circular(12), border: Border.all( color: Colors.grey.shade200, width: 1, ), ), child: TextFormField( controller: controller, style: const TextStyle( fontSize: 15, fontFamily: FontConstants.fontFamily, ), decoration: InputDecoration( hintText: hint, hintStyle: TextStyle( color: Colors.grey.shade400, fontSize: 15, ), prefixIcon: Icon( icon, color: ColorConstants.primaryColor, size: 22, ), filled: true, fillColor: Colors.transparent, contentPadding: const EdgeInsets.symmetric( vertical: 16, horizontal: 16, ), border: InputBorder.none, enabledBorder: InputBorder.none, focusedBorder: OutlineInputBorder( borderRadius: BorderRadius.circular(12), borderSide: BorderSide( color: ColorConstants.primaryColor, width: 1.5, ), ), ), ), ); } /// Shimmer effect while loading Widget _buildShimmer(Size screenSize) { return Padding( padding: const EdgeInsets.symmetric(horizontal: 20), child: Column( crossAxisAlignment: CrossAxisAlignment.center, children: [ SizedBox(height: screenSize.height * 0.04), /// Profile image shimmer Shimmer.fromColors( baseColor: Colors.grey.shade200, highlightColor: Colors.grey.shade50, child: Container( width: screenSize.height * 0.18, height: screenSize.height * 0.18, decoration: BoxDecoration( shape: BoxShape.circle, color: Colors.grey.shade300, ), ), ), SizedBox(height: screenSize.height * 0.01), /// Helper text shimmer Shimmer.fromColors( baseColor: Colors.grey.shade200, highlightColor: Colors.grey.shade50, child: Container( height: 14, width: screenSize.width * 0.4, decoration: BoxDecoration( color: Colors.grey.shade300, borderRadius: BorderRadius.circular(4), ), ), ), SizedBox(height: screenSize.height * 0.04), /// Form container shimmer Shimmer.fromColors( baseColor: Colors.grey.shade200, highlightColor: Colors.grey.shade50, child: Container( width: double.infinity, padding: const EdgeInsets.all(20), decoration: BoxDecoration( color: Colors.grey.shade300, borderRadius: BorderRadius.circular(16), ), child: Column( children: List.generate( 3, (_) => Padding( padding: const EdgeInsets.symmetric(vertical: 12), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Container( height: 14, width: 100, decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(4), ), ), const SizedBox(height: 8), Container( height: 50, width: double.infinity, decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(12), ), ), ], ), ), ), ), ), ), ], ), ); } }