Update user avatar (#1349) Some checks failed ci/woodpecker/push/build Pipeline failed ci/woodpecker/push/locale Pipeline was successful ci/woodpecker/push/check Pipeline failed ci/woodpecker/push/finish unknown status ci/woodpecker/manual/locale Pipeline was successful ci/woodpecker/manual/build Pipeline failed ci/woodpecker/manual/check Pipeline failed ci/woodpecker/cron/build Pipeline failed ci/woodpecker/cron/locale Pipeline was successful ci/woodpecker/cron/check Pipeline failed
Some checks failed
ci/woodpecker/push/build Pipeline failed
ci/woodpecker/push/locale Pipeline was successful
ci/woodpecker/push/check Pipeline failed
ci/woodpecker/push/finish unknown status
ci/woodpecker/manual/locale Pipeline was successful
ci/woodpecker/manual/build Pipeline failed
ci/woodpecker/manual/check Pipeline failed
ci/woodpecker/cron/build Pipeline failed
ci/woodpecker/cron/locale Pipeline was successful
ci/woodpecker/cron/check Pipeline failed
Closes #552 Reviewed-on: #1349 Co-authored-by: M M Arif <mmarif@swatian.com> Co-committed-by: M M Arif <mmarif@swatian.com>
This commit is contained in:
parent 7c435e4f34
commit 254779a324
10 changed files with 233 additions and 50 deletions
| @ -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" | ||||
| |
| @ -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) { | ||||
| |
| @ -71,7 +71,7 @@ public class AttachmentsAdapter extends RecyclerView.Adapter<AttachmentsAdapter. | |||
} | ||||
} | ||||
| ||||
class ViewHolder extends RecyclerView.ViewHolder { | ||||
public class ViewHolder extends RecyclerView.ViewHolder { | ||||
| ||||
public TextView filename; | ||||
public ImageView delete; | ||||
| |
| @ -34,7 +34,6 @@ import org.mian.gitnex.helpers.ColorInverter; | |||
import org.mian.gitnex.helpers.LabelWidthCalculator; | ||||
import org.mian.gitnex.helpers.RoundedTransformation; | ||||
import org.mian.gitnex.helpers.TimeHelper; | ||||
import org.mian.gitnex.helpers.TinyDB; | ||||
import org.mian.gitnex.helpers.contexts.IssueContext; | ||||
import org.mian.gitnex.helpers.contexts.RepositoryContext; | ||||
| ||||
| @ -44,7 +43,6 @@ import org.mian.gitnex.helpers.contexts.RepositoryContext; | |||
public class ExploreIssuesAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> { | ||||
| ||||
private final Context context; | ||||
private final TinyDB tinyDb; | ||||
private List<Issue> searchedList; | ||||
private OnLoadMoreListener loadMoreListener; | ||||
private boolean isLoading = false, isMoreDataAvailable = true; | ||||
| @ -52,7 +50,6 @@ public class ExploreIssuesAdapter extends RecyclerView.Adapter<RecyclerView.View | |||
public ExploreIssuesAdapter(List<Issue> dataList, Context ctx) { | ||||
this.context = ctx; | ||||
this.searchedList = dataList; | ||||
this.tinyDb = TinyDB.getInstance(context); | ||||
} | ||||
| ||||
@NonNull @Override | ||||
| |
| @ -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<Intent> 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<Void> saveUserAvatar = | ||||
RetrofitClient.getApiInterface(context).userUpdateAvatar(updateUserAvatarOption); | ||||
| ||||
saveUserAvatar.enqueue( | ||||
new Callback<>() { | ||||
| ||||
@Override | ||||
public void onResponse( | ||||
@NonNull Call<Void> call, @NonNull retrofit2.Response<Void> 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<Void> 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( | ||||
| |
| @ -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; | ||||
| |
59 app/src/main/res/layout/custom_edit_avatar_dialog.xml Normal file
59
app/src/main/res/layout/custom_edit_avatar_dialog.xml Normal file | @ -0,0 +1,59 @@ | |||
<?xml version="1.0" encoding="utf-8"?> | ||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" | ||||
xmlns:app="http://schemas.android.com/apk/res-auto" | ||||
android:layout_width="match_parent" | ||||
android:layout_height="wrap_content" | ||||
android:orientation="vertical" | ||||
android:padding="@dimen/dimen8dp"> | ||||
| ||||
<androidx.core.widget.NestedScrollView | ||||
android:id="@+id/mainView" | ||||
android:layout_width="match_parent" | ||||
android:layout_height="wrap_content"> | ||||
| ||||
<LinearLayout | ||||
android:layout_width="match_parent" | ||||
android:layout_height="wrap_content" | ||||
android:layout_margin="@dimen/dimen16dp" | ||||
android:orientation="vertical"> | ||||
| ||||
<LinearLayout | ||||
android:layout_width="match_parent" | ||||
android:layout_height="wrap_content" | ||||
android:gravity="center" | ||||
android:orientation="vertical"> | ||||
| ||||
<com.google.android.material.card.MaterialCardView | ||||
android:layout_width="@dimen/dimen140dp" | ||||
android:layout_height="@dimen/dimen140dp" | ||||
style="?attr/materialCardViewFilledStyle" | ||||
android:layout_marginBottom="@dimen/dimen8dp" | ||||
app:cardElevation="@dimen/dimen0dp" | ||||
app:cardCornerRadius="@dimen/dimen48dp"> | ||||
| ||||
<ImageView | ||||
android:id="@+id/userAvatar" | ||||
android:layout_width="match_parent" | ||||
android:layout_height="match_parent" | ||||
android:contentDescription="@string/generalImgContentText" | ||||
android:src="@mipmap/app_logo_round" /> | ||||
| ||||
</com.google.android.material.card.MaterialCardView> | ||||
| ||||
</LinearLayout> | ||||
| ||||
<com.google.android.material.button.MaterialButton | ||||
android:id="@+id/save" | ||||
android:layout_width="match_parent" | ||||
android:layout_height="@dimen/dimen54dp" | ||||
android:layout_marginTop="@dimen/dimen16dp" | ||||
android:text="@string/saveButton" | ||||
android:textColor="?attr/materialCardBackgroundColor" | ||||
android:textSize="16sp" | ||||
android:textStyle="bold" /> | ||||
| ||||
</LinearLayout> | ||||
| ||||
</androidx.core.widget.NestedScrollView> | ||||
| ||||
</LinearLayout> |
| @ -91,17 +91,35 @@ | |||
android:textIsSelectable="true" | ||||
android:textSize="@dimen/dimen14sp" /> | ||||
| ||||
<com.google.android.material.button.MaterialButton | ||||
android:id="@+id/editProfile" | ||||
<com.google.android.material.button.MaterialButtonToggleGroup | ||||
android:id="@+id/metaProfile" | ||||
android:layout_width="wrap_content" | ||||
android:layout_height="wrap_content" | ||||
android:text="@string/editSettings" | ||||
android:textColor="?attr/materialCardBackgroundColor" | ||||
android:backgroundTint="?attr/fabColor" | ||||
app:iconTint="?attr/materialCardBackgroundColor" | ||||
app:icon="@drawable/ic_edit" | ||||
android:textSize="14sp" | ||||
android:visibility="gone" /> | ||||
android:visibility="gone"> | ||||
| ||||
<Button | ||||
android:id="@+id/update_avatar" | ||||
style="?attr/materialButtonToggleGroupStyle" | ||||
android:layout_width="wrap_content" | ||||
android:layout_height="wrap_content" | ||||
app:iconTint="?attr/materialCardBackgroundColor" | ||||
app:icon="@drawable/ic_person" | ||||
android:textColor="?attr/materialCardBackgroundColor" | ||||
android:textSize="@dimen/dimen14sp" | ||||
android:text="@string/userAvatar" /> | ||||
| ||||
<Button | ||||
android:id="@+id/update_profile" | ||||
style="?attr/materialButtonToggleGroupStyle" | ||||
android:layout_width="wrap_content" | ||||
android:layout_height="wrap_content" | ||||
app:iconTint="?attr/materialCardBackgroundColor" | ||||
app:icon="@drawable/ic_edit" | ||||
android:textColor="?attr/materialCardBackgroundColor" | ||||
android:textSize="@dimen/dimen14sp" | ||||
android:text="@string/navProfile" /> | ||||
| ||||
</com.google.android.material.button.MaterialButtonToggleGroup> | ||||
| ||||
</LinearLayout> | ||||
| ||||
| |
| @ -360,6 +360,7 @@ | |||
<string name="editSettings">Edit Profile</string> | ||||
<string name="hideActivity">Hide Activity from profile page</string> | ||||
<string name="hideEmail">Hide Email</string> | ||||
<string name="profileUpdate">Profile updated</string> | ||||
<!-- profile section --> | ||||
| ||||
<!-- account settings --> | ||||
| |
| @ -1,25 +1,8 @@ | |||
<?xml version="1.0" encoding="utf-8"?> | ||||
<changelog> | ||||
| ||||
<release version="5.4.0" versioncode="540"> | ||||
<change>New: Restore/import app data</change> | ||||
<change>New: Backup/export app data</change> | ||||
<change>New: Delete email from account</change> | ||||
<change>New: Update profile settings</change> | ||||
<change>New: Add SSH key to account</change> | ||||
<change>Improvement: Open specific URL in deep links</change> | ||||
<change>Improvement: Show open in browser with other buttons in deep links</change> | ||||
<change>Improvement: Show pinned messages in timeline</change> | ||||
<change>Improvement: Hide Comment button if issue is locked</change> | ||||
<change>Improvement: New languages and translation updates</change> | ||||
<change>Bugfix: Fix crash on dynamic snackbar colors for older Android versions</change> | ||||
<change>Bugfix: Fix missing text after links in issue/pr</change> | ||||
<change>Bugfix: Fix emoji parsing in code blocks in issue/pr</change> | ||||
<change>Bugfix: Fix crash on null objects in timeline from API</change> | ||||
<change>Bugfix: Fix not seeing labels for repo admin when code option is disabled</change> | ||||
<change>Bugfix: Fix Task list in Markdown</change> | ||||
<change>Bugfix: Fix other issues related to Markdown</change> | ||||
<change>Bugfix: Fix crash on images with no path</change> | ||||
<release version="5.5.0-dev" versioncode="545"> | ||||
<change>Under development</change> | ||||
</release> | ||||
| ||||
</changelog> | ||||
| |
Loading…
Add table
Add a link
Reference in a new issue