diff --git a/app/build.gradle.kts b/app/build.gradle.kts index a9ea8f06..93d26ac4 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -37,6 +37,13 @@ dependencies { implementation(libs.activity) implementation(libs.constraintlayout) + + implementation("com.squareup.retrofit2:retrofit:2.9.0") + implementation("com.squareup.retrofit2:converter-gson:2.9.0") + implementation("com.squareup.okhttp3:logging-interceptor:4.9.1") + implementation("com.squareup.okhttp3:okhttp:4.12.0") + + implementation("com.google.android.material:material:1.11.0") implementation("androidx.viewpager2:viewpager2:1.1.0") diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 1e0ba27f..946a685e 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -2,6 +2,7 @@ + { if (item.getItemId() == R.id.nav_list) { diff --git a/app/src/main/java/com/example/petstoremobile/activities/MainActivity.java b/app/src/main/java/com/example/petstoremobile/activities/MainActivity.java index c57caec1..e2f6885d 100644 --- a/app/src/main/java/com/example/petstoremobile/activities/MainActivity.java +++ b/app/src/main/java/com/example/petstoremobile/activities/MainActivity.java @@ -2,6 +2,7 @@ package com.example.petstoremobile.activities; import android.content.Intent; import android.os.Bundle; +import android.util.Log; import android.widget.Button; import android.widget.EditText; import android.widget.TextView; @@ -9,13 +10,21 @@ import android.widget.Toast; import androidx.activity.EdgeToEdge; import androidx.appcompat.app.AppCompatActivity; -import androidx.core.content.ContextCompat; import androidx.core.graphics.Insets; import androidx.core.view.ViewCompat; import androidx.core.view.WindowInsetsCompat; import com.example.petstoremobile.R; +import com.example.petstoremobile.api.Auth.AuthApi; +import com.example.petstoremobile.api.Auth.TokenManager; +import com.example.petstoremobile.api.RetrofitClient; +import com.example.petstoremobile.dtos.AuthDTO; +import retrofit2.Call; +import retrofit2.Callback; +import retrofit2.Response; + +//The login screen activity public class MainActivity extends AppCompatActivity { private EditText etUser; @@ -57,17 +66,47 @@ public class MainActivity extends AppCompatActivity { return; } - //check if username and password are correct TODO: Replace with actual login - if (username.equals("admin") && password.equals("admin")) { - Intent intent = new Intent(this, HomeActivity.class); - startActivity(intent); - Toast.makeText(this, "Login successful", Toast.LENGTH_SHORT).show(); - finish(); - } - else { - Toast.makeText(this, "Login failed", Toast.LENGTH_SHORT).show(); - tvLoginStatus.setText("Login failed"); - } + AuthApi authApi = RetrofitClient.getAuthApi(this); + + //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) { + //save login data in shared preferences + TokenManager.getInstance(MainActivity.this).saveLoginData( + response.body().getToken(), + response.body().getUsername(), + response.body().getRole() + ); + //go to home activity after login + Intent intent = new Intent(MainActivity.this, HomeActivity.class); + startActivity(intent); + Toast.makeText(MainActivity.this, "Login successful", Toast.LENGTH_SHORT).show(); + finish(); + } else { +// Toast.makeText(MainActivity.this, "Login failed", Toast.LENGTH_SHORT).show(); +// tvLoginStatus.setText("Login failed"); + + try { + String errorBody = response.errorBody().string(); + Log.e("LOGIN", "Code: " + response.code() + " Error: " + errorBody); + } catch (Exception e) { + Log.e("LOGIN", "Code: " + response.code()); + } + Toast.makeText(MainActivity.this, + "Login failed: " + response.code(), + Toast.LENGTH_SHORT).show(); + tvLoginStatus.setText("Login failed: " + response.code()); + } + } + + @Override + public void onFailure(Call call, Throwable t) { + Toast.makeText(MainActivity.this, "Login failed", Toast.LENGTH_SHORT).show(); + tvLoginStatus.setText("Login failed"); + } + }); }); } } \ No newline at end of file diff --git a/app/src/main/java/com/example/petstoremobile/api/Auth/AuthApi.java b/app/src/main/java/com/example/petstoremobile/api/Auth/AuthApi.java new file mode 100644 index 00000000..c8ccdcc9 --- /dev/null +++ b/app/src/main/java/com/example/petstoremobile/api/Auth/AuthApi.java @@ -0,0 +1,13 @@ +package com.example.petstoremobile.api.Auth; + +import com.example.petstoremobile.dtos.AuthDTO; + +import retrofit2.Call; +import retrofit2.http.Body; +import retrofit2.http.POST; + +//Api for logging in +public interface AuthApi { + @POST("v1/auth/login") + Call login(@Body AuthDTO.LoginRequest loginRequest); +} diff --git a/app/src/main/java/com/example/petstoremobile/api/Auth/AuthInterceptor.java b/app/src/main/java/com/example/petstoremobile/api/Auth/AuthInterceptor.java new file mode 100644 index 00000000..d892e6e0 --- /dev/null +++ b/app/src/main/java/com/example/petstoremobile/api/Auth/AuthInterceptor.java @@ -0,0 +1,38 @@ +package com.example.petstoremobile.api.Auth; + +import android.content.Context; + +import androidx.annotation.NonNull; + +import java.io.IOException; + +import okhttp3.Interceptor; +import okhttp3.Request; +import okhttp3.Response; + +//Used to get the token from the backend for authenticated api calls +public class AuthInterceptor implements Interceptor { + + private final TokenManager tokenManager; + + public AuthInterceptor(Context context) { + this.tokenManager = TokenManager.getInstance(context); + } + + @NonNull + @Override + public Response intercept(@NonNull Chain chain) throws IOException { + String token = tokenManager.getToken(); + + //If we have a token then add it to the request + if (token != null) { + Request request = chain.request().newBuilder() + .addHeader("Authorization", "Bearer " + token) + .build(); + return chain.proceed(request); + } + + //If no token then just pass the request + return chain.proceed(chain.request()); + } +} diff --git a/app/src/main/java/com/example/petstoremobile/api/Auth/TokenManager.java b/app/src/main/java/com/example/petstoremobile/api/Auth/TokenManager.java new file mode 100644 index 00000000..47322763 --- /dev/null +++ b/app/src/main/java/com/example/petstoremobile/api/Auth/TokenManager.java @@ -0,0 +1,60 @@ +package com.example.petstoremobile.api.Auth; + +import android.content.Context; +import android.content.SharedPreferences; + +//Store login token in shared preferences +public class TokenManager { + private static final String TOKEN_KEY = "token"; + private static final String USERNAME_KEY = "username"; + private static final String ROLE_KEY = "role"; + private static final String PREFS_NAME = "auth_prefs"; + + private static TokenManager instance; + private SharedPreferences prefs; + + private TokenManager(Context context) { + prefs = context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE); + } + + public static TokenManager getInstance(Context context) { + if (instance == null) { + instance = new TokenManager(context); + } + return instance; + } + + //save login data after login + public void saveLoginData(String token, String username, String role) { + prefs.edit() + .putString(TOKEN_KEY, token) + .putString(USERNAME_KEY, username) + .putString(ROLE_KEY, role) + .apply(); + } + + //Getters + public String getToken() { + return prefs.getString(TOKEN_KEY, null); + } + + public String getUsername() { + return prefs.getString(USERNAME_KEY, null); + } + + public String getRole() { + return prefs.getString(ROLE_KEY, null); + } + + //Check if logged in + public boolean isLoggedIn() { + return getToken() != null; + } + + //Clear login data + public void clearLoginData() { + prefs.edit().clear().apply(); + } + + +} diff --git a/app/src/main/java/com/example/petstoremobile/api/PetApi.java b/app/src/main/java/com/example/petstoremobile/api/PetApi.java new file mode 100644 index 00000000..b9a5c5b2 --- /dev/null +++ b/app/src/main/java/com/example/petstoremobile/api/PetApi.java @@ -0,0 +1,39 @@ +package com.example.petstoremobile.api; + +import com.example.petstoremobile.dtos.PageResponse; +import com.example.petstoremobile.dtos.PetDTO; + +import retrofit2.Call; +import retrofit2.http.Body; +import retrofit2.http.DELETE; +import retrofit2.http.GET; +import retrofit2.http.POST; +import retrofit2.http.PUT; +import retrofit2.http.Path; +import retrofit2.http.Query; + +public interface PetApi { + // Get all pets + @GET("v1/pets") + Call> getAllPets( + @Query("page") int page, + @Query("size") int size + ); + + // Get pet by id + @GET("v1/pets/{id}") + Call getPetById(@Path("id") Long id); + + // Create pet + @POST("v1/pets") + Call createPet(@Body PetDTO pet); + + // Update pet + @PUT("v1/pets/{id}") + Call updatePet(@Path("id") Long id, @Body PetDTO pet); + + // Delete pet + @DELETE("v1/pets/{id}") + Call deletePet(@Path("id") Long id); + +} diff --git a/app/src/main/java/com/example/petstoremobile/api/RetrofitClient.java b/app/src/main/java/com/example/petstoremobile/api/RetrofitClient.java new file mode 100644 index 00000000..36a2f04e --- /dev/null +++ b/app/src/main/java/com/example/petstoremobile/api/RetrofitClient.java @@ -0,0 +1,57 @@ +package com.example.petstoremobile.api; + +import android.content.Context; + +import com.example.petstoremobile.api.Auth.AuthApi; +import com.example.petstoremobile.api.Auth.AuthInterceptor; + +import okhttp3.OkHttpClient; +import okhttp3.logging.HttpLoggingInterceptor; +import retrofit2.Retrofit; +import retrofit2.converter.gson.GsonConverterFactory; + +public class RetrofitClient { + //base URL + public static final String BASE_URL = "http://10.0.2.2:8080/api/"; //for emulator testing change to computer ip if using hardware to test + + + private static Retrofit retrofit = null; + + public static Retrofit getClient(Context context) { + //create an http logging using an interceptor + HttpLoggingInterceptor interceptor = new HttpLoggingInterceptor(); + interceptor.setLevel(HttpLoggingInterceptor.Level.BODY); + + OkHttpClient client = new OkHttpClient.Builder() + .addInterceptor(interceptor) + .addInterceptor(new AuthInterceptor(context)) + .build(); + + //build the retrofit object with all needed properties + retrofit = new Retrofit.Builder() + .baseUrl(BASE_URL) + .addConverterFactory(GsonConverterFactory.create()) //JSON converter + .client(client) //logging interceptor - OkHttpClient + .build(); + + return retrofit; + } + + //associate the retrofit object with the API interface + public static PetApi getPetApi(Context context) { + return getClient(context).create(PetApi.class); + } + + public static ServiceApi getServiceApi(Context context) { + return getClient(context).create(ServiceApi.class); + } + + public static SupplierApi getSupplierApi(Context context) { + return getClient(context).create(SupplierApi.class); + } + + public static AuthApi getAuthApi(Context context) { + return getClient(context).create(AuthApi.class); + } + +} diff --git a/app/src/main/java/com/example/petstoremobile/api/ServiceApi.java b/app/src/main/java/com/example/petstoremobile/api/ServiceApi.java new file mode 100644 index 00000000..467b8935 --- /dev/null +++ b/app/src/main/java/com/example/petstoremobile/api/ServiceApi.java @@ -0,0 +1,38 @@ +package com.example.petstoremobile.api; + +import com.example.petstoremobile.dtos.PageResponse; +import com.example.petstoremobile.dtos.ServiceDTO; + +import retrofit2.Call; +import retrofit2.http.Body; +import retrofit2.http.DELETE; +import retrofit2.http.GET; +import retrofit2.http.POST; +import retrofit2.http.PUT; +import retrofit2.http.Path; +import retrofit2.http.Query; + +public interface ServiceApi { + // Get all services + @GET("v1/services") + Call> getAllServices( + @Query("page") int page, + @Query("size") int size + ); + + // Get service by id + @GET("v1/services/{id}") + Call getServiceById(@Path("id") Long id); + + // Create service + @POST("v1/services") + Call createService(@Body ServiceDTO service); + + // Update service + @PUT("v1/services/{id}") + Call updateService(@Path("id") Long id, @Body ServiceDTO service); + + // Delete service + @DELETE("v1/services/{id}") + Call deleteService(@Path("id") Long id); +} diff --git a/app/src/main/java/com/example/petstoremobile/api/SupplierApi.java b/app/src/main/java/com/example/petstoremobile/api/SupplierApi.java new file mode 100644 index 00000000..beb2ded9 --- /dev/null +++ b/app/src/main/java/com/example/petstoremobile/api/SupplierApi.java @@ -0,0 +1,38 @@ +package com.example.petstoremobile.api; + +import com.example.petstoremobile.dtos.PageResponse; +import com.example.petstoremobile.dtos.SupplierDTO; + +import retrofit2.Call; +import retrofit2.http.Body; +import retrofit2.http.DELETE; +import retrofit2.http.GET; +import retrofit2.http.POST; +import retrofit2.http.PUT; +import retrofit2.http.Path; +import retrofit2.http.Query; + +public interface SupplierApi { + // Get all suppliers + @GET("v1/suppliers") + Call> getAllSuppliers( + @Query("page") int page, + @Query("size") int size + ); + + // Get supplier by id + @GET("v1/suppliers/{id}") + Call getSupplierById(@Path("id") Long id); + + // Create supplier + @POST("v1/suppliers") + Call createSupplier(@Body SupplierDTO supplier); + + // Update supplier + @PUT("v1/suppliers/{id}") + Call updateSupplier(@Path("id") Long id, @Body SupplierDTO supplier); + + // Delete supplier + @DELETE("v1/suppliers/{id}") + Call deleteSupplier(@Path("id") Long id); +} diff --git a/app/src/main/java/com/example/petstoremobile/dtos/AuthDTO.java b/app/src/main/java/com/example/petstoremobile/dtos/AuthDTO.java new file mode 100644 index 00000000..36a2db35 --- /dev/null +++ b/app/src/main/java/com/example/petstoremobile/dtos/AuthDTO.java @@ -0,0 +1,28 @@ +package com.example.petstoremobile.dtos; + +//Used to send login data to the backend +public class AuthDTO { + public static class LoginRequest { + private String username; + private String password; + + public LoginRequest(String username, String password) { + this.username = username; + this.password = password; + } + + public String getUsername() { return username; } + public String getPassword() { return password; } + } + + //Used to receive login data from the backend + public static class LoginResponse { + private String token; + private String username; + private String role; + + public String getToken() { return token; } + public String getUsername() { return username; } + public String getRole() { return role; } + } +} diff --git a/app/src/main/java/com/example/petstoremobile/dtos/PageResponse.java b/app/src/main/java/com/example/petstoremobile/dtos/PageResponse.java new file mode 100644 index 00000000..7237105e --- /dev/null +++ b/app/src/main/java/com/example/petstoremobile/dtos/PageResponse.java @@ -0,0 +1,32 @@ +package com.example.petstoremobile.dtos; + +import java.util.List; + +//Used to get data from the API +public class PageResponse { + private List content; + private int totalPages; + private long totalElements; + private int number; + private int size; + private boolean last; + + public List getContent() { + return content; + } + public int getTotalPages() { + return totalPages; + } + public long getTotalElements() { + return totalElements; + } + public int getNumber() { + return number; + } + public int getSize() { + return size; + } + public boolean isLast() { + return last; + } +} \ No newline at end of file diff --git a/app/src/main/java/com/example/petstoremobile/dtos/PetDTO.java b/app/src/main/java/com/example/petstoremobile/dtos/PetDTO.java new file mode 100644 index 00000000..909a6c33 --- /dev/null +++ b/app/src/main/java/com/example/petstoremobile/dtos/PetDTO.java @@ -0,0 +1,23 @@ +package com.example.petstoremobile.dtos; + +public class PetDTO { + private Long petId; + private String petName; + private String petSpecies; + private String petBreed; + private Integer petAge; + private String petStatus; + private String petPrice; + private String createdAt; + private String updatedAt; + + public Long getPetId() { return petId; } + public String getPetName() { return petName; } + public String getPetSpecies() { return petSpecies; } + public String getPetBreed() { return petBreed; } + public Integer getPetAge() { return petAge; } + public String getPetStatus() { return petStatus; } + public String getPetPrice() { return petPrice; } + public String getCreatedAt() { return createdAt; } + public String getUpdatedAt() { return updatedAt; } +} diff --git a/app/src/main/java/com/example/petstoremobile/dtos/ServiceDTO.java b/app/src/main/java/com/example/petstoremobile/dtos/ServiceDTO.java new file mode 100644 index 00000000..e7ae08a3 --- /dev/null +++ b/app/src/main/java/com/example/petstoremobile/dtos/ServiceDTO.java @@ -0,0 +1,19 @@ +package com.example.petstoremobile.dtos; + +public class ServiceDTO { + private Long serviceId; + private String serviceName; + private String serviceDesc; + private Integer serviceDuration; + private Double servicePrice; + private String createdAt; + private String updatedAt; + + public Long getServiceId() { return serviceId; } + public String getServiceName() { return serviceName; } + public String getServiceDesc() { return serviceDesc; } + public Integer getServiceDuration() { return serviceDuration; } + public Double getServicePrice() { return servicePrice; } + public String getCreatedAt() { return createdAt; } + public String getUpdatedAt() { return updatedAt; } +} diff --git a/app/src/main/java/com/example/petstoremobile/dtos/SupplierDTO.java b/app/src/main/java/com/example/petstoremobile/dtos/SupplierDTO.java new file mode 100644 index 00000000..5a80da66 --- /dev/null +++ b/app/src/main/java/com/example/petstoremobile/dtos/SupplierDTO.java @@ -0,0 +1,21 @@ +package com.example.petstoremobile.dtos; + +public class SupplierDTO { + private Long supId; + private String supCompany; + private String supContactFirstName; + private String supContactLastName; + private String supEmail; + private String supPhone; + private String createdAt; + private String updatedAt; + + public Long getSupId() { return supId; } + public String getSupCompany() { return supCompany; } + public String getSupContactFirstName() { return supContactFirstName; } + public String getSupContactLastName() { return supContactLastName; } + public String getSupEmail() { return supEmail; } + public String getSupPhone() { return supPhone; } + public String getCreatedAt() { return createdAt; } + public String getUpdatedAt() { return updatedAt; } +} diff --git a/app/src/main/java/com/example/petstoremobile/fragments/listfragments/PetFragment.java b/app/src/main/java/com/example/petstoremobile/fragments/listfragments/PetFragment.java index d436538e..b774e104 100644 --- a/app/src/main/java/com/example/petstoremobile/fragments/listfragments/PetFragment.java +++ b/app/src/main/java/com/example/petstoremobile/fragments/listfragments/PetFragment.java @@ -6,26 +6,37 @@ import androidx.fragment.app.Fragment; import androidx.recyclerview.widget.LinearLayoutManager; import androidx.recyclerview.widget.RecyclerView; +import android.util.Log; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.ImageButton; +import android.widget.Toast; import com.example.petstoremobile.R; import com.example.petstoremobile.adapters.PetAdapter; +import com.example.petstoremobile.api.PetApi; +import com.example.petstoremobile.api.RetrofitClient; +import com.example.petstoremobile.dtos.PageResponse; +import com.example.petstoremobile.dtos.PetDTO; import com.example.petstoremobile.fragments.ListFragment; import com.example.petstoremobile.fragments.listfragments.detailfragments.PetDetailFragment; +import com.example.petstoremobile.fragments.listfragments.listprofilefragments.PetProfileFragment; import com.example.petstoremobile.models.Pet; import com.google.android.material.floatingactionbutton.FloatingActionButton; import java.util.ArrayList; import java.util.List; -public class PetFragment extends Fragment implements PetAdapter.OnPetClickListener { +import retrofit2.Call; +import retrofit2.Callback; +import retrofit2.Response; +public class PetFragment extends Fragment implements PetAdapter.OnPetClickListener { private List petList = new ArrayList<>(); - private PetAdapter adapter; private ImageButton hamburger; + private PetAdapter adapter; + private PetApi api; //load pet view @Override @@ -33,10 +44,14 @@ public class PetFragment extends Fragment implements PetAdapter.OnPetClickListen Bundle savedInstanceState) { View view = inflater.inflate(R.layout.fragment_pet, container, false); + //get retrofit + api = RetrofitClient.getPetApi(requireContext()); + hamburger = view.findViewById(R.id.btnHamburger); - loadPetData(); //TODO: Replace this with actual data when backend is working setupRecyclerView(view); + loadPetData(); //TODO: Replace this with actual data when backend is working + //Add button to opens the add dialog FloatingActionButton fabAddPet = view.findViewById(R.id.fabAddPet); @@ -54,31 +69,35 @@ public class PetFragment extends Fragment implements PetAdapter.OnPetClickListen return view; } - //Open the pet detail view depending on the mode + //Open pet profile + private void openPetProfile(int position) { + PetProfileFragment profileFragment = new PetProfileFragment(); + + //Make a bundle to pass data to the profile fragment + Bundle args = new Bundle(); + Pet pet = petList.get(position); + args.putInt("petId", pet.getPetId()); + args.putString("petName", pet.getPetName()); + args.putString("petSpecies", pet.getPetSpecies()); + args.putString("petBreed", pet.getPetBreed()); + args.putInt("petAge", pet.getPetAge()); + args.putString("petStatus", pet.getPetStatus()); + args.putDouble("petPrice", pet.getPetPrice()); + + //send the bundle to the profile fragment to display + profileFragment.setArguments(args); + + //get ListFragment to load the the pet profile view + ListFragment listFragment = (ListFragment) getParentFragment(); + if (profileFragment != null) { + listFragment.loadFragment(profileFragment); + } + } + + //Open the pet detail view for adding private void openPetDetails(int position) { PetDetailFragment detailFragment = new PetDetailFragment(); - //Make a bundle to pass data to the detail fragment - Bundle args = new Bundle(); - args.putInt("position", position); - - //if editing a pet, add the pet data to the bundle - if (position != -1) { - Pet pet = petList.get(position); - args.putInt("petId", pet.getPetId()); - args.putString("petName", pet.getPetName()); - args.putString("petSpecies", pet.getPetSpecies()); - args.putString("petBreed", pet.getPetBreed()); - args.putInt("petAge", pet.getPetAge()); - args.putString("petStatus", pet.getPetStatus()); - args.putDouble("petPrice", pet.getPetPrice()); - } - - //send the bundle to the detail fragment to display - detailFragment.setArguments(args); - //set the pet fragment to the parent so we refer back to pet view when save or delete is done - detailFragment.setPetFragment(this); - //get ListFragment to load the the detail view ListFragment listFragment = (ListFragment) getParentFragment(); if (listFragment != null) { @@ -87,36 +106,57 @@ public class PetFragment extends Fragment implements PetAdapter.OnPetClickListen } // Called by PetDetailFragment when save or delete is done - public void onPetSaved(int position, Pet pet) { - if (position == -1) { - petList.add(pet); - adapter.notifyItemInserted(petList.size() - 1); - } else { - petList.set(position, pet); - adapter.notifyItemChanged(position); - } - } - - public void onPetDeleted(int position) { - petList.remove(position); - adapter.notifyItemRemoved(position); - } + //TODO: REPLACE THIS WITH JUST RELOADING THE LIST BY FETCHING FROM THE BACKEND +// public void onPetSaved(int position, Pet pet) { +// if (position == -1) { +// petList.add(pet); +// adapter.notifyItemInserted(petList.size() - 1); +// } else { +// petList.set(position, pet); +// adapter.notifyItemChanged(position); +// } +// } +// +// public void onPetDeleted(int position) { +// petList.remove(position); +// adapter.notifyItemRemoved(position); +// } // Called by PetAdapter when a row is clicked to open the details view @Override public void onPetClick(int position) { - openPetDetails(position); + openPetProfile(position); } - // Helper function to get a list of all pets (Loads hardcoded sample data for now) TODO: REPLACE THIS WITH A METHOD THAT GETS DATA FROM THE DATABASE + // Helper function to get a list of all pets from the backend private void loadPetData() { - petList.clear(); - petList.add(new Pet(1, "Buddy","Dog", "Labrador",2, "Available", 500.00)); - petList.add(new Pet(2, "Milo", "Cat", "Persian",1, "Available", 300.00)); - petList.add(new Pet(3, "Charlie","Dog", "Golden Retriever", 3, "Available", 550.00)); - petList.add(new Pet(4, "Luna", "Cat", "Siamese",2, "Adopted", 350.00)); - petList.add(new Pet(5, "Max", "Dog", "Beagle",1, "Available", 450.00)); - petList.add(new Pet(6, "Bella", "Cat", "Maine Coon",4, "Available", 400.00)); + api.getAllPets(0, 100).enqueue(new Callback>() { + @Override + public void onResponse(Call> call, Response> response) { + if (response.isSuccessful() && response.body() != null) { + petList.clear(); + + // Convert to pets + List pets = response.body().getContent() + .stream() + .map(Pet::new) + .collect(java.util.stream.Collectors.toList()); + + petList.addAll(pets); + adapter.notifyDataSetChanged(); + + } else { + Log.e("onResponse: ", response.message()); + } + } + + @Override + public void onFailure(Call> call, Throwable t) { // 4. here + Toast.makeText(getContext(), + "Failed to load pets", Toast.LENGTH_SHORT).show(); + Log.e("onFailure: ", t.getMessage()); + } + }); } //set up the recyclerview and adapter diff --git a/app/src/main/java/com/example/petstoremobile/fragments/listfragments/ServiceFragment.java b/app/src/main/java/com/example/petstoremobile/fragments/listfragments/ServiceFragment.java index 17426a95..b3e61600 100644 --- a/app/src/main/java/com/example/petstoremobile/fragments/listfragments/ServiceFragment.java +++ b/app/src/main/java/com/example/petstoremobile/fragments/listfragments/ServiceFragment.java @@ -6,13 +6,19 @@ import androidx.fragment.app.Fragment; import androidx.recyclerview.widget.LinearLayoutManager; import androidx.recyclerview.widget.RecyclerView; +import android.util.Log; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.ImageButton; +import android.widget.Toast; import com.example.petstoremobile.R; import com.example.petstoremobile.adapters.ServiceAdapter; +import com.example.petstoremobile.api.RetrofitClient; +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.fragments.listfragments.detailfragments.ServiceDetailFragment; import com.example.petstoremobile.models.Service; @@ -21,11 +27,16 @@ import com.google.android.material.floatingactionbutton.FloatingActionButton; import java.util.ArrayList; import java.util.List; +import retrofit2.Call; +import retrofit2.Callback; +import retrofit2.Response; + public class ServiceFragment extends Fragment implements ServiceAdapter.OnServiceClickListener { private List serviceList = new ArrayList<>(); private ServiceAdapter adapter; private ImageButton hamburger; + private ServiceApi api; //load service view @Override @@ -33,10 +44,11 @@ public class ServiceFragment extends Fragment implements ServiceAdapter.OnServic Bundle savedInstanceState) { View view = inflater.inflate(R.layout.fragment_service, container, false); + api = RetrofitClient.getServiceApi(requireContext()); hamburger = view.findViewById(R.id.btnHamburger); - loadServiceData(); //TODO: Replace this with actual data when backend is working setupRecyclerView(view); + loadServiceData(); //Add button to opens the add dialog FloatingActionButton fabAddService = view.findViewById(R.id.fabAddService); @@ -106,14 +118,37 @@ public class ServiceFragment extends Fragment implements ServiceAdapter.OnServic openServiceDetails(position); } - // Helper function to get a list of all services (Loads hardcoded sample data for now) TODO: REPLACE THIS WITH A METHOD THAT GETS DATA FROM THE DATABASE + // Helper function to get a list of all services from the backend private void loadServiceData() { - serviceList.clear(); - serviceList.add(new Service(1, "Grooming", "Full grooming for your pet", 60, 50.00)); - serviceList.add(new Service(2, "Vaccination", "Standard vaccinations", 30, 75.00)); - serviceList.add(new Service(3, "Health Checkup", "Comprehensive health exam", 45, 100.00)); - serviceList.add(new Service(4, "Pet Sitting", "Overnight stay for your pet", 1440, 40.00)); - serviceList.add(new Service(5, "Training", "Basic obedience training session", 60, 60.00)); + api.getAllServices(0, 100).enqueue(new Callback>() { + @Override + public void onResponse(Call> call, Response> response) { + if (response.isSuccessful() && response.body() != null) { + serviceList.clear(); + + // Convert to services + List services = response.body().getContent() + .stream() + .map(Service::new) + .collect(java.util.stream.Collectors.toList()); + + serviceList.addAll(services); + adapter.notifyDataSetChanged(); + + } else { + Log.e("onResponse: ", response.message()); + } + } + + @Override + public void onFailure(Call> call, Throwable t) { + if (getContext() != null) { + Toast.makeText(getContext(), + "Failed to load services", Toast.LENGTH_SHORT).show(); + } + Log.e("onFailure: ", t.getMessage()); + } + }); } //set up the recyclerview and adapter diff --git a/app/src/main/java/com/example/petstoremobile/fragments/listfragments/SupplierFragment.java b/app/src/main/java/com/example/petstoremobile/fragments/listfragments/SupplierFragment.java index 0e12dcd4..c2a82d4a 100644 --- a/app/src/main/java/com/example/petstoremobile/fragments/listfragments/SupplierFragment.java +++ b/app/src/main/java/com/example/petstoremobile/fragments/listfragments/SupplierFragment.java @@ -6,13 +6,19 @@ import androidx.fragment.app.Fragment; import androidx.recyclerview.widget.LinearLayoutManager; import androidx.recyclerview.widget.RecyclerView; +import android.util.Log; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.ImageButton; +import android.widget.Toast; import com.example.petstoremobile.R; import com.example.petstoremobile.adapters.SupplierAdapter; +import com.example.petstoremobile.api.RetrofitClient; +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.fragments.listfragments.detailfragments.SupplierDetailFragment; import com.example.petstoremobile.models.Supplier; @@ -21,11 +27,16 @@ import com.google.android.material.floatingactionbutton.FloatingActionButton; import java.util.ArrayList; import java.util.List; +import retrofit2.Call; +import retrofit2.Callback; +import retrofit2.Response; + public class SupplierFragment extends Fragment implements SupplierAdapter.OnSupplierClickListener { private List supplierList = new ArrayList<>(); private SupplierAdapter adapter; private ImageButton hamburger; + private SupplierApi api; //load supplier view @Override @@ -33,10 +44,11 @@ public class SupplierFragment extends Fragment implements SupplierAdapter.OnSupp Bundle savedInstanceState) { View view = inflater.inflate(R.layout.fragment_supplier, container, false); + api = RetrofitClient.getSupplierApi(requireContext()); hamburger = view.findViewById(R.id.btnHamburger); - loadSupplierData(); //TODO: Replace this with actual data when backend is working setupRecyclerView(view); + loadSupplierData(); //Add button to opens the add dialog FloatingActionButton fabAddSupplier = view.findViewById(R.id.fabAddSupplier); @@ -107,14 +119,37 @@ public class SupplierFragment extends Fragment implements SupplierAdapter.OnSupp openSupplierDetails(position); } - // Helper function to get a list of all suppliers (Loads hardcoded sample data for now) TODO: REPLACE THIS WITH A METHOD THAT GETS DATA FROM THE DATABASE + // Helper function to get a list of all suppliers from the backend private void loadSupplierData() { - supplierList.clear(); - supplierList.add(new Supplier(1, "Pet Food Co.", "John", "Doe", "john@petfood.com", "123-456-7890")); - supplierList.add(new Supplier(2, "Toy Kingdom", "Jane", "Smith", "jane@toykingdom.com", "987-654-3210")); - supplierList.add(new Supplier(3, "HealthPet", "Robert", "Brown", "robert@healthpet.com", "555-0199-234")); - supplierList.add(new Supplier(4, "Groomers Choice", "Emily", "Davis", "emily@groomers.com", "444-555-6666")); - supplierList.add(new Supplier(5, "Birdy Haven", "Michael", "Wilson", "michael@birdyhaven.com", "111-222-3333")); + api.getAllSuppliers(0, 100).enqueue(new Callback>() { + @Override + public void onResponse(Call> call, Response> response) { + if (response.isSuccessful() && response.body() != null) { + supplierList.clear(); + + // Convert to suppliers + List suppliers = response.body().getContent() + .stream() + .map(Supplier::new) + .collect(java.util.stream.Collectors.toList()); + + supplierList.addAll(suppliers); + adapter.notifyDataSetChanged(); + + } else { + Log.e("onResponse: ", response.message()); + } + } + + @Override + public void onFailure(Call> call, Throwable t) { + if (getContext() != null) { + Toast.makeText(getContext(), + "Failed to load suppliers", Toast.LENGTH_SHORT).show(); + } + Log.e("onFailure: ", t.getMessage()); + } + }); } //set up the recyclerview and adapter diff --git a/app/src/main/java/com/example/petstoremobile/fragments/listfragments/listprofilefragments/PetProfileFragment.java b/app/src/main/java/com/example/petstoremobile/fragments/listfragments/listprofilefragments/PetProfileFragment.java new file mode 100644 index 00000000..280789e8 --- /dev/null +++ b/app/src/main/java/com/example/petstoremobile/fragments/listfragments/listprofilefragments/PetProfileFragment.java @@ -0,0 +1,177 @@ +package com.example.petstoremobile.fragments.listfragments.listprofilefragments; + +import android.Manifest; +import android.app.Activity; +import android.content.Intent; +import android.content.pm.PackageManager; +import android.graphics.Color; +import android.net.Uri; +import android.os.Bundle; + +import androidx.activity.result.ActivityResultLauncher; +import androidx.activity.result.contract.ActivityResultContracts; +import androidx.appcompat.app.AlertDialog; +import androidx.core.content.ContextCompat; +import androidx.core.content.FileProvider; +import androidx.fragment.app.Fragment; + +import android.provider.MediaStore; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.Button; +import android.widget.ImageView; +import android.widget.TextView; + +import com.example.petstoremobile.R; +import com.example.petstoremobile.fragments.ListFragment; +import com.example.petstoremobile.fragments.listfragments.detailfragments.PetDetailFragment; + +import java.io.File; +import java.util.Locale; + +public class PetProfileFragment extends Fragment { + + private TextView tvPetName, tvPetSpecies, tvPetBreed, tvPetAge, tvPetPrice; + private Button btnBack, btnEditPet, btnChangePhoto; + private ImageView imgPet; + private Uri photoUri; + + // launchers for camera and gallery + private ActivityResultLauncher galleryLauncher; + private ActivityResultLauncher cameraLauncher; + private ActivityResultLauncher permissionLauncher; + + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + // Launcher to open gallery to select image + galleryLauncher = registerForActivityResult( + new ActivityResultContracts.StartActivityForResult(), + result -> { + if (result.getResultCode() == Activity.RESULT_OK && result.getData() != null) { + Uri selectedImage = result.getData().getData(); + imgPet.setImageURI(selectedImage); + // TODO: SAVE CHANGED PHOTO TO DATABASE + } + } + ); + + // Launcher for camera to open and capture image + cameraLauncher = registerForActivityResult( + new ActivityResultContracts.TakePicture(), + success -> { + if (success) { + imgPet.setImageURI(null); + imgPet.setImageURI(photoUri); + // TODO: SAVE CHANGED PHOTO TO DATABASE + } + } + ); + + // Launcher to request camera permission + permissionLauncher = registerForActivityResult( + new ActivityResultContracts.RequestPermission(), + granted -> { + if (granted) { + launchCamera(); + } else { + new AlertDialog.Builder(requireContext()) + .setTitle("Permission Required") + .setMessage("Please grant camera permission to use this feature") + .setPositiveButton("Open Settings", (dialog, which) -> { + Intent intent = new Intent(android.provider.Settings.ACTION_APPLICATION_DETAILS_SETTINGS); + intent.setData(Uri.fromParts("package", requireContext().getPackageName(), null)); + startActivity(intent); + }) + .setNegativeButton("Cancel", null) + .show(); + } + } + ); + } + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, + Bundle savedInstanceState) { + View view = inflater.inflate(R.layout.fragment_pet_profile, container, false); + + // Initialize views + tvPetName = view.findViewById(R.id.tvPetName); + tvPetSpecies = view.findViewById(R.id.tvPetSpecies); + tvPetBreed = view.findViewById(R.id.tvPetBreed); + tvPetAge = view.findViewById(R.id.tvPetAge); + tvPetPrice = view.findViewById(R.id.tvPetPrice); + btnBack = view.findViewById(R.id.btnBack); + btnEditPet = view.findViewById(R.id.btnEditPet); + btnChangePhoto = view.findViewById(R.id.btnChangePhoto); + imgPet = view.findViewById(R.id.imgPet); + + + // Set pet details to display + if (getArguments() != null) { + tvPetName.setText(getArguments().getString("petName")); + tvPetSpecies.setText(getArguments().getString("petSpecies")); + tvPetBreed.setText(getArguments().getString("petBreed")); + tvPetAge.setText(String.format(Locale.getDefault(), "%d yr(s)", getArguments().getInt("petAge"))); + tvPetPrice.setText(String.format(Locale.getDefault(), "$%.2f", getArguments().getDouble("petPrice"))); + } + + //set button click listeners + btnBack.setOnClickListener(v -> { + //get the list fragment and pop the back stack to return to the previous view (PetFragment) + ListFragment listFragment = (ListFragment) getParentFragment(); + if (listFragment != null) { + listFragment.getChildFragmentManager().popBackStack(); + } + }); + + //Make the edit button go to the pet detail view + btnEditPet.setOnClickListener(v -> { + if (getArguments() == null) return; + + PetDetailFragment detailFragment = new PetDetailFragment(); + //send the bundle to the pet detail fragment + detailFragment.setArguments(getArguments()); + + //get ListFragment to load the the detail view + ListFragment listFragment = (ListFragment) getParentFragment(); + if (listFragment != null) { + listFragment.loadFragment(detailFragment); + } + }); + + //Make change photo button ask user to select a new photo + btnChangePhoto.setOnClickListener(v -> { + new AlertDialog.Builder(requireContext()) + .setTitle("Change Pet Photo") + .setItems(new String[]{"Take Photo", "Choose from Gallery"}, (dialog, which) -> { + if (which == 0) { + // Choose Camera + //Checks if the user has granted the camera permission already + if (ContextCompat.checkSelfPermission(requireContext(), Manifest.permission.CAMERA) == PackageManager.PERMISSION_GRANTED) { + //if the permission is already granted then launch the camera + launchCamera(); + } else { + //otherwise request the permission + permissionLauncher.launch(Manifest.permission.CAMERA); + } + } else { + Intent intent = new Intent(Intent.ACTION_PICK, MediaStore.Images.Media.EXTERNAL_CONTENT_URI); + galleryLauncher.launch(intent); + } + }) + .show(); + }); + + return view; + } + + private void launchCamera() { + File photoFile = new File(requireContext().getCacheDir(), "pet_photo.jpg"); + photoUri = FileProvider.getUriForFile(requireContext(), requireContext().getPackageName() + ".fileprovider", photoFile); + cameraLauncher.launch(photoUri); + } +} \ No newline at end of file diff --git a/app/src/main/java/com/example/petstoremobile/models/Pet.java b/app/src/main/java/com/example/petstoremobile/models/Pet.java index 22249bad..4d605944 100644 --- a/app/src/main/java/com/example/petstoremobile/models/Pet.java +++ b/app/src/main/java/com/example/petstoremobile/models/Pet.java @@ -1,5 +1,7 @@ package com.example.petstoremobile.models; +import com.example.petstoremobile.dtos.PetDTO; + public class Pet { private int petId; private String petName; @@ -20,6 +22,17 @@ public class Pet { this.petPrice = petPrice; } + //contructor to convert PetDTO to Pet + public Pet(PetDTO dto) { + this.petId = dto.getPetId().intValue(); + this.petName = dto.getPetName(); + this.petSpecies = dto.getPetSpecies(); + this.petBreed = dto.getPetBreed(); + this.petAge = dto.getPetAge(); + this.petStatus = dto.getPetStatus(); + this.petPrice = Double.parseDouble(dto.getPetPrice()); + } + //getter and setters public int getPetId() { return petId; diff --git a/app/src/main/java/com/example/petstoremobile/models/Service.java b/app/src/main/java/com/example/petstoremobile/models/Service.java index 46485c3d..3a935117 100644 --- a/app/src/main/java/com/example/petstoremobile/models/Service.java +++ b/app/src/main/java/com/example/petstoremobile/models/Service.java @@ -1,5 +1,7 @@ package com.example.petstoremobile.models; +import com.example.petstoremobile.dtos.ServiceDTO; + public class Service { private int serviceId; private String serviceName; @@ -16,15 +18,19 @@ public class Service { this.servicePrice = servicePrice; } + public Service(ServiceDTO dto) { + this.serviceId = dto.getServiceId().intValue(); + this.serviceName = dto.getServiceName(); + this.serviceDesc = dto.getServiceDesc(); + this.serviceDuration = dto.getServiceDuration(); + this.servicePrice = dto.getServicePrice(); + } + //getter and setters public int getServiceId() { return serviceId; } -// public void setServiceId(int serviceId) { -// this.serviceId = serviceId; -// } - public String getServiceName() { return serviceName; } diff --git a/app/src/main/java/com/example/petstoremobile/models/Supplier.java b/app/src/main/java/com/example/petstoremobile/models/Supplier.java index 755432f2..8b899c6f 100644 --- a/app/src/main/java/com/example/petstoremobile/models/Supplier.java +++ b/app/src/main/java/com/example/petstoremobile/models/Supplier.java @@ -1,5 +1,7 @@ package com.example.petstoremobile.models; +import com.example.petstoremobile.dtos.SupplierDTO; + public class Supplier { private int supId; private String supCompany; @@ -18,16 +20,21 @@ public class Supplier { this.supPhone = supPhone; } + public Supplier(SupplierDTO dto) { + this.supId = dto.getSupId().intValue(); + this.supCompany = dto.getSupCompany(); + this.supContactFirstName = dto.getSupContactFirstName(); + this.supContactLastName = dto.getSupContactLastName(); + this.supEmail = dto.getSupEmail(); + this.supPhone = dto.getSupPhone(); + } + //getter and setters public int getSupId() { return supId; } -// public void setSupId(int supId) { -// this.supId = supId; -// } - public String getSupCompany() { return supCompany; } diff --git a/app/src/main/res/layout/fragment_pet_profile.xml b/app/src/main/res/layout/fragment_pet_profile.xml new file mode 100644 index 00000000..b750bd29 --- /dev/null +++ b/app/src/main/res/layout/fragment_pet_profile.xml @@ -0,0 +1,244 @@ + + + + + + + + + +