From d66f3577535db48570dc0cc83ef2d0c99600a2eb Mon Sep 17 00:00:00 2001 From: Pau Date: Thu, 22 May 2025 23:03:40 +0200 Subject: [PATCH] Added essential http request and methods for places image upload to server --- .../acloc/activity/AddNewPlaceActivity.java | 50 ++++++++- .../example/acloc/api/LocationApiClient.java | 16 +-- .../example/acloc/service/UploadService.java | 14 +++ .../com/example/acloc/utility/FileUtils.java | 1 + .../acloc/utility/ImageLoaderUtil.java | 47 ++++++++ .../example/acloc/utility/ImagePicker.java | 106 ++++++++++++++++++ .../example/acloc/utility/UploadManager.java | 73 ++++++++++++ 7 files changed, 296 insertions(+), 11 deletions(-) create mode 100644 app/src/main/java/com/example/acloc/service/UploadService.java create mode 100644 app/src/main/java/com/example/acloc/utility/FileUtils.java create mode 100644 app/src/main/java/com/example/acloc/utility/ImageLoaderUtil.java create mode 100644 app/src/main/java/com/example/acloc/utility/ImagePicker.java create mode 100644 app/src/main/java/com/example/acloc/utility/UploadManager.java diff --git a/app/src/main/java/com/example/acloc/activity/AddNewPlaceActivity.java b/app/src/main/java/com/example/acloc/activity/AddNewPlaceActivity.java index cf5cee7..df20e3f 100644 --- a/app/src/main/java/com/example/acloc/activity/AddNewPlaceActivity.java +++ b/app/src/main/java/com/example/acloc/activity/AddNewPlaceActivity.java @@ -1,5 +1,7 @@ package com.example.acloc.activity; +import static com.example.acloc.utility.Constants.BASE_URL; + import android.annotation.SuppressLint; import android.app.Dialog; import android.content.Context; @@ -16,6 +18,7 @@ import androidx.appcompat.app.AppCompatActivity; import androidx.appcompat.widget.AppCompatButton; import androidx.appcompat.widget.Toolbar; +import com.example.acloc.utility.UploadManager; import com.ieslamar.acloc.R; import com.example.acloc.api.LocationApiClient; import com.example.acloc.model.Place; @@ -26,6 +29,10 @@ import com.example.acloc.utility.Helper; import com.example.acloc.utility.SharedPref; import com.google.android.material.textfield.TextInputEditText; import com.google.gson.JsonObject; +import com.squareup.picasso.Picasso; + +import org.json.JSONException; +import org.json.JSONObject; import retrofit2.Call; import retrofit2.Callback; @@ -46,6 +53,10 @@ public class AddNewPlaceActivity extends AppCompatActivity implements View.OnCli private String place_uuid; private static final int PICK_IMAGE_REQUEST = 200; + private Uri selectedImageUri; + private String imageUrl; + private String JSonString; + @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); @@ -140,14 +151,44 @@ public class AddNewPlaceActivity extends AppCompatActivity implements View.OnCli protected void onActivityResult(int requestCode, int resultCode, Intent data) { super.onActivityResult(requestCode, resultCode, data); if (requestCode == PICK_IMAGE_REQUEST && resultCode == RESULT_OK && data != null && data.getData() != null) { - // Get the image URI - Uri selectedImageUri = data.getData(); + selectedImageUri = data.getData(); - // Set the image in the ImageView - ivPlacePhoto.setImageURI(selectedImageUri); + // Load image using Picasso + Picasso.get().load(selectedImageUri).into(ivPlacePhoto); + + // Upload the image + uploadImageToServer(selectedImageUri); } } + private void uploadImageToServer(Uri imageUri) { + UploadManager.uploadImage(this, imageUri, new UploadManager.UploadCallback() { + @Override + public void onSuccess(String response) { + try { + JSONObject json = new JSONObject(response); + if (json.getBoolean("success")) { + String filename = json.getJSONObject("file").getString("filename"); + imageUrl = BASE_URL + "public/" + filename; + JSonString = "[\"" + imageUrl + "\"]"; + Helper.makeSnackBar(rlAddPlace, "Image uploaded successfully"); + Log.d(TAG, "JsonString: " + JSonString); + } else { + Helper.makeSnackBar(rlAddPlace, "Upload failed"); + } + } catch (JSONException e) { + Helper.makeSnackBar(rlAddPlace, "Response parsing error"); + Log.e(TAG, "Failed to parse JSON", e); + } + } + @Override + public void onError(String message) { + Helper.makeSnackBar(rlAddPlace, "Upload failed: " + message); + Log.e(TAG, "Upload error:" + message); + } + }); + } + private void onClickBtnSubmit() { View[] views = {etPlaceName, etLatitude, etLongitude, etAddress, etPlaceDescription}; if (Helper.isEmptyFieldValidation(views)) { @@ -186,6 +227,7 @@ public class AddNewPlaceActivity extends AppCompatActivity implements View.OnCli placeBody.addProperty("latitude", latitude); placeBody.addProperty("longitude", longitude); placeBody.addProperty("createdBy", createdBy); + placeBody.addProperty("images", JSonString); String token = "Bearer " + SharedPref.getAccessToken(context); diff --git a/app/src/main/java/com/example/acloc/api/LocationApiClient.java b/app/src/main/java/com/example/acloc/api/LocationApiClient.java index 5178ee4..4f04e0f 100644 --- a/app/src/main/java/com/example/acloc/api/LocationApiClient.java +++ b/app/src/main/java/com/example/acloc/api/LocationApiClient.java @@ -1,20 +1,14 @@ package com.example.acloc.api; -import static com.example.acloc.utility.Constants.BASE_URL; - import com.example.acloc.service.AuthService; import com.example.acloc.service.FavoriteService; import com.example.acloc.service.PlaceService; import com.example.acloc.service.ReportService; import com.example.acloc.service.RoleService; +import com.example.acloc.service.UploadService; import com.example.acloc.service.UserService; -import java.util.concurrent.TimeUnit; - -import okhttp3.OkHttpClient; -import okhttp3.logging.HttpLoggingInterceptor; import retrofit2.Retrofit; -import retrofit2.converter.gson.GsonConverterFactory; /** * Centralized API client that manages Retrofit instance and provides access to all service interfaces @@ -30,6 +24,7 @@ public class LocationApiClient { private ReportService reportService; private RoleService roleService; private UserService userService; + private UploadService uploadService; // Private constructor for singleton pattern private LocationApiClient() { @@ -112,4 +107,11 @@ public class LocationApiClient { } return userService; } + + public UploadService getUploadService(){ + if(uploadService == null){ + uploadService = retrofit.create(UploadService.class); + } + return uploadService; + } } \ No newline at end of file diff --git a/app/src/main/java/com/example/acloc/service/UploadService.java b/app/src/main/java/com/example/acloc/service/UploadService.java new file mode 100644 index 0000000..cc8024e --- /dev/null +++ b/app/src/main/java/com/example/acloc/service/UploadService.java @@ -0,0 +1,14 @@ +package com.example.acloc.service; + +import okhttp3.MultipartBody; +import okhttp3.ResponseBody; +import retrofit2.Call; +import retrofit2.http.Multipart; +import retrofit2.http.POST; +import retrofit2.http.Part; + +public interface UploadService { + @Multipart + @POST("upload") + Call uploadImage(@Part MultipartBody.Part file); +} diff --git a/app/src/main/java/com/example/acloc/utility/FileUtils.java b/app/src/main/java/com/example/acloc/utility/FileUtils.java new file mode 100644 index 0000000..a255d03 --- /dev/null +++ b/app/src/main/java/com/example/acloc/utility/FileUtils.java @@ -0,0 +1 @@ +package com.example.acloc.utility; \ No newline at end of file diff --git a/app/src/main/java/com/example/acloc/utility/ImageLoaderUtil.java b/app/src/main/java/com/example/acloc/utility/ImageLoaderUtil.java new file mode 100644 index 0000000..3826db4 --- /dev/null +++ b/app/src/main/java/com/example/acloc/utility/ImageLoaderUtil.java @@ -0,0 +1,47 @@ +package com.example.acloc.utility; + +import android.net.Uri; +import android.widget.ImageView; + +import com.squareup.picasso.Picasso; + +/** + * Utility class to handle image loading operations + * Separates image loading responsibility from activities + */ +public class ImageLoaderUtil { + + /** + * Loads an image from a Uri into an ImageView + * + * @param imageUri Source Uri of the image + * @param targetView Target ImageView where the image will be displayed + */ + public static void loadImage(Uri imageUri, ImageView targetView) { + if (imageUri != null && targetView != null) { + Picasso.get().load(imageUri).into(targetView); + } + } + + /** + * Loads an image from a Uri into an ImageView with placeholder and error handling + * + * @param imageUri Source Uri of the image + * @param targetView Target ImageView where the image will be displayed + * @param placeholderResId Resource ID for the placeholder image + * @param errorResId Resource ID for the error image + */ + public static void loadImageWithPlaceholder( + Uri imageUri, + ImageView targetView, + int placeholderResId, + int errorResId) { + if (imageUri != null && targetView != null) { + Picasso.get() + .load(imageUri) + .placeholder(placeholderResId) + .error(errorResId) + .into(targetView); + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/example/acloc/utility/ImagePicker.java b/app/src/main/java/com/example/acloc/utility/ImagePicker.java new file mode 100644 index 0000000..f67bdcd --- /dev/null +++ b/app/src/main/java/com/example/acloc/utility/ImagePicker.java @@ -0,0 +1,106 @@ +package com.example.acloc.utility; + +import android.content.Context; +import android.database.Cursor; +import android.net.Uri; +import android.provider.OpenableColumns; +import android.webkit.MimeTypeMap; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; + +import okhttp3.MediaType; +import okhttp3.MultipartBody; +import okhttp3.RequestBody; + +public class ImagePicker { + + /** + * Get a file from a Uri. + * + * @param context The application context + * @param uri The Uri to get the file from + * @return The file + * @throws IOException + */ + public static File getFileFromUri(Context context, Uri uri) throws IOException { + String fileName = getFileName(context, uri); + File file = new File(context.getCacheDir(), fileName); + + // Copy the file to the cache directory + try (InputStream inputStream = context.getContentResolver().openInputStream(uri); + OutputStream outputStream = new FileOutputStream(file)) { + + if (inputStream == null) { + throw new IOException("Failed to open input stream."); + } + + byte[] buffer = new byte[4 * 1024]; // 4k buffer + int read; + while ((read = inputStream.read(buffer)) != -1) { + outputStream.write(buffer, 0, read); + } + + outputStream.flush(); + return file; + } + } + + /** + * Get the file name from a Uri + * + * @param context The application context + * @param uri The Uri to get the file name from + * @return The file name + */ + private static String getFileName(Context context, Uri uri) { + String result = null; + if (uri.getScheme().equals("content")) { + try (Cursor cursor = context.getContentResolver().query(uri, null, null, null, null)) { + if (cursor != null && cursor.moveToFirst()) { + int nameIndex = cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME); + if (nameIndex != -1) { + result = cursor.getString(nameIndex); + } + } + } + } + + if (result == null) { + result = uri.getPath(); + int cut = result.lastIndexOf('/'); + if (cut != -1) { + result = result.substring(cut + 1); + } + } + + return result; + } + + /** + * Prepare a file part for a multipart request + * + * @param partName The name of the part + * @param file The file to upload + * @return The MultipartBody.Part + */ + public static MultipartBody.Part prepareFilePart(String partName, File file) { + // Get the MIME type + String extension = MimeTypeMap.getFileExtensionFromUrl(file.getPath()); + String mimeType = MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension); + + if (mimeType == null) { + // Default to image/jpeg if MIME type cannot be determined + mimeType = "image/jpeg"; + } + + // Create RequestBody instance from file + RequestBody requestFile = RequestBody.create(MediaType.parse(mimeType), file); + + // MultipartBody.Part is used to send the actual file + return MultipartBody.Part.createFormData(partName, file.getName(), requestFile); + } +} \ No newline at end of file diff --git a/app/src/main/java/com/example/acloc/utility/UploadManager.java b/app/src/main/java/com/example/acloc/utility/UploadManager.java new file mode 100644 index 0000000..fe8211c --- /dev/null +++ b/app/src/main/java/com/example/acloc/utility/UploadManager.java @@ -0,0 +1,73 @@ +package com.example.acloc.utility; + +import android.content.Context; +import android.net.Uri; + +import com.example.acloc.api.LocationApiClient; +import com.example.acloc.service.UploadService; + +import java.io.File; +import java.io.IOException; + +import okhttp3.MultipartBody; +import okhttp3.ResponseBody; +import retrofit2.Call; +import retrofit2.Callback; +import retrofit2.Response; + +public class UploadManager { + public interface UploadCallback { + void onSuccess(String message); + void onError(String message); + } + + /** + * Uploads an image to the server + * + * @param context Application context + * @param imageUri URI of the image to upload + * @param callback Callback to handle the upload result + */ + public static void uploadImage(Context context, Uri imageUri, UploadCallback callback) { + if (context == null || imageUri == null || callback == null) { + if (callback != null) { + callback.onError("Invalid parameters"); + } + return; + } + + try { + File file = ImagePicker.getFileFromUri(context, imageUri); + MultipartBody.Part filePart = ImagePicker.prepareFilePart("file", file); + + performUpload(filePart, callback); + } catch (IOException e) { + callback.onError("Error preparing file: " + e.getMessage()); + } + } + + private static void performUpload(MultipartBody.Part filePart, UploadCallback callback) { + UploadService uploadService = LocationApiClient.getInstance().getUploadService(); + + + uploadService.uploadImage(filePart).enqueue(new Callback() { + @Override + public void onResponse(Call call, Response response) { + if (response.isSuccessful()) { + try { + callback.onSuccess(response.body().string()); + } catch (IOException e) { + throw new RuntimeException(e); + } + } else { + callback.onError("Server error: " + response.code()); + } + } + + @Override + public void onFailure(Call call, Throwable t) { + callback.onError("Connection error: " + t.getMessage()); + } + }); + } +}