From f61a7656245b78be81d5012968739c613f1664d2 Mon Sep 17 00:00:00 2001 From: Harkamal Randhawa Date: Wed, 15 Apr 2026 16:13:50 -0600 Subject: [PATCH] add pet image support --- .../detailfragments/PetDetailFragment.java | 119 +++++++++++++++++- .../viewmodels/PetDetailViewModel.java | 8 ++ .../main/res/layout/fragment_pet_detail.xml | 25 ++++ 3 files changed, 149 insertions(+), 3 deletions(-) diff --git a/android/app/src/main/java/com/example/petstoremobile/fragments/listfragments/detailfragments/PetDetailFragment.java b/android/app/src/main/java/com/example/petstoremobile/fragments/listfragments/detailfragments/PetDetailFragment.java index a22e2c0d..c5e435ef 100644 --- a/android/app/src/main/java/com/example/petstoremobile/fragments/listfragments/detailfragments/PetDetailFragment.java +++ b/android/app/src/main/java/com/example/petstoremobile/fragments/listfragments/detailfragments/PetDetailFragment.java @@ -1,5 +1,6 @@ package com.example.petstoremobile.fragments.listfragments.detailfragments; +import android.net.Uri; import android.os.Bundle; import androidx.annotation.NonNull; @@ -16,7 +17,10 @@ import android.widget.Spinner; import android.widget.TextView; import android.widget.Toast; +import com.bumptech.glide.Glide; +import com.bumptech.glide.load.engine.DiskCacheStrategy; import com.example.petstoremobile.R; +import com.example.petstoremobile.api.PetApi; import com.example.petstoremobile.api.auth.TokenManager; import com.example.petstoremobile.databinding.FragmentPetDetailBinding; import com.example.petstoremobile.dtos.DropdownDTO; @@ -24,18 +28,26 @@ import com.example.petstoremobile.dtos.PetDTO; import com.example.petstoremobile.utils.ActivityLogger; import com.example.petstoremobile.utils.DateTimeUtils; import com.example.petstoremobile.utils.DialogUtils; +import com.example.petstoremobile.utils.FileUtils; +import com.example.petstoremobile.utils.GlideUtils; +import com.example.petstoremobile.utils.ImagePickerHelper; import com.example.petstoremobile.utils.InputValidator; import com.example.petstoremobile.utils.Resource; import com.example.petstoremobile.utils.SpinnerUtils; import com.example.petstoremobile.utils.UIUtils; import com.example.petstoremobile.viewmodels.PetDetailViewModel; +import java.io.File; import java.util.List; import java.util.Locale; import javax.inject.Inject; +import javax.inject.Named; import dagger.hilt.android.AndroidEntryPoint; +import okhttp3.MediaType; +import okhttp3.MultipartBody; +import okhttp3.RequestBody; /** * Fragment for displaying and editing pet details. @@ -45,14 +57,45 @@ public class PetDetailFragment extends Fragment { private FragmentPetDetailBinding binding; private PetDetailViewModel viewModel; + private ImagePickerHelper imagePickerHelper; private boolean isUpdatingUI = false; + private boolean hasImage = false; + private boolean isImageChanged = false; + private boolean isImageRemoved = false; + private Uri photoUri; + + @Inject @Named("baseUrl") String baseUrl; @Inject TokenManager tokenManager; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); viewModel = new ViewModelProvider(this).get(PetDetailViewModel.class); + + imagePickerHelper = new ImagePickerHelper(this, "pet_photo.jpg", new ImagePickerHelper.ImagePickerListener() { + @Override + public void onImagePicked(Uri uri) { + photoUri = uri; + Glide.with(PetDetailFragment.this) + .load(uri) + .diskCacheStrategy(DiskCacheStrategy.NONE) + .skipMemoryCache(true) + .into(binding.ivPetImage); + hasImage = true; + isImageChanged = true; + isImageRemoved = false; + } + + @Override + public void onImageRemoved() { + photoUri = null; + hasImage = false; + isImageChanged = false; + isImageRemoved = true; + binding.ivPetImage.setImageResource(R.drawable.placeholder2); + } + }); } @Override @@ -74,6 +117,7 @@ public class PetDetailFragment extends Fragment { binding.btnBack.setOnClickListener(v -> navigateBack()); binding.btnSavePet.setOnClickListener(v -> savePet()); binding.btnDeletePet.setOnClickListener(v -> deletePet()); + binding.ivPetImage.setOnClickListener(v -> imagePickerHelper.showImagePickerDialog("Select Pet Image", hasImage)); } private void observeViewModel() { @@ -197,14 +241,18 @@ public class PetDetailFragment extends Fragment { if (resource == null) return; setLoading(resource.status == Resource.Status.LOADING); if (resource.status == Resource.Status.SUCCESS) { + if (resource.data != null) { + viewModel.setPetId(resource.data.getPetId()); + } + String msg; if (viewModel.isEditing()) { ActivityLogger.logChange(requireContext(), "Pet", "UPDATED", (int) viewModel.getPetId()); - Toast.makeText(getContext(), "Pet updated successfully!", Toast.LENGTH_SHORT).show(); + msg = "Pet updated successfully!"; } else { ActivityLogger.log(requireContext(), "Added new Pet: " + name); - Toast.makeText(getContext(), "Pet added successfully!", Toast.LENGTH_SHORT).show(); + msg = "Pet added successfully!"; } - navigateToPetList(); + performPendingImageActions(msg); } else if (resource.status == Resource.Status.ERROR) { Toast.makeText(getContext(), "Error: " + resource.message, Toast.LENGTH_SHORT).show(); } @@ -239,10 +287,75 @@ public class PetDetailFragment extends Fragment { if (getArguments() != null && getArguments().containsKey("petId")) { viewModel.setPetId(getArguments().getLong("petId")); loadPetData(); + loadPetImage(); return; } viewModel.setPetId(-1); + hasImage = false; + } + + private void loadPetImage() { + String imageUrl = baseUrl + String.format(Locale.US, PetApi.PET_IMAGE_PATH, viewModel.getPetId()); + String token = tokenManager.getToken(); + GlideUtils.loadImageWithToken(requireContext(), binding.ivPetImage, imageUrl, token, R.drawable.placeholder2, new GlideUtils.ImageLoadListener() { + @Override + public void onResourceReady() { + hasImage = true; + } + + @Override + public void onLoadFailed() { + hasImage = false; + } + }); + } + + private void performPendingImageActions(String successMsg) { + if (isImageRemoved) { + viewModel.deletePetImage().observe(getViewLifecycleOwner(), resource -> { + if (resource == null) return; + setLoading(resource.status == Resource.Status.LOADING); + if (resource.status != Resource.Status.LOADING) { + if (resource.status == Resource.Status.SUCCESS) { + Toast.makeText(getContext(), successMsg, Toast.LENGTH_SHORT).show(); + } else { + Toast.makeText(getContext(), successMsg + " (but image removal failed)", Toast.LENGTH_SHORT).show(); + } + navigateToPetList(); + } + }); + } else if (isImageChanged && photoUri != null) { + uploadPetImageAndNavigate(photoUri, successMsg); + } else { + Toast.makeText(getContext(), successMsg, Toast.LENGTH_SHORT).show(); + navigateToPetList(); + } + } + + private void uploadPetImageAndNavigate(Uri uri, String successMsg) { + File file = FileUtils.getFileFromUri(requireContext(), uri); + if (file == null) { + Toast.makeText(getContext(), successMsg, Toast.LENGTH_SHORT).show(); + navigateToPetList(); + return; + } + + RequestBody requestFile = RequestBody.create(file, MediaType.parse(requireContext().getContentResolver().getType(uri))); + MultipartBody.Part body = MultipartBody.Part.createFormData("image", file.getName(), requestFile); + + viewModel.uploadPetImage(body).observe(getViewLifecycleOwner(), resource -> { + if (resource == null) return; + setLoading(resource.status == Resource.Status.LOADING); + if (resource.status != Resource.Status.LOADING) { + if (resource.status == Resource.Status.SUCCESS) { + Toast.makeText(getContext(), successMsg, Toast.LENGTH_SHORT).show(); + } else { + Toast.makeText(getContext(), successMsg + " (but image upload failed)", Toast.LENGTH_SHORT).show(); + } + navigateToPetList(); + } + }); } private void loadPetData() { diff --git a/android/app/src/main/java/com/example/petstoremobile/viewmodels/PetDetailViewModel.java b/android/app/src/main/java/com/example/petstoremobile/viewmodels/PetDetailViewModel.java index ddb868a7..b1f4a624 100644 --- a/android/app/src/main/java/com/example/petstoremobile/viewmodels/PetDetailViewModel.java +++ b/android/app/src/main/java/com/example/petstoremobile/viewmodels/PetDetailViewModel.java @@ -239,6 +239,14 @@ public class PetDetailViewModel extends ViewModel { return petRepository.deletePet(petId); } + public LiveData> uploadPetImage(okhttp3.MultipartBody.Part image) { + return petRepository.uploadPetImage(petId, image); + } + + public LiveData> deletePetImage() { + return petRepository.deletePetImage(petId); + } + public LiveData> getCustomerList() { return customerList; } diff --git a/android/app/src/main/res/layout/fragment_pet_detail.xml b/android/app/src/main/res/layout/fragment_pet_detail.xml index e42d8e30..53be1557 100644 --- a/android/app/src/main/res/layout/fragment_pet_detail.xml +++ b/android/app/src/main/res/layout/fragment_pet_detail.xml @@ -70,6 +70,31 @@ android:layout_gravity="end" android:layout_marginBottom="8dp"/> + + + + + + + +