Merge pull request #316 from RecentRunner/android-desktop-parity
add pet image support
This commit is contained in:
@@ -1,5 +1,6 @@
|
|||||||
package com.example.petstoremobile.fragments.listfragments.detailfragments;
|
package com.example.petstoremobile.fragments.listfragments.detailfragments;
|
||||||
|
|
||||||
|
import android.net.Uri;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
@@ -16,7 +17,10 @@ import android.widget.Spinner;
|
|||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
import android.widget.Toast;
|
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.R;
|
||||||
|
import com.example.petstoremobile.api.PetApi;
|
||||||
import com.example.petstoremobile.api.auth.TokenManager;
|
import com.example.petstoremobile.api.auth.TokenManager;
|
||||||
import com.example.petstoremobile.databinding.FragmentPetDetailBinding;
|
import com.example.petstoremobile.databinding.FragmentPetDetailBinding;
|
||||||
import com.example.petstoremobile.dtos.DropdownDTO;
|
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.ActivityLogger;
|
||||||
import com.example.petstoremobile.utils.DateTimeUtils;
|
import com.example.petstoremobile.utils.DateTimeUtils;
|
||||||
import com.example.petstoremobile.utils.DialogUtils;
|
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.InputValidator;
|
||||||
import com.example.petstoremobile.utils.Resource;
|
import com.example.petstoremobile.utils.Resource;
|
||||||
import com.example.petstoremobile.utils.SpinnerUtils;
|
import com.example.petstoremobile.utils.SpinnerUtils;
|
||||||
import com.example.petstoremobile.utils.UIUtils;
|
import com.example.petstoremobile.utils.UIUtils;
|
||||||
import com.example.petstoremobile.viewmodels.PetDetailViewModel;
|
import com.example.petstoremobile.viewmodels.PetDetailViewModel;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
|
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
|
import javax.inject.Named;
|
||||||
|
|
||||||
import dagger.hilt.android.AndroidEntryPoint;
|
import dagger.hilt.android.AndroidEntryPoint;
|
||||||
|
import okhttp3.MediaType;
|
||||||
|
import okhttp3.MultipartBody;
|
||||||
|
import okhttp3.RequestBody;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fragment for displaying and editing pet details.
|
* Fragment for displaying and editing pet details.
|
||||||
@@ -45,14 +57,45 @@ public class PetDetailFragment extends Fragment {
|
|||||||
|
|
||||||
private FragmentPetDetailBinding binding;
|
private FragmentPetDetailBinding binding;
|
||||||
private PetDetailViewModel viewModel;
|
private PetDetailViewModel viewModel;
|
||||||
|
private ImagePickerHelper imagePickerHelper;
|
||||||
private boolean isUpdatingUI = false;
|
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;
|
@Inject TokenManager tokenManager;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onCreate(Bundle savedInstanceState) {
|
public void onCreate(Bundle savedInstanceState) {
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
viewModel = new ViewModelProvider(this).get(PetDetailViewModel.class);
|
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
|
@Override
|
||||||
@@ -74,6 +117,7 @@ public class PetDetailFragment extends Fragment {
|
|||||||
binding.btnBack.setOnClickListener(v -> navigateBack());
|
binding.btnBack.setOnClickListener(v -> navigateBack());
|
||||||
binding.btnSavePet.setOnClickListener(v -> savePet());
|
binding.btnSavePet.setOnClickListener(v -> savePet());
|
||||||
binding.btnDeletePet.setOnClickListener(v -> deletePet());
|
binding.btnDeletePet.setOnClickListener(v -> deletePet());
|
||||||
|
binding.ivPetImage.setOnClickListener(v -> imagePickerHelper.showImagePickerDialog("Select Pet Image", hasImage));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void observeViewModel() {
|
private void observeViewModel() {
|
||||||
@@ -197,14 +241,18 @@ public class PetDetailFragment extends Fragment {
|
|||||||
if (resource == null) return;
|
if (resource == null) return;
|
||||||
setLoading(resource.status == Resource.Status.LOADING);
|
setLoading(resource.status == Resource.Status.LOADING);
|
||||||
if (resource.status == Resource.Status.SUCCESS) {
|
if (resource.status == Resource.Status.SUCCESS) {
|
||||||
|
if (resource.data != null) {
|
||||||
|
viewModel.setPetId(resource.data.getPetId());
|
||||||
|
}
|
||||||
|
String msg;
|
||||||
if (viewModel.isEditing()) {
|
if (viewModel.isEditing()) {
|
||||||
ActivityLogger.logChange(requireContext(), "Pet", "UPDATED", (int) viewModel.getPetId());
|
ActivityLogger.logChange(requireContext(), "Pet", "UPDATED", (int) viewModel.getPetId());
|
||||||
Toast.makeText(getContext(), "Pet updated successfully!", Toast.LENGTH_SHORT).show();
|
msg = "Pet updated successfully!";
|
||||||
} else {
|
} else {
|
||||||
ActivityLogger.log(requireContext(), "Added new Pet: " + name);
|
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) {
|
} else if (resource.status == Resource.Status.ERROR) {
|
||||||
Toast.makeText(getContext(), "Error: " + resource.message, Toast.LENGTH_SHORT).show();
|
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")) {
|
if (getArguments() != null && getArguments().containsKey("petId")) {
|
||||||
viewModel.setPetId(getArguments().getLong("petId"));
|
viewModel.setPetId(getArguments().getLong("petId"));
|
||||||
loadPetData();
|
loadPetData();
|
||||||
|
loadPetImage();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
viewModel.setPetId(-1);
|
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() {
|
private void loadPetData() {
|
||||||
|
|||||||
@@ -239,6 +239,14 @@ public class PetDetailViewModel extends ViewModel {
|
|||||||
return petRepository.deletePet(petId);
|
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() {
|
public LiveData<List<DropdownDTO>> getCustomerList() {
|
||||||
return customerList;
|
return customerList;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -70,6 +70,31 @@
|
|||||||
android:layout_gravity="end"
|
android:layout_gravity="end"
|
||||||
android:layout_marginBottom="8dp"/>
|
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
|
<TextView
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
|
|||||||
Reference in New Issue
Block a user