Profile now loads from backend
- can update profile from app - loads profile details from backend to display - changed inputValidator to use andriod phone pattern - added ErrorResponse so we can fetch error messages from the backend - Added UserDTO to get profile info TODO: Still need to get profile images from the backend and beable to load and update them using the backend
This commit is contained in:
@@ -19,6 +19,7 @@ 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 com.example.petstoremobile.dtos.UserDTO;
|
||||
|
||||
import retrofit2.Call;
|
||||
import retrofit2.Callback;
|
||||
@@ -90,11 +91,11 @@ public class MainActivity extends AppCompatActivity {
|
||||
);
|
||||
|
||||
//fetch user id from api then login to home activity
|
||||
RetrofitClient.getAuthApi(MainActivity.this).getCurrentUser()
|
||||
.enqueue(new Callback<AuthDTO.UserResponse>() {
|
||||
RetrofitClient.getAuthApi(MainActivity.this).getMe()
|
||||
.enqueue(new Callback<UserDTO>() {
|
||||
@Override
|
||||
public void onResponse(Call<AuthDTO.UserResponse> call,
|
||||
Response<AuthDTO.UserResponse> response) {
|
||||
public void onResponse(Call<UserDTO> call,
|
||||
Response<UserDTO> response) {
|
||||
if (response.isSuccessful() && response.body() != null) {
|
||||
TokenManager.getInstance(MainActivity.this)
|
||||
.saveUserId(response.body().getId());
|
||||
@@ -106,7 +107,7 @@ public class MainActivity extends AppCompatActivity {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(Call<AuthDTO.UserResponse> call,
|
||||
public void onFailure(Call<UserDTO> call,
|
||||
Throwable t) {
|
||||
Log.e("MainActivity", "Failed to fetch userId", t);
|
||||
|
||||
@@ -129,4 +130,4 @@ public class MainActivity extends AppCompatActivity {
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,17 +1,29 @@
|
||||
package com.example.petstoremobile.api.auth;
|
||||
|
||||
import com.example.petstoremobile.dtos.AuthDTO;
|
||||
import com.example.petstoremobile.dtos.UserDTO;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import retrofit2.Call;
|
||||
import retrofit2.http.Body;
|
||||
import retrofit2.http.GET;
|
||||
import retrofit2.http.POST;
|
||||
import retrofit2.http.PUT;
|
||||
|
||||
//Api for logging in and getting current user
|
||||
public interface AuthApi {
|
||||
|
||||
//login endpoint
|
||||
@POST("api/v1/auth/login")
|
||||
Call<AuthDTO.LoginResponse> login(@Body AuthDTO.LoginRequest loginRequest);
|
||||
|
||||
//get current user endpoint
|
||||
@GET("api/v1/auth/me")
|
||||
Call<AuthDTO.UserResponse> getCurrentUser();
|
||||
Call<UserDTO> getMe();
|
||||
|
||||
//update current user endpoint
|
||||
@PUT("api/v1/auth/me")
|
||||
Call<UserDTO> updateMe(@Body Map<String, String> updates);
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
package com.example.petstoremobile.dtos;
|
||||
|
||||
//Used to get messages of any errors from the backend
|
||||
|
||||
public class ErrorResponse {
|
||||
private String message;
|
||||
|
||||
public String getMessage() {
|
||||
return message;
|
||||
}
|
||||
|
||||
public void setMessage(String message) {
|
||||
this.message = message;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
package com.example.petstoremobile.dtos;
|
||||
|
||||
public class UserDTO {
|
||||
private Long id;
|
||||
private String username;
|
||||
private String email;
|
||||
private String fullName;
|
||||
private String phone;
|
||||
private String avatarUrl;
|
||||
private String role;
|
||||
private Long storeId;
|
||||
private String storeName;
|
||||
|
||||
// Getters
|
||||
public Long getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public String getUsername() {
|
||||
return username;
|
||||
}
|
||||
|
||||
public String getEmail() {
|
||||
return email;
|
||||
}
|
||||
|
||||
public String getFullName() {
|
||||
return fullName;
|
||||
}
|
||||
|
||||
public String getPhone() {
|
||||
return phone;
|
||||
}
|
||||
|
||||
public String getAvatarUrl() {
|
||||
return avatarUrl;
|
||||
}
|
||||
|
||||
public String getRole() {
|
||||
return role;
|
||||
}
|
||||
|
||||
public Long getStoreId() {
|
||||
return storeId;
|
||||
}
|
||||
|
||||
public String getStoreName() {
|
||||
return storeName;
|
||||
}
|
||||
}
|
||||
@@ -16,6 +16,7 @@ import androidx.fragment.app.Fragment;
|
||||
|
||||
import android.provider.MediaStore;
|
||||
import android.text.InputType;
|
||||
import android.util.Log;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
@@ -23,20 +24,33 @@ import android.widget.Button;
|
||||
import android.widget.EditText;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import com.example.petstoremobile.R;
|
||||
import com.example.petstoremobile.activities.MainActivity;
|
||||
import com.example.petstoremobile.api.RetrofitClient;
|
||||
import com.example.petstoremobile.api.auth.AuthApi;
|
||||
import com.example.petstoremobile.api.auth.TokenManager;
|
||||
import com.example.petstoremobile.dtos.ErrorResponse;
|
||||
import com.example.petstoremobile.dtos.UserDTO;
|
||||
import com.example.petstoremobile.utils.InputValidator;
|
||||
import com.google.gson.Gson;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import retrofit2.Call;
|
||||
import retrofit2.Callback;
|
||||
import retrofit2.Response;
|
||||
|
||||
public class ProfileFragment extends Fragment {
|
||||
|
||||
//initialize the view/controls
|
||||
private ImageView imgProfile;
|
||||
private TextView tvProfileName, tvProfileEmail, tvProfilePhone, tvProfileRole;
|
||||
private Button btnChangePhoto, btnEditEmail, btnEditPhone, btnLogout;
|
||||
private Uri photoUri;
|
||||
private UserDTO currentUser;
|
||||
|
||||
//Initialize the launchers for camera and gallery
|
||||
private ActivityResultLauncher<Intent> galleryLauncher;
|
||||
@@ -107,7 +121,6 @@ public class ProfileFragment extends Fragment {
|
||||
);
|
||||
}
|
||||
|
||||
//TODO: MAKE PROFILE VIEW DISPLAY PROFILE DATA FROM DATABASE
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup container,
|
||||
Bundle savedInstanceState) {
|
||||
@@ -119,10 +132,13 @@ public class ProfileFragment extends Fragment {
|
||||
tvProfileEmail = view.findViewById(R.id.tvProfileEmail);
|
||||
tvProfilePhone = view.findViewById(R.id.tvProfilePhone);
|
||||
tvProfileRole = view.findViewById(R.id.tvProfileRole);
|
||||
btnChangePhoto = view.findViewById(R.id.btnChangePhoto);
|
||||
btnEditEmail = view.findViewById(R.id.btnEditEmail);
|
||||
btnEditPhone = view.findViewById(R.id.btnEditPhone);
|
||||
btnLogout = view.findViewById(R.id.btnLogout);
|
||||
Button btnChangePhoto = view.findViewById(R.id.btnChangePhoto);
|
||||
Button btnEditEmail = view.findViewById(R.id.btnEditEmail);
|
||||
Button btnEditPhone = view.findViewById(R.id.btnEditPhone);
|
||||
Button btnLogout = view.findViewById(R.id.btnLogout);
|
||||
|
||||
//Load Profile Data from backend
|
||||
loadProfileData();
|
||||
|
||||
//Set up listeners for the buttons
|
||||
//Change photo button
|
||||
@@ -170,19 +186,10 @@ public class ProfileFragment extends Fragment {
|
||||
.setTitle("Edit Email")
|
||||
.setView(input)
|
||||
.setPositiveButton("Save", (dialog, which) -> {
|
||||
String newEmail = input.getText().toString();
|
||||
//if the new value is a valid email then set the email to the new value
|
||||
if (android.util.Patterns.EMAIL_ADDRESS.matcher(newEmail).matches()) {
|
||||
tvProfileEmail.setText(newEmail);
|
||||
//TODO: UPDATE THE EMAIL IN DATABASE
|
||||
}
|
||||
else {
|
||||
//tell the user to email is invalid
|
||||
new AlertDialog.Builder(requireContext())
|
||||
.setTitle("Error")
|
||||
.setMessage("Email is invalid")
|
||||
.setPositiveButton("OK", null)
|
||||
.show();
|
||||
if (InputValidator.isValidEmail(input)) {
|
||||
updateProfileField("email", input.getText().toString());
|
||||
} else {
|
||||
Toast.makeText(requireContext(), "Email is invalid", Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
})
|
||||
.setNegativeButton("Cancel", null)
|
||||
@@ -210,19 +217,10 @@ public class ProfileFragment extends Fragment {
|
||||
.setTitle("Edit Phone Number")
|
||||
.setView(input)
|
||||
.setPositiveButton("Save", (dialog, which) -> {
|
||||
String newPhone = input.getText().toString();
|
||||
//if the new value is format: (XXX) XXX-XXXX then set the phone to the new value
|
||||
if (newPhone.matches("\\(\\d{3}\\) \\d{3}-\\d{4}")) { //TODO MAKE VALIDATION CLASS INSTEAD FOR THIS
|
||||
tvProfilePhone.setText(newPhone);
|
||||
//TODO: UPDATE PHONE IN DATABASE
|
||||
}
|
||||
else {
|
||||
//tell the user to email cannot be empty
|
||||
new AlertDialog.Builder(requireContext())
|
||||
.setTitle("Error")
|
||||
.setMessage("Phone number is invalid. Format: (XXX) XXX-XXXX")
|
||||
.setPositiveButton("OK", null)
|
||||
.show();
|
||||
if (InputValidator.isValidPhone(input)) {
|
||||
updateProfileField("phone", input.getText().toString());
|
||||
} else {
|
||||
Toast.makeText(requireContext(), "Phone number is invalid", Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
})
|
||||
.setNegativeButton("Cancel", null)
|
||||
@@ -252,4 +250,72 @@ public class ProfileFragment extends Fragment {
|
||||
//launch the camera to capture the photo and save the photo to photoUri
|
||||
cameraLauncher.launch(photoUri);
|
||||
}
|
||||
}
|
||||
|
||||
//Helper function to call the backend to get profile data and load it to the view
|
||||
private void loadProfileData() {
|
||||
AuthApi authApi = RetrofitClient.getAuthApi(requireContext());
|
||||
|
||||
authApi.getMe().enqueue(new Callback<UserDTO>() {
|
||||
@Override
|
||||
public void onResponse(Call<UserDTO> call, Response<UserDTO> response) {
|
||||
//if the response is successful and the body is not null then set the user to the view
|
||||
if (response.isSuccessful() && response.body() != null) {
|
||||
currentUser = response.body();
|
||||
|
||||
//set the user data to the view
|
||||
tvProfileName.setText(currentUser.getFullName());
|
||||
tvProfileEmail.setText(currentUser.getEmail());
|
||||
tvProfilePhone.setText(currentUser.getPhone());
|
||||
tvProfileRole.setText(currentUser.getRole());
|
||||
//TODO: LOAD PHOTO FROM DATABASE
|
||||
}
|
||||
else {
|
||||
Log.e("onResponse: ", response.message());
|
||||
Toast.makeText(getContext(), "Failed to load profile: ", Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(Call<UserDTO> call, Throwable t) {
|
||||
Log.e("PROFILE", "onFailure: " + t.getMessage());
|
||||
Toast.makeText(getContext(), "Network error: could not load profile", Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
//Helper function to update a profile field in the backend
|
||||
private void updateProfileField(String fieldName, String value) {
|
||||
AuthApi authApi = RetrofitClient.getAuthApi(requireContext());
|
||||
Map<String, String> updates = new HashMap<>();
|
||||
updates.put(fieldName, value);
|
||||
|
||||
authApi.updateMe(updates).enqueue(new Callback<UserDTO>() {
|
||||
@Override
|
||||
public void onResponse(Call<UserDTO> call, Response<UserDTO> response) {
|
||||
if (response.isSuccessful() && response.body() != null) {
|
||||
currentUser = response.body();
|
||||
// Update the view with the new data from backend
|
||||
tvProfileEmail.setText(currentUser.getEmail());
|
||||
tvProfilePhone.setText(currentUser.getPhone());
|
||||
Toast.makeText(requireContext(), "Profile updated successfully", Toast.LENGTH_SHORT).show();
|
||||
} else {
|
||||
try {
|
||||
String errorJson = response.errorBody().string();
|
||||
ErrorResponse errorResponse = new Gson().fromJson(errorJson, ErrorResponse.class);
|
||||
String errorMessage = errorResponse.getMessage();
|
||||
Toast.makeText(requireContext(), errorMessage, Toast.LENGTH_LONG).show();
|
||||
} catch (Exception e) {
|
||||
Log.e("UPDATE_PROFILE", "Error parsing error body", e);
|
||||
Toast.makeText(requireContext(), "Failed to update profile", Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(Call<UserDTO> call, Throwable t) {
|
||||
Log.e("UPDATE_PROFILE", "Failure: " + t.getMessage());
|
||||
Toast.makeText(requireContext(), "Network error", Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -61,10 +61,11 @@ public class InputValidator {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Checks if the phone number is valid (digits, spaces, dashes, brackets allowed)
|
||||
// Checks if the phone number is valid
|
||||
public static boolean isValidPhone(EditText field) {
|
||||
String phone = field.getText().toString().trim();
|
||||
if (phone.isEmpty() || !phone.matches("[0-9\\-\\s\\(\\)\\+]+")) {
|
||||
// Android built in phone validation pattern
|
||||
if (phone.isEmpty() || !android.util.Patterns.PHONE.matcher(phone).matches()) {
|
||||
field.setError("Enter a valid phone number");
|
||||
field.requestFocus();
|
||||
return false;
|
||||
@@ -94,4 +95,3 @@ public class InputValidator {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -83,7 +83,7 @@
|
||||
android:id="@+id/tvProfileEmail"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="example@email.com"
|
||||
android:text="No email loaded"
|
||||
android:textColor="@color/text_dark"
|
||||
android:textSize="16sp" />
|
||||
|
||||
@@ -129,7 +129,7 @@
|
||||
android:id="@+id/tvProfilePhone"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="(123) 123-1234"
|
||||
android:text="No phone loaded"
|
||||
android:textColor="@color/text_dark"
|
||||
android:textSize="16sp" />
|
||||
|
||||
@@ -174,7 +174,7 @@
|
||||
android:id="@+id/tvProfileRole"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Manager"
|
||||
android:text="No role loaded"
|
||||
android:textSize="16sp"
|
||||
android:textColor="@color/accent_coral"/>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user