add pet image support

This commit is contained in:
2026-04-15 16:13:50 -06:00
parent 7154a4a7ba
commit f61a765624
3 changed files with 149 additions and 3 deletions

View File

@@ -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() {

View File

@@ -239,6 +239,14 @@ public class PetDetailViewModel extends ViewModel {
return petRepository.deletePet(petId);
}
public LiveData<Resource<Void>> uploadPetImage(okhttp3.MultipartBody.Part image) {
return petRepository.uploadPetImage(petId, image);
}
public LiveData<Resource<Void>> deletePetImage() {
return petRepository.deletePetImage(petId);
}
public LiveData<List<DropdownDTO>> getCustomerList() {
return customerList;
}

View File

@@ -70,6 +70,31 @@
android:layout_gravity="end"
android:layout_marginBottom="8dp"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Pet Image"
android:textColor="@color/text_dark"
android:textSize="12sp"
android:layout_marginBottom="4dp"/>
<FrameLayout
android:layout_width="match_parent"
android:layout_height="200dp"
android:layout_marginBottom="16dp">
<ImageView
android:id="@+id/ivPetImage"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scaleType="centerCrop"
android:src="@drawable/placeholder2"
android:background="@color/text_light"
android:clickable="true"
android:focusable="true"/>
</FrameLayout>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"