From 453cb54f19dadc4f5d89e96a8869f3924205dd5d Mon Sep 17 00:00:00 2001 From: Alex <78383757+Lextical@users.noreply.github.com> Date: Sat, 4 Apr 2026 23:35:38 -0600 Subject: [PATCH] Refactored Andriod project to use MVVM structure (Need to apply this so sales too after merge) - Used MVVM structure so fragments are not doing all the operation from views to data and calls - organized the structure of our proejct --- .../activities/MainActivity.java | 148 +++++-------- .../listfragments/AdoptionFragment.java | 65 +++--- .../listfragments/AppointmentFragment.java | 122 +++++----- .../listfragments/InventoryFragment.java | 194 ++++++++-------- .../fragments/listfragments/PetFragment.java | 84 ++++--- .../listfragments/ProductFragment.java | 64 ++++-- .../ProductSupplierFragment.java | 70 +++--- .../listfragments/PurchaseOrderFragment.java | 69 +++--- .../fragments/listfragments/SaleFragment.java | 2 + .../listfragments/ServiceFragment.java | 82 ++++--- .../listfragments/SupplierFragment.java | 86 ++++--- .../ProductDetailFragment.java | 164 +++++--------- .../petstoremobile/models/Adoption.java | 79 ------- .../petstoremobile/models/Appointment.java | 76 ------- .../petstoremobile/models/Inventory.java | 66 ------ .../petstoremobile/models/Product.java | 66 ------ .../models/ProductSupplier.java | 49 ---- .../petstoremobile/models/PurchaseOrder.java | 31 --- .../repositories/AdoptionRepository.java | 156 +++++++++++++ .../repositories/AppointmentRepository.java | 156 +++++++++++++ .../repositories/AuthRepository.java | 174 +++++++++++++++ .../repositories/CategoryRepository.java | 52 +++++ .../repositories/InventoryRepository.java | 184 +++++++++++++++ .../repositories/PetRepository.java | 208 +++++++++++++++++ .../repositories/ProductRepository.java | 209 ++++++++++++++++++ .../ProductSupplierRepository.java | 130 +++++++++++ .../repositories/PurchaseOrderRepository.java | 78 +++++++ .../repositories/ServiceRepository.java | 156 +++++++++++++ .../repositories/SupplierRepository.java | 156 +++++++++++++ .../petstoremobile/utils/FileUtils.java | 27 +++ .../petstoremobile/utils/Resource.java | 30 +++ .../viewmodels/AdoptionViewModel.java | 58 +++++ .../viewmodels/AppointmentViewModel.java | 58 +++++ .../viewmodels/AuthViewModel.java | 68 ++++++ .../viewmodels/InventoryViewModel.java | 80 +++++++ .../viewmodels/PetViewModel.java | 75 +++++++ .../viewmodels/ProductSupplierViewModel.java | 51 +++++ .../viewmodels/ProductViewModel.java | 84 +++++++ .../viewmodels/PurchaseOrderViewModel.java | 37 ++++ .../viewmodels/ServiceViewModel.java | 58 +++++ .../viewmodels/SupplierViewModel.java | 58 +++++ 41 files changed, 2869 insertions(+), 991 deletions(-) delete mode 100644 android/app/src/main/java/com/example/petstoremobile/models/Adoption.java delete mode 100644 android/app/src/main/java/com/example/petstoremobile/models/Appointment.java delete mode 100644 android/app/src/main/java/com/example/petstoremobile/models/Inventory.java delete mode 100644 android/app/src/main/java/com/example/petstoremobile/models/Product.java delete mode 100644 android/app/src/main/java/com/example/petstoremobile/models/ProductSupplier.java delete mode 100644 android/app/src/main/java/com/example/petstoremobile/models/PurchaseOrder.java create mode 100644 android/app/src/main/java/com/example/petstoremobile/repositories/AdoptionRepository.java create mode 100644 android/app/src/main/java/com/example/petstoremobile/repositories/AppointmentRepository.java create mode 100644 android/app/src/main/java/com/example/petstoremobile/repositories/AuthRepository.java create mode 100644 android/app/src/main/java/com/example/petstoremobile/repositories/CategoryRepository.java create mode 100644 android/app/src/main/java/com/example/petstoremobile/repositories/InventoryRepository.java create mode 100644 android/app/src/main/java/com/example/petstoremobile/repositories/PetRepository.java create mode 100644 android/app/src/main/java/com/example/petstoremobile/repositories/ProductRepository.java create mode 100644 android/app/src/main/java/com/example/petstoremobile/repositories/ProductSupplierRepository.java create mode 100644 android/app/src/main/java/com/example/petstoremobile/repositories/PurchaseOrderRepository.java create mode 100644 android/app/src/main/java/com/example/petstoremobile/repositories/ServiceRepository.java create mode 100644 android/app/src/main/java/com/example/petstoremobile/repositories/SupplierRepository.java create mode 100644 android/app/src/main/java/com/example/petstoremobile/utils/FileUtils.java create mode 100644 android/app/src/main/java/com/example/petstoremobile/utils/Resource.java create mode 100644 android/app/src/main/java/com/example/petstoremobile/viewmodels/AdoptionViewModel.java create mode 100644 android/app/src/main/java/com/example/petstoremobile/viewmodels/AppointmentViewModel.java create mode 100644 android/app/src/main/java/com/example/petstoremobile/viewmodels/AuthViewModel.java create mode 100644 android/app/src/main/java/com/example/petstoremobile/viewmodels/InventoryViewModel.java create mode 100644 android/app/src/main/java/com/example/petstoremobile/viewmodels/PetViewModel.java create mode 100644 android/app/src/main/java/com/example/petstoremobile/viewmodels/ProductSupplierViewModel.java create mode 100644 android/app/src/main/java/com/example/petstoremobile/viewmodels/ProductViewModel.java create mode 100644 android/app/src/main/java/com/example/petstoremobile/viewmodels/PurchaseOrderViewModel.java create mode 100644 android/app/src/main/java/com/example/petstoremobile/viewmodels/ServiceViewModel.java create mode 100644 android/app/src/main/java/com/example/petstoremobile/viewmodels/SupplierViewModel.java 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 b4b00db4..d090a09e 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 @@ -14,20 +14,17 @@ import androidx.appcompat.app.AppCompatActivity; import androidx.core.graphics.Insets; import androidx.core.view.ViewCompat; import androidx.core.view.WindowInsetsCompat; +import androidx.lifecycle.ViewModelProvider; import com.example.petstoremobile.R; -import com.example.petstoremobile.api.auth.AuthApi; import com.example.petstoremobile.api.auth.TokenManager; -import com.example.petstoremobile.dtos.AuthDTO; -import com.example.petstoremobile.dtos.UserDTO; +import com.example.petstoremobile.viewmodels.AuthViewModel; +import com.example.petstoremobile.utils.Resource; import javax.inject.Inject; import javax.inject.Named; import dagger.hilt.android.AndroidEntryPoint; -import retrofit2.Call; -import retrofit2.Callback; -import retrofit2.Response; //The login screen activity @AndroidEntryPoint @@ -37,12 +34,11 @@ public class MainActivity extends AppCompatActivity { private EditText etPassword; private Button btnLogin; private TextView tvLoginStatus; + private AuthViewModel viewModel; - @Inject AuthApi authApi; @Inject TokenManager tokenManager; @Inject @Named("baseUrl") String baseUrl; - @Override protected void onCreate(Bundle savedInstanceState) { EdgeToEdge.enable(this); @@ -54,8 +50,7 @@ public class MainActivity extends AppCompatActivity { // If a customer somehow remained logged in, clear them out tokenManager.clearLoginData(); } else { - Intent intent = new Intent(this, HomeActivity.class); - startActivity(intent); + startActivity(new Intent(this, HomeActivity.class)); finish(); return; } @@ -99,92 +94,51 @@ public class MainActivity extends AppCompatActivity { return; } - //Call login from api and get response - authApi.login(new AuthDTO.LoginRequest(username,password)).enqueue(new Callback() { - @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.saveLoginData( - response.body().getToken(), - response.body().getUsername(), - role - ); - - //fetch user id from api then login to home activity - authApi.getMe() - .enqueue(new Callback() { - @Override - public void onResponse(Call call, - Response response) { - if (response.isSuccessful() && response.body() != null) { - tokenManager.saveUserId(response.body().getId()); - } - - Toast.makeText(MainActivity.this, "Login successful", Toast.LENGTH_SHORT).show(); - startActivity(new Intent(MainActivity.this, HomeActivity.class)); - finish(); - } - - @Override - public void onFailure(Call call, - Throwable t) { - Log.e("MainActivity", "Failed to fetch userId", t); - - Toast.makeText(MainActivity.this, "Login successful", Toast.LENGTH_SHORT).show(); - startActivity(new Intent(MainActivity.this, HomeActivity.class)); - finish(); - } - }); - } else { - String errorMessage; - switch (response.code()) { - case 401: - errorMessage = "Invalid username or password"; - break; - case 500: - errorMessage = "Server error. Please try again later."; - break; - case 503: - errorMessage = "Service unavailable. Backend may be starting up."; - break; - default: - errorMessage = "Login failed (Error " + response.code() + ")"; - } - Toast.makeText(MainActivity.this, errorMessage, Toast.LENGTH_LONG).show(); - tvLoginStatus.setText(errorMessage); - } - } - - @Override - public void onFailure(Call call, Throwable t) { - Log.e("MainActivity", "Login request failed", t); - - String errorMessage; - if (t instanceof java.net.ConnectException || - t instanceof java.net.SocketTimeoutException || - t instanceof java.net.UnknownHostException) { - errorMessage = "Cannot connect to server at " + baseUrl + - ". Please check if the backend is running."; - } else if (t instanceof java.io.IOException) { - errorMessage = "Network error. Please check your connection."; - } else { - errorMessage = "Login failed: " + t.getMessage(); - } - - Toast.makeText(MainActivity.this, errorMessage, Toast.LENGTH_LONG).show(); - tvLoginStatus.setText(errorMessage); - } - }); + performLogin(username, password); }); } -} \ No newline at end of file + + private void performLogin(String username, String password) { + viewModel.login(username, password).observe(this, resource -> { + if (resource == null) return; + + switch (resource.status) { + case LOADING: + btnLogin.setEnabled(false); + tvLoginStatus.setText("Logging in..."); + break; + case SUCCESS: + if (resource.data != null) { + String role = resource.data.getRole(); + if ("CUSTOMER".equalsIgnoreCase(role)) { + btnLogin.setEnabled(true); + tvLoginStatus.setText("Customers are not allowed to log in"); + Toast.makeText(this, "Access denied: Customers are not allowed to log in.", Toast.LENGTH_LONG).show(); + } else { + tokenManager.saveLoginData(resource.data.getToken(), resource.data.getUsername(), role); + fetchUserIdAndNavigate(); + } + } + break; + case ERROR: + btnLogin.setEnabled(true); + tvLoginStatus.setText(resource.message); + Toast.makeText(this, resource.message, Toast.LENGTH_LONG).show(); + break; + } + }); + } + + private void fetchUserIdAndNavigate() { + viewModel.getMe().observe(this, resource -> { + if (resource != null && resource.status != Resource.Status.LOADING) { + if (resource.status == Resource.Status.SUCCESS && resource.data != null) { + tokenManager.saveUserId(resource.data.getId()); + } + Toast.makeText(this, "Login successful", Toast.LENGTH_SHORT).show(); + startActivity(new Intent(this, HomeActivity.class)); + finish(); + } + }); + } +} diff --git a/android/app/src/main/java/com/example/petstoremobile/fragments/listfragments/AdoptionFragment.java b/android/app/src/main/java/com/example/petstoremobile/fragments/listfragments/AdoptionFragment.java index 92c8cdea..69bcee15 100644 --- a/android/app/src/main/java/com/example/petstoremobile/fragments/listfragments/AdoptionFragment.java +++ b/android/app/src/main/java/com/example/petstoremobile/fragments/listfragments/AdoptionFragment.java @@ -7,17 +7,19 @@ import android.util.Log; import android.view.*; import android.widget.*; import androidx.annotation.NonNull; +import androidx.annotation.Nullable; import androidx.fragment.app.Fragment; +import androidx.lifecycle.ViewModelProvider; import androidx.navigation.fragment.NavHostFragment; import androidx.recyclerview.widget.LinearLayoutManager; import androidx.recyclerview.widget.RecyclerView; import androidx.swiperefreshlayout.widget.SwipeRefreshLayout; import com.example.petstoremobile.R; import com.example.petstoremobile.adapters.AdoptionAdapter; -import com.example.petstoremobile.api.AdoptionApi; import com.example.petstoremobile.dtos.AdoptionDTO; -import com.example.petstoremobile.dtos.PageResponse; import com.example.petstoremobile.fragments.ListFragment; +import com.example.petstoremobile.viewmodels.AdoptionViewModel; +import com.example.petstoremobile.utils.Resource; import com.example.petstoremobile.utils.EventDecorator; import com.google.android.material.floatingactionbutton.FloatingActionButton; import com.prolificinteractive.materialcalendarview.CalendarDay; @@ -28,10 +30,7 @@ import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.*; -import javax.inject.Inject; - import dagger.hilt.android.AndroidEntryPoint; -import retrofit2.*; @AndroidEntryPoint public class AdoptionFragment extends Fragment implements AdoptionAdapter.OnAdoptionClickListener { @@ -39,7 +38,7 @@ public class AdoptionFragment extends Fragment implements AdoptionAdapter.OnAdop private List adoptionList = new ArrayList<>(); private List filteredList = new ArrayList<>(); private AdoptionAdapter adapter; - @Inject AdoptionApi api; + private AdoptionViewModel viewModel; private SwipeRefreshLayout swipeRefresh; private EditText etSearch; private ImageButton hamburger; @@ -50,7 +49,13 @@ public class AdoptionFragment extends Fragment implements AdoptionAdapter.OnAdop private final SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd", Locale.getDefault()); @Override - public View onCreateView(LayoutInflater inflater, ViewGroup container, + public void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + viewModel = new ViewModelProvider(this).get(AdoptionViewModel.class); + } + + @Override + public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View view = inflater.inflate(R.layout.fragment_adoption, container, false); @@ -177,26 +182,34 @@ public class AdoptionFragment extends Fragment implements AdoptionAdapter.OnAdop adapter.notifyDataSetChanged(); } + // Helper function to get a list of all adoptions from the backend private void loadAdoptions() { - if (swipeRefresh != null) swipeRefresh.setRefreshing(true); - api.getAllAdoptions(0, 500).enqueue(new Callback>() { - public void onResponse(Call> c, - Response> r) { - if (swipeRefresh != null) swipeRefresh.setRefreshing(false); - if (r.isSuccessful() && r.body() != null) { - adoptionList.clear(); - adoptionList.addAll(r.body().getContent()); - updateCalendarDecorators(); - filter(etSearch != null ? etSearch.getText().toString() : ""); - } else { - Toast.makeText(getContext(), "Failed to load adoptions", Toast.LENGTH_SHORT).show(); - Log.e("AdoptionFragment", "Error: " + r.message()); - } - } - public void onFailure(Call> c, Throwable t) { - if (swipeRefresh != null) swipeRefresh.setRefreshing(false); - Toast.makeText(getContext(), "Network error", Toast.LENGTH_SHORT).show(); - Log.e("AdoptionFragment", t.getMessage()); + //Load all adoptions from the backend using viewModel + viewModel.getAllAdoptions(0, 500).observe(getViewLifecycleOwner(), resource -> { + if (resource == null) return; + + // Check the status to see if the resource is loaded and display the data + switch (resource.status) { + case LOADING: + // Show loading indicator + if (swipeRefresh != null) swipeRefresh.setRefreshing(true); + break; + case SUCCESS: + // Hide loading indicator and display data + if (swipeRefresh != null) swipeRefresh.setRefreshing(false); + if (resource.data != null) { + adoptionList.clear(); + adoptionList.addAll(resource.data.getContent()); + updateCalendarDecorators(); + filter(etSearch != null ? etSearch.getText().toString() : ""); + } + break; + case ERROR: + // Hide loading indicator and toast error message + if (swipeRefresh != null) swipeRefresh.setRefreshing(false); + Toast.makeText(getContext(), "Failed to load adoptions: " + resource.message, Toast.LENGTH_SHORT).show(); + Log.e("AdoptionFragment", "Error loading adoptions: " + resource.message); + break; } }); } diff --git a/android/app/src/main/java/com/example/petstoremobile/fragments/listfragments/AppointmentFragment.java b/android/app/src/main/java/com/example/petstoremobile/fragments/listfragments/AppointmentFragment.java index 3f50f7cc..b284a2bf 100644 --- a/android/app/src/main/java/com/example/petstoremobile/fragments/listfragments/AppointmentFragment.java +++ b/android/app/src/main/java/com/example/petstoremobile/fragments/listfragments/AppointmentFragment.java @@ -4,7 +4,9 @@ import android.graphics.Color; import android.os.Bundle; import androidx.annotation.NonNull; +import androidx.annotation.Nullable; import androidx.fragment.app.Fragment; +import androidx.lifecycle.ViewModelProvider; import androidx.navigation.fragment.NavHostFragment; import androidx.recyclerview.widget.LinearLayoutManager; import androidx.recyclerview.widget.RecyclerView; @@ -21,14 +23,14 @@ import android.widget.Toast; import com.example.petstoremobile.R; import com.example.petstoremobile.adapters.AppointmentAdapter; -import com.example.petstoremobile.api.AppointmentApi; -import com.example.petstoremobile.api.PetApi; -import com.example.petstoremobile.api.ServiceApi; import com.example.petstoremobile.dtos.AppointmentDTO; import com.example.petstoremobile.dtos.ServiceDTO; -import com.example.petstoremobile.dtos.PageResponse; import com.example.petstoremobile.dtos.PetDTO; import com.example.petstoremobile.fragments.ListFragment; +import com.example.petstoremobile.viewmodels.AppointmentViewModel; +import com.example.petstoremobile.viewmodels.PetViewModel; +import com.example.petstoremobile.viewmodels.ServiceViewModel; +import com.example.petstoremobile.utils.Resource; import com.example.petstoremobile.utils.EventDecorator; import com.google.android.material.floatingactionbutton.FloatingActionButton; import com.prolificinteractive.materialcalendarview.CalendarDay; @@ -45,12 +47,7 @@ import java.util.HashSet; import java.util.List; import java.util.Locale; -import javax.inject.Inject; - import dagger.hilt.android.AndroidEntryPoint; -import retrofit2.Call; -import retrofit2.Callback; -import retrofit2.Response; @AndroidEntryPoint public class AppointmentFragment extends Fragment implements AppointmentAdapter.OnAppointmentClickListener { @@ -61,9 +58,9 @@ public class AppointmentFragment extends Fragment implements AppointmentAdapter. private List serviceList = new ArrayList<>(); private AppointmentAdapter adapter; - @Inject AppointmentApi api; - @Inject PetApi petApi; - @Inject ServiceApi serviceApi; + private AppointmentViewModel appointmentViewModel; + private PetViewModel petViewModel; + private ServiceViewModel serviceViewModel; private SwipeRefreshLayout swipeRefreshLayout; private EditText etSearch; @@ -75,7 +72,15 @@ public class AppointmentFragment extends Fragment implements AppointmentAdapter. private final SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd", Locale.getDefault()); @Override - public View onCreateView(LayoutInflater inflater, ViewGroup container, + public void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + appointmentViewModel = new ViewModelProvider(this).get(AppointmentViewModel.class); + petViewModel = new ViewModelProvider(this).get(PetViewModel.class); + serviceViewModel = new ViewModelProvider(this).get(ServiceViewModel.class); + } + + @Override + public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View view = inflater.inflate(R.layout.fragment_appointment, container, false); @@ -91,7 +96,6 @@ public class AppointmentFragment extends Fragment implements AppointmentAdapter. loadPets(); loadServices(); - FloatingActionButton fabAdd = view.findViewById(R.id.fabAddAppointment); fabAdd.setOnClickListener(v -> openAppointmentDetails(-1)); @@ -239,76 +243,54 @@ public class AppointmentFragment extends Fragment implements AppointmentAdapter. openAppointmentDetails(position); } + // Helper function to get a list of all appointments from the backend private void loadAppointmentData() { - if (swipeRefreshLayout != null) - swipeRefreshLayout.setRefreshing(true); - api.getAllAppointments(0, 500).enqueue(new Callback>() { - @Override - public void onResponse(Call> call, - Response> response) { - if (swipeRefreshLayout != null) - swipeRefreshLayout.setRefreshing(false); - if (response.isSuccessful() && response.body() != null) { - appointmentList.clear(); - appointmentList.addAll(response.body().getContent()); - updateCalendarDecorators(); - filterAppointments(etSearch != null ? etSearch.getText().toString() : ""); - } else { - Log.e("AppointmentFragment", "Error: " + response.message()); - Toast.makeText(getContext(), "Failed to load appointments", Toast.LENGTH_SHORT).show(); - } - } + //Load all appointments from the backend using viewModel + appointmentViewModel.getAllAppointments(0, 500).observe(getViewLifecycleOwner(), resource -> { + if (resource == null) return; - @Override - public void onFailure(Call> call, Throwable t) { - if (swipeRefreshLayout != null) - swipeRefreshLayout.setRefreshing(false); - Toast.makeText(getContext(), "Network error: " + t.getMessage(), Toast.LENGTH_SHORT).show(); - Log.e("AppointmentFragment", t.getMessage()); + // Check the status to see if the resource is loaded and display the data + switch (resource.status) { + case LOADING: + // Show loading indicator + if (swipeRefreshLayout != null) swipeRefreshLayout.setRefreshing(true); + break; + case SUCCESS: + // Hide loading indicator and display data + if (swipeRefreshLayout != null) swipeRefreshLayout.setRefreshing(false); + if (resource.data != null) { + appointmentList.clear(); + appointmentList.addAll(resource.data.getContent()); + updateCalendarDecorators(); + filterAppointments(etSearch != null ? etSearch.getText().toString() : ""); + } + break; + case ERROR: + // Hide loading indicator and toast error message + if (swipeRefreshLayout != null) swipeRefreshLayout.setRefreshing(false); + Toast.makeText(getContext(), "Failed to load appointments: " + resource.message, Toast.LENGTH_SHORT).show(); + Log.e("AppointmentFragment", "Error loading appointments: " + resource.message); + break; } }); } - - // Load Pets private void loadPets() { - petApi.getAllPets(0,100).enqueue(new Callback>() { - - @Override - public void onResponse(Call> call, Response> response) { - if (response.isSuccessful() && response.body() !=null) { - petList.clear(); - petList.addAll(response.body().getContent()); - } - } - - @Override - public void onFailure(Call> call, Throwable t) { - - Log.e("AppointmentFragment", "Pet load error:" + t.getMessage()); - + petViewModel.getAllPets(0, 100).observe(getViewLifecycleOwner(), resource -> { + if (resource != null && resource.status == Resource.Status.SUCCESS && resource.data != null) { + petList.clear(); + petList.addAll(resource.data.getContent()); } }); } // Load Services - private void loadServices() { - serviceApi.getAllServices(0,100).enqueue(new Callback>() { - @Override - public void onResponse(Call> call, Response> response) { - if (response.isSuccessful() && response.body() != null) { - serviceList.clear(); - serviceList.addAll(response.body().getContent()); - - } - } - - @Override - public void onFailure(Call> call, Throwable t) { - Log.e("AppointmentFragmnet", "Service load error: " + t.getMessage()); - + serviceViewModel.getAllServices(0, 100).observe(getViewLifecycleOwner(), resource -> { + if (resource != null && resource.status == Resource.Status.SUCCESS && resource.data != null) { + serviceList.clear(); + serviceList.addAll(resource.data.getContent()); } }); } diff --git a/android/app/src/main/java/com/example/petstoremobile/fragments/listfragments/InventoryFragment.java b/android/app/src/main/java/com/example/petstoremobile/fragments/listfragments/InventoryFragment.java index 0bfe389b..e77ae958 100644 --- a/android/app/src/main/java/com/example/petstoremobile/fragments/listfragments/InventoryFragment.java +++ b/android/app/src/main/java/com/example/petstoremobile/fragments/listfragments/InventoryFragment.java @@ -20,6 +20,7 @@ import android.widget.Toast; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.fragment.app.Fragment; +import androidx.lifecycle.ViewModelProvider; import androidx.navigation.fragment.NavHostFragment; import androidx.recyclerview.widget.LinearLayoutManager; import androidx.recyclerview.widget.RecyclerView; @@ -28,23 +29,16 @@ import androidx.swiperefreshlayout.widget.SwipeRefreshLayout; import com.example.petstoremobile.R; import com.example.petstoremobile.adapters.BlackTextArrayAdapter; import com.example.petstoremobile.adapters.InventoryAdapter; -import com.example.petstoremobile.api.CategoryApi; -import com.example.petstoremobile.api.InventoryApi; -import com.example.petstoremobile.dtos.BulkDeleteRequest; import com.example.petstoremobile.dtos.CategoryDTO; import com.example.petstoremobile.dtos.InventoryDTO; -import com.example.petstoremobile.dtos.PageResponse; import com.example.petstoremobile.fragments.ListFragment; +import com.example.petstoremobile.viewmodels.InventoryViewModel; +import com.example.petstoremobile.utils.Resource; import java.util.ArrayList; import java.util.List; -import javax.inject.Inject; - import dagger.hilt.android.AndroidEntryPoint; -import retrofit2.Call; -import retrofit2.Callback; -import retrofit2.Response; @AndroidEntryPoint public class InventoryFragment extends Fragment implements InventoryAdapter.OnInventoryClickListener { @@ -55,8 +49,7 @@ public class InventoryFragment extends Fragment implements InventoryAdapter.OnIn private final List inventoryList = new ArrayList<>(); private final List categoryList = new ArrayList<>(); private InventoryAdapter adapter; - @Inject InventoryApi inventoryApi; - @Inject CategoryApi categoryApi; + private InventoryViewModel viewModel; private SwipeRefreshLayout swipeRefreshLayout; private EditText etSearch; @@ -82,7 +75,13 @@ public class InventoryFragment extends Fragment implements InventoryAdapter.OnIn private boolean spinnerReady = false; @Override - public View onCreateView(LayoutInflater inflater, ViewGroup container, + public void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + viewModel = new ViewModelProvider(this).get(InventoryViewModel.class); + } + + @Override + public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View view = inflater.inflate(R.layout.fragment_inventory, container, false); @@ -117,21 +116,13 @@ public class InventoryFragment extends Fragment implements InventoryAdapter.OnIn // Categories private void loadCategories() { - categoryApi.getAllCategories(0, 100).enqueue(new Callback>() { - @Override - public void onResponse(Call> call, - Response> response) { - if (response.isSuccessful() && response.body() != null) { - categoryList.clear(); - categoryList.addAll(response.body().getContent()); - setupCategorySpinner(); - } - } - - @Override - public void onFailure(Call> call, Throwable t) { - Log.e(TAG, "Failed to load categories", t); - // Still setup spinner with just "All" + viewModel.getAllCategories(0, 100).observe(getViewLifecycleOwner(), resource -> { + if (resource != null && resource.status == Resource.Status.SUCCESS && resource.data != null) { + categoryList.clear(); + categoryList.addAll(resource.data.getContent()); + setupCategorySpinner(); + } else if (resource != null && resource.status == Resource.Status.ERROR) { + Log.e(TAG, "Failed to load categories: " + resource.message); setupCategorySpinner(); } }); @@ -145,33 +136,35 @@ public class InventoryFragment extends Fragment implements InventoryAdapter.OnIn categoryNames.add(c.getCategoryName()); } - BlackTextArrayAdapter spinnerAdapter = new BlackTextArrayAdapter<>( - requireContext(), - android.R.layout.simple_spinner_item, - categoryNames); - spinnerAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); - spinnerCategory.setAdapter(spinnerAdapter); + if (getContext() != null) { + BlackTextArrayAdapter spinnerAdapter = new BlackTextArrayAdapter<>( + requireContext(), + android.R.layout.simple_spinner_item, + categoryNames); + spinnerAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); + spinnerCategory.setAdapter(spinnerAdapter); - spinnerCategory.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() { - @Override - public void onItemSelected(AdapterView parent, View view, int position, long id) { - if (!spinnerReady) { - // Skip the first automatic trigger on setup - spinnerReady = true; - return; + spinnerCategory.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() { + @Override + public void onItemSelected(AdapterView parent, View view, int position, long id) { + if (!spinnerReady) { + // Skip the first automatic trigger on setup + spinnerReady = true; + return; + } + if (position == 0) { + selectedCategory = null; // "All Categories" + } else { + selectedCategory = categoryList.get(position - 1).getCategoryName(); + } + loadInventory(true); } - if (position == 0) { - selectedCategory = null; // "All Categories" - } else { - selectedCategory = categoryList.get(position - 1).getCategoryName(); - } - loadInventory(true); - } - @Override - public void onNothingSelected(AdapterView parent) { - } - }); + @Override + public void onNothingSelected(AdapterView parent) { + } + }); + } } // Search @@ -209,7 +202,7 @@ public class InventoryFragment extends Fragment implements InventoryAdapter.OnIn rv.addOnScrollListener(new RecyclerView.OnScrollListener() { @Override - public void onScrolled(RecyclerView recyclerView, int dx, int dy) { + public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy) { if (dy <= 0) return; LinearLayoutManager lm = (LinearLayoutManager) rv.getLayoutManager(); @@ -230,11 +223,10 @@ public class InventoryFragment extends Fragment implements InventoryAdapter.OnIn swipeRefreshLayout.setOnRefreshListener(() -> loadInventory(true)); } - // Load inventory + // Helper function to get a list of all inventory items from the backend private void loadInventory(boolean reset) { if (isLoading) return; - isLoading = true; if (reset) { currentPage = 0; @@ -244,39 +236,38 @@ public class InventoryFragment extends Fragment implements InventoryAdapter.OnIn // Build query: combine search text + selected category String q = buildQuery(); - inventoryApi.getAllInventory(q, currentPage, PAGE_SIZE, "inventoryId,asc") - .enqueue(new Callback>() { - @Override - public void onResponse(Call> call, - Response> response) { - isLoading = false; - if (swipeRefreshLayout != null) - swipeRefreshLayout.setRefreshing(false); + //Load all inventory items from the backend using viewModel + viewModel.getAllInventory(q, currentPage, PAGE_SIZE, "inventoryId,asc").observe(getViewLifecycleOwner(), resource -> { + if (resource == null) return; - if (response.isSuccessful() && response.body() != null) { - PageResponse page = response.body(); - if (reset) - inventoryList.clear(); - inventoryList.addAll(page.getContent()); - adapter.notifyDataSetChanged(); - isLastPage = page.isLast(); - if (!isLastPage) - currentPage++; - } else { - Log.e(TAG, "Error " + response.code()); - Toast.makeText(getContext(), "Failed to load inventory", Toast.LENGTH_SHORT).show(); - } + // Check the status to see if the resource is loaded and display the data + switch (resource.status) { + case LOADING: + // Show loading indicator + isLoading = true; + if (swipeRefreshLayout != null) swipeRefreshLayout.setRefreshing(true); + break; + case SUCCESS: + // Hide loading indicator and display data + isLoading = false; + if (swipeRefreshLayout != null) swipeRefreshLayout.setRefreshing(false); + if (resource.data != null) { + if (reset) inventoryList.clear(); + inventoryList.addAll(resource.data.getContent()); + adapter.notifyDataSetChanged(); + isLastPage = resource.data.isLast(); + if (!isLastPage) currentPage++; } - - @Override - public void onFailure(Call> call, Throwable t) { - isLoading = false; - if (swipeRefreshLayout != null) - swipeRefreshLayout.setRefreshing(false); - Log.e(TAG, "Network error", t); - Toast.makeText(getContext(), "Network error: " + t.getMessage(), Toast.LENGTH_SHORT).show(); - } - }); + break; + case ERROR: + // Hide loading indicator and toast error message + isLoading = false; + if (swipeRefreshLayout != null) swipeRefreshLayout.setRefreshing(false); + Log.e(TAG, "Error: " + resource.message); + Toast.makeText(getContext(), "Failed to load inventory: " + resource.message, Toast.LENGTH_SHORT).show(); + break; + } + }); } // Combines search text and category into one query string for ?q= @@ -308,25 +299,18 @@ public class InventoryFragment extends Fragment implements InventoryAdapter.OnIn } private void bulkDelete(List ids) { - inventoryApi.bulkDeleteInventory(new BulkDeleteRequest(ids)) - .enqueue(new Callback() { - @Override - public void onResponse(Call call, Response response) { - if (response.isSuccessful()) { - adapter.clearSelection(); - hideBulkDeleteBar(); - loadInventory(true); - Toast.makeText(getContext(), ids.size() + " item(s) deleted", Toast.LENGTH_SHORT).show(); - } else { - Toast.makeText(getContext(), "Delete failed", Toast.LENGTH_SHORT).show(); - } - } - - @Override - public void onFailure(Call call, Throwable t) { - Toast.makeText(getContext(), "Network error", Toast.LENGTH_SHORT).show(); - } - }); + viewModel.bulkDeleteInventory(ids).observe(getViewLifecycleOwner(), resource -> { + if (resource != null && resource.status != Resource.Status.LOADING) { + if (resource.status == Resource.Status.SUCCESS) { + adapter.clearSelection(); + hideBulkDeleteBar(); + loadInventory(true); + Toast.makeText(getContext(), ids.size() + " item(s) deleted", Toast.LENGTH_SHORT).show(); + } else { + Toast.makeText(getContext(), "Delete failed: " + resource.message, Toast.LENGTH_SHORT).show(); + } + } + }); } private void hideBulkDeleteBar() { @@ -374,4 +358,4 @@ public class InventoryFragment extends Fragment implements InventoryAdapter.OnIn hideBulkDeleteBar(); } } -} \ No newline at end of file +} diff --git a/android/app/src/main/java/com/example/petstoremobile/fragments/listfragments/PetFragment.java b/android/app/src/main/java/com/example/petstoremobile/fragments/listfragments/PetFragment.java index ac456828..fdbc38f4 100644 --- a/android/app/src/main/java/com/example/petstoremobile/fragments/listfragments/PetFragment.java +++ b/android/app/src/main/java/com/example/petstoremobile/fragments/listfragments/PetFragment.java @@ -5,6 +5,7 @@ import android.os.Bundle; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.fragment.app.Fragment; +import androidx.lifecycle.ViewModelProvider; import androidx.navigation.fragment.NavHostFragment; import androidx.recyclerview.widget.LinearLayoutManager; import androidx.recyclerview.widget.RecyclerView; @@ -25,10 +26,10 @@ import android.widget.Toast; import com.example.petstoremobile.R; import com.example.petstoremobile.adapters.BlackTextArrayAdapter; import com.example.petstoremobile.adapters.PetAdapter; -import com.example.petstoremobile.api.PetApi; -import com.example.petstoremobile.dtos.PageResponse; import com.example.petstoremobile.dtos.PetDTO; import com.example.petstoremobile.fragments.ListFragment; +import com.example.petstoremobile.viewmodels.PetViewModel; +import com.example.petstoremobile.utils.Resource; import com.google.android.material.floatingactionbutton.FloatingActionButton; import java.util.ArrayList; @@ -38,9 +39,6 @@ import javax.inject.Inject; import javax.inject.Named; import dagger.hilt.android.AndroidEntryPoint; -import retrofit2.Call; -import retrofit2.Callback; -import retrofit2.Response; @AndroidEntryPoint public class PetFragment extends Fragment implements PetAdapter.OnPetClickListener { @@ -48,13 +46,19 @@ public class PetFragment extends Fragment implements PetAdapter.OnPetClickListen private List filteredList = new ArrayList<>(); private ImageButton hamburger; private PetAdapter adapter; - @Inject PetApi api; + private PetViewModel viewModel; + @Inject @Named("baseUrl") String baseUrl; private SwipeRefreshLayout swipeRefreshLayout; private EditText etSearch; private Spinner spinnerStatus; - //load pet view + @Override + public void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + viewModel = new ViewModelProvider(this).get(PetViewModel.class); + } + @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { @@ -67,12 +71,9 @@ public class PetFragment extends Fragment implements PetAdapter.OnPetClickListen setupStatusFilter(view); setupSwipeRefresh(view); - - //Add button to opens the add dialog FloatingActionButton fabAddPet = view.findViewById(R.id.fabAddPet); fabAddPet.setOnClickListener(v -> openPetDetails(-1)); - //Make the hamburger button open the drawer from listFragment hamburger.setOnClickListener(v -> { Fragment parent = getParentFragment(); if (parent != null) { @@ -103,7 +104,6 @@ public class PetFragment extends Fragment implements PetAdapter.OnPetClickListen }); } - //Setup the status filter spinner private void setupStatusFilter(View view) { spinnerStatus = view.findViewById(R.id.spinnerStatus); String[] statuses = {"All Statuses", "Available", "Adopted"}; @@ -122,7 +122,6 @@ public class PetFragment extends Fragment implements PetAdapter.OnPetClickListen }); } - // Helper function to filter pets based on search and status filter private void filterPets() { String query = etSearch.getText().toString().toLowerCase(); String selectedStatus = spinnerStatus.getSelectedItem().toString(); @@ -146,14 +145,10 @@ public class PetFragment extends Fragment implements PetAdapter.OnPetClickListen private void setupSwipeRefresh(View view) { swipeRefreshLayout = view.findViewById(R.id.swipeRefreshPet); - swipeRefreshLayout.setOnRefreshListener(() -> { - loadPetData(); - }); + swipeRefreshLayout.setOnRefreshListener(this::loadPetData); } - //Open pet profile private void openPetProfile(int position) { - //Make a bundle to pass data to the profile fragment Bundle args = new Bundle(); PetDTO pet = filteredList.get(position); args.putInt("petId", pet.getPetId().intValue()); @@ -172,12 +167,10 @@ public class PetFragment extends Fragment implements PetAdapter.OnPetClickListen NavHostFragment.findNavController(this).navigate(R.id.nav_pet_profile, args); } - //Open the pet detail view for adding private void openPetDetails(int position) { NavHostFragment.findNavController(this).navigate(R.id.nav_pet_detail); } - // Called by PetAdapter when a row is clicked to open the details view @Override public void onPetClick(int position) { openPetProfile(position); @@ -185,38 +178,35 @@ public class PetFragment extends Fragment implements PetAdapter.OnPetClickListen // Helper function to get a list of all pets from the backend private void loadPetData() { - if (swipeRefreshLayout != null) { - swipeRefreshLayout.setRefreshing(true); - } - api.getAllPets(0, 100).enqueue(new Callback>() { - @Override - public void onResponse(Call> call, Response> response) { - if (swipeRefreshLayout != null) { - swipeRefreshLayout.setRefreshing(false); - } - if (response.isSuccessful() && response.body() != null) { - petList.clear(); - petList.addAll(response.body().getContent()); - filterPets(); - - } else { - Log.e("onResponse: ", response.message()); - } - } - - @Override - public void onFailure(Call> call, Throwable t) { - if (swipeRefreshLayout != null) { - swipeRefreshLayout.setRefreshing(false); - } - Toast.makeText(getContext(), - "Failed to load pets", Toast.LENGTH_SHORT).show(); - Log.e("onFailure: ", t.getMessage()); + //Load all pets from the backend using viewModel + viewModel.getAllPets(0, 100).observe(getViewLifecycleOwner(), resource -> { + if (resource == null) return; + + // Check the status to see if the resource is loaded and display the data + switch (resource.status) { + case LOADING: + // Show loading indicator + if (swipeRefreshLayout != null) swipeRefreshLayout.setRefreshing(true); + break; + case SUCCESS: + // Hide loading indicator and display data + if (swipeRefreshLayout != null) swipeRefreshLayout.setRefreshing(false); + if (resource.data != null) { + petList.clear(); + petList.addAll(resource.data.getContent()); + filterPets(); + } + break; + case ERROR: + // Hide loading indicator and toast error message + if (swipeRefreshLayout != null) swipeRefreshLayout.setRefreshing(false); + Toast.makeText(getContext(), "Failed to load pets: " + resource.message, Toast.LENGTH_SHORT).show(); + Log.e("PetFragment", "Error loading pets: " + resource.message); + break; } }); } - //set up the recyclerview and adapter private void setupRecyclerView(View view) { RecyclerView recyclerView = view.findViewById(R.id.recyclerViewPets); adapter = new PetAdapter(filteredList, this); diff --git a/android/app/src/main/java/com/example/petstoremobile/fragments/listfragments/ProductFragment.java b/android/app/src/main/java/com/example/petstoremobile/fragments/listfragments/ProductFragment.java index 16d6347f..d56da91a 100644 --- a/android/app/src/main/java/com/example/petstoremobile/fragments/listfragments/ProductFragment.java +++ b/android/app/src/main/java/com/example/petstoremobile/fragments/listfragments/ProductFragment.java @@ -5,18 +5,21 @@ import android.text.*; import android.util.Log; import android.view.*; import android.widget.*; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; import androidx.fragment.app.Fragment; +import androidx.lifecycle.ViewModelProvider; import androidx.navigation.fragment.NavHostFragment; import androidx.recyclerview.widget.LinearLayoutManager; import androidx.recyclerview.widget.RecyclerView; import androidx.swiperefreshlayout.widget.SwipeRefreshLayout; import com.example.petstoremobile.R; import com.example.petstoremobile.adapters.ProductAdapter; -import com.example.petstoremobile.api.ProductApi; import com.example.petstoremobile.api.auth.TokenManager; -import com.example.petstoremobile.dtos.PageResponse; import com.example.petstoremobile.dtos.ProductDTO; import com.example.petstoremobile.fragments.ListFragment; +import com.example.petstoremobile.viewmodels.ProductViewModel; +import com.example.petstoremobile.utils.Resource; import com.google.android.material.floatingactionbutton.FloatingActionButton; import java.util.*; @@ -24,7 +27,6 @@ import javax.inject.Inject; import javax.inject.Named; import dagger.hilt.android.AndroidEntryPoint; -import retrofit2.*; @AndroidEntryPoint public class ProductFragment extends Fragment implements ProductAdapter.OnProductClickListener { @@ -34,13 +36,19 @@ public class ProductFragment extends Fragment implements ProductAdapter.OnProduc private ProductAdapter adapter; private SwipeRefreshLayout swipeRefresh; private EditText etSearch; + private ProductViewModel viewModel; - @Inject ProductApi api; @Inject @Named("baseUrl") String baseUrl; @Inject TokenManager tokenManager; @Override - public View onCreateView(LayoutInflater inflater, ViewGroup container, + public void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + viewModel = new ViewModelProvider(this).get(ProductViewModel.class); + } + + @Override + public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View view = inflater.inflate(R.layout.fragment_product, container, false); @@ -109,27 +117,35 @@ public class ProductFragment extends Fragment implements ProductAdapter.OnProduc adapter.notifyDataSetChanged(); } + // Helper function to get a list of all products from the backend private void loadProducts() { - if (swipeRefresh != null) swipeRefresh.setRefreshing(true); - api.getAllProducts(null, 0, 100) - .enqueue(new Callback>() { - public void onResponse(Call> c, - Response> r) { - if (swipeRefresh != null) swipeRefresh.setRefreshing(false); - if (r.isSuccessful() && r.body() != null) { - productList.clear(); - productList.addAll(r.body().getContent()); - filter(); - } else { - Toast.makeText(getContext(), "Failed to load products", - Toast.LENGTH_SHORT).show(); - } + //Load all products from the backend using viewModel + viewModel.getAllProducts(null, 0, 100).observe(getViewLifecycleOwner(), resource -> { + if (resource == null) return; + + // Check the status to see if the resource is loaded and display the data + switch (resource.status) { + case LOADING: + // Show loading indicator + if (swipeRefresh != null) swipeRefresh.setRefreshing(true); + break; + case SUCCESS: + // Hide loading indicator and display data + if (swipeRefresh != null) swipeRefresh.setRefreshing(false); + if (resource.data != null) { + productList.clear(); + productList.addAll(resource.data.getContent()); + filter(); } - public void onFailure(Call> c, Throwable t) { - if (swipeRefresh != null) swipeRefresh.setRefreshing(false); - Log.e("ProductFragment", t.getMessage()); - } - }); + break; + case ERROR: + // Hide loading indicator and toast error message + if (swipeRefresh != null) swipeRefresh.setRefreshing(false); + Toast.makeText(getContext(), "Failed to load products: " + resource.message, Toast.LENGTH_SHORT).show(); + Log.e("ProductFragment", "Error loading products: " + resource.message); + break; + } + }); } private void openDetail(int position) { diff --git a/android/app/src/main/java/com/example/petstoremobile/fragments/listfragments/ProductSupplierFragment.java b/android/app/src/main/java/com/example/petstoremobile/fragments/listfragments/ProductSupplierFragment.java index 18d97c9d..a7b16298 100644 --- a/android/app/src/main/java/com/example/petstoremobile/fragments/listfragments/ProductSupplierFragment.java +++ b/android/app/src/main/java/com/example/petstoremobile/fragments/listfragments/ProductSupplierFragment.java @@ -5,25 +5,24 @@ import android.text.*; import android.util.Log; import android.view.*; import android.widget.*; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; import androidx.fragment.app.Fragment; +import androidx.lifecycle.ViewModelProvider; import androidx.navigation.fragment.NavHostFragment; import androidx.recyclerview.widget.LinearLayoutManager; import androidx.recyclerview.widget.RecyclerView; import androidx.swiperefreshlayout.widget.SwipeRefreshLayout; import com.example.petstoremobile.R; import com.example.petstoremobile.adapters.ProductSupplierAdapter; -import com.example.petstoremobile.api.ProductSupplierApi; -import com.example.petstoremobile.dtos.PageResponse; import com.example.petstoremobile.dtos.ProductSupplierDTO; import com.example.petstoremobile.fragments.ListFragment; -import com.example.petstoremobile.fragments.listfragments.detailfragments.ProductSupplierDetailFragment; +import com.example.petstoremobile.viewmodels.ProductSupplierViewModel; +import com.example.petstoremobile.utils.Resource; import com.google.android.material.floatingactionbutton.FloatingActionButton; import java.util.*; -import javax.inject.Inject; - import dagger.hilt.android.AndroidEntryPoint; -import retrofit2.*; @AndroidEntryPoint public class ProductSupplierFragment extends Fragment @@ -34,11 +33,16 @@ public class ProductSupplierFragment extends Fragment private ProductSupplierAdapter adapter; private SwipeRefreshLayout swipeRefresh; private EditText etSearch; - - @Inject ProductSupplierApi api; + private ProductSupplierViewModel viewModel; @Override - public View onCreateView(LayoutInflater inflater, ViewGroup container, + public void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + viewModel = new ViewModelProvider(this).get(ProductSupplierViewModel.class); + } + + @Override + public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View view = inflater.inflate(R.layout.fragment_product_supplier, container, false); @@ -103,27 +107,35 @@ public class ProductSupplierFragment extends Fragment adapter.notifyDataSetChanged(); } + // Helper function to get a list of all product suppliers from the backend private void loadData() { - if (swipeRefresh != null) swipeRefresh.setRefreshing(true); - api.getAllProductSuppliers(0, 100) - .enqueue(new Callback>() { - public void onResponse(Call> c, - Response> r) { - if (swipeRefresh != null) swipeRefresh.setRefreshing(false); - if (r.isSuccessful() && r.body() != null) { - psList.clear(); - psList.addAll(r.body().getContent()); - filter(etSearch != null ? etSearch.getText().toString() : ""); - } else { - Toast.makeText(getContext(), "Failed to load", - Toast.LENGTH_SHORT).show(); - } + //Load all product suppliers from the backend using viewModel + viewModel.getAllProductSuppliers(0, 100).observe(getViewLifecycleOwner(), resource -> { + if (resource == null) return; + + // Check the status to see if the resource is loaded and display the data + switch (resource.status) { + case LOADING: + // Show loading indicator + if (swipeRefresh != null) swipeRefresh.setRefreshing(true); + break; + case SUCCESS: + // Hide loading indicator and display data + if (swipeRefresh != null) swipeRefresh.setRefreshing(false); + if (resource.data != null) { + psList.clear(); + psList.addAll(resource.data.getContent()); + filter(etSearch != null ? etSearch.getText().toString() : ""); } - public void onFailure(Call> c, Throwable t) { - if (swipeRefresh != null) swipeRefresh.setRefreshing(false); - Log.e("PSFragment", t.getMessage()); - } - }); + break; + case ERROR: + // Hide loading indicator and toast error message + if (swipeRefresh != null) swipeRefresh.setRefreshing(false); + Toast.makeText(getContext(), "Failed to load: " + resource.message, Toast.LENGTH_SHORT).show(); + Log.e("PSFragment", "Error loading: " + resource.message); + break; + } + }); } private void openDetail(int position) { @@ -141,4 +153,4 @@ public class ProductSupplierFragment extends Fragment @Override public void onProductSupplierClick(int position) { openDetail(position); } -} \ No newline at end of file +} diff --git a/android/app/src/main/java/com/example/petstoremobile/fragments/listfragments/PurchaseOrderFragment.java b/android/app/src/main/java/com/example/petstoremobile/fragments/listfragments/PurchaseOrderFragment.java index 9a7226a8..35366e83 100644 --- a/android/app/src/main/java/com/example/petstoremobile/fragments/listfragments/PurchaseOrderFragment.java +++ b/android/app/src/main/java/com/example/petstoremobile/fragments/listfragments/PurchaseOrderFragment.java @@ -5,23 +5,23 @@ import android.text.*; import android.util.Log; import android.view.*; import android.widget.*; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; import androidx.fragment.app.Fragment; +import androidx.lifecycle.ViewModelProvider; import androidx.navigation.fragment.NavHostFragment; import androidx.recyclerview.widget.LinearLayoutManager; import androidx.recyclerview.widget.RecyclerView; import androidx.swiperefreshlayout.widget.SwipeRefreshLayout; import com.example.petstoremobile.R; import com.example.petstoremobile.adapters.PurchaseOrderAdapter; -import com.example.petstoremobile.api.PurchaseOrderApi; -import com.example.petstoremobile.dtos.PageResponse; import com.example.petstoremobile.dtos.PurchaseOrderDTO; import com.example.petstoremobile.fragments.ListFragment; +import com.example.petstoremobile.viewmodels.PurchaseOrderViewModel; +import com.example.petstoremobile.utils.Resource; import java.util.*; -import javax.inject.Inject; - import dagger.hilt.android.AndroidEntryPoint; -import retrofit2.*; @AndroidEntryPoint public class PurchaseOrderFragment extends Fragment @@ -32,8 +32,13 @@ public class PurchaseOrderFragment extends Fragment private PurchaseOrderAdapter adapter; private SwipeRefreshLayout swipeRefresh; private EditText etSearch; + private PurchaseOrderViewModel viewModel; - @Inject PurchaseOrderApi api; + @Override + public void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + viewModel = new ViewModelProvider(this).get(PurchaseOrderViewModel.class); + } @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, @@ -75,7 +80,7 @@ public class PurchaseOrderFragment extends Fragment public void afterTextChanged(Editable s) { } - public void onTextChanged(CharSequence s, int a, int b, int c) { + public void onTextChanged(CharSequence s, int start, int before, int count) { filter(s.toString()); } }); @@ -102,31 +107,35 @@ public class PurchaseOrderFragment extends Fragment adapter.notifyDataSetChanged(); } + // Helper function to get a list of all purchase orders from the backend private void loadData() { - if (swipeRefresh != null) - swipeRefresh.setRefreshing(true); - api.getAllPurchaseOrders(0, 100) - .enqueue(new Callback>() { - public void onResponse(Call> c, - Response> r) { - if (swipeRefresh != null) - swipeRefresh.setRefreshing(false); - if (r.isSuccessful() && r.body() != null) { - poList.clear(); - poList.addAll(r.body().getContent()); - filter(etSearch != null ? etSearch.getText().toString() : ""); - } else { - Toast.makeText(getContext(), "Failed to load purchase orders", - Toast.LENGTH_SHORT).show(); - } - } + //Load all purchase orders from the backend using viewModel + viewModel.getAllPurchaseOrders(0, 100).observe(getViewLifecycleOwner(), resource -> { + if (resource == null) return; - public void onFailure(Call> c, Throwable t) { - if (swipeRefresh != null) - swipeRefresh.setRefreshing(false); - Log.e("POFragment", t.getMessage()); + // Check the status to see if the resource is loaded and display the data + switch (resource.status) { + case LOADING: + // Show loading indicator + if (swipeRefresh != null) swipeRefresh.setRefreshing(true); + break; + case SUCCESS: + // Hide loading indicator and display data + if (swipeRefresh != null) swipeRefresh.setRefreshing(false); + if (resource.data != null) { + poList.clear(); + poList.addAll(resource.data.getContent()); + filter(etSearch != null ? etSearch.getText().toString() : ""); } - }); + break; + case ERROR: + // Hide loading indicator and toast error message + if (swipeRefresh != null) swipeRefresh.setRefreshing(false); + Toast.makeText(getContext(), "Failed to load purchase orders: " + resource.message, Toast.LENGTH_SHORT).show(); + Log.e("POFragment", "Error loading purchase orders: " + resource.message); + break; + } + }); } private void openDetail(int position) { @@ -143,4 +152,4 @@ public class PurchaseOrderFragment extends Fragment public void onPurchaseOrderClick(int position) { openDetail(position); } -} \ No newline at end of file +} diff --git a/android/app/src/main/java/com/example/petstoremobile/fragments/listfragments/SaleFragment.java b/android/app/src/main/java/com/example/petstoremobile/fragments/listfragments/SaleFragment.java index 9ea74d19..859bacca 100644 --- a/android/app/src/main/java/com/example/petstoremobile/fragments/listfragments/SaleFragment.java +++ b/android/app/src/main/java/com/example/petstoremobile/fragments/listfragments/SaleFragment.java @@ -1,6 +1,8 @@ package com.example.petstoremobile.fragments.listfragments; import android.os.Bundle; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; import androidx.fragment.app.Fragment; import androidx.navigation.fragment.NavHostFragment; import androidx.recyclerview.widget.LinearLayoutManager; diff --git a/android/app/src/main/java/com/example/petstoremobile/fragments/listfragments/ServiceFragment.java b/android/app/src/main/java/com/example/petstoremobile/fragments/listfragments/ServiceFragment.java index dc9ef0fe..1c12cbf8 100644 --- a/android/app/src/main/java/com/example/petstoremobile/fragments/listfragments/ServiceFragment.java +++ b/android/app/src/main/java/com/example/petstoremobile/fragments/listfragments/ServiceFragment.java @@ -5,6 +5,7 @@ import android.os.Bundle; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.fragment.app.Fragment; +import androidx.lifecycle.ViewModelProvider; import androidx.navigation.fragment.NavHostFragment; import androidx.recyclerview.widget.LinearLayoutManager; import androidx.recyclerview.widget.RecyclerView; @@ -22,21 +23,16 @@ import android.widget.Toast; import com.example.petstoremobile.R; import com.example.petstoremobile.adapters.ServiceAdapter; -import com.example.petstoremobile.api.ServiceApi; -import com.example.petstoremobile.dtos.PageResponse; import com.example.petstoremobile.dtos.ServiceDTO; import com.example.petstoremobile.fragments.ListFragment; +import com.example.petstoremobile.viewmodels.ServiceViewModel; +import com.example.petstoremobile.utils.Resource; import com.google.android.material.floatingactionbutton.FloatingActionButton; import java.util.ArrayList; import java.util.List; -import javax.inject.Inject; - import dagger.hilt.android.AndroidEntryPoint; -import retrofit2.Call; -import retrofit2.Callback; -import retrofit2.Response; @AndroidEntryPoint public class ServiceFragment extends Fragment implements ServiceAdapter.OnServiceClickListener { @@ -45,13 +41,19 @@ public class ServiceFragment extends Fragment implements ServiceAdapter.OnServic private List filteredList = new ArrayList<>(); private ServiceAdapter adapter; private ImageButton hamburger; - @Inject ServiceApi api; + private ServiceViewModel viewModel; private SwipeRefreshLayout swipeRefreshLayout; private EditText etSearch; + @Override + public void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + viewModel = new ViewModelProvider(this).get(ServiceViewModel.class); + } + //load service view @Override - public View onCreateView(LayoutInflater inflater, ViewGroup container, + public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View view = inflater.inflate(R.layout.fragment_service, container, false); @@ -98,8 +100,8 @@ public class ServiceFragment extends Fragment implements ServiceAdapter.OnServic } else { String lower = query.toLowerCase(); for (ServiceDTO s : serviceList) { - if (s.getServiceName().toLowerCase().contains(lower) - || s.getServiceDesc().toLowerCase().contains(lower)) { + if ((s.getServiceName() != null && s.getServiceName().toLowerCase().contains(lower)) + || (s.getServiceDesc() != null && s.getServiceDesc().toLowerCase().contains(lower))) { filteredList.add(s); } } @@ -109,9 +111,7 @@ public class ServiceFragment extends Fragment implements ServiceAdapter.OnServic private void setupSwipeRefresh(View view) { swipeRefreshLayout = view.findViewById(R.id.swipeRefreshService); - swipeRefreshLayout.setOnRefreshListener(() -> { - loadServiceData(); - }); + swipeRefreshLayout.setOnRefreshListener(this::loadServiceData); } //Open the service detail view depending on the mode @@ -141,35 +141,33 @@ public class ServiceFragment extends Fragment implements ServiceAdapter.OnServic // Helper function to get a list of all services from the backend private void loadServiceData() { - if (swipeRefreshLayout != null) { - swipeRefreshLayout.setRefreshing(true); - } - api.getAllServices(0, 100).enqueue(new Callback>() { - @Override - public void onResponse(Call> call, Response> response) { - if (swipeRefreshLayout != null) { - swipeRefreshLayout.setRefreshing(false); - } - if (response.isSuccessful() && response.body() != null) { - serviceList.clear(); - serviceList.addAll(response.body().getContent()); - filterServices(etSearch.getText().toString()); + //Load all services from the backend using viewModel + viewModel.getAllServices(0, 100).observe(getViewLifecycleOwner(), resource -> { + if (resource == null) return; - } else { - Log.e("onResponse: ", response.message()); - } - } - - @Override - public void onFailure(Call> call, Throwable t) { - if (swipeRefreshLayout != null) { - swipeRefreshLayout.setRefreshing(false); - } - if (getContext() != null) { - Toast.makeText(getContext(), - "Failed to load services", Toast.LENGTH_SHORT).show(); - } - Log.e("onFailure: ", t.getMessage()); + // Check the status to see if the resource is loaded and display the data + switch (resource.status) { + case LOADING: + // Show loading indicator + if (swipeRefreshLayout != null) swipeRefreshLayout.setRefreshing(true); + break; + case SUCCESS: + // Hide loading indicator and display data + if (swipeRefreshLayout != null) swipeRefreshLayout.setRefreshing(false); + if (resource.data != null) { + serviceList.clear(); + serviceList.addAll(resource.data.getContent()); + filterServices(etSearch != null ? etSearch.getText().toString() : ""); + } + break; + case ERROR: + // Hide loading indicator and toast error message + if (swipeRefreshLayout != null) swipeRefreshLayout.setRefreshing(false); + if (getContext() != null) { + Toast.makeText(getContext(), "Failed to load services: " + resource.message, Toast.LENGTH_SHORT).show(); + } + Log.e("ServiceFragment", "Error loading services: " + resource.message); + break; } }); } diff --git a/android/app/src/main/java/com/example/petstoremobile/fragments/listfragments/SupplierFragment.java b/android/app/src/main/java/com/example/petstoremobile/fragments/listfragments/SupplierFragment.java index f9de9227..5e26a432 100644 --- a/android/app/src/main/java/com/example/petstoremobile/fragments/listfragments/SupplierFragment.java +++ b/android/app/src/main/java/com/example/petstoremobile/fragments/listfragments/SupplierFragment.java @@ -5,6 +5,7 @@ import android.os.Bundle; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.fragment.app.Fragment; +import androidx.lifecycle.ViewModelProvider; import androidx.navigation.fragment.NavHostFragment; import androidx.recyclerview.widget.LinearLayoutManager; import androidx.recyclerview.widget.RecyclerView; @@ -22,21 +23,16 @@ import android.widget.Toast; import com.example.petstoremobile.R; import com.example.petstoremobile.adapters.SupplierAdapter; -import com.example.petstoremobile.api.SupplierApi; -import com.example.petstoremobile.dtos.PageResponse; import com.example.petstoremobile.dtos.SupplierDTO; import com.example.petstoremobile.fragments.ListFragment; +import com.example.petstoremobile.viewmodels.SupplierViewModel; +import com.example.petstoremobile.utils.Resource; import com.google.android.material.floatingactionbutton.FloatingActionButton; import java.util.ArrayList; import java.util.List; -import javax.inject.Inject; - import dagger.hilt.android.AndroidEntryPoint; -import retrofit2.Call; -import retrofit2.Callback; -import retrofit2.Response; @AndroidEntryPoint public class SupplierFragment extends Fragment implements SupplierAdapter.OnSupplierClickListener { @@ -45,13 +41,19 @@ public class SupplierFragment extends Fragment implements SupplierAdapter.OnSupp private List filteredList = new ArrayList<>(); private SupplierAdapter adapter; private ImageButton hamburger; - @Inject SupplierApi api; + private SupplierViewModel viewModel; private SwipeRefreshLayout swipeRefreshLayout; private EditText etSearch; + @Override + public void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + viewModel = new ViewModelProvider(this).get(SupplierViewModel.class); + } + //load supplier view @Override - public View onCreateView(LayoutInflater inflater, ViewGroup container, + public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View view = inflater.inflate(R.layout.fragment_supplier, container, false); @@ -98,9 +100,9 @@ public class SupplierFragment extends Fragment implements SupplierAdapter.OnSupp } else { String lower = query.toLowerCase(); for (SupplierDTO s : supplierList) { - if (s.getSupCompany().toLowerCase().contains(lower) - || s.getSupContactFirstName().toLowerCase().contains(lower) - || s.getSupContactLastName().toLowerCase().contains(lower)) { + if ((s.getSupCompany() != null && s.getSupCompany().toLowerCase().contains(lower)) + || (s.getSupContactFirstName() != null && s.getSupContactFirstName().toLowerCase().contains(lower)) + || (s.getSupContactLastName() != null && s.getSupContactLastName().toLowerCase().contains(lower))) { filteredList.add(s); } } @@ -110,9 +112,7 @@ public class SupplierFragment extends Fragment implements SupplierAdapter.OnSupp private void setupSwipeRefresh(View view) { swipeRefreshLayout = view.findViewById(R.id.swipeRefreshSupplier); - swipeRefreshLayout.setOnRefreshListener(() -> { - loadSupplierData(); - }); + swipeRefreshLayout.setOnRefreshListener(this::loadSupplierData); } //Open the supplier detail view depending on the mode @@ -144,35 +144,33 @@ public class SupplierFragment extends Fragment implements SupplierAdapter.OnSupp // Helper function to get a list of all suppliers from the backend private void loadSupplierData() { - if (swipeRefreshLayout != null) { - swipeRefreshLayout.setRefreshing(true); - } - api.getAllSuppliers(0, 100).enqueue(new Callback>() { - @Override - public void onResponse(Call> call, Response> response) { - if (swipeRefreshLayout != null) { - swipeRefreshLayout.setRefreshing(false); - } - if (response.isSuccessful() && response.body() != null) { - supplierList.clear(); - supplierList.addAll(response.body().getContent()); - filterSuppliers(etSearch.getText().toString()); + //Load all suppliers from the backend using viewModel + viewModel.getAllSuppliers(0, 100).observe(getViewLifecycleOwner(), resource -> { + if (resource == null) return; - } else { - Log.e("onResponse: ", response.message()); - } - } - - @Override - public void onFailure(Call> call, Throwable t) { - if (swipeRefreshLayout != null) { - swipeRefreshLayout.setRefreshing(false); - } - if (getContext() != null) { - Toast.makeText(getContext(), - "Failed to load suppliers", Toast.LENGTH_SHORT).show(); - } - Log.e("onFailure: ", t.getMessage()); + // Check the status to see if the resource is loaded and display the data + switch (resource.status) { + case LOADING: + // Show loading indicator + if (swipeRefreshLayout != null) swipeRefreshLayout.setRefreshing(true); + break; + case SUCCESS: + // Hide loading indicator and display data + if (swipeRefreshLayout != null) swipeRefreshLayout.setRefreshing(false); + if (resource.data != null) { + supplierList.clear(); + supplierList.addAll(resource.data.getContent()); + filterSuppliers(etSearch.getText().toString()); + } + break; + case ERROR: + // Hide loading indicator and toast error message + if (swipeRefreshLayout != null) swipeRefreshLayout.setRefreshing(false); + if (getContext() != null) { + Toast.makeText(getContext(), "Failed to load suppliers: " + resource.message, Toast.LENGTH_SHORT).show(); + } + Log.e("SupplierFragment", "Error loading suppliers: " + resource.message); + break; } }); } @@ -184,4 +182,4 @@ public class SupplierFragment extends Fragment implements SupplierAdapter.OnSupp recyclerView.setLayoutManager(new LinearLayoutManager(getContext())); recyclerView.setAdapter(adapter); } -} \ No newline at end of file +} diff --git a/android/app/src/main/java/com/example/petstoremobile/fragments/listfragments/detailfragments/ProductDetailFragment.java b/android/app/src/main/java/com/example/petstoremobile/fragments/listfragments/detailfragments/ProductDetailFragment.java index bb830f51..0eecece6 100644 --- a/android/app/src/main/java/com/example/petstoremobile/fragments/listfragments/detailfragments/ProductDetailFragment.java +++ b/android/app/src/main/java/com/example/petstoremobile/fragments/listfragments/detailfragments/ProductDetailFragment.java @@ -13,10 +13,12 @@ import android.widget.*; import androidx.activity.result.ActivityResultLauncher; import androidx.activity.result.contract.ActivityResultContracts; import androidx.annotation.NonNull; +import androidx.annotation.Nullable; import androidx.appcompat.app.AlertDialog; import androidx.core.content.ContextCompat; import androidx.core.content.FileProvider; import androidx.fragment.app.Fragment; +import androidx.lifecycle.ViewModelProvider; import androidx.navigation.fragment.NavHostFragment; import com.bumptech.glide.Glide; @@ -28,9 +30,11 @@ import com.example.petstoremobile.adapters.BlackTextArrayAdapter; import com.example.petstoremobile.api.*; import com.example.petstoremobile.api.auth.TokenManager; import com.example.petstoremobile.dtos.*; +import com.example.petstoremobile.viewmodels.ProductViewModel; +import com.example.petstoremobile.utils.Resource; +import com.example.petstoremobile.utils.FileUtils; + import java.io.File; -import java.io.FileOutputStream; -import java.io.InputStream; import java.math.BigDecimal; import java.util.*; @@ -41,7 +45,6 @@ import dagger.hilt.android.AndroidEntryPoint; import okhttp3.MediaType; import okhttp3.MultipartBody; import okhttp3.RequestBody; -import retrofit2.*; @AndroidEntryPoint public class ProductDetailFragment extends Fragment { @@ -61,9 +64,8 @@ public class ProductDetailFragment extends Fragment { private List categoryList = new ArrayList<>(); private Uri photoUri; + private ProductViewModel viewModel; - @Inject ProductApi productApi; - @Inject CategoryApi categoryApi; @Inject @Named("baseUrl") String baseUrl; @Inject TokenManager tokenManager; @@ -74,12 +76,13 @@ public class ProductDetailFragment extends Fragment { @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); + viewModel = new ViewModelProvider(this).get(ProductViewModel.class); + galleryLauncher = registerForActivityResult( new ActivityResultContracts.StartActivityForResult(), result -> { if (result.getResultCode() == Activity.RESULT_OK && result.getData() != null) { Uri selectedImage = result.getData().getData(); - // Update image view locally Glide.with(this).load(selectedImage).into(ivProductImage); photoUri = selectedImage; hasImage = true; @@ -92,7 +95,6 @@ public class ProductDetailFragment extends Fragment { new ActivityResultContracts.TakePicture(), success -> { if (success) { - // Update image view locally Glide.with(this).load(photoUri).into(ivProductImage); hasImage = true; isImageChanged = true; @@ -185,19 +187,12 @@ public class ProductDetailFragment extends Fragment { // Helper function to load categories from the backend for the spinner private void loadCategories() { - categoryApi.getAllCategories(0, 100) - .enqueue(new Callback>() { - public void onResponse(Call> c, - Response> r) { - if (r.isSuccessful() && r.body() != null) { - categoryList = r.body().getContent(); - populateCategorySpinner(); - } - } - public void onFailure(Call> c, Throwable t) { - Log.e("ProductDetail", "Category load failed: " + t.getMessage()); - } - }); + viewModel.getAllCategories(0, 100).observe(getViewLifecycleOwner(), resource -> { + if (resource != null && resource.status == Resource.Status.SUCCESS && resource.data != null) { + categoryList = resource.data.getContent(); + populateCategorySpinner(); + } + }); } // Helper function to populate the category spinner @@ -265,24 +260,19 @@ public class ProductDetailFragment extends Fragment { // updating/adding photo, removing photo or no change private void performPendingImageActions(String successMsg) { if (isImageRemoved) { - //if the image is removed then delete the image - productApi.deleteProductImage(prodId).enqueue(new Callback() { - @Override - public void onResponse(Call call, Response response) { - Toast.makeText(getContext(), successMsg, Toast.LENGTH_SHORT).show(); - navigateBack(); - } - @Override - public void onFailure(Call call, Throwable t) { - Toast.makeText(getContext(), successMsg + " (but image removal failed)", Toast.LENGTH_SHORT).show(); + viewModel.deleteProductImage(prodId).observe(getViewLifecycleOwner(), resource -> { + if (resource != null && 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(); + } navigateBack(); } }); } else if (isImageChanged && photoUri != null) { - //if the image is changed then upload it uploadProductImageAndNavigate(photoUri, successMsg); } else { - //if no changes then navigate back Toast.makeText(getContext(), successMsg, Toast.LENGTH_SHORT).show(); navigateBack(); } @@ -291,61 +281,28 @@ public class ProductDetailFragment extends Fragment { // Helper function to upload the product image by calling the backend // and then navigate back to the previous screen private void uploadProductImageAndNavigate(Uri uri, String successMsg) { - try { - File file = getFileFromUri(uri); - if (file == null) { - Toast.makeText(getContext(), successMsg, Toast.LENGTH_SHORT).show(); - navigateBack(); - return; - } - - RequestBody requestFile = RequestBody.create(file, MediaType.parse(requireContext().getContentResolver().getType(uri))); - MultipartBody.Part body = MultipartBody.Part.createFormData("image", file.getName(), requestFile); - - productApi.uploadProductImage(prodId, body) - .enqueue(new Callback() { - @Override - public void onResponse(Call call, Response response) { - if (response.isSuccessful()) { - Toast.makeText(getContext(), successMsg, Toast.LENGTH_SHORT).show(); - } else { - Toast.makeText(getContext(), successMsg + " (but image upload failed)", Toast.LENGTH_SHORT).show(); - } - navigateBack(); - } - @Override - public void onFailure(Call call, Throwable t) { - Toast.makeText(getContext(), successMsg + " (network error during upload)", Toast.LENGTH_SHORT).show(); - navigateBack(); - } - }); - } catch (Exception e) { - Log.e("ProductDetail", "Error uploading image", e); + File file = FileUtils.getFileFromUri(requireContext(), uri); + if (file == null) { Toast.makeText(getContext(), successMsg, Toast.LENGTH_SHORT).show(); navigateBack(); + return; } - } - // Helper function to get the File from the Uri - private File getFileFromUri(Uri uri) { - try { - InputStream inputStream = requireContext().getContentResolver().openInputStream(uri); - File tempFile = new File(requireContext().getCacheDir(), "upload_product_image.jpg"); - FileOutputStream outputStream = new FileOutputStream(tempFile); - byte[] buffer = new byte[1024]; - int length; - while ((length = inputStream.read(buffer)) > 0) { - outputStream.write(buffer, 0, length); + RequestBody requestFile = RequestBody.create(file, MediaType.parse(requireContext().getContentResolver().getType(uri))); + MultipartBody.Part body = MultipartBody.Part.createFormData("image", file.getName(), requestFile); + + viewModel.uploadProductImage(prodId, body).observe(getViewLifecycleOwner(), resource -> { + if (resource != null && 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(); + } + navigateBack(); } - outputStream.close(); - inputStream.close(); - return tempFile; - } catch (Exception e) { - return null; - } + }); } - // Function to save the product to the server private void saveProduct() { String name = etProductName.getText().toString().trim(); String desc = etProductDesc.getText().toString().trim(); @@ -372,35 +329,25 @@ public class ProductDetailFragment extends Fragment { ProductDTO dto = new ProductDTO(name, category.getCategoryId(), desc, price); if (isEditing) { - productApi.updateProduct(prodId, dto).enqueue(new Callback() { - @Override - public void onResponse(Call call, Response response) { - if (response.isSuccessful()) { + viewModel.updateProduct(prodId, dto).observe(getViewLifecycleOwner(), resource -> { + if (resource != null && resource.status != Resource.Status.LOADING) { + if (resource.status == Resource.Status.SUCCESS) { performPendingImageActions("Updated"); } else { - Toast.makeText(getContext(), "Error " + response.code(), Toast.LENGTH_SHORT).show(); + Toast.makeText(getContext(), "Error: " + resource.message, Toast.LENGTH_SHORT).show(); } } - @Override - public void onFailure(Call call, Throwable t) { - Toast.makeText(getContext(), "Network error", Toast.LENGTH_SHORT).show(); - } }); } else { - productApi.createProduct(dto).enqueue(new Callback() { - @Override - public void onResponse(Call call, Response response) { - if (response.isSuccessful() && response.body() != null) { - prodId = response.body().getProdId(); + viewModel.createProduct(dto).observe(getViewLifecycleOwner(), resource -> { + if (resource != null && resource.status != Resource.Status.LOADING) { + if (resource.status == Resource.Status.SUCCESS && resource.data != null) { + prodId = resource.data.getProdId(); performPendingImageActions("Saved"); } else { - Toast.makeText(getContext(), "Error saving", Toast.LENGTH_SHORT).show(); + Toast.makeText(getContext(), "Error saving: " + resource.message, Toast.LENGTH_SHORT).show(); } } - @Override - public void onFailure(Call call, Throwable t) { - Toast.makeText(getContext(), "Network error", Toast.LENGTH_SHORT).show(); - } }); } } @@ -410,20 +357,17 @@ public class ProductDetailFragment extends Fragment { new AlertDialog.Builder(requireContext()) .setTitle("Delete Product?") .setPositiveButton("Yes", (d, w) -> - productApi.deleteProduct(prodId) - .enqueue(new Callback() { - public void onResponse(Call c, Response r) { - navigateBack(); - } - public void onFailure(Call c, Throwable t) { - Toast.makeText(getContext(), "Delete failed", - Toast.LENGTH_SHORT).show(); - } - })) + viewModel.deleteProduct(prodId).observe(getViewLifecycleOwner(), resource -> { + if (resource != null && resource.status == Resource.Status.SUCCESS) { + navigateBack(); + } else if (resource != null && resource.status == Resource.Status.ERROR) { + Toast.makeText(getContext(), "Delete failed: " + resource.message, Toast.LENGTH_SHORT).show(); + } + })) .setNegativeButton("No", null).show(); } private void navigateBack() { NavHostFragment.findNavController(this).popBackStack(); } -} \ No newline at end of file +} diff --git a/android/app/src/main/java/com/example/petstoremobile/models/Adoption.java b/android/app/src/main/java/com/example/petstoremobile/models/Adoption.java deleted file mode 100644 index e227bc9b..00000000 --- a/android/app/src/main/java/com/example/petstoremobile/models/Adoption.java +++ /dev/null @@ -1,79 +0,0 @@ -package com.example.petstoremobile.models; - -public class Adoption { - private int adoptionId; - private String adopterName; - private String adopterEmail; - private String adopterPhone; - private String petName; - private String adoptionDate; - private String status; - - // Constructor - public Adoption(int adoptionId, String adopterName, String adopterEmail, String adopterPhone, String petName, String adoptionDate, String status) { - this.adoptionId = adoptionId; - this.adopterName = adopterName; - this.adopterEmail = adopterEmail; - this.adopterPhone = adopterPhone; - this.petName = petName; - this.adoptionDate = adoptionDate; - this.status = status; - } - - // Getters and setters - public int getAdoptionId() { - return adoptionId; - } - - public void setAdoptionId(int adoptionId) { - this.adoptionId = adoptionId; - } - - public String getAdopterName() { - return adopterName; - } - - public void setAdopterName(String adopterName) { - this.adopterName = adopterName; - } - - public String getAdopterEmail() { - return adopterEmail; - } - - public void setAdopterEmail(String adopterEmail) { - this.adopterEmail = adopterEmail; - } - - public String getAdopterPhone() { - return adopterPhone; - } - - public void setAdopterPhone(String adopterPhone) { - this.adopterPhone = adopterPhone; - } - - public String getPetName() { - return petName; - } - - public void setPetName(String petName) { - this.petName = petName; - } - - public String getAdoptionDate() { - return adoptionDate; - } - - public void setAdoptionDate(String adoptionDate) { - this.adoptionDate = adoptionDate; - } - - public String getStatus() { - return status; - } - - public void setStatus(String status) { - this.status = status; - } -} diff --git a/android/app/src/main/java/com/example/petstoremobile/models/Appointment.java b/android/app/src/main/java/com/example/petstoremobile/models/Appointment.java deleted file mode 100644 index 38e8da10..00000000 --- a/android/app/src/main/java/com/example/petstoremobile/models/Appointment.java +++ /dev/null @@ -1,76 +0,0 @@ -package com.example.petstoremobile.models; - - -public class Appointment { - private int appointmentId; - private String customerName; - private String petName; - private String serviceType; - private String appointmentDate; - private String appointmentTime; - private String status; - - // Constructor - public Appointment(int appointmentId, String customerName, String petName, String serviceType, String appointmentDate, String appointmentTime, String status) { - this.appointmentId = appointmentId; - this.customerName = customerName; - this.petName = petName; - this.serviceType = serviceType; - this.appointmentDate = appointmentDate; - this.appointmentTime = appointmentTime; - this.status = status; - } - - // Getters and setters - public int getAppointmentId() { - return appointmentId; - } - - public String getCustomerName() { - return customerName; - } - - public void setCustomerName(String customerName) { - this.customerName = customerName; - } - - public String getPetName() { - return petName; - } - - public void setPetName(String petName) { - this.petName = petName; - } - - public String getServiceType() { - return serviceType; - } - - public void setServiceType(String serviceType) { - this.serviceType = serviceType; - } - - public String getAppointmentDate() { - return appointmentDate; - } - - public void setAppointmentDate(String appointmentDate) { - this.appointmentDate = appointmentDate; - } - - public String getAppointmentTime() { - return appointmentTime; - } - - public void setAppointmentTime(String appointmentTime) { - this.appointmentTime = appointmentTime; - } - - public String getStatus() { - return status; - } - - public void setStatus(String status) { - this.status = status; - } -} diff --git a/android/app/src/main/java/com/example/petstoremobile/models/Inventory.java b/android/app/src/main/java/com/example/petstoremobile/models/Inventory.java deleted file mode 100644 index 7aacd2df..00000000 --- a/android/app/src/main/java/com/example/petstoremobile/models/Inventory.java +++ /dev/null @@ -1,66 +0,0 @@ -package com.example.petstoremobile.models; - - -public class Inventory { - private int inventoryId; - private String itemName; - private String category; - private int quantity; - private double unitPrice; - private String supplier; - - // Constructor - public Inventory(int inventoryId, String itemName, String category, int quantity, double unitPrice, String supplier) { - this.inventoryId = inventoryId; - this.itemName = itemName; - this.category = category; - this.quantity = quantity; - this.unitPrice = unitPrice; - this.supplier = supplier; - } - - // Getters and setters - public int getInventoryId() { - return inventoryId; - } - - public String getItemName() { - return itemName; - } - - public void setItemName(String itemName) { - this.itemName = itemName; - } - - public String getCategory() { - return category; - } - - public void setCategory(String category) { - this.category = category; - } - - public int getQuantity() { - return quantity; - } - - public void setQuantity(int quantity) { - this.quantity = quantity; - } - - public double getUnitPrice() { - return unitPrice; - } - - public void setUnitPrice(double unitPrice) { - this.unitPrice = unitPrice; - } - - public String getSupplier() { - return supplier; - } - - public void setSupplier(String supplier) { - this.supplier = supplier; - } -} diff --git a/android/app/src/main/java/com/example/petstoremobile/models/Product.java b/android/app/src/main/java/com/example/petstoremobile/models/Product.java deleted file mode 100644 index 90a56eab..00000000 --- a/android/app/src/main/java/com/example/petstoremobile/models/Product.java +++ /dev/null @@ -1,66 +0,0 @@ -package com.example.petstoremobile.models; - - -public class Product { - private int productId; - private String productName; - private String productDesc; - private String category; - private double productPrice; - private int stockQuantity; - - // Constructor - public Product(int productId, String productName, String productDesc, String category, double productPrice, int stockQuantity) { - this.productId = productId; - this.productName = productName; - this.productDesc = productDesc; - this.category = category; - this.productPrice = productPrice; - this.stockQuantity = stockQuantity; - } - - // Getters and setters - public int getProductId() { - return productId; - } - - public String getProductName() { - return productName; - } - - public void setProductName(String productName) { - this.productName = productName; - } - - public String getProductDesc() { - return productDesc; - } - - public void setProductDesc(String productDesc) { - this.productDesc = productDesc; - } - - public String getCategory() { - return category; - } - - public void setCategory(String category) { - this.category = category; - } - - public double getProductPrice() { - return productPrice; - } - - public void setProductPrice(double productPrice) { - this.productPrice = productPrice; - } - - public int getStockQuantity() { - return stockQuantity; - } - - public void setStockQuantity(int stockQuantity) { - this.stockQuantity = stockQuantity; - } -} diff --git a/android/app/src/main/java/com/example/petstoremobile/models/ProductSupplier.java b/android/app/src/main/java/com/example/petstoremobile/models/ProductSupplier.java deleted file mode 100644 index b624b5e4..00000000 --- a/android/app/src/main/java/com/example/petstoremobile/models/ProductSupplier.java +++ /dev/null @@ -1,49 +0,0 @@ -package com.example.petstoremobile.models; - -public class ProductSupplier { - private int supId; - private int prodId; - private String supCompany; - private String prodName; - private double cost; - - public ProductSupplier(int supId, int prodId, String supCompany, String prodName, double cost) { - this.supId = supId; - this.prodId = prodId; - this.supCompany = supCompany; - this.prodName = prodName; - this.cost = cost; - } - - public int getSupId() { - return supId; - } - - public int getProdId() { - return prodId; - } - - public String getSupCompany() { - return supCompany; - } - - public String getProdName() { - return prodName; - } - - public double getCost() { - return cost; - } - - public void setSupCompany(String supCompany) { - this.supCompany = supCompany; - } - - public void setProdName(String prodName) { - this.prodName = prodName; - } - - public void setCost(double cost) { - this.cost = cost; - } -} diff --git a/android/app/src/main/java/com/example/petstoremobile/models/PurchaseOrder.java b/android/app/src/main/java/com/example/petstoremobile/models/PurchaseOrder.java deleted file mode 100644 index 971ff400..00000000 --- a/android/app/src/main/java/com/example/petstoremobile/models/PurchaseOrder.java +++ /dev/null @@ -1,31 +0,0 @@ -package com.example.petstoremobile.models; - -public class PurchaseOrder { - private int purchaseOrderId; - private String supplierName; - private String orderDate; - private String status; - - public PurchaseOrder(int purchaseOrderId, String supplierName, String orderDate, String status) { - this.purchaseOrderId = purchaseOrderId; - this.supplierName = supplierName; - this.orderDate = orderDate; - this.status = status; - } - - public int getPurchaseOrderId() { - return purchaseOrderId; - } - - public String getSupplierName() { - return supplierName; - } - - public String getOrderDate() { - return orderDate; - } - - public String getStatus() { - return status; - } -} diff --git a/android/app/src/main/java/com/example/petstoremobile/repositories/AdoptionRepository.java b/android/app/src/main/java/com/example/petstoremobile/repositories/AdoptionRepository.java new file mode 100644 index 00000000..8164d34b --- /dev/null +++ b/android/app/src/main/java/com/example/petstoremobile/repositories/AdoptionRepository.java @@ -0,0 +1,156 @@ +package com.example.petstoremobile.repositories; + +import androidx.lifecycle.LiveData; +import androidx.lifecycle.MutableLiveData; + +import com.example.petstoremobile.api.AdoptionApi; +import com.example.petstoremobile.dtos.AdoptionDTO; +import com.example.petstoremobile.dtos.PageResponse; +import com.example.petstoremobile.utils.Resource; + +import javax.inject.Inject; +import javax.inject.Singleton; + +import retrofit2.Call; +import retrofit2.Callback; +import retrofit2.Response; + +@Singleton +public class AdoptionRepository { + private final AdoptionApi adoptionApi; + + @Inject + public AdoptionRepository(AdoptionApi adoptionApi) { + this.adoptionApi = adoptionApi; + } + + /** + * Retrieves a paginated list of all adoptions from the API. + */ + public LiveData>> getAllAdoptions(int page, int size) { + MutableLiveData>> data = new MutableLiveData<>(); + data.setValue(Resource.loading(null)); + + adoptionApi.getAllAdoptions(page, size).enqueue(new Callback>() { + @Override + public void onResponse(Call> call, Response> response) { + if (response.isSuccessful() && response.body() != null) { + data.setValue(Resource.success(response.body())); + } else { + data.setValue(Resource.error("Error: " + response.message(), null)); + } + } + + @Override + public void onFailure(Call> call, Throwable t) { + data.setValue(Resource.error(t.getMessage(), null)); + } + }); + + return data; + } + + /** + * Retrieves a specific adoption record by its ID from the API. + */ + public LiveData> getAdoptionById(Long id) { + MutableLiveData> data = new MutableLiveData<>(); + data.setValue(Resource.loading(null)); + + adoptionApi.getAdoptionById(id).enqueue(new Callback() { + @Override + public void onResponse(Call call, Response response) { + if (response.isSuccessful() && response.body() != null) { + data.setValue(Resource.success(response.body())); + } else { + data.setValue(Resource.error("Error: " + response.message(), null)); + } + } + + @Override + public void onFailure(Call call, Throwable t) { + data.setValue(Resource.error(t.getMessage(), null)); + } + }); + + return data; + } + + /** + * Sends a request to the API to create a new adoption record. + */ + public LiveData> createAdoption(AdoptionDTO adoption) { + MutableLiveData> data = new MutableLiveData<>(); + data.setValue(Resource.loading(null)); + + adoptionApi.createAdoption(adoption).enqueue(new Callback() { + @Override + public void onResponse(Call call, Response response) { + if (response.isSuccessful() && response.body() != null) { + data.setValue(Resource.success(response.body())); + } else { + data.setValue(Resource.error("Error: " + response.message(), null)); + } + } + + @Override + public void onFailure(Call call, Throwable t) { + data.setValue(Resource.error(t.getMessage(), null)); + } + }); + + return data; + } + + /** + * Sends a request to the API to update an existing adoption record by ID. + */ + public LiveData> updateAdoption(Long id, AdoptionDTO adoption) { + MutableLiveData> data = new MutableLiveData<>(); + data.setValue(Resource.loading(null)); + + adoptionApi.updateAdoption(id, adoption).enqueue(new Callback() { + @Override + public void onResponse(Call call, Response response) { + if (response.isSuccessful() && response.body() != null) { + data.setValue(Resource.success(response.body())); + } else { + data.setValue(Resource.error("Error: " + response.message(), null)); + } + } + + @Override + public void onFailure(Call call, Throwable t) { + data.setValue(Resource.error(t.getMessage(), null)); + } + }); + + return data; + } + + /** + * Sends a request to the API to delete a specific adoption record. + */ + public LiveData> deleteAdoption(Long id) { + MutableLiveData> data = new MutableLiveData<>(); + data.setValue(Resource.loading(null)); + + adoptionApi.deleteAdoption(id).enqueue(new Callback() { + @Override + public void onResponse(Call call, Response response) { + if (response.isSuccessful()) { + data.setValue(Resource.success(null)); + } else { + data.setValue(Resource.error("Error: " + response.message(), null)); + } + } + + @Override + public void onFailure(Call call, Throwable t) { + data.setValue(Resource.error(t.getMessage(), null)); + } + }); + + return data; + } +} diff --git a/android/app/src/main/java/com/example/petstoremobile/repositories/AppointmentRepository.java b/android/app/src/main/java/com/example/petstoremobile/repositories/AppointmentRepository.java new file mode 100644 index 00000000..cea28d0a --- /dev/null +++ b/android/app/src/main/java/com/example/petstoremobile/repositories/AppointmentRepository.java @@ -0,0 +1,156 @@ +package com.example.petstoremobile.repositories; + +import androidx.lifecycle.LiveData; +import androidx.lifecycle.MutableLiveData; + +import com.example.petstoremobile.api.AppointmentApi; +import com.example.petstoremobile.dtos.AppointmentDTO; +import com.example.petstoremobile.dtos.PageResponse; +import com.example.petstoremobile.utils.Resource; + +import javax.inject.Inject; +import javax.inject.Singleton; + +import retrofit2.Call; +import retrofit2.Callback; +import retrofit2.Response; + +@Singleton +public class AppointmentRepository { + private final AppointmentApi appointmentApi; + + @Inject + public AppointmentRepository(AppointmentApi appointmentApi) { + this.appointmentApi = appointmentApi; + } + + /** + * Retrieves a paginated list of all appointments from the API. + */ + public LiveData>> getAllAppointments(int page, int size) { + MutableLiveData>> data = new MutableLiveData<>(); + data.setValue(Resource.loading(null)); + + appointmentApi.getAllAppointments(page, size).enqueue(new Callback>() { + @Override + public void onResponse(Call> call, Response> response) { + if (response.isSuccessful() && response.body() != null) { + data.setValue(Resource.success(response.body())); + } else { + data.setValue(Resource.error("Error: " + response.message(), null)); + } + } + + @Override + public void onFailure(Call> call, Throwable t) { + data.setValue(Resource.error(t.getMessage(), null)); + } + }); + + return data; + } + + /** + * Retrieves a specific appointment by its ID from the API. + */ + public LiveData> getAppointmentById(Long id) { + MutableLiveData> data = new MutableLiveData<>(); + data.setValue(Resource.loading(null)); + + appointmentApi.getAppointmentById(id).enqueue(new Callback() { + @Override + public void onResponse(Call call, Response response) { + if (response.isSuccessful() && response.body() != null) { + data.setValue(Resource.success(response.body())); + } else { + data.setValue(Resource.error("Error: " + response.message(), null)); + } + } + + @Override + public void onFailure(Call call, Throwable t) { + data.setValue(Resource.error(t.getMessage(), null)); + } + }); + + return data; + } + + /** + * Sends a request to the API to create a new appointment record. + */ + public LiveData> createAppointment(AppointmentDTO appointment) { + MutableLiveData> data = new MutableLiveData<>(); + data.setValue(Resource.loading(null)); + + appointmentApi.createAppointment(appointment).enqueue(new Callback() { + @Override + public void onResponse(Call call, Response response) { + if (response.isSuccessful() && response.body() != null) { + data.setValue(Resource.success(response.body())); + } else { + data.setValue(Resource.error("Error: " + response.message(), null)); + } + } + + @Override + public void onFailure(Call call, Throwable t) { + data.setValue(Resource.error(t.getMessage(), null)); + } + }); + + return data; + } + + /** + * Sends a request to the API to update an existing appointment record by ID. + */ + public LiveData> updateAppointment(Long id, AppointmentDTO appointment) { + MutableLiveData> data = new MutableLiveData<>(); + data.setValue(Resource.loading(null)); + + appointmentApi.updateAppointment(id, appointment).enqueue(new Callback() { + @Override + public void onResponse(Call call, Response response) { + if (response.isSuccessful() && response.body() != null) { + data.setValue(Resource.success(response.body())); + } else { + data.setValue(Resource.error("Error: " + response.message(), null)); + } + } + + @Override + public void onFailure(Call call, Throwable t) { + data.setValue(Resource.error(t.getMessage(), null)); + } + }); + + return data; + } + + /** + * Sends a request to the API to delete a specific appointment record. + */ + public LiveData> deleteAppointment(Long id) { + MutableLiveData> data = new MutableLiveData<>(); + data.setValue(Resource.loading(null)); + + appointmentApi.deleteAppointment(id).enqueue(new Callback() { + @Override + public void onResponse(Call call, Response response) { + if (response.isSuccessful()) { + data.setValue(Resource.success(null)); + } else { + data.setValue(Resource.error("Error: " + response.message(), null)); + } + } + + @Override + public void onFailure(Call call, Throwable t) { + data.setValue(Resource.error(t.getMessage(), null)); + } + }); + + return data; + } +} diff --git a/android/app/src/main/java/com/example/petstoremobile/repositories/AuthRepository.java b/android/app/src/main/java/com/example/petstoremobile/repositories/AuthRepository.java new file mode 100644 index 00000000..c89de328 --- /dev/null +++ b/android/app/src/main/java/com/example/petstoremobile/repositories/AuthRepository.java @@ -0,0 +1,174 @@ +package com.example.petstoremobile.repositories; + +import androidx.lifecycle.LiveData; +import androidx.lifecycle.MutableLiveData; + +import com.example.petstoremobile.api.auth.AuthApi; +import com.example.petstoremobile.api.auth.TokenManager; +import com.example.petstoremobile.dtos.AuthDTO; +import com.example.petstoremobile.dtos.UserDTO; +import com.example.petstoremobile.utils.Resource; + +import java.util.Map; + +import javax.inject.Inject; +import javax.inject.Singleton; + +import okhttp3.MultipartBody; +import retrofit2.Call; +import retrofit2.Callback; +import retrofit2.Response; + +@Singleton +public class AuthRepository { + private final AuthApi authApi; + private final TokenManager tokenManager; + + @Inject + public AuthRepository(AuthApi authApi, TokenManager tokenManager) { + this.authApi = authApi; + this.tokenManager = tokenManager; + } + + /** + * Authenticates the user and saves login data (token, username, role) upon success. + */ + public LiveData> login(AuthDTO.LoginRequest loginRequest) { + MutableLiveData> data = new MutableLiveData<>(); + data.setValue(Resource.loading(null)); + + authApi.login(loginRequest).enqueue(new Callback() { + @Override + public void onResponse(Call call, Response response) { + if (response.isSuccessful() && response.body() != null) { + tokenManager.saveLoginData( + response.body().getToken(), + response.body().getUsername(), + response.body().getRole() + ); + data.setValue(Resource.success(response.body())); + } else { + data.setValue(Resource.error("Login failed: " + response.message(), null)); + } + } + + @Override + public void onFailure(Call call, Throwable t) { + data.setValue(Resource.error(t.getMessage(), null)); + } + }); + + return data; + } + + /** + * Retrieves the current user's profile information from the API. + */ + public LiveData> getMe() { + MutableLiveData> data = new MutableLiveData<>(); + data.setValue(Resource.loading(null)); + + authApi.getMe().enqueue(new Callback() { + @Override + public void onResponse(Call call, Response response) { + if (response.isSuccessful() && response.body() != null) { + data.setValue(Resource.success(response.body())); + } else { + data.setValue(Resource.error("Error: " + response.message(), null)); + } + } + + @Override + public void onFailure(Call call, Throwable t) { + data.setValue(Resource.error(t.getMessage(), null)); + } + }); + + return data; + } + + /** + * Updates the current user's profile details. + */ + public LiveData> updateMe(Map updates) { + MutableLiveData> data = new MutableLiveData<>(); + data.setValue(Resource.loading(null)); + + authApi.updateMe(updates).enqueue(new Callback() { + @Override + public void onResponse(Call call, Response response) { + if (response.isSuccessful() && response.body() != null) { + data.setValue(Resource.success(response.body())); + } else { + data.setValue(Resource.error("Error: " + response.message(), null)); + } + } + + @Override + public void onFailure(Call call, Throwable t) { + data.setValue(Resource.error(t.getMessage(), null)); + } + }); + + return data; + } + + /** + * Uploads a multipart image to be used as the current user's avatar. + */ + public LiveData> uploadAvatar(MultipartBody.Part avatar) { + MutableLiveData> data = new MutableLiveData<>(); + data.setValue(Resource.loading(null)); + + authApi.uploadAvatar(avatar).enqueue(new Callback() { + @Override + public void onResponse(Call call, Response response) { + if (response.isSuccessful() && response.body() != null) { + data.setValue(Resource.success(response.body())); + } else { + data.setValue(Resource.error("Error: " + response.message(), null)); + } + } + + @Override + public void onFailure(Call call, Throwable t) { + data.setValue(Resource.error(t.getMessage(), null)); + } + }); + + return data; + } + + /** + * Sends a request to the API to remove the current user's avatar. + */ + public LiveData> deleteAvatar() { + MutableLiveData> data = new MutableLiveData<>(); + data.setValue(Resource.loading(null)); + + authApi.deleteAvatar().enqueue(new Callback() { + @Override + public void onResponse(Call call, Response response) { + if (response.isSuccessful()) { + data.setValue(Resource.success(null)); + } else { + data.setValue(Resource.error("Error: " + response.message(), null)); + } + } + + @Override + public void onFailure(Call call, Throwable t) { + data.setValue(Resource.error(t.getMessage(), null)); + } + }); + + return data; + } + + /** + * Clears all authentication and login data from storage. + */ + public void logout() { + tokenManager.clearLoginData(); + } +} diff --git a/android/app/src/main/java/com/example/petstoremobile/repositories/CategoryRepository.java b/android/app/src/main/java/com/example/petstoremobile/repositories/CategoryRepository.java new file mode 100644 index 00000000..7837f582 --- /dev/null +++ b/android/app/src/main/java/com/example/petstoremobile/repositories/CategoryRepository.java @@ -0,0 +1,52 @@ +package com.example.petstoremobile.repositories; + +import androidx.lifecycle.LiveData; +import androidx.lifecycle.MutableLiveData; + +import com.example.petstoremobile.api.CategoryApi; +import com.example.petstoremobile.dtos.CategoryDTO; +import com.example.petstoremobile.dtos.PageResponse; +import com.example.petstoremobile.utils.Resource; + +import javax.inject.Inject; +import javax.inject.Singleton; + +import retrofit2.Call; +import retrofit2.Callback; +import retrofit2.Response; + +@Singleton +public class CategoryRepository { + private final CategoryApi categoryApi; + + @Inject + public CategoryRepository(CategoryApi categoryApi) { + this.categoryApi = categoryApi; + } + + /** + * Retrieves a paginated list of all product categories from the API. + */ + public LiveData>> getAllCategories(int page, int size) { + MutableLiveData>> data = new MutableLiveData<>(); + data.setValue(Resource.loading(null)); + + categoryApi.getAllCategories(page, size).enqueue(new Callback>() { + @Override + public void onResponse(Call> call, Response> response) { + if (response.isSuccessful() && response.body() != null) { + data.setValue(Resource.success(response.body())); + } else { + data.setValue(Resource.error("Error: " + response.message(), null)); + } + } + + @Override + public void onFailure(Call> call, Throwable t) { + data.setValue(Resource.error(t.getMessage(), null)); + } + }); + + return data; + } +} diff --git a/android/app/src/main/java/com/example/petstoremobile/repositories/InventoryRepository.java b/android/app/src/main/java/com/example/petstoremobile/repositories/InventoryRepository.java new file mode 100644 index 00000000..ee2503cd --- /dev/null +++ b/android/app/src/main/java/com/example/petstoremobile/repositories/InventoryRepository.java @@ -0,0 +1,184 @@ +package com.example.petstoremobile.repositories; + +import androidx.lifecycle.LiveData; +import androidx.lifecycle.MutableLiveData; + +import com.example.petstoremobile.api.InventoryApi; +import com.example.petstoremobile.dtos.BulkDeleteRequest; +import com.example.petstoremobile.dtos.InventoryDTO; +import com.example.petstoremobile.dtos.InventoryRequest; +import com.example.petstoremobile.dtos.PageResponse; +import com.example.petstoremobile.utils.Resource; + +import javax.inject.Inject; +import javax.inject.Singleton; + +import retrofit2.Call; +import retrofit2.Callback; +import retrofit2.Response; + +@Singleton +public class InventoryRepository { + private final InventoryApi inventoryApi; + + @Inject + public InventoryRepository(InventoryApi inventoryApi) { + this.inventoryApi = inventoryApi; + } + + /** + * Retrieves a paginated list of inventory items from the API with optional search and sort. + */ + public LiveData>> getAllInventory(String query, int page, int size, String sort) { + MutableLiveData>> data = new MutableLiveData<>(); + data.setValue(Resource.loading(null)); + + inventoryApi.getAllInventory(query, page, size, sort).enqueue(new Callback>() { + @Override + public void onResponse(Call> call, Response> response) { + if (response.isSuccessful() && response.body() != null) { + data.setValue(Resource.success(response.body())); + } else { + data.setValue(Resource.error("Error: " + response.message(), null)); + } + } + + @Override + public void onFailure(Call> call, Throwable t) { + data.setValue(Resource.error(t.getMessage(), null)); + } + }); + + return data; + } + + /** + * Retrieves a specific inventory item by its ID from the API. + */ + public LiveData> getInventoryById(Long id) { + MutableLiveData> data = new MutableLiveData<>(); + data.setValue(Resource.loading(null)); + + inventoryApi.getInventoryById(id).enqueue(new Callback() { + @Override + public void onResponse(Call call, Response response) { + if (response.isSuccessful() && response.body() != null) { + data.setValue(Resource.success(response.body())); + } else { + data.setValue(Resource.error("Error: " + response.message(), null)); + } + } + + @Override + public void onFailure(Call call, Throwable t) { + data.setValue(Resource.error(t.getMessage(), null)); + } + }); + + return data; + } + + /** + * Sends a request to the API to create a new inventory record. + */ + public LiveData> createInventory(InventoryRequest request) { + MutableLiveData> data = new MutableLiveData<>(); + data.setValue(Resource.loading(null)); + + inventoryApi.createInventory(request).enqueue(new Callback() { + @Override + public void onResponse(Call call, Response response) { + if (response.isSuccessful() && response.body() != null) { + data.setValue(Resource.success(response.body())); + } else { + data.setValue(Resource.error("Error: " + response.message(), null)); + } + } + + @Override + public void onFailure(Call call, Throwable t) { + data.setValue(Resource.error(t.getMessage(), null)); + } + }); + + return data; + } + + /** + * Sends a request to the API to update an existing inventory record. + */ + public LiveData> updateInventory(Long id, InventoryRequest request) { + MutableLiveData> data = new MutableLiveData<>(); + data.setValue(Resource.loading(null)); + + inventoryApi.updateInventory(id, request).enqueue(new Callback() { + @Override + public void onResponse(Call call, Response response) { + if (response.isSuccessful() && response.body() != null) { + data.setValue(Resource.success(response.body())); + } else { + data.setValue(Resource.error("Error: " + response.message(), null)); + } + } + + @Override + public void onFailure(Call call, Throwable t) { + data.setValue(Resource.error(t.getMessage(), null)); + } + }); + + return data; + } + + /** + * Sends a request to the API to delete a specific inventory record. + */ + public LiveData> deleteInventory(Long id) { + MutableLiveData> data = new MutableLiveData<>(); + data.setValue(Resource.loading(null)); + + inventoryApi.deleteInventory(id).enqueue(new Callback() { + @Override + public void onResponse(Call call, Response response) { + if (response.isSuccessful()) { + data.setValue(Resource.success(null)); + } else { + data.setValue(Resource.error("Error: " + response.message(), null)); + } + } + + @Override + public void onFailure(Call call, Throwable t) { + data.setValue(Resource.error(t.getMessage(), null)); + } + }); + + return data; + } + + /** + * Sends a request to the API to delete multiple inventory records at once. + */ + public LiveData> bulkDeleteInventory(BulkDeleteRequest request) { + MutableLiveData> data = new MutableLiveData<>(); + data.setValue(Resource.loading(null)); + + inventoryApi.bulkDeleteInventory(request).enqueue(new Callback() { + @Override + public void onResponse(Call call, Response response) { + if (response.isSuccessful()) { + data.setValue(Resource.success(null)); + } else { + data.setValue(Resource.error("Error: " + response.message(), null)); + } + } + + @Override + public void onFailure(Call call, Throwable t) { + data.setValue(Resource.error(t.getMessage(), null)); + } + }); + + return data; + } +} diff --git a/android/app/src/main/java/com/example/petstoremobile/repositories/PetRepository.java b/android/app/src/main/java/com/example/petstoremobile/repositories/PetRepository.java new file mode 100644 index 00000000..0737e569 --- /dev/null +++ b/android/app/src/main/java/com/example/petstoremobile/repositories/PetRepository.java @@ -0,0 +1,208 @@ +package com.example.petstoremobile.repositories; + +import com.example.petstoremobile.api.PetApi; +import com.example.petstoremobile.dtos.PageResponse; +import com.example.petstoremobile.dtos.PetDTO; +import com.example.petstoremobile.utils.Resource; + +import javax.inject.Inject; +import javax.inject.Singleton; + +import okhttp3.MultipartBody; +import retrofit2.Call; +import retrofit2.Callback; +import retrofit2.Response; +import androidx.lifecycle.LiveData; +import androidx.lifecycle.MutableLiveData; + +@Singleton +public class PetRepository { + private final PetApi petApi; + + @Inject + public PetRepository(PetApi petApi) { + this.petApi = petApi; + } + + /** + * Retrieves a paginated list of all pets from the API. + */ + public LiveData>> getAllPets(int page, int size) { + MutableLiveData>> data = new MutableLiveData<>(); + data.setValue(Resource.loading(null)); + + petApi.getAllPets(page, size).enqueue(new Callback>() { + @Override + public void onResponse(Call> call, Response> response) { + if (response.isSuccessful() && response.body() != null) { + data.setValue(Resource.success(response.body())); + } else { + data.setValue(Resource.error("Error: " + response.message(), null)); + } + } + + @Override + public void onFailure(Call> call, Throwable t) { + data.setValue(Resource.error(t.getMessage(), null)); + } + }); + + return data; + } + + /** + * Retrieves a specific pet by its ID from the API. + */ + public LiveData> getPetById(Long id) { + MutableLiveData> data = new MutableLiveData<>(); + data.setValue(Resource.loading(null)); + + petApi.getPetById(id).enqueue(new Callback() { + @Override + public void onResponse(Call call, Response response) { + if (response.isSuccessful() && response.body() != null) { + data.setValue(Resource.success(response.body())); + } else { + data.setValue(Resource.error("Error: " + response.message(), null)); + } + } + + @Override + public void onFailure(Call call, Throwable t) { + data.setValue(Resource.error(t.getMessage(), null)); + } + }); + + return data; + } + + /** + * Sends a request to the API to create a new pet record. + */ + public LiveData> createPet(PetDTO pet) { + MutableLiveData> data = new MutableLiveData<>(); + data.setValue(Resource.loading(null)); + + petApi.createPet(pet).enqueue(new Callback() { + @Override + public void onResponse(Call call, Response response) { + if (response.isSuccessful() && response.body() != null) { + data.setValue(Resource.success(response.body())); + } else { + data.setValue(Resource.error("Error: " + response.message(), null)); + } + } + + @Override + public void onFailure(Call call, Throwable t) { + data.setValue(Resource.error(t.getMessage(), null)); + } + }); + + return data; + } + + /** + * Sends a request to the API to update an existing pet record. + */ + public LiveData> updatePet(Long id, PetDTO pet) { + MutableLiveData> data = new MutableLiveData<>(); + data.setValue(Resource.loading(null)); + + petApi.updatePet(id, pet).enqueue(new Callback() { + @Override + public void onResponse(Call call, Response response) { + if (response.isSuccessful() && response.body() != null) { + data.setValue(Resource.success(response.body())); + } else { + data.setValue(Resource.error("Error: " + response.message(), null)); + } + } + + @Override + public void onFailure(Call call, Throwable t) { + data.setValue(Resource.error(t.getMessage(), null)); + } + }); + + return data; + } + + /** + * Sends a request to the API to delete a specific pet record. + */ + public LiveData> deletePet(Long id) { + MutableLiveData> data = new MutableLiveData<>(); + data.setValue(Resource.loading(null)); + + petApi.deletePet(id).enqueue(new Callback() { + @Override + public void onResponse(Call call, Response response) { + if (response.isSuccessful()) { + data.setValue(Resource.success(null)); + } else { + data.setValue(Resource.error("Error: " + response.message(), null)); + } + } + + @Override + public void onFailure(Call call, Throwable t) { + data.setValue(Resource.error(t.getMessage(), null)); + } + }); + + return data; + } + + /** + * Uploads an image file for a specific pet via the API. + */ + public LiveData> uploadPetImage(Long id, MultipartBody.Part image) { + MutableLiveData> data = new MutableLiveData<>(); + data.setValue(Resource.loading(null)); + + petApi.uploadPetImage(id, image).enqueue(new Callback() { + @Override + public void onResponse(Call call, Response response) { + if (response.isSuccessful()) { + data.setValue(Resource.success(null)); + } else { + data.setValue(Resource.error("Error: " + response.message(), null)); + } + } + + @Override + public void onFailure(Call call, Throwable t) { + data.setValue(Resource.error(t.getMessage(), null)); + } + }); + + return data; + } + + /** + * Sends a request to the API to delete the image of a specific pet. + */ + public LiveData> deletePetImage(Long id) { + MutableLiveData> data = new MutableLiveData<>(); + data.setValue(Resource.loading(null)); + + petApi.deletePetImage(id).enqueue(new Callback() { + @Override + public void onResponse(Call call, Response response) { + if (response.isSuccessful()) { + data.setValue(Resource.success(null)); + } else { + data.setValue(Resource.error("Error: " + response.message(), null)); + } + } + + @Override + public void onFailure(Call call, Throwable t) { + data.setValue(Resource.error(t.getMessage(), null)); + } + }); + + return data; + } +} diff --git a/android/app/src/main/java/com/example/petstoremobile/repositories/ProductRepository.java b/android/app/src/main/java/com/example/petstoremobile/repositories/ProductRepository.java new file mode 100644 index 00000000..df79335f --- /dev/null +++ b/android/app/src/main/java/com/example/petstoremobile/repositories/ProductRepository.java @@ -0,0 +1,209 @@ +package com.example.petstoremobile.repositories; + +import androidx.lifecycle.LiveData; +import androidx.lifecycle.MutableLiveData; + +import com.example.petstoremobile.api.ProductApi; +import com.example.petstoremobile.dtos.PageResponse; +import com.example.petstoremobile.dtos.ProductDTO; +import com.example.petstoremobile.utils.Resource; + +import javax.inject.Inject; +import javax.inject.Singleton; + +import okhttp3.MultipartBody; +import retrofit2.Call; +import retrofit2.Callback; +import retrofit2.Response; + +@Singleton +public class ProductRepository { + private final ProductApi productApi; + + @Inject + public ProductRepository(ProductApi productApi) { + this.productApi = productApi; + } + + /** + * Retrieves a paginated list of products from the API, filtered by an optional query. + */ + public LiveData>> getAllProducts(String query, int page, int size) { + MutableLiveData>> data = new MutableLiveData<>(); + data.setValue(Resource.loading(null)); + + productApi.getAllProducts(query, page, size).enqueue(new Callback>() { + @Override + public void onResponse(Call> call, Response> response) { + if (response.isSuccessful() && response.body() != null) { + data.setValue(Resource.success(response.body())); + } else { + data.setValue(Resource.error("Error: " + response.message(), null)); + } + } + + @Override + public void onFailure(Call> call, Throwable t) { + data.setValue(Resource.error(t.getMessage(), null)); + } + }); + + return data; + } + + /** + * Retrieves a specific product by its ID from the API. + */ + public LiveData> getProductById(Long id) { + MutableLiveData> data = new MutableLiveData<>(); + data.setValue(Resource.loading(null)); + + productApi.getProductById(id).enqueue(new Callback() { + @Override + public void onResponse(Call call, Response response) { + if (response.isSuccessful() && response.body() != null) { + data.setValue(Resource.success(response.body())); + } else { + data.setValue(Resource.error("Error: " + response.message(), null)); + } + } + + @Override + public void onFailure(Call call, Throwable t) { + data.setValue(Resource.error(t.getMessage(), null)); + } + }); + + return data; + } + + /** + * Sends a request to the API to create a new product. + */ + public LiveData> createProduct(ProductDTO product) { + MutableLiveData> data = new MutableLiveData<>(); + data.setValue(Resource.loading(null)); + + productApi.createProduct(product).enqueue(new Callback() { + @Override + public void onResponse(Call call, Response response) { + if (response.isSuccessful() && response.body() != null) { + data.setValue(Resource.success(response.body())); + } else { + data.setValue(Resource.error("Error: " + response.message(), null)); + } + } + + @Override + public void onFailure(Call call, Throwable t) { + data.setValue(Resource.error(t.getMessage(), null)); + } + }); + + return data; + } + + /** + * Sends a request to the API to update an existing product by ID. + */ + public LiveData> updateProduct(Long id, ProductDTO product) { + MutableLiveData> data = new MutableLiveData<>(); + data.setValue(Resource.loading(null)); + + productApi.updateProduct(id, product).enqueue(new Callback() { + @Override + public void onResponse(Call call, Response response) { + if (response.isSuccessful() && response.body() != null) { + data.setValue(Resource.success(response.body())); + } else { + data.setValue(Resource.error("Error: " + response.message(), null)); + } + } + + @Override + public void onFailure(Call call, Throwable t) { + data.setValue(Resource.error(t.getMessage(), null)); + } + }); + + return data; + } + + /** + * Sends a request to the API to delete a specific product. + */ + public LiveData> deleteProduct(Long id) { + MutableLiveData> data = new MutableLiveData<>(); + data.setValue(Resource.loading(null)); + + productApi.deleteProduct(id).enqueue(new Callback() { + @Override + public void onResponse(Call call, Response response) { + if (response.isSuccessful()) { + data.setValue(Resource.success(null)); + } else { + data.setValue(Resource.error("Error: " + response.message(), null)); + } + } + + @Override + public void onFailure(Call call, Throwable t) { + data.setValue(Resource.error(t.getMessage(), null)); + } + }); + + return data; + } + + /** + * Uploads an image file for a specific product via the API. + */ + public LiveData> uploadProductImage(Long id, MultipartBody.Part image) { + MutableLiveData> data = new MutableLiveData<>(); + data.setValue(Resource.loading(null)); + + productApi.uploadProductImage(id, image).enqueue(new Callback() { + @Override + public void onResponse(Call call, Response response) { + if (response.isSuccessful()) { + data.setValue(Resource.success(null)); + } else { + data.setValue(Resource.error("Error: " + response.message(), null)); + } + } + + @Override + public void onFailure(Call call, Throwable t) { + data.setValue(Resource.error(t.getMessage(), null)); + } + }); + + return data; + } + + /** + * Sends a request to the API to delete the image of a specific product. + */ + public LiveData> deleteProductImage(Long id) { + MutableLiveData> data = new MutableLiveData<>(); + data.setValue(Resource.loading(null)); + + productApi.deleteProductImage(id).enqueue(new Callback() { + @Override + public void onResponse(Call call, Response response) { + if (response.isSuccessful()) { + data.setValue(Resource.success(null)); + } else { + data.setValue(Resource.error("Error: " + response.message(), null)); + } + } + + @Override + public void onFailure(Call call, Throwable t) { + data.setValue(Resource.error(t.getMessage(), null)); + } + }); + + return data; + } +} diff --git a/android/app/src/main/java/com/example/petstoremobile/repositories/ProductSupplierRepository.java b/android/app/src/main/java/com/example/petstoremobile/repositories/ProductSupplierRepository.java new file mode 100644 index 00000000..2e961f6c --- /dev/null +++ b/android/app/src/main/java/com/example/petstoremobile/repositories/ProductSupplierRepository.java @@ -0,0 +1,130 @@ +package com.example.petstoremobile.repositories; + +import androidx.lifecycle.LiveData; +import androidx.lifecycle.MutableLiveData; + +import com.example.petstoremobile.api.ProductSupplierApi; +import com.example.petstoremobile.dtos.PageResponse; +import com.example.petstoremobile.dtos.ProductSupplierDTO; +import com.example.petstoremobile.utils.Resource; + +import javax.inject.Inject; +import javax.inject.Singleton; + +import retrofit2.Call; +import retrofit2.Callback; +import retrofit2.Response; + +@Singleton +public class ProductSupplierRepository { + private final ProductSupplierApi api; + + @Inject + public ProductSupplierRepository(ProductSupplierApi api) { + this.api = api; + } + + /** + * Retrieves a paginated list of all product-supplier relationships from the API. + */ + public LiveData>> getAllProductSuppliers(int page, int size) { + MutableLiveData>> data = new MutableLiveData<>(); + data.setValue(Resource.loading(null)); + + api.getAllProductSuppliers(page, size).enqueue(new Callback>() { + @Override + public void onResponse(Call> call, Response> response) { + if (response.isSuccessful() && response.body() != null) { + data.setValue(Resource.success(response.body())); + } else { + data.setValue(Resource.error("Error: " + response.message(), null)); + } + } + + @Override + public void onFailure(Call> call, Throwable t) { + data.setValue(Resource.error(t.getMessage(), null)); + } + }); + + return data; + } + + /** + * Sends a request to the API to create a new product-supplier relationship. + */ + public LiveData> createProductSupplier(ProductSupplierDTO dto) { + MutableLiveData> data = new MutableLiveData<>(); + data.setValue(Resource.loading(null)); + + api.createProductSupplier(dto).enqueue(new Callback() { + @Override + public void onResponse(Call call, Response response) { + if (response.isSuccessful() && response.body() != null) { + data.setValue(Resource.success(response.body())); + } else { + data.setValue(Resource.error("Error: " + response.message(), null)); + } + } + + @Override + public void onFailure(Call call, Throwable t) { + data.setValue(Resource.error(t.getMessage(), null)); + } + }); + + return data; + } + + /** + * Sends a request to the API to update an existing product-supplier relationship. + */ + public LiveData> updateProductSupplier(Long productId, Long supplierId, ProductSupplierDTO dto) { + MutableLiveData> data = new MutableLiveData<>(); + data.setValue(Resource.loading(null)); + + api.updateProductSupplier(productId, supplierId, dto).enqueue(new Callback() { + @Override + public void onResponse(Call call, Response response) { + if (response.isSuccessful() && response.body() != null) { + data.setValue(Resource.success(response.body())); + } else { + data.setValue(Resource.error("Error: " + response.message(), null)); + } + } + + @Override + public void onFailure(Call call, Throwable t) { + data.setValue(Resource.error(t.getMessage(), null)); + } + }); + + return data; + } + + /** + * Sends a request to the API to delete a specific product-supplier relationship. + */ + public LiveData> deleteProductSupplier(Long productId, Long supplierId) { + MutableLiveData> data = new MutableLiveData<>(); + data.setValue(Resource.loading(null)); + + api.deleteProductSupplier(productId, supplierId).enqueue(new Callback() { + @Override + public void onResponse(Call call, Response response) { + if (response.isSuccessful()) { + data.setValue(Resource.success(null)); + } else { + data.setValue(Resource.error("Error: " + response.message(), null)); + } + } + + @Override + public void onFailure(Call call, Throwable t) { + data.setValue(Resource.error(t.getMessage(), null)); + } + }); + + return data; + } +} diff --git a/android/app/src/main/java/com/example/petstoremobile/repositories/PurchaseOrderRepository.java b/android/app/src/main/java/com/example/petstoremobile/repositories/PurchaseOrderRepository.java new file mode 100644 index 00000000..f804c2e3 --- /dev/null +++ b/android/app/src/main/java/com/example/petstoremobile/repositories/PurchaseOrderRepository.java @@ -0,0 +1,78 @@ +package com.example.petstoremobile.repositories; + +import androidx.lifecycle.LiveData; +import androidx.lifecycle.MutableLiveData; + +import com.example.petstoremobile.api.PurchaseOrderApi; +import com.example.petstoremobile.dtos.PageResponse; +import com.example.petstoremobile.dtos.PurchaseOrderDTO; +import com.example.petstoremobile.utils.Resource; + +import javax.inject.Inject; +import javax.inject.Singleton; + +import retrofit2.Call; +import retrofit2.Callback; +import retrofit2.Response; + +@Singleton +public class PurchaseOrderRepository { + private final PurchaseOrderApi api; + + @Inject + public PurchaseOrderRepository(PurchaseOrderApi api) { + this.api = api; + } + + /** + * Retrieves a paginated list of all purchase orders from the API. + */ + public LiveData>> getAllPurchaseOrders(int page, int size) { + MutableLiveData>> data = new MutableLiveData<>(); + data.setValue(Resource.loading(null)); + + api.getAllPurchaseOrders(page, size).enqueue(new Callback>() { + @Override + public void onResponse(Call> call, Response> response) { + if (response.isSuccessful() && response.body() != null) { + data.setValue(Resource.success(response.body())); + } else { + data.setValue(Resource.error("Error: " + response.message(), null)); + } + } + + @Override + public void onFailure(Call> call, Throwable t) { + data.setValue(Resource.error(t.getMessage(), null)); + } + }); + + return data; + } + + /** + * Retrieves a specific purchase order by its ID from the API. + */ + public LiveData> getPurchaseOrderById(Long id) { + MutableLiveData> data = new MutableLiveData<>(); + data.setValue(Resource.loading(null)); + + api.getPurchaseOrderById(id).enqueue(new Callback() { + @Override + public void onResponse(Call call, Response response) { + if (response.isSuccessful() && response.body() != null) { + data.setValue(Resource.success(response.body())); + } else { + data.setValue(Resource.error("Error: " + response.message(), null)); + } + } + + @Override + public void onFailure(Call call, Throwable t) { + data.setValue(Resource.error(t.getMessage(), null)); + } + }); + + return data; + } +} diff --git a/android/app/src/main/java/com/example/petstoremobile/repositories/ServiceRepository.java b/android/app/src/main/java/com/example/petstoremobile/repositories/ServiceRepository.java new file mode 100644 index 00000000..8d81ccfb --- /dev/null +++ b/android/app/src/main/java/com/example/petstoremobile/repositories/ServiceRepository.java @@ -0,0 +1,156 @@ +package com.example.petstoremobile.repositories; + +import androidx.lifecycle.LiveData; +import androidx.lifecycle.MutableLiveData; + +import com.example.petstoremobile.api.ServiceApi; +import com.example.petstoremobile.dtos.PageResponse; +import com.example.petstoremobile.dtos.ServiceDTO; +import com.example.petstoremobile.utils.Resource; + +import javax.inject.Inject; +import javax.inject.Singleton; + +import retrofit2.Call; +import retrofit2.Callback; +import retrofit2.Response; + +@Singleton +public class ServiceRepository { + private final ServiceApi serviceApi; + + @Inject + public ServiceRepository(ServiceApi serviceApi) { + this.serviceApi = serviceApi; + } + + /** + * Retrieves a paginated list of all services from the API. + */ + public LiveData>> getAllServices(int page, int size) { + MutableLiveData>> data = new MutableLiveData<>(); + data.setValue(Resource.loading(null)); + + serviceApi.getAllServices(page, size).enqueue(new Callback>() { + @Override + public void onResponse(Call> call, Response> response) { + if (response.isSuccessful() && response.body() != null) { + data.setValue(Resource.success(response.body())); + } else { + data.setValue(Resource.error("Error: " + response.message(), null)); + } + } + + @Override + public void onFailure(Call> call, Throwable t) { + data.setValue(Resource.error(t.getMessage(), null)); + } + }); + + return data; + } + + /** + * Retrieves a specific service by its ID from the API. + */ + public LiveData> getServiceById(Long id) { + MutableLiveData> data = new MutableLiveData<>(); + data.setValue(Resource.loading(null)); + + serviceApi.getServiceById(id).enqueue(new Callback() { + @Override + public void onResponse(Call call, Response response) { + if (response.isSuccessful() && response.body() != null) { + data.setValue(Resource.success(response.body())); + } else { + data.setValue(Resource.error("Error: " + response.message(), null)); + } + } + + @Override + public void onFailure(Call call, Throwable t) { + data.setValue(Resource.error(t.getMessage(), null)); + } + }); + + return data; + } + + /** + * Sends a request to the API to create a new service. + */ + public LiveData> createService(ServiceDTO service) { + MutableLiveData> data = new MutableLiveData<>(); + data.setValue(Resource.loading(null)); + + serviceApi.createService(service).enqueue(new Callback() { + @Override + public void onResponse(Call call, Response response) { + if (response.isSuccessful() && response.body() != null) { + data.setValue(Resource.success(response.body())); + } else { + data.setValue(Resource.error("Error: " + response.message(), null)); + } + } + + @Override + public void onFailure(Call call, Throwable t) { + data.setValue(Resource.error(t.getMessage(), null)); + } + }); + + return data; + } + + /** + * Sends a request to the API to update an existing service by ID. + */ + public LiveData> updateService(Long id, ServiceDTO service) { + MutableLiveData> data = new MutableLiveData<>(); + data.setValue(Resource.loading(null)); + + serviceApi.updateService(id, service).enqueue(new Callback() { + @Override + public void onResponse(Call call, Response response) { + if (response.isSuccessful() && response.body() != null) { + data.setValue(Resource.success(response.body())); + } else { + data.setValue(Resource.error("Error: " + response.message(), null)); + } + } + + @Override + public void onFailure(Call call, Throwable t) { + data.setValue(Resource.error(t.getMessage(), null)); + } + }); + + return data; + } + + /** + * Sends a request to the API to delete a specific service. + */ + public LiveData> deleteService(Long id) { + MutableLiveData> data = new MutableLiveData<>(); + data.setValue(Resource.loading(null)); + + serviceApi.deleteService(id).enqueue(new Callback() { + @Override + public void onResponse(Call call, Response response) { + if (response.isSuccessful()) { + data.setValue(Resource.success(null)); + } else { + data.setValue(Resource.error("Error: " + response.message(), null)); + } + } + + @Override + public void onFailure(Call call, Throwable t) { + data.setValue(Resource.error(t.getMessage(), null)); + } + }); + + return data; + } +} diff --git a/android/app/src/main/java/com/example/petstoremobile/repositories/SupplierRepository.java b/android/app/src/main/java/com/example/petstoremobile/repositories/SupplierRepository.java new file mode 100644 index 00000000..eb7b8b61 --- /dev/null +++ b/android/app/src/main/java/com/example/petstoremobile/repositories/SupplierRepository.java @@ -0,0 +1,156 @@ +package com.example.petstoremobile.repositories; + +import androidx.lifecycle.LiveData; +import androidx.lifecycle.MutableLiveData; + +import com.example.petstoremobile.api.SupplierApi; +import com.example.petstoremobile.dtos.PageResponse; +import com.example.petstoremobile.dtos.SupplierDTO; +import com.example.petstoremobile.utils.Resource; + +import javax.inject.Inject; +import javax.inject.Singleton; + +import retrofit2.Call; +import retrofit2.Callback; +import retrofit2.Response; + +@Singleton +public class SupplierRepository { + private final SupplierApi supplierApi; + + @Inject + public SupplierRepository(SupplierApi supplierApi) { + this.supplierApi = supplierApi; + } + + /** + * Retrieves a paginated list of all suppliers from the API. + */ + public LiveData>> getAllSuppliers(int page, int size) { + MutableLiveData>> data = new MutableLiveData<>(); + data.setValue(Resource.loading(null)); + + supplierApi.getAllSuppliers(page, size).enqueue(new Callback>() { + @Override + public void onResponse(Call> call, Response> response) { + if (response.isSuccessful() && response.body() != null) { + data.setValue(Resource.success(response.body())); + } else { + data.setValue(Resource.error("Error: " + response.message(), null)); + } + } + + @Override + public void onFailure(Call> call, Throwable t) { + data.setValue(Resource.error(t.getMessage(), null)); + } + }); + + return data; + } + + /** + * Retrieves a specific supplier by its ID from the API. + */ + public LiveData> getSupplierById(Long id) { + MutableLiveData> data = new MutableLiveData<>(); + data.setValue(Resource.loading(null)); + + supplierApi.getSupplierById(id).enqueue(new Callback() { + @Override + public void onResponse(Call call, Response response) { + if (response.isSuccessful() && response.body() != null) { + data.setValue(Resource.success(response.body())); + } else { + data.setValue(Resource.error("Error: " + response.message(), null)); + } + } + + @Override + public void onFailure(Call call, Throwable t) { + data.setValue(Resource.error(t.getMessage(), null)); + } + }); + + return data; + } + + /** + * Sends a request to the API to create a new supplier record. + */ + public LiveData> createSupplier(SupplierDTO supplier) { + MutableLiveData> data = new MutableLiveData<>(); + data.setValue(Resource.loading(null)); + + supplierApi.createSupplier(supplier).enqueue(new Callback() { + @Override + public void onResponse(Call call, Response response) { + if (response.isSuccessful() && response.body() != null) { + data.setValue(Resource.success(response.body())); + } else { + data.setValue(Resource.error("Error: " + response.message(), null)); + } + } + + @Override + public void onFailure(Call call, Throwable t) { + data.setValue(Resource.error(t.getMessage(), null)); + } + }); + + return data; + } + + /** + * Sends a request to the API to update an existing supplier record by ID. + */ + public LiveData> updateSupplier(Long id, SupplierDTO supplier) { + MutableLiveData> data = new MutableLiveData<>(); + data.setValue(Resource.loading(null)); + + supplierApi.updateSupplier(id, supplier).enqueue(new Callback() { + @Override + public void onResponse(Call call, Response response) { + if (response.isSuccessful() && response.body() != null) { + data.setValue(Resource.success(response.body())); + } else { + data.setValue(Resource.error("Error: " + response.message(), null)); + } + } + + @Override + public void onFailure(Call call, Throwable t) { + data.setValue(Resource.error(t.getMessage(), null)); + } + }); + + return data; + } + + /** + * Sends a request to the API to delete a specific supplier record. + */ + public LiveData> deleteSupplier(Long id) { + MutableLiveData> data = new MutableLiveData<>(); + data.setValue(Resource.loading(null)); + + supplierApi.deleteSupplier(id).enqueue(new Callback() { + @Override + public void onResponse(Call call, Response response) { + if (response.isSuccessful()) { + data.setValue(Resource.success(null)); + } else { + data.setValue(Resource.error("Error: " + response.message(), null)); + } + } + + @Override + public void onFailure(Call call, Throwable t) { + data.setValue(Resource.error(t.getMessage(), null)); + } + }); + + return data; + } +} diff --git a/android/app/src/main/java/com/example/petstoremobile/utils/FileUtils.java b/android/app/src/main/java/com/example/petstoremobile/utils/FileUtils.java new file mode 100644 index 00000000..dcdc1bd7 --- /dev/null +++ b/android/app/src/main/java/com/example/petstoremobile/utils/FileUtils.java @@ -0,0 +1,27 @@ +package com.example.petstoremobile.utils; + +import android.content.Context; +import android.net.Uri; +import java.io.File; +import java.io.FileOutputStream; +import java.io.InputStream; + +public class FileUtils { + public static File getFileFromUri(Context context, Uri uri) { + try { + InputStream inputStream = context.getContentResolver().openInputStream(uri); + File tempFile = new File(context.getCacheDir(), "upload_image_" + System.currentTimeMillis() + ".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) { + return null; + } + } +} diff --git a/android/app/src/main/java/com/example/petstoremobile/utils/Resource.java b/android/app/src/main/java/com/example/petstoremobile/utils/Resource.java new file mode 100644 index 00000000..4c766cc2 --- /dev/null +++ b/android/app/src/main/java/com/example/petstoremobile/utils/Resource.java @@ -0,0 +1,30 @@ +package com.example.petstoremobile.utils; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +public class Resource { + public enum Status { SUCCESS, ERROR, LOADING } + + public final Status status; + public final T data; + public final String message; + + private Resource(Status status, @Nullable T data, @Nullable String message) { + this.status = status; + this.data = data; + this.message = message; + } + + public static Resource success(@Nullable T data) { + return new Resource<>(Status.SUCCESS, data, null); + } + + public static Resource error(String msg, @Nullable T data) { + return new Resource<>(Status.ERROR, data, msg); + } + + public static Resource loading(@Nullable T data) { + return new Resource<>(Status.LOADING, data, null); + } +} diff --git a/android/app/src/main/java/com/example/petstoremobile/viewmodels/AdoptionViewModel.java b/android/app/src/main/java/com/example/petstoremobile/viewmodels/AdoptionViewModel.java new file mode 100644 index 00000000..039cef30 --- /dev/null +++ b/android/app/src/main/java/com/example/petstoremobile/viewmodels/AdoptionViewModel.java @@ -0,0 +1,58 @@ +package com.example.petstoremobile.viewmodels; + +import androidx.lifecycle.LiveData; +import androidx.lifecycle.ViewModel; + +import com.example.petstoremobile.dtos.AdoptionDTO; +import com.example.petstoremobile.dtos.PageResponse; +import com.example.petstoremobile.repositories.AdoptionRepository; +import com.example.petstoremobile.utils.Resource; + +import javax.inject.Inject; + +import dagger.hilt.android.lifecycle.HiltViewModel; + +@HiltViewModel +public class AdoptionViewModel extends ViewModel { + private final AdoptionRepository repository; + + @Inject + public AdoptionViewModel(AdoptionRepository repository) { + this.repository = repository; + } + + /** + * Fetches a paginated list of all adoptions. + */ + public LiveData>> getAllAdoptions(int page, int size) { + return repository.getAllAdoptions(page, size); + } + + /** + * Retrieves a single adoption by its ID. + */ + public LiveData> getAdoptionById(Long id) { + return repository.getAdoptionById(id); + } + + /** + * Creates a new adoption record. + */ + public LiveData> createAdoption(AdoptionDTO adoption) { + return repository.createAdoption(adoption); + } + + /** + * Updates an existing adoption record by ID. + */ + public LiveData> updateAdoption(Long id, AdoptionDTO adoption) { + return repository.updateAdoption(id, adoption); + } + + /** + * Deletes an adoption record by ID. + */ + public LiveData> deleteAdoption(Long id) { + return repository.deleteAdoption(id); + } +} diff --git a/android/app/src/main/java/com/example/petstoremobile/viewmodels/AppointmentViewModel.java b/android/app/src/main/java/com/example/petstoremobile/viewmodels/AppointmentViewModel.java new file mode 100644 index 00000000..23db67d0 --- /dev/null +++ b/android/app/src/main/java/com/example/petstoremobile/viewmodels/AppointmentViewModel.java @@ -0,0 +1,58 @@ +package com.example.petstoremobile.viewmodels; + +import androidx.lifecycle.LiveData; +import androidx.lifecycle.ViewModel; + +import com.example.petstoremobile.dtos.AppointmentDTO; +import com.example.petstoremobile.dtos.PageResponse; +import com.example.petstoremobile.repositories.AppointmentRepository; +import com.example.petstoremobile.utils.Resource; + +import javax.inject.Inject; + +import dagger.hilt.android.lifecycle.HiltViewModel; + +@HiltViewModel +public class AppointmentViewModel extends ViewModel { + private final AppointmentRepository repository; + + @Inject + public AppointmentViewModel(AppointmentRepository repository) { + this.repository = repository; + } + + /** + * Fetches a paginated list of all appointments. + */ + public LiveData>> getAllAppointments(int page, int size) { + return repository.getAllAppointments(page, size); + } + + /** + * Retrieves a single appointment by its ID. + */ + public LiveData> getAppointmentById(Long id) { + return repository.getAppointmentById(id); + } + + /** + * Creates a new appointment. + */ + public LiveData> createAppointment(AppointmentDTO appointment) { + return repository.createAppointment(appointment); + } + + /** + * Updates an existing appointment record by ID. + */ + public LiveData> updateAppointment(Long id, AppointmentDTO appointment) { + return repository.updateAppointment(id, appointment); + } + + /** + * Deletes an appointment record by ID. + */ + public LiveData> deleteAppointment(Long id) { + return repository.deleteAppointment(id); + } +} diff --git a/android/app/src/main/java/com/example/petstoremobile/viewmodels/AuthViewModel.java b/android/app/src/main/java/com/example/petstoremobile/viewmodels/AuthViewModel.java new file mode 100644 index 00000000..061ee687 --- /dev/null +++ b/android/app/src/main/java/com/example/petstoremobile/viewmodels/AuthViewModel.java @@ -0,0 +1,68 @@ +package com.example.petstoremobile.viewmodels; + +import androidx.lifecycle.LiveData; +import androidx.lifecycle.ViewModel; + +import com.example.petstoremobile.dtos.AuthDTO; +import com.example.petstoremobile.dtos.UserDTO; +import com.example.petstoremobile.repositories.AuthRepository; +import com.example.petstoremobile.utils.Resource; + +import java.util.Map; + +import javax.inject.Inject; + +import dagger.hilt.android.lifecycle.HiltViewModel; +import okhttp3.MultipartBody; + +@HiltViewModel +public class AuthViewModel extends ViewModel { + private final AuthRepository repository; + + @Inject + public AuthViewModel(AuthRepository repository) { + this.repository = repository; + } + + /** + * Authenticates a user with username and password. + */ + public LiveData> login(String username, String password) { + return repository.login(new AuthDTO.LoginRequest(username, password)); + } + + /** + * Retrieves the profile information of the currently authenticated user. + */ + public LiveData> getMe() { + return repository.getMe(); + } + + /** + * Updates the profile information of the current user. + */ + public LiveData> updateMe(Map updates) { + return repository.updateMe(updates); + } + + /** + * Uploads a new avatar image for the current user. + */ + public LiveData> uploadAvatar(MultipartBody.Part avatar) { + return repository.uploadAvatar(avatar); + } + + /** + * Deletes the avatar image of the current user. + */ + public LiveData> deleteAvatar() { + return repository.deleteAvatar(); + } + + /** + * Logs out the current user by clearing stored credentials. + */ + public void logout() { + repository.logout(); + } +} diff --git a/android/app/src/main/java/com/example/petstoremobile/viewmodels/InventoryViewModel.java b/android/app/src/main/java/com/example/petstoremobile/viewmodels/InventoryViewModel.java new file mode 100644 index 00000000..3af31b5c --- /dev/null +++ b/android/app/src/main/java/com/example/petstoremobile/viewmodels/InventoryViewModel.java @@ -0,0 +1,80 @@ +package com.example.petstoremobile.viewmodels; + +import androidx.lifecycle.LiveData; +import androidx.lifecycle.ViewModel; + +import com.example.petstoremobile.dtos.BulkDeleteRequest; +import com.example.petstoremobile.dtos.CategoryDTO; +import com.example.petstoremobile.dtos.InventoryDTO; +import com.example.petstoremobile.dtos.InventoryRequest; +import com.example.petstoremobile.dtos.PageResponse; +import com.example.petstoremobile.repositories.CategoryRepository; +import com.example.petstoremobile.repositories.InventoryRepository; +import com.example.petstoremobile.utils.Resource; + +import java.util.List; + +import javax.inject.Inject; + +import dagger.hilt.android.lifecycle.HiltViewModel; + +@HiltViewModel +public class InventoryViewModel extends ViewModel { + private final InventoryRepository inventoryRepository; + private final CategoryRepository categoryRepository; + + @Inject + public InventoryViewModel(InventoryRepository inventoryRepository, CategoryRepository categoryRepository) { + this.inventoryRepository = inventoryRepository; + this.categoryRepository = categoryRepository; + } + + /** + * Retrieves a paginated list of inventory items, with optional filtering and sorting. + */ + public LiveData>> getAllInventory(String query, int page, int size, String sort) { + return inventoryRepository.getAllInventory(query, page, size, sort); + } + + /** + * Retrieves a single inventory item by its ID. + */ + public LiveData> getInventoryById(Long id) { + return inventoryRepository.getInventoryById(id); + } + + /** + * Creates a new inventory record. + */ + public LiveData> createInventory(InventoryRequest request) { + return inventoryRepository.createInventory(request); + } + + /** + * Updates an existing inventory record by ID. + */ + public LiveData> updateInventory(Long id, InventoryRequest request) { + return inventoryRepository.updateInventory(id, request); + } + + /** + * Deletes an inventory record by ID. + */ + public LiveData> deleteInventory(Long id) { + return inventoryRepository.deleteInventory(id); + } + + /** + * Deletes multiple inventory records in a single request. + */ + public LiveData> bulkDeleteInventory(List ids) { + return inventoryRepository.bulkDeleteInventory(new BulkDeleteRequest(ids)); + } + + /** + * Retrieves a paginated list of categories. + */ + public LiveData>> getAllCategories(int page, int size) { + return categoryRepository.getAllCategories(page, size); + } +} diff --git a/android/app/src/main/java/com/example/petstoremobile/viewmodels/PetViewModel.java b/android/app/src/main/java/com/example/petstoremobile/viewmodels/PetViewModel.java new file mode 100644 index 00000000..8982dd51 --- /dev/null +++ b/android/app/src/main/java/com/example/petstoremobile/viewmodels/PetViewModel.java @@ -0,0 +1,75 @@ +package com.example.petstoremobile.viewmodels; + +import androidx.lifecycle.LiveData; +import androidx.lifecycle.MutableLiveData; +import androidx.lifecycle.ViewModel; + +import com.example.petstoremobile.dtos.PageResponse; +import com.example.petstoremobile.dtos.PetDTO; +import com.example.petstoremobile.repositories.PetRepository; +import com.example.petstoremobile.utils.Resource; + +import javax.inject.Inject; + +import dagger.hilt.android.lifecycle.HiltViewModel; +import okhttp3.MultipartBody; + +@HiltViewModel +public class PetViewModel extends ViewModel { + private final PetRepository repository; + private final MutableLiveData _petId = new MutableLiveData<>(); + + @Inject + public PetViewModel(PetRepository repository) { + this.repository = repository; + } + + /** + * Fetches a paginated list of all pets. + */ + public LiveData>> getAllPets(int page, int size) { + return repository.getAllPets(page, size); + } + + /** + * Retrieves a single pet by its ID. + */ + public LiveData> getPetById(Long id) { + return repository.getPetById(id); + } + + /** + * Creates a new pet record. + */ + public LiveData> createPet(PetDTO pet) { + return repository.createPet(pet); + } + + /** + * Updates an existing pet record by ID. + */ + public LiveData> updatePet(Long id, PetDTO pet) { + return repository.updatePet(id, pet); + } + + /** + * Deletes a pet record by ID. + */ + public LiveData> deletePet(Long id) { + return repository.deletePet(id); + } + + /** + * Uploads an image for a specific pet. + */ + public LiveData> uploadPetImage(Long id, MultipartBody.Part image) { + return repository.uploadPetImage(id, image); + } + + /** + * Deletes the image associated with a specific pet. + */ + public LiveData> deletePetImage(Long id) { + return repository.deletePetImage(id); + } +} diff --git a/android/app/src/main/java/com/example/petstoremobile/viewmodels/ProductSupplierViewModel.java b/android/app/src/main/java/com/example/petstoremobile/viewmodels/ProductSupplierViewModel.java new file mode 100644 index 00000000..dbc55534 --- /dev/null +++ b/android/app/src/main/java/com/example/petstoremobile/viewmodels/ProductSupplierViewModel.java @@ -0,0 +1,51 @@ +package com.example.petstoremobile.viewmodels; + +import androidx.lifecycle.LiveData; +import androidx.lifecycle.ViewModel; + +import com.example.petstoremobile.dtos.PageResponse; +import com.example.petstoremobile.dtos.ProductSupplierDTO; +import com.example.petstoremobile.repositories.ProductSupplierRepository; +import com.example.petstoremobile.utils.Resource; + +import javax.inject.Inject; + +import dagger.hilt.android.lifecycle.HiltViewModel; + +@HiltViewModel +public class ProductSupplierViewModel extends ViewModel { + private final ProductSupplierRepository repository; + + @Inject + public ProductSupplierViewModel(ProductSupplierRepository repository) { + this.repository = repository; + } + + /** + * Fetches a paginated list of all product-supplier relationships. + */ + public LiveData>> getAllProductSuppliers(int page, int size) { + return repository.getAllProductSuppliers(page, size); + } + + /** + * Creates a new product-supplier relationship. + */ + public LiveData> createProductSupplier(ProductSupplierDTO dto) { + return repository.createProductSupplier(dto); + } + + /** + * Updates an existing product-supplier relationship. + */ + public LiveData> updateProductSupplier(Long productId, Long supplierId, ProductSupplierDTO dto) { + return repository.updateProductSupplier(productId, supplierId, dto); + } + + /** + * Deletes a product-supplier relationship by product and supplier IDs. + */ + public LiveData> deleteProductSupplier(Long productId, Long supplierId) { + return repository.deleteProductSupplier(productId, supplierId); + } +} diff --git a/android/app/src/main/java/com/example/petstoremobile/viewmodels/ProductViewModel.java b/android/app/src/main/java/com/example/petstoremobile/viewmodels/ProductViewModel.java new file mode 100644 index 00000000..6edcdd4b --- /dev/null +++ b/android/app/src/main/java/com/example/petstoremobile/viewmodels/ProductViewModel.java @@ -0,0 +1,84 @@ +package com.example.petstoremobile.viewmodels; + +import androidx.lifecycle.LiveData; +import androidx.lifecycle.ViewModel; + +import com.example.petstoremobile.dtos.CategoryDTO; +import com.example.petstoremobile.dtos.PageResponse; +import com.example.petstoremobile.dtos.ProductDTO; +import com.example.petstoremobile.repositories.CategoryRepository; +import com.example.petstoremobile.repositories.ProductRepository; +import com.example.petstoremobile.utils.Resource; + +import javax.inject.Inject; + +import dagger.hilt.android.lifecycle.HiltViewModel; +import okhttp3.MultipartBody; + +@HiltViewModel +public class ProductViewModel extends ViewModel { + private final ProductRepository productRepository; + private final CategoryRepository categoryRepository; + + @Inject + public ProductViewModel(ProductRepository productRepository, CategoryRepository categoryRepository) { + this.productRepository = productRepository; + this.categoryRepository = categoryRepository; + } + + /** + * Retrieves a paginated list of products, optionally filtered by a query string. + */ + public LiveData>> getAllProducts(String query, int page, int size) { + return productRepository.getAllProducts(query, page, size); + } + + /** + * Retrieves a single product by its ID. + */ + public LiveData> getProductById(Long id) { + return productRepository.getProductById(id); + } + + /** + * Creates a new product. + */ + public LiveData> createProduct(ProductDTO product) { + return productRepository.createProduct(product); + } + + /** + * Updates an existing product by ID. + */ + public LiveData> updateProduct(Long id, ProductDTO product) { + return productRepository.updateProduct(id, product); + } + + /** + * Deletes a product by its ID. + */ + public LiveData> deleteProduct(Long id) { + return productRepository.deleteProduct(id); + } + + /** + * Uploads an image for a specific product. + */ + public LiveData> uploadProductImage(Long id, MultipartBody.Part image) { + return productRepository.uploadProductImage(id, image); + } + + /** + * Deletes the image associated with a specific product. + */ + public LiveData> deleteProductImage(Long id) { + return productRepository.deleteProductImage(id); + } + + /** + * Retrieves a paginated list of all product categories. + */ + public LiveData>> getAllCategories(int page, int size) { + return categoryRepository.getAllCategories(page, size); + } +} diff --git a/android/app/src/main/java/com/example/petstoremobile/viewmodels/PurchaseOrderViewModel.java b/android/app/src/main/java/com/example/petstoremobile/viewmodels/PurchaseOrderViewModel.java new file mode 100644 index 00000000..0b5d7ea1 --- /dev/null +++ b/android/app/src/main/java/com/example/petstoremobile/viewmodels/PurchaseOrderViewModel.java @@ -0,0 +1,37 @@ +package com.example.petstoremobile.viewmodels; + +import androidx.lifecycle.LiveData; +import androidx.lifecycle.ViewModel; + +import com.example.petstoremobile.dtos.PageResponse; +import com.example.petstoremobile.dtos.PurchaseOrderDTO; +import com.example.petstoremobile.repositories.PurchaseOrderRepository; +import com.example.petstoremobile.utils.Resource; + +import javax.inject.Inject; + +import dagger.hilt.android.lifecycle.HiltViewModel; + +@HiltViewModel +public class PurchaseOrderViewModel extends ViewModel { + private final PurchaseOrderRepository repository; + + @Inject + public PurchaseOrderViewModel(PurchaseOrderRepository repository) { + this.repository = repository; + } + + /** + * Fetches a paginated list of all purchase orders. + */ + public LiveData>> getAllPurchaseOrders(int page, int size) { + return repository.getAllPurchaseOrders(page, size); + } + + /** + * Retrieves a single purchase order by its ID. + */ + public LiveData> getPurchaseOrderById(Long id) { + return repository.getPurchaseOrderById(id); + } +} diff --git a/android/app/src/main/java/com/example/petstoremobile/viewmodels/ServiceViewModel.java b/android/app/src/main/java/com/example/petstoremobile/viewmodels/ServiceViewModel.java new file mode 100644 index 00000000..be4cee20 --- /dev/null +++ b/android/app/src/main/java/com/example/petstoremobile/viewmodels/ServiceViewModel.java @@ -0,0 +1,58 @@ +package com.example.petstoremobile.viewmodels; + +import androidx.lifecycle.LiveData; +import androidx.lifecycle.ViewModel; + +import com.example.petstoremobile.dtos.PageResponse; +import com.example.petstoremobile.dtos.ServiceDTO; +import com.example.petstoremobile.repositories.ServiceRepository; +import com.example.petstoremobile.utils.Resource; + +import javax.inject.Inject; + +import dagger.hilt.android.lifecycle.HiltViewModel; + +@HiltViewModel +public class ServiceViewModel extends ViewModel { + private final ServiceRepository repository; + + @Inject + public ServiceViewModel(ServiceRepository repository) { + this.repository = repository; + } + + /** + * Fetches a paginated list of all services. + */ + public LiveData>> getAllServices(int page, int size) { + return repository.getAllServices(page, size); + } + + /** + * Retrieves a single service by its ID. + */ + public LiveData> getServiceById(Long id) { + return repository.getServiceById(id); + } + + /** + * Creates a new service. + */ + public LiveData> createService(ServiceDTO service) { + return repository.createService(service); + } + + /** + * Updates an existing service by ID. + */ + public LiveData> updateService(Long id, ServiceDTO service) { + return repository.updateService(id, service); + } + + /** + * Deletes a service by ID. + */ + public LiveData> deleteService(Long id) { + return repository.deleteService(id); + } +} diff --git a/android/app/src/main/java/com/example/petstoremobile/viewmodels/SupplierViewModel.java b/android/app/src/main/java/com/example/petstoremobile/viewmodels/SupplierViewModel.java new file mode 100644 index 00000000..7885c898 --- /dev/null +++ b/android/app/src/main/java/com/example/petstoremobile/viewmodels/SupplierViewModel.java @@ -0,0 +1,58 @@ +package com.example.petstoremobile.viewmodels; + +import androidx.lifecycle.LiveData; +import androidx.lifecycle.ViewModel; + +import com.example.petstoremobile.dtos.PageResponse; +import com.example.petstoremobile.dtos.SupplierDTO; +import com.example.petstoremobile.repositories.SupplierRepository; +import com.example.petstoremobile.utils.Resource; + +import javax.inject.Inject; + +import dagger.hilt.android.lifecycle.HiltViewModel; + +@HiltViewModel +public class SupplierViewModel extends ViewModel { + private final SupplierRepository repository; + + @Inject + public SupplierViewModel(SupplierRepository repository) { + this.repository = repository; + } + + /** + * Fetches a paginated list of all suppliers. + */ + public LiveData>> getAllSuppliers(int page, int size) { + return repository.getAllSuppliers(page, size); + } + + /** + * Retrieves a single supplier by its ID. + */ + public LiveData> getSupplierById(Long id) { + return repository.getSupplierById(id); + } + + /** + * Creates a new supplier record. + */ + public LiveData> createSupplier(SupplierDTO supplier) { + return repository.createSupplier(supplier); + } + + /** + * Updates an existing supplier record by ID. + */ + public LiveData> updateSupplier(Long id, SupplierDTO supplier) { + return repository.updateSupplier(id, supplier); + } + + /** + * Deletes a supplier record by ID. + */ + public LiveData> deleteSupplier(Long id) { + return repository.deleteSupplier(id); + } +}