Added profile photo loading and uploading
- profile photos now load from backend - profile photos can be uploaded to the backend - RetrofitClient now automatically determines if the device is an emulator or hardware so we dont have to comment and uncomment everytime we test with a different device
This commit is contained in:
@@ -56,6 +56,9 @@ dependencies {
|
|||||||
implementation("io.reactivex.rxjava2:rxjava:2.2.21")
|
implementation("io.reactivex.rxjava2:rxjava:2.2.21")
|
||||||
implementation("io.reactivex.rxjava2:rxandroid:2.1.1")
|
implementation("io.reactivex.rxjava2:rxandroid:2.1.1")
|
||||||
|
|
||||||
|
implementation("com.github.bumptech.glide:glide:4.16.0")
|
||||||
|
annotationProcessor("com.github.bumptech.glide:compiler:4.16.0")
|
||||||
|
|
||||||
testImplementation(libs.junit)
|
testImplementation(libs.junit)
|
||||||
androidTestImplementation(libs.ext.junit)
|
androidTestImplementation(libs.ext.junit)
|
||||||
androidTestImplementation(libs.espresso.core)
|
androidTestImplementation(libs.espresso.core)
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package com.example.petstoremobile.api;
|
package com.example.petstoremobile.api;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
|
import android.os.Build;
|
||||||
|
|
||||||
import com.example.petstoremobile.api.auth.AuthApi;
|
import com.example.petstoremobile.api.auth.AuthApi;
|
||||||
import com.example.petstoremobile.api.auth.AuthInterceptor;
|
import com.example.petstoremobile.api.auth.AuthInterceptor;
|
||||||
@@ -12,9 +13,23 @@ import retrofit2.converter.gson.GsonConverterFactory;
|
|||||||
|
|
||||||
//Retrofit client Used for API calls
|
//Retrofit client Used for API calls
|
||||||
public class RetrofitClient {
|
public class RetrofitClient {
|
||||||
//base URL
|
public static final String BASE_URL = getBaseUrl();
|
||||||
public static final String BASE_URL = "http://10.0.2.2:8080"; //for emulator testing
|
|
||||||
// public static final String BASE_URL = "http://10.0.0.200:8080/"; //for hardware testing
|
// Helper function to determine BASE_URL based on whether we are testing on an emulator or a real device
|
||||||
|
private static String getBaseUrl() {
|
||||||
|
if (Build.FINGERPRINT.contains("generic")
|
||||||
|
|| Build.FINGERPRINT.contains("unknown")
|
||||||
|
|| Build.MODEL.contains("google_sdk")
|
||||||
|
|| Build.MODEL.contains("Emulator")
|
||||||
|
|| Build.MODEL.contains("Android SDK built for x86")
|
||||||
|
|| Build.MANUFACTURER.contains("Genymotion")
|
||||||
|
|| (Build.BRAND.startsWith("generic") && Build.DEVICE.startsWith("generic"))
|
||||||
|
|| "google_sdk".equals(Build.PRODUCT)) {
|
||||||
|
return "http://10.0.2.2:8080/"; //emulator testing
|
||||||
|
} else {
|
||||||
|
return "http://10.0.0.200:8080/"; //Hardware testing
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private static Retrofit retrofit = null;
|
private static Retrofit retrofit = null;
|
||||||
|
|
||||||
@@ -67,4 +82,4 @@ public class RetrofitClient {
|
|||||||
return getClient(context).create(MessageApi.class);
|
return getClient(context).create(MessageApi.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,15 +5,21 @@ import com.example.petstoremobile.dtos.UserDTO;
|
|||||||
|
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
|
import okhttp3.MultipartBody;
|
||||||
import retrofit2.Call;
|
import retrofit2.Call;
|
||||||
import retrofit2.http.Body;
|
import retrofit2.http.Body;
|
||||||
import retrofit2.http.GET;
|
import retrofit2.http.GET;
|
||||||
|
import retrofit2.http.Multipart;
|
||||||
import retrofit2.http.POST;
|
import retrofit2.http.POST;
|
||||||
import retrofit2.http.PUT;
|
import retrofit2.http.PUT;
|
||||||
|
import retrofit2.http.Part;
|
||||||
|
|
||||||
//Api for logging in and getting current user
|
//Api for logging in and getting current user
|
||||||
public interface AuthApi {
|
public interface AuthApi {
|
||||||
|
|
||||||
|
// endpoint for downloading the current user's avatar file
|
||||||
|
String AVATAR_FILE_PATH = "api/v1/auth/me/avatar/file";
|
||||||
|
|
||||||
//login endpoint
|
//login endpoint
|
||||||
@POST("api/v1/auth/login")
|
@POST("api/v1/auth/login")
|
||||||
Call<AuthDTO.LoginResponse> login(@Body AuthDTO.LoginRequest loginRequest);
|
Call<AuthDTO.LoginResponse> login(@Body AuthDTO.LoginRequest loginRequest);
|
||||||
@@ -26,4 +32,9 @@ public interface AuthApi {
|
|||||||
@PUT("api/v1/auth/me")
|
@PUT("api/v1/auth/me")
|
||||||
Call<UserDTO> updateMe(@Body Map<String, String> updates);
|
Call<UserDTO> updateMe(@Body Map<String, String> updates);
|
||||||
|
|
||||||
|
//upload avatar endpoint
|
||||||
|
@Multipart
|
||||||
|
@POST("api/v1/auth/me/avatar")
|
||||||
|
Call<UserDTO> uploadAvatar(@Part MultipartBody.Part avatar);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,6 +26,10 @@ import android.widget.ImageView;
|
|||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
import android.widget.Toast;
|
import android.widget.Toast;
|
||||||
|
|
||||||
|
import com.bumptech.glide.Glide;
|
||||||
|
import com.bumptech.glide.load.engine.DiskCacheStrategy;
|
||||||
|
import com.bumptech.glide.load.model.GlideUrl;
|
||||||
|
import com.bumptech.glide.load.model.LazyHeaders;
|
||||||
import com.example.petstoremobile.R;
|
import com.example.petstoremobile.R;
|
||||||
import com.example.petstoremobile.activities.MainActivity;
|
import com.example.petstoremobile.activities.MainActivity;
|
||||||
import com.example.petstoremobile.api.RetrofitClient;
|
import com.example.petstoremobile.api.RetrofitClient;
|
||||||
@@ -38,9 +42,14 @@ import com.example.petstoremobile.utils.InputValidator;
|
|||||||
import com.google.gson.Gson;
|
import com.google.gson.Gson;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
|
import java.io.FileOutputStream;
|
||||||
|
import java.io.InputStream;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
|
import okhttp3.MediaType;
|
||||||
|
import okhttp3.MultipartBody;
|
||||||
|
import okhttp3.RequestBody;
|
||||||
import retrofit2.Call;
|
import retrofit2.Call;
|
||||||
import retrofit2.Callback;
|
import retrofit2.Callback;
|
||||||
import retrofit2.Response;
|
import retrofit2.Response;
|
||||||
@@ -73,8 +82,7 @@ public class ProfileFragment extends Fragment {
|
|||||||
&& result.getData() != null) {
|
&& result.getData() != null) {
|
||||||
//get the selected image and set the image to the profile
|
//get the selected image and set the image to the profile
|
||||||
Uri selectedImage = result.getData().getData();
|
Uri selectedImage = result.getData().getData();
|
||||||
imgProfile.setImageURI(selectedImage);
|
uploadAvatar(selectedImage);
|
||||||
//TODO: SAVE CHANGED PHOTO TO DATABASE
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
@@ -86,10 +94,7 @@ public class ProfileFragment extends Fragment {
|
|||||||
success -> {
|
success -> {
|
||||||
//if a photo is taken set the image profile to it otherwise do nothing
|
//if a photo is taken set the image profile to it otherwise do nothing
|
||||||
if (success) {
|
if (success) {
|
||||||
//Clear the old image and set the new one
|
uploadAvatar(photoUri);
|
||||||
imgProfile.setImageURI(null);
|
|
||||||
imgProfile.setImageURI(photoUri);
|
|
||||||
//TODO: SAVE CHANGED PHOTO TO DATABASE
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
@@ -167,7 +172,6 @@ public class ProfileFragment extends Fragment {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
.show();
|
.show();
|
||||||
//TODO: UPDATE PHOTO IN DATABASE
|
|
||||||
});
|
});
|
||||||
|
|
||||||
//Edit email button
|
//Edit email button
|
||||||
@@ -272,7 +276,31 @@ public class ProfileFragment extends Fragment {
|
|||||||
tvProfileEmail.setText(currentUser.getEmail());
|
tvProfileEmail.setText(currentUser.getEmail());
|
||||||
tvProfilePhone.setText(currentUser.getPhone());
|
tvProfilePhone.setText(currentUser.getPhone());
|
||||||
tvProfileRole.setText(currentUser.getRole());
|
tvProfileRole.setText(currentUser.getRole());
|
||||||
//TODO: LOAD PHOTO FROM DATABASE
|
|
||||||
|
// get the avatar endpoint to load profile image and the token for authorization
|
||||||
|
String avatarUrl = RetrofitClient.BASE_URL + AuthApi.AVATAR_FILE_PATH;
|
||||||
|
String token = TokenManager.getInstance(requireContext()).getToken();
|
||||||
|
|
||||||
|
if (token != null) {
|
||||||
|
// Create GlideUrl with token to fetch the image
|
||||||
|
GlideUrl glideUrl = new GlideUrl(avatarUrl, new LazyHeaders.Builder()
|
||||||
|
.addHeader("Authorization", "Bearer " + token)
|
||||||
|
.build());
|
||||||
|
|
||||||
|
// Load image using Glide
|
||||||
|
Glide.with(ProfileFragment.this)
|
||||||
|
.load(glideUrl)
|
||||||
|
.diskCacheStrategy(DiskCacheStrategy.NONE)
|
||||||
|
.skipMemoryCache(true)
|
||||||
|
.placeholder(R.drawable.placeholder)
|
||||||
|
.error(R.drawable.placeholder)
|
||||||
|
.into(imgProfile);
|
||||||
|
} else {
|
||||||
|
// load placeholder image if token is null
|
||||||
|
Glide.with(ProfileFragment.this)
|
||||||
|
.load(R.drawable.placeholder)
|
||||||
|
.into(imgProfile);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
Log.e("onResponse: ", response.message());
|
Log.e("onResponse: ", response.message());
|
||||||
@@ -288,6 +316,62 @@ public class ProfileFragment extends Fragment {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//Helper function to call the backend to upload a profile image
|
||||||
|
private void uploadAvatar(Uri uri) {
|
||||||
|
try {
|
||||||
|
File file = getFileFromUri(uri);
|
||||||
|
if (file == null) return;
|
||||||
|
|
||||||
|
// Create RequestBody for file upload
|
||||||
|
RequestBody requestFile = RequestBody.create(file, MediaType.parse(requireContext().getContentResolver().getType(uri)));
|
||||||
|
MultipartBody.Part body = MultipartBody.Part.createFormData("avatar", file.getName(), requestFile);
|
||||||
|
|
||||||
|
//Call the backend to upload the avatar
|
||||||
|
AuthApi authApi = RetrofitClient.getAuthApi(requireContext());
|
||||||
|
authApi.uploadAvatar(body).enqueue(new Callback<UserDTO>() {
|
||||||
|
@Override
|
||||||
|
public void onResponse(Call<UserDTO> call, Response<UserDTO> response) {
|
||||||
|
if (response.isSuccessful() && response.body() != null) {
|
||||||
|
currentUser = response.body();
|
||||||
|
Toast.makeText(requireContext(), "Avatar updated successfully", Toast.LENGTH_SHORT).show();
|
||||||
|
// Reload image after successful upload
|
||||||
|
loadProfileData();
|
||||||
|
} else {
|
||||||
|
Toast.makeText(requireContext(), "Failed to upload avatar", Toast.LENGTH_SHORT).show();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onFailure(Call<UserDTO> call, Throwable t) {
|
||||||
|
Log.e("UPLOAD_AVATAR", "Failure: " + t.getMessage());
|
||||||
|
Toast.makeText(requireContext(), "Network error", Toast.LENGTH_SHORT).show();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} catch (Exception e) {
|
||||||
|
Log.e("UPLOAD_AVATAR", "Error: " + e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper function to create a temporary File object from a Uri for uploading the avatar
|
||||||
|
private File getFileFromUri(Uri uri) {
|
||||||
|
try {
|
||||||
|
InputStream inputStream = requireContext().getContentResolver().openInputStream(uri);
|
||||||
|
File tempFile = new File(requireContext().getCacheDir(), "upload_avatar.jpg");
|
||||||
|
FileOutputStream outputStream = new FileOutputStream(tempFile);
|
||||||
|
byte[] buffer = new byte[1024];
|
||||||
|
int length;
|
||||||
|
while ((length = inputStream.read(buffer)) > 0) {
|
||||||
|
outputStream.write(buffer, 0, length);
|
||||||
|
}
|
||||||
|
outputStream.close();
|
||||||
|
inputStream.close();
|
||||||
|
return tempFile;
|
||||||
|
} catch (Exception e) {
|
||||||
|
Log.e("FILE_UTILS", "Error creating temp file", e);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
//Helper function to update a profile field in the backend
|
//Helper function to update a profile field in the backend
|
||||||
private void updateProfileField(String fieldName, String value) {
|
private void updateProfileField(String fieldName, String value) {
|
||||||
AuthApi authApi = RetrofitClient.getAuthApi(requireContext());
|
AuthApi authApi = RetrofitClient.getAuthApi(requireContext());
|
||||||
|
|||||||
Reference in New Issue
Block a user