From 254779a32421e42bd26a857fe99c7e3dc6778cf8 Mon Sep 17 00:00:00 2001 From: M M Arif Date: Sun, 7 Apr 2024 10:15:22 +0000 Subject: [PATCH] Update user avatar (#1349) Closes #552 Reviewed-on: https://codeberg.org/gitnex/GitNex/pulls/1349 Co-authored-by: M M Arif Co-committed-by: M M Arif --- app/build.gradle | 10 +- .../mian/gitnex/activities/MainActivity.java | 9 +- .../gitnex/adapters/AttachmentsAdapter.java | 2 +- .../gitnex/adapters/ExploreIssuesAdapter.java | 3 - .../fragments/profile/DetailFragment.java | 131 ++++++++++++++++-- .../java/org/mian/gitnex/helpers/AppUtil.java | 11 ++ .../res/layout/custom_edit_avatar_dialog.xml | 59 ++++++++ .../res/layout/fragment_profile_detail.xml | 36 +++-- app/src/main/res/values/strings.xml | 1 + app/src/main/res/xml/changelog.xml | 21 +-- 10 files changed, 233 insertions(+), 50 deletions(-) create mode 100644 app/src/main/res/layout/custom_edit_avatar_dialog.xml diff --git a/app/build.gradle b/app/build.gradle index 704d6a05..300f192f 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -8,8 +8,8 @@ android { applicationId "org.mian.gitnex" minSdkVersion 23 targetSdkVersion 34 - versionCode 540 - versionName "5.4.0" + versionCode 545 + versionName "5.5.0-dev" multiDexEnabled true testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" compileSdk 34 @@ -71,9 +71,9 @@ dependencies { implementation 'com.google.code.gson:gson:2.10.1' implementation "com.squareup.picasso:picasso:2.71828" implementation 'com.github.ramseth001:TextDrawable:1.1.3' - implementation 'com.squareup.retrofit2:retrofit:2.9.0' - implementation 'com.squareup.retrofit2:converter-gson:2.9.0' - implementation 'com.squareup.retrofit2:converter-scalars:2.9.0' + implementation 'com.squareup.retrofit2:retrofit:2.11.0' + implementation 'com.squareup.retrofit2:converter-gson:2.11.0' + implementation 'com.squareup.retrofit2:converter-scalars:2.11.0' implementation 'com.squareup.okhttp3:logging-interceptor:5.0.0-alpha.12' implementation 'org.ocpsoft.prettytime:prettytime:5.0.7.Final' implementation "com.github.skydoves:colorpickerview:2.3.0" diff --git a/app/src/main/java/org/mian/gitnex/activities/MainActivity.java b/app/src/main/java/org/mian/gitnex/activities/MainActivity.java index 1af81d1d..cf118988 100644 --- a/app/src/main/java/org/mian/gitnex/activities/MainActivity.java +++ b/app/src/main/java/org/mian/gitnex/activities/MainActivity.java @@ -54,6 +54,7 @@ import org.mian.gitnex.fragments.RepositoriesFragment; import org.mian.gitnex.fragments.SettingsFragment; import org.mian.gitnex.fragments.StarredRepositoriesFragment; import org.mian.gitnex.fragments.WatchedRepositoriesFragment; +import org.mian.gitnex.fragments.profile.DetailFragment; import org.mian.gitnex.helpers.AlertDialogs; import org.mian.gitnex.helpers.AppDatabaseSettings; import org.mian.gitnex.helpers.AppUtil; @@ -298,10 +299,6 @@ public class MainActivity extends BaseActivity .setVisible(false); } - if (getAccount().requiresVersion("1.14.0")) { - navigationView.getMenu().findItem(R.id.nav_my_issues).setVisible(true); - } - if (getAccount().requiresVersion("1.20.0")) { navigationView.getMenu().findItem(R.id.nav_dashboard).setVisible(true); } @@ -586,6 +583,10 @@ public class MainActivity extends BaseActivity this.overridePendingTransition(0, 0); refActivity = false; } + if (DetailFragment.refProfile) { + loadUserInfo(); + DetailFragment.refProfile = false; + } } public void setActionBarTitle(String title) { diff --git a/app/src/main/java/org/mian/gitnex/adapters/AttachmentsAdapter.java b/app/src/main/java/org/mian/gitnex/adapters/AttachmentsAdapter.java index 47cd526d..fb4b9315 100644 --- a/app/src/main/java/org/mian/gitnex/adapters/AttachmentsAdapter.java +++ b/app/src/main/java/org/mian/gitnex/adapters/AttachmentsAdapter.java @@ -71,7 +71,7 @@ public class AttachmentsAdapter extends RecyclerView.Adapter { private final Context context; - private final TinyDB tinyDb; private List searchedList; private OnLoadMoreListener loadMoreListener; private boolean isLoading = false, isMoreDataAvailable = true; @@ -52,7 +50,6 @@ public class ExploreIssuesAdapter extends RecyclerView.Adapter dataList, Context ctx) { this.context = ctx; this.searchedList = dataList; - this.tinyDb = TinyDB.getInstance(context); } @NonNull @Override diff --git a/app/src/main/java/org/mian/gitnex/fragments/profile/DetailFragment.java b/app/src/main/java/org/mian/gitnex/fragments/profile/DetailFragment.java index e9d0c7b3..2514bfbb 100644 --- a/app/src/main/java/org/mian/gitnex/fragments/profile/DetailFragment.java +++ b/app/src/main/java/org/mian/gitnex/fragments/profile/DetailFragment.java @@ -1,18 +1,28 @@ package org.mian.gitnex.fragments.profile; +import android.app.Activity; import android.content.Context; +import android.content.Intent; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.net.Uri; import android.os.Bundle; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; +import androidx.activity.result.ActivityResultLauncher; +import androidx.activity.result.contract.ActivityResultContracts; import androidx.annotation.NonNull; import androidx.appcompat.app.AlertDialog; import androidx.fragment.app.Fragment; import com.google.android.material.dialog.MaterialAlertDialogBuilder; +import java.io.FileNotFoundException; import java.io.IOException; +import java.io.InputStream; import java.util.Locale; import okhttp3.ResponseBody; import org.gitnex.tea4j.v2.models.Repository; +import org.gitnex.tea4j.v2.models.UpdateUserAvatarOption; import org.gitnex.tea4j.v2.models.User; import org.gitnex.tea4j.v2.models.UserSettings; import org.gitnex.tea4j.v2.models.UserSettingsOptions; @@ -21,6 +31,7 @@ import org.mian.gitnex.activities.BaseActivity; import org.mian.gitnex.activities.ProfileActivity; import org.mian.gitnex.clients.PicassoService; import org.mian.gitnex.clients.RetrofitClient; +import org.mian.gitnex.databinding.CustomEditAvatarDialogBinding; import org.mian.gitnex.databinding.CustomEditProfileBinding; import org.mian.gitnex.databinding.FragmentProfileDetailBinding; import org.mian.gitnex.helpers.AlertDialogs; @@ -29,7 +40,6 @@ import org.mian.gitnex.helpers.ClickListener; import org.mian.gitnex.helpers.Markdown; import org.mian.gitnex.helpers.RoundedTransformation; import org.mian.gitnex.helpers.TimeHelper; -import org.mian.gitnex.helpers.TinyDB; import org.mian.gitnex.helpers.Toasty; import retrofit2.Call; import retrofit2.Callback; @@ -41,13 +51,17 @@ public class DetailFragment extends Fragment { private static final String usernameBundle = ""; Locale locale; - TinyDB tinyDb; private Context context; private FragmentProfileDetailBinding binding; private String username; private CustomEditProfileBinding customEditProfileBinding; + private CustomEditAvatarDialogBinding customEditAvatarDialogBinding; private MaterialAlertDialogBuilder materialAlertDialogBuilder; private AlertDialog dialogEditSettings; + private AlertDialog dialogEditAvatar; + private int imgRadius; + private static Uri avatarUri = null; + public static boolean refProfile = false; public DetailFragment() {} @@ -73,8 +87,8 @@ public class DetailFragment extends Fragment { binding = FragmentProfileDetailBinding.inflate(inflater, container, false); context = getContext(); - tinyDb = TinyDB.getInstance(context); locale = getResources().getConfiguration().locale; + imgRadius = AppUtil.getPixelsFromDensity(context, 3); getProfileDetail(username); getProfileRepository(username); @@ -94,21 +108,121 @@ public class DetailFragment extends Fragment { ((ProfileActivity) requireActivity()).viewPager.setCurrentItem(2)); if (username.equals(((BaseActivity) context).getAccount().getAccount().getUserName())) { - binding.editProfile.setVisibility(View.VISIBLE); + binding.metaProfile.setVisibility(View.VISIBLE); } else { - binding.editProfile.setVisibility(View.GONE); + binding.metaProfile.setVisibility(View.GONE); } - binding.editProfile.setOnClickListener( + binding.updateProfile.setOnClickListener( editProfileSettings -> { customEditProfileBinding = CustomEditProfileBinding.inflate(LayoutInflater.from(context)); showEditProfileDialog(); }); + binding.updateAvatar.setOnClickListener(updateAvatar -> openFileAttachment()); + return binding.getRoot(); } + public void onDestroy() { + avatarUri = null; + super.onDestroy(); + } + + ActivityResultLauncher activityForAvatarUpdate = + registerForActivityResult( + new ActivityResultContracts.StartActivityForResult(), + result -> { + if (result.getResultCode() == Activity.RESULT_OK) { + Intent data = result.getData(); + assert data != null; + avatarUri = data.getData(); + if (avatarUri != null) { + customEditAvatarDialogBinding = + CustomEditAvatarDialogBinding.inflate( + LayoutInflater.from(context)); + showUpdateAvatarDialog(); + } + } + }); + + private void openFileAttachment() { + + String[] mimeTypes = {"image/webp", "image/gif", "image/jpg", "image/jpeg", "image/png"}; + Intent data = new Intent(Intent.ACTION_GET_CONTENT); + data.addCategory(Intent.CATEGORY_OPENABLE); + data.setType("image/*"); + data.putExtra(Intent.EXTRA_MIME_TYPES, mimeTypes); + Intent intent = Intent.createChooser(data, "Choose an image"); + activityForAvatarUpdate.launch(intent); + } + + private void showUpdateAvatarDialog() { + + View view = customEditAvatarDialogBinding.getRoot(); + materialAlertDialogBuilder.setView(view); + + PicassoService.getInstance(context) + .get() + .load(avatarUri) + .transform(new RoundedTransformation(imgRadius, 0)) + .placeholder(R.drawable.loader_animated) + .resize(180, 180) + .centerCrop() + .into(customEditAvatarDialogBinding.userAvatar); + + customEditAvatarDialogBinding.save.setOnClickListener( + saveUserAvatar -> saveUserAvatar(avatarUri)); + + dialogEditAvatar = materialAlertDialogBuilder.show(); + } + + private void saveUserAvatar(Uri avatar) { + + InputStream imageStream = null; + try { + imageStream = context.getContentResolver().openInputStream(avatar); + } catch (FileNotFoundException e) { + e.getMessage(); + } + Bitmap selectedImage = BitmapFactory.decodeStream(imageStream); + + String encodedString = AppUtil.imageEncodeToBase64(selectedImage); + + UpdateUserAvatarOption updateUserAvatarOption = new UpdateUserAvatarOption(); + updateUserAvatarOption.setImage(encodedString); + + Call saveUserAvatar = + RetrofitClient.getApiInterface(context).userUpdateAvatar(updateUserAvatarOption); + + saveUserAvatar.enqueue( + new Callback<>() { + + @Override + public void onResponse( + @NonNull Call call, @NonNull retrofit2.Response response) { + + if (response.code() == 204) { + + dialogEditAvatar.dismiss(); + getProfileDetail(username); + Toasty.success(context, getString(R.string.profileUpdate)); + refProfile = true; + } else { + + Toasty.error(context, getString(R.string.genericError)); + } + } + + @Override + public void onFailure(@NonNull Call call, @NonNull Throwable t) { + + Toasty.error(context, getString(R.string.genericServerResponseError)); + } + }); + } + private void showEditProfileDialog() { View view = customEditProfileBinding.getRoot(); @@ -160,7 +274,8 @@ public class DetailFragment extends Fragment { dialogEditSettings.dismiss(); getProfileDetail(username); - Toasty.success(context, getString(R.string.settingsSave)); + Toasty.success(context, getString(R.string.profileUpdate)); + refProfile = true; } else { Toasty.error(context, getString(R.string.genericError)); @@ -246,8 +361,6 @@ public class DetailFragment extends Fragment { ? response.body().getEmail() : ""; - int imgRadius = AppUtil.getPixelsFromDensity(context, 3); - binding.userFullName.setText(username); binding.userLogin.setText( getString( diff --git a/app/src/main/java/org/mian/gitnex/helpers/AppUtil.java b/app/src/main/java/org/mian/gitnex/helpers/AppUtil.java index 4bae20cf..8d3f10b2 100644 --- a/app/src/main/java/org/mian/gitnex/helpers/AppUtil.java +++ b/app/src/main/java/org/mian/gitnex/helpers/AppUtil.java @@ -12,6 +12,7 @@ import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; import android.content.res.Configuration; import android.content.res.Resources; +import android.graphics.Bitmap; import android.graphics.Typeface; import android.net.Uri; import android.os.Build; @@ -24,6 +25,7 @@ import androidx.annotation.ColorInt; import androidx.browser.customtabs.CustomTabColorSchemeParams; import androidx.browser.customtabs.CustomTabsIntent; import androidx.core.content.pm.PackageInfoCompat; +import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; @@ -413,6 +415,15 @@ public class AppUtil { return base64Str; } + public static String imageEncodeToBase64(Bitmap image) { + + ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); + image.compress(Bitmap.CompressFormat.PNG, 100, byteArrayOutputStream); + byte[] bytes = byteArrayOutputStream.toByteArray(); + + return Base64.encodeToString(bytes, Base64.DEFAULT); + } + public static String decodeBase64(String str) { String base64Str = str; diff --git a/app/src/main/res/layout/custom_edit_avatar_dialog.xml b/app/src/main/res/layout/custom_edit_avatar_dialog.xml new file mode 100644 index 00000000..2e921c26 --- /dev/null +++ b/app/src/main/res/layout/custom_edit_avatar_dialog.xml @@ -0,0 +1,59 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/fragment_profile_detail.xml b/app/src/main/res/layout/fragment_profile_detail.xml index 784a4a12..ad3ed811 100644 --- a/app/src/main/res/layout/fragment_profile_detail.xml +++ b/app/src/main/res/layout/fragment_profile_detail.xml @@ -91,17 +91,35 @@ android:textIsSelectable="true" android:textSize="@dimen/dimen14sp" /> - + android:visibility="gone"> + +