import 'dart:convert'; import 'dart:developer'; import 'package:flutter/material.dart'; import 'package:get/get.dart'; import 'package:image_picker/image_picker.dart'; import 'package:intl/intl.dart'; import 'package:onufitness/constants/api_endpoints.dart'; import 'package:onufitness/models/user_details_response_model.dart'; import 'package:onufitness/services/api_services/base_api_services.dart'; import 'package:onufitness/utils/custom_sneakbar.dart'; class EditAccountController extends GetxController { // User data UserDetailsResponseModel userDetailsResponseModel = UserDetailsResponseModel(); final ImagePicker _picker = ImagePicker(); var profileImage = Rx(null); var isUploadingImage = false.obs; TextEditingController firstNameController = TextEditingController(); TextEditingController lastNameController = TextEditingController(); TextEditingController aboutMeController = TextEditingController(); TextEditingController facebookController = TextEditingController(); TextEditingController instagramController = TextEditingController(); TextEditingController linkedInController = TextEditingController(); TextEditingController twitterController = TextEditingController(); TextEditingController phoneNumberController = TextEditingController(); // Form Data var selectedDate = "".obs; var selectedGender = 'Male'.obs; var selectedCountryCode = 'IN'.obs; // Loading states var isUpdating = false.obs; var isLoading = false.obs; @override void onInit() { firstNameController = TextEditingController(); lastNameController = TextEditingController(); aboutMeController = TextEditingController(); facebookController = TextEditingController(); instagramController = TextEditingController(); linkedInController = TextEditingController(); twitterController = TextEditingController(); phoneNumberController = TextEditingController(); super.onInit(); loadUserData(); } RxBool fetchUserDetailsLoading = false.obs; Future fetchUserDetails() async { fetchUserDetailsLoading(true); update(); try { final response = await ApiBase.getRequest( extendedURL: ApiUrl.fetchUserDetails, ); if (response.statusCode == 200 || response.statusCode == 201) { userDetailsResponseModel = userDetailsResponseModelFromJson( response.body, ); _populateFormFields(); update(); return true; } else { customSnackbar(title: "Error", message: "Failed to fetch user details"); return false; } } catch (e) { log("Exception: $e"); customSnackbar(title: "Error", message: "Failed to fetch user details"); return false; } finally { fetchUserDetailsLoading(false); update(); } } Future loadUserData() async { isLoading.value = true; final success = await fetchUserDetails(); if (success) { _populateFormFields(); } isLoading.value = false; } void _populateFormFields() { final userData = userDetailsResponseModel.data; if (userData == null) return; firstNameController.text = userData.firstName ?? ''; lastNameController.text = userData.lastName ?? ''; aboutMeController.text = userData.aboutMe ?? ''; facebookController.text = userData.facebookSocialLink ?? ''; instagramController.text = userData.instagramSocialLink ?? ''; linkedInController.text = userData.linkedInSocialLink ?? ''; twitterController.text = userData.twitterSocialLink ?? ''; phoneNumberController.text = userData.phoneNumber ?? ''; selectedGender.value = userData.gender ?? 'Male'; selectedCountryCode.value = userData.countryCode ?? 'IN'; if (userData.dateOfBirth != null) { selectedDate.value = DateFormat( 'yyyy-MM-dd', ).format(userData.dateOfBirth!); } } // Simple age validation........................................................................... bool _isOver18(DateTime birthDate) { final now = DateTime.now(); final age = now.year - birthDate.year; if (now.month < birthDate.month || (now.month == birthDate.month && now.day < birthDate.day)) { return (age - 1) >= 18; } return age >= 18; } Future pickDate(BuildContext context) async { final DateTime? pickedDate = await showDatePicker( context: context, initialDate: DateTime.now().subtract(Duration(days: 18 * 365)), firstDate: DateTime(1800), lastDate: DateTime.now(), ); if (pickedDate != null) { if (_isOver18(pickedDate)) { selectedDate.value = DateFormat('yyyy-MM-dd').format(pickedDate); } else { customSnackbar( title: "Age Restriction", message: "You must be at least 18 years old to proceed.", duration: 2, ); } } } //............................................................................................ Future updateUserProfile() async { isUpdating.value = true; try { final requestBody = { "firstName": firstNameController.text.trim(), "lastName": lastNameController.text.trim(), "aboutMe": aboutMeController.text.trim(), "dateOfBirth": selectedDate.value, "gender": selectedGender.value, "facebookSocialLink": facebookController.text.trim(), "linkedInSocialLink": linkedInController.text.trim(), "instagramSocialLink": instagramController.text.trim(), "twitterSocialLink": twitterController.text.trim(), "phoneNumber": phoneNumberController.text.trim(), "countryCode": selectedCountryCode.value, }; final response = await ApiBase.patchRequest( extendedURL: "/api/Users/update-user", body: requestBody, sendHeaders: true, ); if (response.statusCode == 200 || response.statusCode == 204) { // Refresh user data after successful update await fetchUserDetails(); update(); Get.back(); Future.delayed(const Duration(milliseconds: 300), () { customSnackbar( title: 'Success', message: 'Profile updated successfully', duration: 1, ); }); return true; } else { customSnackbar( title: 'Failed', message: 'Failed to update profile details', ); return false; } } catch (e) { log('Update profile error: $e'); customSnackbar( title: 'Failed', message: 'Failed to update profile details', ); return false; } finally { isUpdating.value = false; } } Future uploadProfilePictureAPIcall() async { if (profileImage.value == null) return; isUploadingImage.value = true; try { final userData = userDetailsResponseModel.data; final userId = userData?.userId; if (userId == null) { customSnackbar(title: "Error", message: "User ID not found"); return; } final response = await ApiBase.postSingleFileRequest( extendedURL: "${ApiUrl.uploadProfileImage}/$userId", sendHeaders: false, file: profileImage.value!, fileNameKey: 'file', ); if (response.statusCode == 200 || response.statusCode == 201) { // Refresh user data to get updated profile image await fetchUserDetails(); customSnackbar( title: "Success", message: "Image uploaded successfully", duration: 1, ); } else { final errorMessage = jsonDecode(response.body)["message"] ?? "Upload failed"; customSnackbar(title: "Error", message: errorMessage); } } catch (e) { log("Upload image error: $e"); customSnackbar(title: "Error", message: "Failed to upload image"); } finally { isUploadingImage.value = false; } } // Getters for UI String? get userProfileImageUrl => userDetailsResponseModel.data?.userProfilePic; bool get hasNetworkImage => userProfileImageUrl?.isNotEmpty == true; //........... Delete user Account............................................................................ RxBool isAccountDeleting = false.obs; Future deleteUserAccount() async { bool ret = false; isAccountDeleting(true); try { final response = await ApiBase.patchRequest( extendedURL: "/api/Users/delete-user-account", ); log(response.body.toString()); if (response.statusCode == 200 || response.statusCode == 201) { ret = true; } else { ret = false; customSnackbar( title: "Deletion Failed", message: "We were unable to delete your account. Please try again later.", duration: 2, ); } } catch (e) { customSnackbar( title: "Deletion Failed", message: "We were unable to delete your account. Please try again later.", duration: 2, ); ret = false; } finally { isAccountDeleting(false); } return ret; } // Form validation............................................................................ bool validateForm() { if (firstNameController.text.trim().isEmpty) { customSnackbar( title: "First Name empty", message: "Please enter your first name", duration: 2, ); return false; } if (lastNameController.text.trim().isEmpty) { customSnackbar( title: "Last Name empty", message: "Please enter your last name", duration: 2, ); return false; } if (phoneNumberController.text.trim().isEmpty) { customSnackbar( title: "Phone Number empty", message: "Please enter your phone number", duration: 2, ); return false; } if (!isValidName(firstNameController.text.trim())) { customSnackbar( title: "First Name Input error", message: "First name should only contain letters, spaces, apostrophes, and hyphens", duration: 2, ); return false; } if (!isValidName(lastNameController.text.trim())) { customSnackbar( title: "Last Name Input error", message: "Last name should only contain letters, spaces, apostrophes, and hyphens", duration: 2, ); return false; } return true; } bool isValidName(String name) { final nameRegex = RegExp(r"^[a-zA-Z\s\-\']+$"); return nameRegex.hasMatch(name) && name.length >= 2; } @override void onClose() { firstNameController.dispose(); lastNameController.dispose(); aboutMeController.dispose(); facebookController.dispose(); instagramController.dispose(); linkedInController.dispose(); twitterController.dispose(); phoneNumberController.dispose(); super.onClose(); } }