Commit ccd159e2 authored by Chamod Ishankha's avatar Chamod Ishankha

complete user profile with image upload option

parent 1ca662d8
......@@ -43,6 +43,7 @@ dependencies {
implementation(libs.retrofit2)
implementation(libs.convertor.gson)
implementation(libs.jackson.databind)
implementation(libs.ucrop)
testImplementation(libs.junit)
androidTestImplementation(libs.ext.junit)
androidTestImplementation(libs.espresso.core)
......
......@@ -4,6 +4,7 @@
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.READ_MEDIA_IMAGES" />
<application
android:allowBackup="true"
......@@ -16,6 +17,11 @@
android:supportsRtl="true"
android:theme="@style/Theme.BabyCare"
tools:targetApi="31">
<activity
android:name="com.yalantis.ucrop.UCropActivity"
android:screenOrientation="fullSensor"
android:theme="@style/Theme.AppCompat.Light.NoActionBar"/>
<activity
android:name=".activities.UserProfileActivity"
android:exported="false" />
......
......@@ -7,26 +7,15 @@ import android.content.Intent;
import android.os.Bundle;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.google.android.material.snackbar.Snackbar;
import androidx.appcompat.app.AppCompatActivity;
import android.util.Log;
import android.view.View;
import androidx.navigation.NavController;
import androidx.navigation.Navigation;
import androidx.navigation.ui.AppBarConfiguration;
import androidx.navigation.ui.NavigationUI;
import com.kaluwa.enterprises.babycare.activities.DashboardActivity;
import com.kaluwa.enterprises.babycare.activities.auth.LoginActivity;
import com.kaluwa.enterprises.babycare.config.TokenSaver;
import com.kaluwa.enterprises.babycare.databinding.ActivityMainBinding;
import com.kaluwa.enterprises.babycare.dto.AuthenticationDto;
import com.kaluwa.enterprises.babycare.dto.responseDto.AuthenticationDto;
import android.view.Menu;
import android.view.MenuItem;
import android.widget.Button;
import android.widget.Toast;
......
......@@ -10,7 +10,11 @@ import static com.kaluwa.enterprises.babycare.utils.Utils.mobileNumberValidation
import static com.kaluwa.enterprises.babycare.utils.Utils.setUpDOBPicker;
import android.annotation.SuppressLint;
import android.app.Activity;
import android.content.Intent;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.net.Uri;
import android.os.Bundle;
import android.text.TextUtils;
import android.util.Log;
......@@ -19,11 +23,14 @@ import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.webkit.MimeTypeMap;
import android.widget.Button;
import android.widget.EditText;
import android.widget.ImageView;
import android.widget.Toast;
import androidx.activity.result.ActivityResultLauncher;
import androidx.activity.result.contract.ActivityResultContracts;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.widget.PopupMenu;
......@@ -31,22 +38,31 @@ import androidx.appcompat.widget.Toolbar;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.github.ybq.android.spinkit.SpinKitView;
import com.google.android.material.imageview.ShapeableImageView;
import com.google.gson.Gson;
import com.kaluwa.enterprises.babycare.MainActivity;
import com.kaluwa.enterprises.babycare.R;
import com.kaluwa.enterprises.babycare.config.ApiConfig;
import com.kaluwa.enterprises.babycare.dto.AuthenticationDto;
import com.kaluwa.enterprises.babycare.dto.responseDto.AuthenticationDto;
import com.kaluwa.enterprises.babycare.dto.UserDto;
import com.kaluwa.enterprises.babycare.dto.responseDto.ResponseDto;
import com.kaluwa.enterprises.babycare.error.ErrorDto;
import com.kaluwa.enterprises.babycare.service.UserApiService;
import com.kaluwa.enterprises.babycare.utils.Utils;
import com.yalantis.ucrop.UCrop;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.InputStream;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.atomic.AtomicBoolean;
import okhttp3.MediaType;
import okhttp3.MultipartBody;
import okhttp3.RequestBody;
import okhttp3.ResponseBody;
import retrofit2.Call;
import retrofit2.Callback;
import retrofit2.Response;
......@@ -55,12 +71,15 @@ public class UserProfileActivity extends AppCompatActivity {
private final static String TAG = "UserProfileActivity";
private EditText etFirstName, etLastName, etType, etEmail, etMobile, etDob;
private ImageView imVerifiedStatus;
private ImageView ivVerifiedStatus;
private ShapeableImageView ivProfilePicture;
private Button btnEdit, btnCancel;
private SpinKitView progressBar;
private View overlay;
private UserApiService userApiService;
private AuthenticationDto authDto;
private ActivityResultLauncher<Intent> pickImageLauncher;
private ActivityResultLauncher<Intent> cropImageLauncher;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
......@@ -88,11 +107,15 @@ public class UserProfileActivity extends AppCompatActivity {
etEmail = findViewById(R.id.up_et_email);
etMobile = findViewById(R.id.up_et_mobile);
etDob = findViewById(R.id.up_et_dob);
imVerifiedStatus = findViewById(R.id.up_im_email_verification_status);
ivVerifiedStatus = findViewById(R.id.up_im_email_verification_status);
ivProfilePicture = findViewById(R.id.up_iv_profile_image);
// buttons
btnEdit = findViewById(R.id.l_btn_edit);
btnCancel = findViewById(R.id.l_btn_cancel);
// register launchers
launcherReg();
// disable all edit texts
// call custom date-picker
disableET();
......@@ -122,6 +145,175 @@ public class UserProfileActivity extends AppCompatActivity {
}
}
});
// open image selector
ivProfilePicture.setOnClickListener(v -> {
Intent intent = new Intent(Intent.ACTION_PICK, android.provider.MediaStore.Images.Media.EXTERNAL_CONTENT_URI);
pickImageLauncher.launch(intent);
});
}
private void launcherReg() {
// Register the ActivityResultLauncher for picking an image
pickImageLauncher = registerForActivityResult(
new ActivityResultContracts.StartActivityForResult(),
result -> {
if (result.getResultCode() == Activity.RESULT_OK) {
Intent data = result.getData();
if (data != null) {
Uri selectedImageUri = data.getData();
if (selectedImageUri != null) {
startCrop(selectedImageUri);
}
}
}
});
// Register the ActivityResultLauncher for cropping the image
cropImageLauncher = registerForActivityResult(
new ActivityResultContracts.StartActivityForResult(),
result -> {
if (result.getResultCode() == Activity.RESULT_OK) {
Intent data = result.getData();
if (data != null) {
Uri resultUri = UCrop.getOutput(data);
if (resultUri != null) {
// Upload the cropped image to the server
uploadImageToServer(resultUri);
}
}
} else if (result.getResultCode() == UCrop.RESULT_ERROR) {
final Throwable cropError = UCrop.getError(result.getData());
// Handle the error
Toast.makeText(this, "Something went wrong: "+cropError.getMessage(), Toast.LENGTH_SHORT).show();
}
});
}
private void uploadImageToServer(Uri imageUri) {
Bitmap originalBitmap = BitmapFactory.decodeFile(imageUri.getPath());
int maxWidth = 800; // Set your desired maximum width
int maxHeight = 800; // Set your desired maximum height
Bitmap resizedBitmap = Bitmap.createScaledBitmap(originalBitmap, maxWidth, maxHeight, true);
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
resizedBitmap.compress(Bitmap.CompressFormat.JPEG, 80, outputStream); // Adjust the quality (80 is just an example)
byte[] compressedData = outputStream.toByteArray();
// Create RequestBody for the compressed image
RequestBody requestFile = RequestBody.create(MediaType.parse("image/jpeg"), compressedData);
MultipartBody.Part body = MultipartBody.Part.createFormData("image", "image.jpg", requestFile);
Call<ResponseDto> call = userApiService.uploadUserImage(authDto.getUserId(), body);
call.enqueue(new Callback<ResponseDto>() {
@Override
public void onResponse(Call<ResponseDto> call, Response<ResponseDto> response) {
if (response.isSuccessful()) {
loadUserProfileImage();
loader(overlay, progressBar, false);
} else {
try {
Gson gson = new Gson();
assert response.errorBody() != null;
String errorBodyString = response.errorBody().string();
// Check if the error body is in JSON format
if (errorBodyString.startsWith("{")) {
ErrorDto errorDto = gson.fromJson(errorBodyString, ErrorDto.class);
Toast.makeText(UserProfileActivity.this, errorDto.getMessage(), Toast.LENGTH_LONG).show();
} else {
// If the error body is not in JSON format, display a generic error message
Log.e(TAG, errorBodyString);
Toast.makeText(UserProfileActivity.this, "An unexpected error occurred", Toast.LENGTH_LONG).show();
}
} catch (Exception e) {
Log.e(TAG, "else-error: "+e.getMessage());
Toast.makeText(UserProfileActivity.this, e.getMessage(), Toast.LENGTH_LONG).show();
}
loader(overlay, progressBar, false);
}
}
@Override
public void onFailure(Call<ResponseDto> call, Throwable t) {
Toast.makeText(UserProfileActivity.this, "Error to Failure", Toast.LENGTH_LONG).show();
loader(overlay, progressBar, false);
}
});
}
private void loadUserProfileImage() {
Call<ResponseBody> call = userApiService.getImage(authDto.getUserId());
call.enqueue(new Callback<ResponseBody>() {
@Override
public void onResponse(Call<ResponseBody> call, Response<ResponseBody> response) {
if (response.isSuccessful()) {
setByteArrayToImageView(response.body());
loader(overlay, progressBar, false);
} else {
try {
Gson gson = new Gson();
assert response.errorBody() != null;
String errorBodyString = response.errorBody().string();
// Check if the error body is in JSON format
if (errorBodyString.startsWith("{")) {
ErrorDto errorDto = gson.fromJson(errorBodyString, ErrorDto.class);
Toast.makeText(UserProfileActivity.this, errorDto.getMessage(), Toast.LENGTH_LONG).show();
} else {
// If the error body is not in JSON format, display a generic error message
Log.e(TAG, errorBodyString);
Toast.makeText(UserProfileActivity.this, "An unexpected error occurred", Toast.LENGTH_LONG).show();
}
} catch (Exception e) {
Log.e(TAG, "else-error: "+e.getMessage());
Toast.makeText(UserProfileActivity.this, e.getMessage(), Toast.LENGTH_LONG).show();
}
loader(overlay, progressBar, false);
}
}
@Override
public void onFailure(Call<ResponseBody> call, Throwable t) {
Toast.makeText(UserProfileActivity.this, "Error to Failure", Toast.LENGTH_LONG).show();
loader(overlay, progressBar, false);
}
});
}
private void setByteArrayToImageView(ResponseBody responseBody) {
if (responseBody != null) {
try {
InputStream inputStream = responseBody.byteStream();
Bitmap bitmap = BitmapFactory.decodeStream(inputStream);
ivProfilePicture.setImageBitmap(bitmap);
} catch (Exception e) {
Log.e(TAG, "Error decoding image byte array: " + e.getMessage());
Toast.makeText(UserProfileActivity.this, "Error decoding image byte array", Toast.LENGTH_LONG).show();
}
} else {
Log.e(TAG, "Response body is null");
Toast.makeText(UserProfileActivity.this, "Response body is null", Toast.LENGTH_LONG).show();
}
}
private void startCrop(@NonNull Uri uri) {
// Generate a unique destination file name based on current time
String destinationFileName = "CroppedImage_" + System.currentTimeMillis() + ".jpg";
UCrop uCrop = UCrop.of(uri, Uri.fromFile(new File(getCacheDir(), destinationFileName)));
uCrop.withAspectRatio(1, 1);
uCrop.withOptions(getUCropOptions());
cropImageLauncher.launch(uCrop.getIntent(this));
}
private UCrop.Options getUCropOptions() {
UCrop.Options options = new UCrop.Options();
options.setCircleDimmedLayer(true);
options.setShowCropFrame(false);
options.setShowCropGrid(false);
return options;
}
private void updateUser(UserDto user, AtomicBoolean enableEdit) {
......@@ -218,7 +410,7 @@ public class UserProfileActivity extends AppCompatActivity {
etLastName.setText(user.getLastName());
etType.setText(!Objects.equals(user.getRole(), "USER") ? user.getRole():"PARENT");
etEmail.setText(user.getEmail());
imVerifiedStatus.setImageDrawable(user.getStatus().equals("NEW")?getDrawable(R.drawable.ico_unverified) : getDrawable(R.drawable.ico_verified));
ivVerifiedStatus.setImageDrawable(user.getStatus().equals("NEW")?getDrawable(R.drawable.ico_unverified) : getDrawable(R.drawable.ico_verified));
etMobile.setText(user.getPhone());
if (user.getDob() != null) {
LocalDate localDate = Utils.getLocalDate(user.getDob());
......@@ -312,6 +504,7 @@ public class UserProfileActivity extends AppCompatActivity {
super.onStart();
// load data
loadData();
loadUserProfileImage();
}
@Override
......
package com.kaluwa.enterprises.babycare.activities.auth;
import static com.kaluwa.enterprises.babycare.config.TokenSaver.getToken;
import static com.kaluwa.enterprises.babycare.config.TokenSaver.setToken;
import static com.kaluwa.enterprises.babycare.utils.Utils.animationChanger;
import static com.kaluwa.enterprises.babycare.utils.Utils.emailAddressValidation;
......@@ -16,29 +15,19 @@ import android.widget.EditText;
import android.widget.TextView;
import android.widget.Toast;
import androidx.activity.EdgeToEdge;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.graphics.Insets;
import androidx.core.view.ViewCompat;
import androidx.core.view.WindowInsetsCompat;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.github.ybq.android.spinkit.SpinKitView;
import com.google.gson.Gson;
import com.kaluwa.enterprises.babycare.R;
import com.kaluwa.enterprises.babycare.activities.DashboardActivity;
import com.kaluwa.enterprises.babycare.activities.UserProfileActivity;
import com.kaluwa.enterprises.babycare.config.ApiConfig;
import com.kaluwa.enterprises.babycare.dto.AuthenticationDto;
import com.kaluwa.enterprises.babycare.dto.responseDto.AuthenticationDto;
import com.kaluwa.enterprises.babycare.dto.LoginRequest;
import com.kaluwa.enterprises.babycare.dto.UserDto;
import com.kaluwa.enterprises.babycare.error.ErrorDto;
import com.kaluwa.enterprises.babycare.service.AuthApiService;
import java.io.IOException;
import java.util.Objects;
import okhttp3.ResponseBody;
import retrofit2.Call;
import retrofit2.Callback;
import retrofit2.Response;
......
......@@ -11,7 +11,7 @@ import retrofit2.Retrofit;
import retrofit2.converter.gson.GsonConverterFactory;
public class ApiConfig {
private static final String BASE_URL = "http://192.168.1.6:8080/api/v1/baby-care/";
private static final String BASE_URL = "http://192.168.1.2:8080/api/v1/baby-care/";
private static ApiConfig instance;
private static Retrofit retrofitAuth = null;
private static Retrofit retrofitOther = null;
......
......@@ -5,7 +5,7 @@ import android.content.SharedPreferences;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.kaluwa.enterprises.babycare.dto.AuthenticationDto;
import com.kaluwa.enterprises.babycare.dto.responseDto.AuthenticationDto;
public class TokenSaver {
private final static String SHARED_PREF_NAME = "net.kaluwa.SHARED_PREF_NAME";
......
package com.kaluwa.enterprises.babycare.dto;
package com.kaluwa.enterprises.babycare.dto.responseDto;
import lombok.AllArgsConstructor;
import lombok.Data;
......
package com.kaluwa.enterprises.babycare.dto.responseDto;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@AllArgsConstructor
@NoArgsConstructor
public class ResponseDto {
private Long id;
private String message;
}
package com.kaluwa.enterprises.babycare.dto;
package com.kaluwa.enterprises.babycare.dto.responseDto;
import lombok.AllArgsConstructor;
import lombok.Data;
......
package com.kaluwa.enterprises.babycare.service;
import com.kaluwa.enterprises.babycare.dto.AuthenticationDto;
import com.kaluwa.enterprises.babycare.dto.responseDto.AuthenticationDto;
import com.kaluwa.enterprises.babycare.dto.LoginRequest;
import com.kaluwa.enterprises.babycare.dto.RegisterRequest;
import com.kaluwa.enterprises.babycare.dto.UserDto;
import retrofit2.Call;
import retrofit2.Response;
import retrofit2.http.Body;
import retrofit2.http.POST;
......
package com.kaluwa.enterprises.babycare.service;
import com.kaluwa.enterprises.babycare.dto.UserDto;
import com.kaluwa.enterprises.babycare.dto.responseDto.ResponseDto;
import okhttp3.MultipartBody;
import okhttp3.ResponseBody;
import retrofit2.Call;
import retrofit2.http.Body;
import retrofit2.http.GET;
import retrofit2.http.Multipart;
import retrofit2.http.PUT;
import retrofit2.http.Part;
import retrofit2.http.Path;
public interface UserApiService {
......@@ -16,4 +21,11 @@ public interface UserApiService {
@PUT("user/{userId}")
Call<UserDto> updateUserById(@Path("userId") Long userId, @Body UserDto user);
@Multipart
@PUT("user/image/{userId}")
Call<ResponseDto> uploadUserImage(@Path("userId") Long userId, @Part MultipartBody.Part image);
@GET("user/image/{userId}")
Call<ResponseBody> getImage(@Path("userId") Long userId);
}
......@@ -26,6 +26,7 @@ public class Utils {
private static final String TAG = "Utils";
public static final String DATE_FORMAT = "dd/M/yyyy";
public static final int PICK_IMAGE_REQUEST = 1;
public static void loader(View overlay, SpinKitView spinKitView, boolean status) {
if (status) {
......
......@@ -32,15 +32,17 @@
app:layout_constraintStart_toStartOf="@id/background"
app:layout_constraintTop_toTopOf="@+id/background">
<ImageView
android:id="@+id/forground_img"
<com.google.android.material.imageview.ShapeableImageView
android:id="@+id/up_iv_profile_image"
android:layout_width="160dp"
android:layout_height="160dp"
android:layout_centerHorizontal="true"
android:layout_marginTop="20dp"
android:layout_marginBottom="20dp"
android:contentDescription="foreground-image"
android:src="@drawable/icon_alt_user_pic_32" />
android:contentDescription="user-profile-image"
android:src="@drawable/icon_alt_user_pic_32"
app:shapeAppearanceOverlay="@style/RoundImage"
android:scaleType="centerCrop"/>
</RelativeLayout>
......
<?xml version="1.0" encoding="utf-8"?>
<resources>
<style name="RoundImage" parent="ShapeAppearance.MaterialComponents.SmallComponent">
<item name="cornerFamily">rounded</item>
<item name="cornerSize">50%</item>
</style>
</resources>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<resources>
<style name="RoundImage" parent="ShapeAppearance.MaterialComponents.SmallComponent">
<item name="cornerFamily">rounded</item>
<item name="cornerSize">50%</item>
</style>
</resources>
\ No newline at end of file
......@@ -4,32 +4,32 @@ junit = "4.13.2"
junitVersion = "1.1.5"
espressoCore = "3.5.1"
appcompat = "1.6.1"
material = "1.12.0"
constraintlayout = "2.1.4"
navigationFragment = "2.7.7"
navigationUi = "2.7.7"
activity = "1.9.0"
androidSpinkit = "1.4.0"
projectLombok = "1.18.32"
retrofit2 = "2.9.0"
retrofit2Convertor = "2.9.0"
retrofit2 = "2.11.0"
jacksonDatabind = "2.17.1"
materialVersion = "1.13.0-alpha02"
[libraries]
junit = { group = "junit", name = "junit", version.ref = "junit" }
ext-junit = { group = "androidx.test.ext", name = "junit", version.ref = "junitVersion" }
espresso-core = { group = "androidx.test.espresso", name = "espresso-core", version.ref = "espressoCore" }
appcompat = { group = "androidx.appcompat", name = "appcompat", version.ref = "appcompat" }
material = { group = "com.google.android.material", name = "material", version.ref = "material" }
material = { group = "com.google.android.material", name = "material", version.ref = "materialVersion" }
constraintlayout = { group = "androidx.constraintlayout", name = "constraintlayout", version.ref = "constraintlayout" }
navigation-fragment = { group = "androidx.navigation", name = "navigation-fragment", version.ref = "navigationFragment" }
navigation-ui = { group = "androidx.navigation", name = "navigation-ui", version.ref = "navigationUi" }
activity = { group = "androidx.activity", name = "activity", version.ref = "activity" }
android-spinkit = { group = "com.github.ybq", name = "Android-SpinKit", version.ref = "androidSpinkit" }
projectlombok = { group = "org.projectlombok", name = "lombok", version.ref = "projectLombok"}
retrofit2 = { group = "com.squareup.retrofit2", name = "retrofit", version.ref = "retrofit2" }
convertor-gson = { group = "com.squareup.retrofit2", name = "converter-gson", version.ref = "retrofit2Convertor" }
retrofit2 = { group = "com.squareup.retrofit2", name = "retrofit", version = "2.11.0" }
convertor-gson = { group = "com.squareup.retrofit2", name = "converter-gson", version.ref = "retrofit2" }
jackson-databind = { group = "com.fasterxml.jackson.core", name = "jackson-databind", version.ref = "jacksonDatabind" }
ucrop = { group = "com.github.yalantis", name = "ucrop", version = "2.2.9" }
[plugins]
androidApplication = { id = "com.android.application", version.ref = "agp" }
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment