diff --git a/android/app/build.gradle.kts b/android/app/build.gradle.kts index d1cc3c30..fa8c88a7 100644 --- a/android/app/build.gradle.kts +++ b/android/app/build.gradle.kts @@ -56,6 +56,9 @@ dependencies { implementation("io.reactivex.rxjava2:rxjava:2.2.21") implementation("io.reactivex.rxjava2:rxandroid:2.1.1") + implementation("com.github.bumptech.glide:glide:4.16.0") + annotationProcessor("com.github.bumptech.glide:compiler:4.16.0") + testImplementation(libs.junit) androidTestImplementation(libs.ext.junit) androidTestImplementation(libs.espresso.core) diff --git a/android/app/src/main/java/com/example/petstoremobile/activities/MainActivity.java b/android/app/src/main/java/com/example/petstoremobile/activities/MainActivity.java index be50fd5a..242000c8 100644 --- a/android/app/src/main/java/com/example/petstoremobile/activities/MainActivity.java +++ b/android/app/src/main/java/com/example/petstoremobile/activities/MainActivity.java @@ -39,11 +39,17 @@ public class MainActivity extends AppCompatActivity { super.onCreate(savedInstanceState); // Check if user is already logged in - if (TokenManager.getInstance(this).isLoggedIn()) { - Intent intent = new Intent(this, HomeActivity.class); - startActivity(intent); - finish(); - return; + TokenManager tokenManager = TokenManager.getInstance(this); + if (tokenManager.isLoggedIn()) { + if ("CUSTOMER".equalsIgnoreCase(tokenManager.getRole())) { + // If a customer somehow remained logged in, clear them out + tokenManager.clearLoginData(); + } else { + Intent intent = new Intent(this, HomeActivity.class); + startActivity(intent); + finish(); + return; + } } EdgeToEdge.enable(this); @@ -83,11 +89,20 @@ public class MainActivity extends AppCompatActivity { @Override public void onResponse(Call call, Response response) { if (response.isSuccessful() && response.body() != null) { + String role = response.body().getRole(); + + // Check if the user is a CUSTOMER and deny login if so + if ("CUSTOMER".equalsIgnoreCase(role)) { + Toast.makeText(MainActivity.this, "Access denied: Customers are not allowed to log in.", Toast.LENGTH_LONG).show(); + tvLoginStatus.setText("Customers are not allowed to log in"); + return; + } + //save login data in shared preferences TokenManager.getInstance(MainActivity.this).saveLoginData( response.body().getToken(), response.body().getUsername(), - response.body().getRole() + role ); //fetch user id from api then login to home activity diff --git a/android/app/src/main/java/com/example/petstoremobile/api/RetrofitClient.java b/android/app/src/main/java/com/example/petstoremobile/api/RetrofitClient.java index 8a7f48bc..1500315f 100644 --- a/android/app/src/main/java/com/example/petstoremobile/api/RetrofitClient.java +++ b/android/app/src/main/java/com/example/petstoremobile/api/RetrofitClient.java @@ -1,6 +1,7 @@ package com.example.petstoremobile.api; import android.content.Context; +import android.os.Build; import com.example.petstoremobile.api.auth.AuthApi; import com.example.petstoremobile.api.auth.AuthInterceptor; @@ -12,9 +13,23 @@ import retrofit2.converter.gson.GsonConverterFactory; //Retrofit client Used for API calls public class RetrofitClient { - //base URL - public static final String BASE_URL = "http://10.0.2.2:8080"; //for emulator testing -// public static final String BASE_URL = "http://10.0.0.200:8080/"; //for hardware testing + public static final String BASE_URL = getBaseUrl(); + + // Helper function to determine BASE_URL based on whether we are testing on an emulator or a real device + private static String getBaseUrl() { + if (Build.FINGERPRINT.contains("generic") + || Build.FINGERPRINT.contains("unknown") + || Build.MODEL.contains("google_sdk") + || Build.MODEL.contains("Emulator") + || Build.MODEL.contains("Android SDK built for x86") + || Build.MANUFACTURER.contains("Genymotion") + || (Build.BRAND.startsWith("generic") && Build.DEVICE.startsWith("generic")) + || "google_sdk".equals(Build.PRODUCT)) { + return "http://10.0.2.2:8080/"; //emulator testing + } else { + return "http://10.0.0.200:8080/"; //Hardware testing + } + } private static Retrofit retrofit = null; @@ -67,4 +82,4 @@ public class RetrofitClient { return getClient(context).create(MessageApi.class); } -} \ No newline at end of file +} diff --git a/android/app/src/main/java/com/example/petstoremobile/api/auth/AuthApi.java b/android/app/src/main/java/com/example/petstoremobile/api/auth/AuthApi.java index a60fc536..75605083 100644 --- a/android/app/src/main/java/com/example/petstoremobile/api/auth/AuthApi.java +++ b/android/app/src/main/java/com/example/petstoremobile/api/auth/AuthApi.java @@ -5,15 +5,21 @@ import com.example.petstoremobile.dtos.UserDTO; import java.util.Map; +import okhttp3.MultipartBody; import retrofit2.Call; import retrofit2.http.Body; import retrofit2.http.GET; +import retrofit2.http.Multipart; import retrofit2.http.POST; import retrofit2.http.PUT; +import retrofit2.http.Part; //Api for logging in and getting current user public interface AuthApi { + // endpoint for downloading the current user's avatar file + String AVATAR_FILE_PATH = "api/v1/auth/me/avatar/file"; + //login endpoint @POST("api/v1/auth/login") Call login(@Body AuthDTO.LoginRequest loginRequest); @@ -26,4 +32,9 @@ public interface AuthApi { @PUT("api/v1/auth/me") Call updateMe(@Body Map updates); + //upload avatar endpoint + @Multipart + @POST("api/v1/auth/me/avatar") + Call uploadAvatar(@Part MultipartBody.Part avatar); + } diff --git a/android/app/src/main/java/com/example/petstoremobile/fragments/ListFragment.java b/android/app/src/main/java/com/example/petstoremobile/fragments/ListFragment.java index cd07df63..75d8aacd 100644 --- a/android/app/src/main/java/com/example/petstoremobile/fragments/ListFragment.java +++ b/android/app/src/main/java/com/example/petstoremobile/fragments/ListFragment.java @@ -14,6 +14,7 @@ import android.widget.LinearLayout; import com.example.petstoremobile.R; +import com.example.petstoremobile.api.auth.TokenManager; import com.example.petstoremobile.fragments.listfragments.PetFragment; import com.example.petstoremobile.fragments.listfragments.ServiceFragment; import com.example.petstoremobile.fragments.listfragments.SupplierFragment; @@ -49,6 +50,13 @@ public class ListFragment extends Fragment { drawerInventory = view.findViewById(R.id.drawerInventory); drawerProducts = view.findViewById(R.id.drawerProducts); + // Check user role and restrict access for STAFF + String role = TokenManager.getInstance(requireContext()).getRole(); + if ("STAFF".equalsIgnoreCase(role)) { + drawerSuppliers.setVisibility(View.GONE); + drawerInventory.setVisibility(View.GONE); + } + //needed to disable touches on the innerContainer while the drawer is open touchBlocker = view.findViewById(R.id.touchBlocker); @@ -142,4 +150,4 @@ public class ListFragment extends Fragment { .addToBackStack(null) .commit(); } -} \ No newline at end of file +} diff --git a/android/app/src/main/java/com/example/petstoremobile/fragments/ProfileFragment.java b/android/app/src/main/java/com/example/petstoremobile/fragments/ProfileFragment.java index e754b7fa..c7253c70 100644 --- a/android/app/src/main/java/com/example/petstoremobile/fragments/ProfileFragment.java +++ b/android/app/src/main/java/com/example/petstoremobile/fragments/ProfileFragment.java @@ -26,6 +26,10 @@ import android.widget.ImageView; import android.widget.TextView; import android.widget.Toast; +import com.bumptech.glide.Glide; +import com.bumptech.glide.load.engine.DiskCacheStrategy; +import com.bumptech.glide.load.model.GlideUrl; +import com.bumptech.glide.load.model.LazyHeaders; import com.example.petstoremobile.R; import com.example.petstoremobile.activities.MainActivity; import com.example.petstoremobile.api.RetrofitClient; @@ -38,9 +42,14 @@ import com.example.petstoremobile.utils.InputValidator; import com.google.gson.Gson; import java.io.File; +import java.io.FileOutputStream; +import java.io.InputStream; import java.util.HashMap; import java.util.Map; +import okhttp3.MediaType; +import okhttp3.MultipartBody; +import okhttp3.RequestBody; import retrofit2.Call; import retrofit2.Callback; import retrofit2.Response; @@ -73,8 +82,7 @@ public class ProfileFragment extends Fragment { && result.getData() != null) { //get the selected image and set the image to the profile Uri selectedImage = result.getData().getData(); - imgProfile.setImageURI(selectedImage); - //TODO: SAVE CHANGED PHOTO TO DATABASE + uploadAvatar(selectedImage); } } ); @@ -86,10 +94,7 @@ public class ProfileFragment extends Fragment { success -> { //if a photo is taken set the image profile to it otherwise do nothing if (success) { - //Clear the old image and set the new one - imgProfile.setImageURI(null); - imgProfile.setImageURI(photoUri); - //TODO: SAVE CHANGED PHOTO TO DATABASE + uploadAvatar(photoUri); } } ); @@ -167,7 +172,6 @@ public class ProfileFragment extends Fragment { } }) .show(); - //TODO: UPDATE PHOTO IN DATABASE }); //Edit email button @@ -272,7 +276,31 @@ public class ProfileFragment extends Fragment { tvProfileEmail.setText(currentUser.getEmail()); tvProfilePhone.setText(currentUser.getPhone()); tvProfileRole.setText(currentUser.getRole()); - //TODO: LOAD PHOTO FROM DATABASE + + // get the avatar endpoint to load profile image and the token for authorization + String avatarUrl = RetrofitClient.BASE_URL + AuthApi.AVATAR_FILE_PATH; + String token = TokenManager.getInstance(requireContext()).getToken(); + + if (token != null) { + // Create GlideUrl with token to fetch the image + GlideUrl glideUrl = new GlideUrl(avatarUrl, new LazyHeaders.Builder() + .addHeader("Authorization", "Bearer " + token) + .build()); + + // Load image using Glide + Glide.with(ProfileFragment.this) + .load(glideUrl) + .diskCacheStrategy(DiskCacheStrategy.NONE) + .skipMemoryCache(true) + .placeholder(R.drawable.placeholder) + .error(R.drawable.placeholder) + .into(imgProfile); + } else { + // load placeholder image if token is null + Glide.with(ProfileFragment.this) + .load(R.drawable.placeholder) + .into(imgProfile); + } } else { Log.e("onResponse: ", response.message()); @@ -288,6 +316,62 @@ public class ProfileFragment extends Fragment { }); } + //Helper function to call the backend to upload a profile image + private void uploadAvatar(Uri uri) { + try { + File file = getFileFromUri(uri); + if (file == null) return; + + // Create RequestBody for file upload + RequestBody requestFile = RequestBody.create(file, MediaType.parse(requireContext().getContentResolver().getType(uri))); + MultipartBody.Part body = MultipartBody.Part.createFormData("avatar", file.getName(), requestFile); + + //Call the backend to upload the avatar + AuthApi authApi = RetrofitClient.getAuthApi(requireContext()); + authApi.uploadAvatar(body).enqueue(new Callback() { + @Override + public void onResponse(Call call, Response response) { + if (response.isSuccessful() && response.body() != null) { + currentUser = response.body(); + Toast.makeText(requireContext(), "Avatar updated successfully", Toast.LENGTH_SHORT).show(); + // Reload image after successful upload + loadProfileData(); + } else { + Toast.makeText(requireContext(), "Failed to upload avatar", Toast.LENGTH_SHORT).show(); + } + } + + @Override + public void onFailure(Call call, Throwable t) { + Log.e("UPLOAD_AVATAR", "Failure: " + t.getMessage()); + Toast.makeText(requireContext(), "Network error", Toast.LENGTH_SHORT).show(); + } + }); + } catch (Exception e) { + Log.e("UPLOAD_AVATAR", "Error: " + e.getMessage()); + } + } + + // Helper function to create a temporary File object from a Uri for uploading the avatar + private File getFileFromUri(Uri uri) { + try { + InputStream inputStream = requireContext().getContentResolver().openInputStream(uri); + File tempFile = new File(requireContext().getCacheDir(), "upload_avatar.jpg"); + FileOutputStream outputStream = new FileOutputStream(tempFile); + byte[] buffer = new byte[1024]; + int length; + while ((length = inputStream.read(buffer)) > 0) { + outputStream.write(buffer, 0, length); + } + outputStream.close(); + inputStream.close(); + return tempFile; + } catch (Exception e) { + Log.e("FILE_UTILS", "Error creating temp file", e); + return null; + } + } + //Helper function to update a profile field in the backend private void updateProfileField(String fieldName, String value) { AuthApi authApi = RetrofitClient.getAuthApi(requireContext()); 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 6906bc73..70c19833 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 @@ -25,6 +25,8 @@ import com.example.petstoremobile.api.RetrofitClient; import com.example.petstoremobile.dtos.PetDTO; import com.example.petstoremobile.fragments.ListFragment; import com.example.petstoremobile.fragments.listfragments.PetFragment; +import com.example.petstoremobile.utils.ActivityLogger; +import com.example.petstoremobile.utils.InputValidator; import retrofit2.Call; import retrofit2.Callback; @@ -65,26 +67,27 @@ public class PetDetailFragment extends Fragment { //Method to Update or Add a pet private void savePet() { + // Validates all fields using InputValidator + if (!InputValidator.isNotEmpty(etPetName, "Pet Name")) return; + if (!InputValidator.isNotEmpty(etPetSpecies, "Species")) return; + if (!InputValidator.isNotEmpty(etPetBreed, "Breed")) return; + if (!InputValidator.isPositiveInteger(etPetAge, "Age")) return; + if (!InputValidator.isPositiveDecimal(etPetPrice, "Price")) return; + //get all the values from the fields String name = etPetName.getText().toString().trim(); String species = etPetSpecies.getText().toString().trim(); String breed = etPetBreed.getText().toString().trim(); - String ageStr = etPetAge.getText().toString().trim(); + int age = Integer.parseInt(etPetAge.getText().toString().trim()); String priceStr = etPetPrice.getText().toString().trim(); String status = spinnerPetStatus.getSelectedItem().toString(); - //check if all the fields are filled - if (name.isEmpty() || species.isEmpty() || breed.isEmpty() || ageStr.isEmpty() || priceStr.isEmpty()) { - Toast.makeText(getContext(), "Please fill in all fields", Toast.LENGTH_SHORT).show(); - return; - } - //create a pet object to send to the API PetDTO petDTO = new PetDTO(); petDTO.setPetName(name); petDTO.setPetSpecies(species); petDTO.setPetBreed(breed); - petDTO.setPetAge(Integer.parseInt(ageStr)); + petDTO.setPetAge(age); petDTO.setPetPrice(priceStr); petDTO.setPetStatus(status); @@ -98,6 +101,7 @@ public class PetDetailFragment extends Fragment { @Override public void onResponse(Call call, Response response) { if (response.isSuccessful()) { + ActivityLogger.logChange(requireContext(), "Pet", "UPDATED", petId); Toast.makeText(getContext(), "Pet updated successfully!", Toast.LENGTH_SHORT).show(); navigateBack(); } else { @@ -107,6 +111,7 @@ public class PetDetailFragment extends Fragment { @Override public void onFailure(Call call, Throwable t) { + ActivityLogger.logException(requireContext(), "PetDetailFragment.updatePet", new Exception(t)); Log.e("PetDetailFragment", "Error updating pet", t); Toast.makeText(getContext(), "Error: " + t.getMessage(), Toast.LENGTH_SHORT).show(); } @@ -117,6 +122,7 @@ public class PetDetailFragment extends Fragment { @Override public void onResponse(Call call, Response response) { if (response.isSuccessful()) { + ActivityLogger.log(requireContext(), "Added new Pet: " + name); Toast.makeText(getContext(), "Pet added successfully!", Toast.LENGTH_SHORT).show(); navigateBack(); } else { @@ -126,6 +132,7 @@ public class PetDetailFragment extends Fragment { @Override public void onFailure(Call call, Throwable t) { + ActivityLogger.logException(requireContext(), "PetDetailFragment.createPet", new Exception(t)); Log.e("PetDetailFragment", "Error adding pet", t); Toast.makeText(getContext(), "Error: " + t.getMessage(), Toast.LENGTH_SHORT).show(); } @@ -146,6 +153,7 @@ public class PetDetailFragment extends Fragment { @Override public void onResponse(Call call, Response response) { if (response.isSuccessful()) { + ActivityLogger.logChange(requireContext(), "Pet", "DELETED", petId); Toast.makeText(getContext(), "Pet deleted successfully!", Toast.LENGTH_SHORT).show(); navigateBack(); } else { @@ -155,6 +163,7 @@ public class PetDetailFragment extends Fragment { @Override public void onFailure(Call call, Throwable t) { + ActivityLogger.logException(requireContext(), "PetDetailFragment.deletePet", new Exception(t)); Log.e("PetDetailFragment", "Error deleting pet", t); Toast.makeText(getContext(), "Error: " + t.getMessage(), Toast.LENGTH_SHORT).show(); } diff --git a/android/app/src/main/java/com/example/petstoremobile/fragments/listfragments/detailfragments/ServiceDetailFragment.java b/android/app/src/main/java/com/example/petstoremobile/fragments/listfragments/detailfragments/ServiceDetailFragment.java index 2defbb69..c7b0a4c5 100644 --- a/android/app/src/main/java/com/example/petstoremobile/fragments/listfragments/detailfragments/ServiceDetailFragment.java +++ b/android/app/src/main/java/com/example/petstoremobile/fragments/listfragments/detailfragments/ServiceDetailFragment.java @@ -20,6 +20,8 @@ import com.example.petstoremobile.api.ServiceApi; import com.example.petstoremobile.dtos.ServiceDTO; import com.example.petstoremobile.fragments.ListFragment; import com.example.petstoremobile.fragments.listfragments.ServiceFragment; +import com.example.petstoremobile.utils.ActivityLogger; +import com.example.petstoremobile.utils.InputValidator; import retrofit2.Call; import retrofit2.Callback; @@ -58,24 +60,24 @@ public class ServiceDetailFragment extends Fragment { //Method to Update or Add a service private void saveService() { + // Validates all fields using InputValidator + if (!InputValidator.isNotEmpty(etServiceName, "Service Name")) return; + if (!InputValidator.isNotEmpty(etServiceDesc, "Description")) return; + if (!InputValidator.isPositiveInteger(etServiceDuration, "Duration")) return; + if (!InputValidator.isPositiveDecimal(etServicePrice, "Price")) return; + //get all the values from the fields String name = etServiceName.getText().toString().trim(); String desc = etServiceDesc.getText().toString().trim(); - String durationStr = etServiceDuration.getText().toString().trim(); - String priceStr = etServicePrice.getText().toString().trim(); - - //check if all the fields are filled (desc is optional) - if (name.isEmpty() || durationStr.isEmpty() || priceStr.isEmpty()) { - Toast.makeText(getContext(), "Please fill in all fields", Toast.LENGTH_SHORT).show(); - return; - } + int duration = Integer.parseInt(etServiceDuration.getText().toString().trim()); + double price = Double.parseDouble(etServicePrice.getText().toString().trim()); //create a service object to send to the API ServiceDTO serviceDTO = new ServiceDTO(); serviceDTO.setServiceName(name); serviceDTO.setServiceDesc(desc); - serviceDTO.setServiceDuration(Integer.parseInt(durationStr)); - serviceDTO.setServicePrice(Double.parseDouble(priceStr)); + serviceDTO.setServiceDuration(duration); + serviceDTO.setServicePrice(price); ServiceApi serviceApi = RetrofitClient.getServiceApi(requireContext()); @@ -87,6 +89,7 @@ public class ServiceDetailFragment extends Fragment { @Override public void onResponse(Call call, Response response) { if (response.isSuccessful()) { + ActivityLogger.logChange(requireContext(), "Service", "UPDATED", serviceId); Toast.makeText(getContext(), "Service updated successfully!", Toast.LENGTH_SHORT).show(); navigateBack(); } else { @@ -96,6 +99,7 @@ public class ServiceDetailFragment extends Fragment { @Override public void onFailure(Call call, Throwable t) { + ActivityLogger.logException(requireContext(), "ServiceDetailFragment.updateService", new Exception(t)); Log.e("ServiceDetailFragment", "Error updating service", t); Toast.makeText(getContext(), "Error: " + t.getMessage(), Toast.LENGTH_SHORT).show(); } @@ -106,6 +110,7 @@ public class ServiceDetailFragment extends Fragment { @Override public void onResponse(Call call, Response response) { if (response.isSuccessful()) { + ActivityLogger.log(requireContext(), "Added new Service: " + name); Toast.makeText(getContext(), "Service added successfully!", Toast.LENGTH_SHORT).show(); navigateBack(); } else { @@ -115,6 +120,7 @@ public class ServiceDetailFragment extends Fragment { @Override public void onFailure(Call call, Throwable t) { + ActivityLogger.logException(requireContext(), "ServiceDetailFragment.createService", new Exception(t)); Log.e("ServiceDetailFragment", "Error adding service", t); Toast.makeText(getContext(), "Error: " + t.getMessage(), Toast.LENGTH_SHORT).show(); } @@ -134,6 +140,7 @@ public class ServiceDetailFragment extends Fragment { @Override public void onResponse(Call call, Response response) { if (response.isSuccessful()) { + ActivityLogger.logChange(requireContext(), "Service", "DELETED", serviceId); Toast.makeText(getContext(), "Service deleted successfully!", Toast.LENGTH_SHORT).show(); navigateBack(); } else { @@ -143,6 +150,7 @@ public class ServiceDetailFragment extends Fragment { @Override public void onFailure(Call call, Throwable t) { + ActivityLogger.logException(requireContext(), "ServiceDetailFragment.deleteService", new Exception(t)); Log.e("ServiceDetailFragment", "Error deleting service", t); Toast.makeText(getContext(), "Error: " + t.getMessage(), Toast.LENGTH_SHORT).show(); } diff --git a/android/app/src/main/java/com/example/petstoremobile/fragments/listfragments/detailfragments/SupplierDetailFragment.java b/android/app/src/main/java/com/example/petstoremobile/fragments/listfragments/detailfragments/SupplierDetailFragment.java index 8537d6c2..df5c5520 100644 --- a/android/app/src/main/java/com/example/petstoremobile/fragments/listfragments/detailfragments/SupplierDetailFragment.java +++ b/android/app/src/main/java/com/example/petstoremobile/fragments/listfragments/detailfragments/SupplierDetailFragment.java @@ -20,6 +20,8 @@ import com.example.petstoremobile.api.SupplierApi; import com.example.petstoremobile.dtos.SupplierDTO; import com.example.petstoremobile.fragments.ListFragment; import com.example.petstoremobile.fragments.listfragments.SupplierFragment; +import com.example.petstoremobile.utils.ActivityLogger; +import com.example.petstoremobile.utils.InputValidator; import retrofit2.Call; import retrofit2.Callback; @@ -58,6 +60,13 @@ public class SupplierDetailFragment extends Fragment { //Method to Update or Add a supplier private void saveSupplier() { + // Validates all fields using InputValidator + if (!InputValidator.isNotEmpty(etSupCompany, "Company Name")) return; + if (!InputValidator.isNotEmpty(etSupContactFirstName, "First Name")) return; + if (!InputValidator.isNotEmpty(etSupContactLastName, "Last Name")) return; + if (!InputValidator.isValidEmail(etSupEmail)) return; + if (!InputValidator.isValidPhone(etSupPhone)) return; + //get all the values from the fields String company = etSupCompany.getText().toString().trim(); String firstName = etSupContactFirstName.getText().toString().trim(); @@ -65,12 +74,6 @@ public class SupplierDetailFragment extends Fragment { String email = etSupEmail.getText().toString().trim(); String phone = etSupPhone.getText().toString().trim(); - //check if all the fields are filled - if (company.isEmpty() || firstName.isEmpty() || lastName.isEmpty() || email.isEmpty() || phone.isEmpty()) { - Toast.makeText(getContext(), "Please fill in all fields", Toast.LENGTH_SHORT).show(); - return; - } - //create a supplier object to send to the API SupplierDTO supplierDTO = new SupplierDTO(); supplierDTO.setSupCompany(company); @@ -89,6 +92,7 @@ public class SupplierDetailFragment extends Fragment { @Override public void onResponse(Call call, Response response) { if (response.isSuccessful()) { + ActivityLogger.logChange(requireContext(), "Supplier", "UPDATED", supId); Toast.makeText(getContext(), "Supplier updated successfully!", Toast.LENGTH_SHORT).show(); navigateBack(); } else { @@ -98,6 +102,7 @@ public class SupplierDetailFragment extends Fragment { @Override public void onFailure(Call call, Throwable t) { + ActivityLogger.logException(requireContext(), "SupplierDetailFragment.updateSupplier", new Exception(t)); Log.e("SupplierDetailFragment", "Error updating supplier", t); Toast.makeText(getContext(), "Error: " + t.getMessage(), Toast.LENGTH_SHORT).show(); } @@ -108,6 +113,7 @@ public class SupplierDetailFragment extends Fragment { @Override public void onResponse(Call call, Response response) { if (response.isSuccessful()) { + ActivityLogger.log(requireContext(), "Added new Supplier: " + company); Toast.makeText(getContext(), "Supplier added successfully!", Toast.LENGTH_SHORT).show(); navigateBack(); } else { @@ -117,6 +123,7 @@ public class SupplierDetailFragment extends Fragment { @Override public void onFailure(Call call, Throwable t) { + ActivityLogger.logException(requireContext(), "SupplierDetailFragment.createSupplier", new Exception(t)); Log.e("SupplierDetailFragment", "Error adding supplier", t); Toast.makeText(getContext(), "Error: " + t.getMessage(), Toast.LENGTH_SHORT).show(); } @@ -136,6 +143,7 @@ public class SupplierDetailFragment extends Fragment { @Override public void onResponse(Call call, Response response) { if (response.isSuccessful()) { + ActivityLogger.logChange(requireContext(), "Supplier", "DELETED", supId); Toast.makeText(getContext(), "Supplier deleted successfully!", Toast.LENGTH_SHORT).show(); navigateBack(); } else { @@ -145,6 +153,7 @@ public class SupplierDetailFragment extends Fragment { @Override public void onFailure(Call call, Throwable t) { + ActivityLogger.logException(requireContext(), "SupplierDetailFragment.deleteSupplier", new Exception(t)); Log.e("SupplierDetailFragment", "Error deleting supplier", t); Toast.makeText(getContext(), "Error: " + t.getMessage(), Toast.LENGTH_SHORT).show(); } @@ -197,6 +206,11 @@ public class SupplierDetailFragment extends Fragment { etSupContactLastName = view.findViewById(R.id.etSupContactLastName); etSupEmail = view.findViewById(R.id.etSupEmail); etSupPhone = view.findViewById(R.id.etSupPhone); + + // Add phone number formatting (CA) and limit length to 14 characters + etSupPhone.addTextChangedListener(new android.telephony.PhoneNumberFormattingTextWatcher("CA")); + etSupPhone.setFilters(new android.text.InputFilter[]{new android.text.InputFilter.LengthFilter(14)}); + btnSaveSupplier = view.findViewById(R.id.btnSaveSupplier); btnDeleteSupplier = view.findViewById(R.id.btnDeleteSupplier); btnBack = view.findViewById(R.id.btnBack);