Bug fixes and improvements (#1360) All checks were successful ci/woodpecker/push/locale Pipeline was successful ci/woodpecker/push/build Pipeline was successful ci/woodpecker/push/check Pipeline was successful ci/woodpecker/push/finish Pipeline was successful ci/woodpecker/cron/locale Pipeline was successful ci/woodpecker/cron/build Pipeline was successful ci/woodpecker/cron/check Pipeline was successful
All checks were successful
ci/woodpecker/push/locale Pipeline was successful
ci/woodpecker/push/build Pipeline was successful
ci/woodpecker/push/check Pipeline was successful
ci/woodpecker/push/finish Pipeline was successful
ci/woodpecker/cron/locale Pipeline was successful
ci/woodpecker/cron/build Pipeline was successful
ci/woodpecker/cron/check Pipeline was successful
Fixes #1361 Fixes #1363 Closes #1376 Fixes #1374 Fixes #1375 Closes #1371 Closes #1369 Closes #1367 Reviewed-on: #1360
This commit is contained in:
parent 51a4d1ec85
commit 13d68b9408
55 changed files with 602 additions and 180 deletions
1 .gitignore vendored
1
.gitignore vendored | @ -10,6 +10,7 @@ | |||
| ||||
# Release dir | ||||
app/release/* | ||||
app/free/* | ||||
| ||||
# Pro dir | ||||
app/pro/* | ||||
| |
| @ -1,6 +1,6 @@ | |||
[](https://www.gnu.org/licenses/gpl-3.0) [](https://ci.codeberg.org/gitnex/GitNex) [](https://codeberg.org/gitnex/GitNex/releases) [](https://crowdin.com/project/gitnex) [](https://discord.gg/FbSS4rf) | ||||
| ||||
[<img alt="Become a Patreon" src="https://codeberg.org/gitnex/GitNex/raw/branch/main/assets/patreon.png" height="40"/>](https://www.patreon.com/mmarif) [<img alt="Buy me a coffee" src="https://codeberg.org/gitnex/GitNex/raw/branch/main/assets/buy-me-a-coffee.png" height="40"/>](https://www.buymeacoffee.com/mmarif) | ||||
[<img alt="Become a Patreon" src="https://codeberg.org/gitnex/GitNex/raw/branch/main/assets/patreon.png" height="40"/>](https://www.patreon.com/mmarif) | ||||
| ||||
# GitNex - Android client for Forgejo and Gitea | ||||
| ||||
| |
| @ -1,5 +1,5 @@ | |||
plugins { | ||||
id "com.diffplug.spotless" version "6.11.0" | ||||
id "com.diffplug.spotless" version "6.25.0" | ||||
} | ||||
apply plugin: 'com.android.application' | ||||
| ||||
| @ -8,8 +8,8 @@ android { | |||
applicationId "org.mian.gitnex" | ||||
minSdkVersion 23 | ||||
targetSdkVersion 34 | ||||
versionCode 550 | ||||
versionName "5.5.0" | ||||
versionCode 595 | ||||
versionName "6.0.0-dev" | ||||
multiDexEnabled true | ||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" | ||||
compileSdk 34 | ||||
| @ -56,25 +56,25 @@ configurations { | |||
dependencies { | ||||
| ||||
implementation fileTree(include: ['*.jar'], dir: 'libs') | ||||
implementation 'androidx.appcompat:appcompat:1.6.1' | ||||
implementation 'androidx.appcompat:appcompat:1.7.0' | ||||
implementation 'com.google.android.material:material:1.12.0' | ||||
implementation 'androidx.compose.material3:material3:1.2.1' | ||||
implementation 'androidx.compose.material3:material3-window-size-class:1.2.1' | ||||
implementation 'androidx.compose.material3:material3:1.3.0' | ||||
implementation 'androidx.compose.material3:material3-window-size-class:1.3.0' | ||||
implementation 'androidx.viewpager2:viewpager2:1.1.0' | ||||
implementation 'androidx.constraintlayout:constraintlayout:2.1.4' | ||||
implementation "androidx.legacy:legacy-support-v4:1.0.0" | ||||
implementation "androidx.lifecycle:lifecycle-viewmodel:2.7.0" | ||||
testImplementation 'junit:junit:4.13.2' | ||||
androidTestImplementation 'androidx.test.ext:junit:1.1.5' | ||||
androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1' | ||||
implementation 'com.squareup.okhttp3:okhttp:5.0.0-alpha.12' | ||||
implementation 'com.google.code.gson:gson:2.10.1' | ||||
androidTestImplementation 'androidx.test.ext:junit:1.2.1' | ||||
androidTestImplementation 'androidx.test.espresso:espresso-core:3.6.1' | ||||
implementation 'com.squareup.okhttp3:okhttp:5.0.0-alpha.14' | ||||
implementation 'com.google.code.gson:gson:2.11.0' | ||||
implementation "com.squareup.picasso:picasso:2.71828" | ||||
implementation 'com.github.ramseth001:TextDrawable:1.1.3' | ||||
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 'com.squareup.okhttp3:logging-interceptor:5.0.0-alpha.14' | ||||
implementation 'org.ocpsoft.prettytime:prettytime:5.0.7.Final' | ||||
implementation "com.github.skydoves:colorpickerview:2.3.0" | ||||
implementation "io.noties.markwon:core:4.6.2" | ||||
| @ -89,8 +89,7 @@ dependencies { | |||
implementation "io.noties.markwon:recycler:4.6.2" | ||||
implementation "io.noties.markwon:recycler-table:4.6.2" | ||||
implementation "io.noties.markwon:simple-ext:4.6.2" | ||||
implementation 'com.google.guava:guava:32.1.2-jre' | ||||
implementation "io.noties.markwon:image-picasso:4.6.2" | ||||
implementation 'com.google.guava:guava:32.1.3-jre' | ||||
implementation "com.github.HamidrezaAmz:BreadcrumbsView:0.2.9" | ||||
//noinspection GradleDependency | ||||
implementation 'commons-io:commons-io:2.5' | ||||
| @ -101,7 +100,7 @@ dependencies { | |||
implementation 'ch.acra:acra-notification:5.11.3' | ||||
implementation 'androidx.room:room-runtime:2.6.1' | ||||
annotationProcessor 'androidx.room:room-compiler:2.6.1' | ||||
implementation "androidx.work:work-runtime:2.9.0" | ||||
implementation "androidx.work:work-runtime:2.9.1" | ||||
implementation "io.mikael:urlbuilder:2.0.9" | ||||
implementation "org.codeberg.gitnex-garage:emoji-java:v5.1.2" | ||||
//noinspection GradleDependency | ||||
| |
| @ -4,6 +4,7 @@ import android.content.Context; | |||
import android.content.Intent; | ||||
import android.os.Bundle; | ||||
import androidx.appcompat.app.AppCompatActivity; | ||||
import androidx.biometric.BiometricManager; | ||||
import java.util.Locale; | ||||
import org.mian.gitnex.R; | ||||
import org.mian.gitnex.core.MainApplication; | ||||
| @ -118,15 +119,20 @@ public abstract class BaseActivity extends AppCompatActivity { | |||
public void onResume() { | ||||
super.onResume(); | ||||
| ||||
if (Boolean.parseBoolean( | ||||
AppDatabaseSettings.getSettingsValue( | ||||
ctx, AppDatabaseSettings.APP_BIOMETRIC_KEY)) | ||||
&& !Boolean.parseBoolean( | ||||
AppDatabaseSettings.getSettingsValue( | ||||
ctx, AppDatabaseSettings.APP_BIOMETRIC_LIFE_CYCLE_KEY))) { | ||||
if (BiometricManager.from(ctx) | ||||
.canAuthenticate(BiometricManager.Authenticators.BIOMETRIC_WEAK) | ||||
== BiometricManager.BIOMETRIC_SUCCESS) { | ||||
| ||||
Intent unlockIntent = new Intent(ctx, BiometricUnlock.class); | ||||
ctx.startActivity(unlockIntent); | ||||
if (Boolean.parseBoolean( | ||||
AppDatabaseSettings.getSettingsValue( | ||||
ctx, AppDatabaseSettings.APP_BIOMETRIC_KEY)) | ||||
&& !Boolean.parseBoolean( | ||||
AppDatabaseSettings.getSettingsValue( | ||||
ctx, AppDatabaseSettings.APP_BIOMETRIC_LIFE_CYCLE_KEY))) { | ||||
| ||||
Intent unlockIntent = new Intent(ctx, BiometricUnlock.class); | ||||
ctx.startActivity(unlockIntent); | ||||
} | ||||
} | ||||
} | ||||
| ||||
| |
| @ -45,7 +45,7 @@ public class BiometricUnlock extends AppCompatActivity { | |||
| ||||
super.onAuthenticationError(errorCode, errString); | ||||
| ||||
// Authentication error, close the app | ||||
MainActivity.closeActivity = true; | ||||
finish(); | ||||
} | ||||
| ||||
| |
| @ -507,6 +507,8 @@ public class CreateIssueActivity extends BaseActivity | |||
String newIssueTitleForm, | ||||
String newIssueDueDateForm) { | ||||
| ||||
viewBinding.topAppBar.getMenu().getItem(2).setVisible(false); | ||||
| ||||
ArrayList<Long> labelIds = new ArrayList<>(); | ||||
for (Integer i : labelsIds) { | ||||
labelIds.add((long) i); | ||||
| @ -562,9 +564,11 @@ public class CreateIssueActivity extends BaseActivity | |||
| ||||
} else if (response2.code() == 401) { | ||||
| ||||
viewBinding.topAppBar.getMenu().getItem(2).setVisible(true); | ||||
AlertDialogs.authorizationTokenRevokedDialog(ctx); | ||||
} else { | ||||
| ||||
viewBinding.topAppBar.getMenu().getItem(2).setVisible(true); | ||||
SnackBar.error( | ||||
ctx, | ||||
findViewById(android.R.id.content), | ||||
| @ -575,6 +579,7 @@ public class CreateIssueActivity extends BaseActivity | |||
@Override | ||||
public void onFailure(@NonNull Call<Issue> call, @NonNull Throwable t) { | ||||
| ||||
viewBinding.topAppBar.getMenu().getItem(2).setVisible(true); | ||||
SnackBar.error( | ||||
ctx, | ||||
findViewById(android.R.id.content), | ||||
| |
| @ -414,6 +414,8 @@ public class CreatePullRequestActivity extends BaseActivity | |||
List<String> assignees, | ||||
String prDueDate) { | ||||
| ||||
viewBinding.topAppBar.getMenu().getItem(2).setVisible(false); | ||||
| ||||
ArrayList<Long> labelIds = new ArrayList<>(); | ||||
for (Integer i : labelsIds) { | ||||
labelIds.add((long) i); | ||||
| @ -470,18 +472,21 @@ public class CreatePullRequestActivity extends BaseActivity | |||
} else if (response.code() == 409 | ||||
|| response.message().equals("Conflict")) { | ||||
| ||||
viewBinding.topAppBar.getMenu().getItem(2).setVisible(false); | ||||
SnackBar.error( | ||||
ctx, | ||||
findViewById(android.R.id.content), | ||||
getString(R.string.prAlreadyExists)); | ||||
} else if (response.code() == 404) { | ||||
| ||||
viewBinding.topAppBar.getMenu().getItem(2).setVisible(false); | ||||
SnackBar.error( | ||||
ctx, | ||||
findViewById(android.R.id.content), | ||||
getString(R.string.apiNotFound)); | ||||
} else { | ||||
| ||||
viewBinding.topAppBar.getMenu().getItem(2).setVisible(false); | ||||
SnackBar.error( | ||||
ctx, | ||||
findViewById(android.R.id.content), | ||||
| @ -492,6 +497,7 @@ public class CreatePullRequestActivity extends BaseActivity | |||
@Override | ||||
public void onFailure(@NonNull Call<PullRequest> call, @NonNull Throwable t) { | ||||
| ||||
viewBinding.topAppBar.getMenu().getItem(2).setVisible(false); | ||||
SnackBar.error( | ||||
ctx, | ||||
findViewById(android.R.id.content), | ||||
| |
| @ -119,10 +119,9 @@ public class DeepLinksActivity extends BaseActivity { | |||
finish(); | ||||
} else if (Objects.equals( | ||||
data.getLastPathSegment(), | ||||
getAccount().getAccount().getUserName())) { // your user profile | ||||
mainIntent.putExtra("launchFragmentByLinkHandler", "profile"); | ||||
ctx.startActivity(mainIntent); | ||||
finish(); | ||||
getAccount().getAccount().getUserName())) { // user profile | ||||
new Handler(Looper.getMainLooper()) | ||||
.postDelayed(() -> getUserOrOrg(data.getLastPathSegment()), 500); | ||||
} else if (Objects.equals(data.getLastPathSegment(), "admin")) { | ||||
mainIntent.putExtra("launchFragmentByLinkHandler", "admin"); | ||||
ctx.startActivity(mainIntent); | ||||
| @ -577,7 +576,9 @@ public class DeepLinksActivity extends BaseActivity { | |||
public void onResponse( | ||||
@NonNull Call<Organization> call, | ||||
@NonNull Response<Organization> response) { | ||||
if (response.code() == 404) { // org doesn't exist or it's a user user | ||||
if (response.code() == 404 | ||||
|| response.code() | ||||
== 504) { // org doesn't exist or it's a user user | ||||
Log.d("getUserOrOrg-404", String.valueOf(response.code())); | ||||
getUser(userOrgName); | ||||
} else if (response.code() == 200) { // org | ||||
| |
| @ -373,8 +373,6 @@ public class IssueDetailActivity extends BaseActivity | |||
.setSoftInputMode( | ||||
WindowManager.LayoutParams | ||||
.SOFT_INPUT_STATE_ALWAYS_VISIBLE); | ||||
viewBinding.commentReply.setSelection( | ||||
viewBinding.commentReply.length()); | ||||
viewBinding.send.setAlpha(buttonAlphaStatEnabled); | ||||
viewBinding.send.setEnabled(true); | ||||
} else { | ||||
| |
| @ -60,7 +60,6 @@ import org.mian.gitnex.helpers.AlertDialogs; | |||
import org.mian.gitnex.helpers.AppDatabaseSettings; | ||||
import org.mian.gitnex.helpers.AppUtil; | ||||
import org.mian.gitnex.helpers.ChangeLog; | ||||
import org.mian.gitnex.helpers.RoundedTransformation; | ||||
import org.mian.gitnex.helpers.Toasty; | ||||
import org.mian.gitnex.structs.BottomSheetListener; | ||||
import org.mian.gitnex.structs.FragmentRefreshListener; | ||||
| @ -87,6 +86,7 @@ public class MainActivity extends BaseActivity | |||
private BottomSheetListener profileInitListener; | ||||
private FragmentRefreshListener fragmentRefreshListenerMyIssues; | ||||
private String username; | ||||
public static boolean closeActivity = false; | ||||
| ||||
@Override | ||||
public void onCreate(Bundle savedInstanceState) { | ||||
| @ -168,11 +168,11 @@ public class MainActivity extends BaseActivity | |||
| ||||
Menu menu = navigationView.getMenu(); | ||||
navNotifications = menu.findItem(R.id.nav_notifications); | ||||
MenuItem navDashboard = menu.findItem(R.id.nav_dashboard); | ||||
/*MenuItem navDashboard = menu.findItem(R.id.nav_dashboard); | ||||
| ||||
navDashboard.getActionView().findViewById(R.id.betaBadge).setVisibility(View.VISIBLE); | ||||
TextView dashboardBetaView = navDashboard.getActionView().findViewById(R.id.betaBadge); | ||||
dashboardBetaView.setText(R.string.beta); | ||||
dashboardBetaView.setText(R.string.beta);*/ | ||||
| ||||
navigationView | ||||
.getViewTreeObserver() | ||||
| @ -250,8 +250,16 @@ public class MainActivity extends BaseActivity | |||
String userFullNameNav = getAccount().getFullName(); | ||||
String userAvatarNav = getAccount().getUserInfo().getAvatarUrl(); | ||||
| ||||
if (!userEmailNav.isEmpty()) { | ||||
userEmail.setText(userEmailNav); | ||||
if (Boolean.parseBoolean( | ||||
AppDatabaseSettings.getSettingsValue( | ||||
ctx, | ||||
AppDatabaseSettings.APP_USER_HIDE_EMAIL_IN_NAV_KEY))) { | ||||
userEmail.setVisibility(View.GONE); | ||||
} else { | ||||
userEmail.setVisibility(View.VISIBLE); | ||||
if (!userEmailNav.isEmpty()) { | ||||
userEmail.setText(userEmailNav); | ||||
} | ||||
} | ||||
| ||||
if (!userFullNameNav.isEmpty()) { | ||||
| @ -260,13 +268,10 @@ public class MainActivity extends BaseActivity | |||
| ||||
if (!userAvatarNav.isEmpty()) { | ||||
| ||||
int avatarRadius = AppUtil.getPixelsFromDensity(ctx, 60); | ||||
| ||||
PicassoService.getInstance(ctx) | ||||
.get() | ||||
.load(userAvatarNav) | ||||
.placeholder(R.drawable.loader_animated) | ||||
.transform(new RoundedTransformation(avatarRadius, 0)) | ||||
.resize(160, 160) | ||||
.centerCrop() | ||||
.into(userAvatar); | ||||
| @ -582,6 +587,11 @@ public class MainActivity extends BaseActivity | |||
public void onResume() { | ||||
super.onResume(); | ||||
| ||||
if (closeActivity) { | ||||
finishAndRemoveTask(); | ||||
closeActivity = false; | ||||
} | ||||
| ||||
if (refActivity) { | ||||
this.recreate(); | ||||
this.overridePendingTransition(0, 0); | ||||
| |
| @ -126,7 +126,7 @@ public class ProfileActivity extends BaseActivity implements BottomSheetListener | |||
RetrofitClient.getApiInterface(this) | ||||
.userCurrentCheckFollowing(username) | ||||
.enqueue( | ||||
new Callback<Void>() { | ||||
new Callback<>() { | ||||
| ||||
@Override | ||||
public void onResponse( | ||||
| |
| @ -141,6 +141,53 @@ public class SettingsAppearanceActivity extends BaseActivity { | |||
activitySettingsAppearanceBinding.switchCounterBadge.setChecked( | ||||
!activitySettingsAppearanceBinding.switchCounterBadge.isChecked())); | ||||
| ||||
// hide email and language in user profile screen switcher | ||||
activitySettingsAppearanceBinding.switchHideEmailLangInProfile.setChecked( | ||||
Boolean.parseBoolean( | ||||
AppDatabaseSettings.getSettingsValue( | ||||
ctx, | ||||
AppDatabaseSettings.APP_USER_PROFILE_HIDE_EMAIL_LANGUAGE_KEY))); | ||||
| ||||
activitySettingsAppearanceBinding.switchHideEmailLangInProfile.setOnCheckedChangeListener( | ||||
(buttonView, isChecked) -> { | ||||
AppDatabaseSettings.updateSettingsValue( | ||||
ctx, | ||||
String.valueOf(isChecked), | ||||
AppDatabaseSettings.APP_USER_PROFILE_HIDE_EMAIL_LANGUAGE_KEY); | ||||
SnackBar.success( | ||||
ctx, | ||||
findViewById(android.R.id.content), | ||||
getString(R.string.settingsSave)); | ||||
}); | ||||
activitySettingsAppearanceBinding.hideEmailLangInProfileFrame.setOnClickListener( | ||||
v -> | ||||
activitySettingsAppearanceBinding.switchHideEmailLangInProfile.setChecked( | ||||
!activitySettingsAppearanceBinding.switchHideEmailLangInProfile | ||||
.isChecked())); | ||||
| ||||
// hide email in app navigation drawer switcher | ||||
activitySettingsAppearanceBinding.switchHideEmailNavDrawer.setChecked( | ||||
Boolean.parseBoolean( | ||||
AppDatabaseSettings.getSettingsValue( | ||||
ctx, AppDatabaseSettings.APP_USER_HIDE_EMAIL_IN_NAV_KEY))); | ||||
| ||||
activitySettingsAppearanceBinding.switchHideEmailNavDrawer.setOnCheckedChangeListener( | ||||
(buttonView, isChecked) -> { | ||||
AppDatabaseSettings.updateSettingsValue( | ||||
ctx, | ||||
String.valueOf(isChecked), | ||||
AppDatabaseSettings.APP_USER_HIDE_EMAIL_IN_NAV_KEY); | ||||
SnackBar.success( | ||||
ctx, | ||||
findViewById(android.R.id.content), | ||||
getString(R.string.settingsSave)); | ||||
}); | ||||
activitySettingsAppearanceBinding.hideEmailNavDrawerFrame.setOnClickListener( | ||||
v -> | ||||
activitySettingsAppearanceBinding.switchHideEmailNavDrawer.setChecked( | ||||
!activitySettingsAppearanceBinding.switchHideEmailNavDrawer | ||||
.isChecked())); | ||||
| ||||
// show labels in lists(issues, pr) - default is color dots | ||||
activitySettingsAppearanceBinding.switchLabelsInListBadge.setChecked( | ||||
Boolean.parseBoolean( | ||||
| |
| @ -29,7 +29,7 @@ public class AdminCronTasksAdapter | |||
| ||||
private final List<Cron> tasksList; | ||||
| ||||
static class CronTasksViewHolder extends RecyclerView.ViewHolder { | ||||
public static class CronTasksViewHolder extends RecyclerView.ViewHolder { | ||||
| ||||
private Cron cronTasks; | ||||
| ||||
| |
| @ -181,7 +181,7 @@ public class AdminGetUsersAdapter extends RecyclerView.Adapter<RecyclerView.View | |||
| ||||
userLoginId = users.getLogin(); | ||||
| ||||
if (!users.getFullName().equals("")) { | ||||
if (!users.getFullName().isEmpty()) { | ||||
| ||||
userFullName.setText(Html.fromHtml(users.getFullName())); | ||||
userName.setText( | ||||
| @ -195,7 +195,7 @@ public class AdminGetUsersAdapter extends RecyclerView.Adapter<RecyclerView.View | |||
userName.setVisibility(View.GONE); | ||||
} | ||||
| ||||
if (!users.getEmail().equals("")) { | ||||
if (!users.getEmail().isEmpty()) { | ||||
userEmail.setText(users.getEmail()); | ||||
} else { | ||||
userEmail.setVisibility(View.GONE); | ||||
| |
| @ -182,7 +182,7 @@ public class AdminUnadoptedReposAdapter | |||
isLoading = false; | ||||
} | ||||
| ||||
class UnadoptedViewHolder extends RecyclerView.ViewHolder { | ||||
public class UnadoptedViewHolder extends RecyclerView.ViewHolder { | ||||
| ||||
private final TextView name; | ||||
private String repoName; | ||||
| |
| @ -61,7 +61,7 @@ public class AssigneesListAdapter | |||
User currentItem = assigneesList.get(position); | ||||
int imgRadius = AppUtil.getPixelsFromDensity(context, 90); | ||||
| ||||
if (currentItem.getFullName().equals("")) { | ||||
if (currentItem.getFullName().isEmpty()) { | ||||
| ||||
holder.assigneesName.setText(currentItem.getLogin()); | ||||
} else { | ||||
| @ -132,7 +132,7 @@ public class AssigneesListAdapter | |||
void assigneesInterface(List<String> data); | ||||
} | ||||
| ||||
static class AssigneesViewHolder extends RecyclerView.ViewHolder { | ||||
public static class AssigneesViewHolder extends RecyclerView.ViewHolder { | ||||
| ||||
private final CheckBox assigneesSelection; | ||||
private final TextView assigneesName; | ||||
| |
| @ -64,7 +64,7 @@ public class CollaboratorSearchAdapter | |||
int imgRadius = AppUtil.getPixelsFromDensity(context, 60); | ||||
holder.userInfo = currentItem; | ||||
| ||||
if (!currentItem.getFullName().equals("")) { | ||||
if (!currentItem.getFullName().isEmpty()) { | ||||
| ||||
holder.userFullName.setText(Html.fromHtml(currentItem.getFullName())); | ||||
} else { | ||||
| @ -77,7 +77,7 @@ public class CollaboratorSearchAdapter | |||
holder.userName.setText( | ||||
context.getResources().getString(R.string.usernameWithAt, currentItem.getLogin())); | ||||
| ||||
if (!currentItem.getAvatarUrl().equals("")) { | ||||
if (!currentItem.getAvatarUrl().isEmpty()) { | ||||
PicassoService.getInstance(context) | ||||
.get() | ||||
.load(currentItem.getAvatarUrl()) | ||||
| @ -140,7 +140,7 @@ public class CollaboratorSearchAdapter | |||
return usersSearchList.size(); | ||||
} | ||||
| ||||
class CollaboratorSearchViewHolder extends RecyclerView.ViewHolder { | ||||
public class CollaboratorSearchViewHolder extends RecyclerView.ViewHolder { | ||||
| ||||
private final ImageView userAvatar; | ||||
private final TextView userFullName; | ||||
| |
| @ -83,7 +83,7 @@ public class CollaboratorsAdapter extends BaseAdapter { | |||
| ||||
viewHolder.userLoginId = currentItem.getLogin(); | ||||
| ||||
if (!currentItem.getFullName().equals("")) { | ||||
if (!currentItem.getFullName().isEmpty()) { | ||||
| ||||
viewHolder.collaboratorName.setText(Html.fromHtml(currentItem.getFullName())); | ||||
viewHolder.userName.setText( | ||||
| |
| @ -24,7 +24,7 @@ public class CommitStatusesAdapter | |||
| ||||
private final List<CommitStatus> statuses; | ||||
| ||||
static class CommitStatusesViewHolder extends RecyclerView.ViewHolder { | ||||
public static class CommitStatusesViewHolder extends RecyclerView.ViewHolder { | ||||
| ||||
private CommitStatus status; | ||||
| ||||
| @ -44,7 +44,7 @@ public class CommitStatusesAdapter | |||
} | ||||
| ||||
private void openUrl() { | ||||
if (status.getTargetUrl() != null && !status.getTargetUrl().equals("")) { | ||||
if (status.getTargetUrl() != null && !status.getTargetUrl().isEmpty()) { | ||||
AppUtil.openUrlInBrowser(itemView.getContext(), status.getTargetUrl()); | ||||
} else { | ||||
Toasty.info( | ||||
| |
| @ -147,7 +147,7 @@ public class DiffAdapter extends BaseAdapter { | |||
| ||||
private int getLineColor(String line) { | ||||
| ||||
if (line.length() == 0) { | ||||
if (line.isEmpty()) { | ||||
return COLOR_NORMAL; | ||||
} | ||||
| ||||
| |
| @ -113,7 +113,7 @@ public class DraftsAdapter extends RecyclerView.Adapter<DraftsAdapter.DraftsView | |||
notifyDataChanged(); | ||||
} | ||||
| ||||
class DraftsViewHolder extends RecyclerView.ViewHolder { | ||||
public class DraftsViewHolder extends RecyclerView.ViewHolder { | ||||
| ||||
private final TextView draftText; | ||||
private final TextView repoInfo; | ||||
| |
| @ -152,7 +152,7 @@ public class ExploreRepositoriesAdapter extends RecyclerView.Adapter<RecyclerVie | |||
.buildRoundRect(firstCharacter, color, 14); | ||||
| ||||
if (userRepositories.getAvatarUrl() != null) { | ||||
if (!userRepositories.getAvatarUrl().equals("")) { | ||||
if (!userRepositories.getAvatarUrl().isEmpty()) { | ||||
PicassoService.getInstance(context) | ||||
.get() | ||||
.load(userRepositories.getAvatarUrl()) | ||||
| @ -182,7 +182,7 @@ public class ExploreRepositoriesAdapter extends RecyclerView.Adapter<RecyclerVie | |||
repoLastUpdated.setVisibility(View.GONE); | ||||
} | ||||
| ||||
if (!userRepositories.getDescription().equals("")) { | ||||
if (!userRepositories.getDescription().isEmpty()) { | ||||
repoDescription.setVisibility(View.VISIBLE); | ||||
repoDescription.setText(userRepositories.getDescription()); | ||||
spacerView.setVisibility(View.GONE); | ||||
| |
| @ -1,5 +1,6 @@ | |||
package org.mian.gitnex.adapters; | ||||
| ||||
import android.annotation.SuppressLint; | ||||
import android.content.Context; | ||||
import android.view.LayoutInflater; | ||||
import android.view.View; | ||||
| @ -57,6 +58,7 @@ public class FilesAdapter extends RecyclerView.Adapter<FilesAdapter.FilesViewHol | |||
return results; | ||||
} | ||||
| ||||
@SuppressLint("NotifyDataSetChanged") | ||||
@Override | ||||
protected void publishResults(CharSequence constraint, FilterResults results) { | ||||
| ||||
| @ -77,6 +79,7 @@ public class FilesAdapter extends RecyclerView.Adapter<FilesAdapter.FilesViewHol | |||
return originalFiles; | ||||
} | ||||
| ||||
@SuppressLint("NotifyDataSetChanged") | ||||
public void notifyOriginalDataSetChanged() { | ||||
| ||||
alteredFiles.clear(); | ||||
| @ -150,7 +153,7 @@ public class FilesAdapter extends RecyclerView.Adapter<FilesAdapter.FilesViewHol | |||
void onClickFile(ContentsResponse file); | ||||
} | ||||
| ||||
class FilesViewHolder extends RecyclerView.ViewHolder { | ||||
public class FilesViewHolder extends RecyclerView.ViewHolder { | ||||
| ||||
private final ImageView fileTypeIs; | ||||
private final TextView fileName; | ||||
| |
| @ -79,7 +79,7 @@ public class LabelsAdapter extends RecyclerView.Adapter<LabelsAdapter.LabelsView | |||
notifyDataSetChanged(); | ||||
} | ||||
| ||||
class LabelsViewHolder extends RecyclerView.ViewHolder { | ||||
public class LabelsViewHolder extends RecyclerView.ViewHolder { | ||||
| ||||
private final MaterialCardView labelView; | ||||
private final ImageView labelIcon; | ||||
| |
| @ -1,5 +1,6 @@ | |||
package org.mian.gitnex.adapters; | ||||
| ||||
import android.annotation.SuppressLint; | ||||
import android.graphics.Color; | ||||
import android.view.LayoutInflater; | ||||
import android.view.View; | ||||
| @ -103,6 +104,7 @@ public class LabelsListAdapter extends RecyclerView.Adapter<LabelsListAdapter.La | |||
return labels.size(); | ||||
} | ||||
| ||||
@SuppressLint("NotifyDataSetChanged") | ||||
public void updateList(List<Integer> list) { | ||||
| ||||
currentLabelsIds = list; | ||||
| @ -116,7 +118,7 @@ public class LabelsListAdapter extends RecyclerView.Adapter<LabelsListAdapter.La | |||
void labelsIdsInterface(List<Integer> data); | ||||
} | ||||
| ||||
static class LabelsViewHolder extends RecyclerView.ViewHolder { | ||||
public static class LabelsViewHolder extends RecyclerView.ViewHolder { | ||||
| ||||
private final CheckBox labelSelection; | ||||
private final TextView labelText; | ||||
| |
| @ -34,7 +34,7 @@ public class MostVisitedReposAdapter | |||
private List<Repository> mostVisitedReposList; | ||||
private final Context ctx; | ||||
| ||||
class MostVisitedViewHolder extends RecyclerView.ViewHolder { | ||||
public class MostVisitedViewHolder extends RecyclerView.ViewHolder { | ||||
| ||||
private Repository repository; | ||||
| ||||
| |
| @ -63,7 +63,7 @@ public class OrganizationAddUserToTeamMemberAdapter | |||
holder.userInfo = currentItem; | ||||
int imgRadius = AppUtil.getPixelsFromDensity(context, 3); | ||||
| ||||
if (!currentItem.getFullName().equals("")) { | ||||
if (!currentItem.getFullName().isEmpty()) { | ||||
| ||||
holder.userFullName.setText(Html.fromHtml(currentItem.getFullName())); | ||||
} else { | ||||
| @ -76,7 +76,7 @@ public class OrganizationAddUserToTeamMemberAdapter | |||
holder.userName.setText( | ||||
context.getResources().getString(R.string.usernameWithAt, currentItem.getLogin())); | ||||
| ||||
if (!currentItem.getAvatarUrl().equals("")) { | ||||
if (!currentItem.getAvatarUrl().isEmpty()) { | ||||
PicassoService.getInstance(context) | ||||
.get() | ||||
.load(currentItem.getAvatarUrl()) | ||||
| @ -142,7 +142,7 @@ public class OrganizationAddUserToTeamMemberAdapter | |||
return usersSearchList.size(); | ||||
} | ||||
| ||||
class UserSearchViewHolder extends RecyclerView.ViewHolder { | ||||
public class UserSearchViewHolder extends RecyclerView.ViewHolder { | ||||
| ||||
private final ImageView userAvatar; | ||||
private final TextView userFullName; | ||||
| |
| @ -55,7 +55,7 @@ public class OrganizationTeamMembersPreviewAdapter | |||
return userData.size(); | ||||
} | ||||
| ||||
static class ViewHolder extends RecyclerView.ViewHolder { | ||||
public static class ViewHolder extends RecyclerView.ViewHolder { | ||||
| ||||
private final ImageView avatar; | ||||
| ||||
| |
| @ -50,7 +50,7 @@ public class OrganizationTeamRepositoriesAdapter | |||
reposArr = new ArrayList<>(); | ||||
} | ||||
| ||||
class TeamReposViewHolder extends RecyclerView.ViewHolder { | ||||
public class TeamReposViewHolder extends RecyclerView.ViewHolder { | ||||
| ||||
private Repository repoInfo; | ||||
| ||||
| @ -75,7 +75,7 @@ public class OrganizationTeamRepositoriesAdapter | |||
new Handler(Looper.getMainLooper()) | ||||
.postDelayed( | ||||
() -> { | ||||
if (reposArr.size() > 0) { | ||||
if (!reposArr.isEmpty()) { | ||||
for (int i = 0; i < reposArr.size(); i++) { | ||||
if (!reposArr.get(i).getName().equals(repoInfo.getName())) { | ||||
addRepoButtonAdd.setVisibility(View.VISIBLE); | ||||
| @ -144,7 +144,7 @@ public class OrganizationTeamRepositoriesAdapter | |||
.getColor(currentItem.getName()), | ||||
14); | ||||
| ||||
if (currentItem.getAvatarUrl() != null && !currentItem.getAvatarUrl().equals("")) { | ||||
if (currentItem.getAvatarUrl() != null && !currentItem.getAvatarUrl().isEmpty()) { | ||||
PicassoService.getInstance(context) | ||||
.get() | ||||
.load(currentItem.getAvatarUrl()) | ||||
| |
| @ -1,5 +1,6 @@ | |||
package org.mian.gitnex.adapters; | ||||
| ||||
import android.annotation.SuppressLint; | ||||
import android.content.Context; | ||||
import android.content.Intent; | ||||
import android.view.LayoutInflater; | ||||
| @ -38,7 +39,7 @@ public class OrganizationTeamsAdapter | |||
private final OrganizationPermissions permissions; | ||||
private final String orgName; | ||||
| ||||
static class OrgTeamsViewHolder extends RecyclerView.ViewHolder { | ||||
public static class OrgTeamsViewHolder extends RecyclerView.ViewHolder { | ||||
| ||||
private Team team; | ||||
| ||||
| @ -101,6 +102,7 @@ public class OrganizationTeamsAdapter | |||
return new OrganizationTeamsAdapter.OrgTeamsViewHolder(v); | ||||
} | ||||
| ||||
@SuppressLint("NotifyDataSetChanged") | ||||
@Override | ||||
public void onBindViewHolder( | ||||
@NonNull OrganizationTeamsAdapter.OrgTeamsViewHolder holder, int position) { | ||||
| @ -126,7 +128,7 @@ public class OrganizationTeamsAdapter | |||
@NonNull Response<List<User>> response) { | ||||
if (response.isSuccessful() | ||||
&& response.body() != null | ||||
&& response.body().size() > 0) { | ||||
&& !response.body().isEmpty()) { | ||||
| ||||
holder.membersPreviewFrame.setVisibility(View.VISIBLE); | ||||
holder.userInfos.addAll( | ||||
| @ -189,6 +191,7 @@ public class OrganizationTeamsAdapter | |||
return results; | ||||
} | ||||
| ||||
@SuppressLint("NotifyDataSetChanged") | ||||
@Override | ||||
protected void publishResults(CharSequence constraint, FilterResults results) { | ||||
teamList.clear(); | ||||
| |
| @ -182,7 +182,7 @@ public class OrganizationsListAdapter extends RecyclerView.Adapter<RecyclerView. | |||
.centerCrop() | ||||
.into(image); | ||||
| ||||
if (!org.getDescription().equals("")) { | ||||
if (!org.getDescription().isEmpty()) { | ||||
orgDescription.setVisibility(View.VISIBLE); | ||||
orgDescription.setText(org.getDescription()); | ||||
} else { | ||||
| |
| @ -64,7 +64,7 @@ public class ReactionAuthorsAdapter | |||
return userInfos.size(); | ||||
} | ||||
| ||||
static class ViewHolder extends RecyclerView.ViewHolder { | ||||
public static class ViewHolder extends RecyclerView.ViewHolder { | ||||
| ||||
private final ImageView authorAvatar; | ||||
| ||||
| |
| @ -125,7 +125,7 @@ public class ReleasesAdapter extends RecyclerView.Adapter<ReleasesAdapter.Releas | |||
TimeHelper.customDateFormatForToastDateFormat(currentItem.getPublishedAt()), | ||||
context)); | ||||
| ||||
if (!currentItem.getBody().equals("")) { | ||||
if (!currentItem.getBody().isEmpty()) { | ||||
Markdown.render(context, currentItem.getBody(), holder.releaseBodyContent); | ||||
} else { | ||||
holder.releaseBodyContent.setText(R.string.noReleaseBodyContent); | ||||
| @ -279,7 +279,7 @@ public class ReleasesAdapter extends RecyclerView.Adapter<ReleasesAdapter.Releas | |||
void onLoadFinished(); | ||||
} | ||||
| ||||
protected class ReleasesViewHolder extends RecyclerView.ViewHolder { | ||||
public class ReleasesViewHolder extends RecyclerView.ViewHolder { | ||||
| ||||
private final TextView releaseType; | ||||
private final TextView releaseName; | ||||
| |
| @ -55,7 +55,7 @@ public class ReleasesDownloadsAdapter | |||
return releasesDownloadsList.size(); | ||||
} | ||||
| ||||
static class ReleasesDownloadsViewHolder extends RecyclerView.ViewHolder { | ||||
public static class ReleasesDownloadsViewHolder extends RecyclerView.ViewHolder { | ||||
| ||||
private final TextView downloadName; | ||||
| ||||
| |
| @ -155,7 +155,7 @@ public class RepoForksAdapter extends RecyclerView.Adapter<RecyclerView.ViewHold | |||
void onLoadFinished(); | ||||
} | ||||
| ||||
class ForksHolder extends RecyclerView.ViewHolder { | ||||
public class ForksHolder extends RecyclerView.ViewHolder { | ||||
| ||||
private final ImageView image; | ||||
private final TextView repoName; | ||||
| @ -206,7 +206,7 @@ public class RepoForksAdapter extends RecyclerView.Adapter<RecyclerView.ViewHold | |||
.buildRoundRect(firstCharacter, color, 3); | ||||
| ||||
if (forksModel.getAvatarUrl() != null) { | ||||
if (!forksModel.getAvatarUrl().equals("")) { | ||||
if (!forksModel.getAvatarUrl().isEmpty()) { | ||||
PicassoService.getInstance(context) | ||||
.get() | ||||
.load(forksModel.getAvatarUrl()) | ||||
| @ -236,7 +236,7 @@ public class RepoForksAdapter extends RecyclerView.Adapter<RecyclerView.ViewHold | |||
repoLastUpdated.setVisibility(View.GONE); | ||||
} | ||||
| ||||
if (!forksModel.getDescription().equals("")) { | ||||
if (!forksModel.getDescription().isEmpty()) { | ||||
repoDescription.setText(forksModel.getDescription()); | ||||
} else { | ||||
repoDescription.setText(context.getString(R.string.noDataDescription)); | ||||
| |
| @ -153,7 +153,7 @@ public class ReposListAdapter extends RecyclerView.Adapter<RecyclerView.ViewHold | |||
void onLoadFinished(); | ||||
} | ||||
| ||||
class ReposHolder extends RecyclerView.ViewHolder { | ||||
public class ReposHolder extends RecyclerView.ViewHolder { | ||||
| ||||
private final ImageView image; | ||||
private final TextView repoName; | ||||
| @ -217,7 +217,7 @@ public class ReposListAdapter extends RecyclerView.Adapter<RecyclerView.ViewHold | |||
.buildRoundRect(firstCharacter, color, 14); | ||||
| ||||
if (repositories.getAvatarUrl() != null) { | ||||
if (!repositories.getAvatarUrl().equals("")) { | ||||
if (!repositories.getAvatarUrl().isEmpty()) { | ||||
PicassoService.getInstance(context) | ||||
.get() | ||||
.load(repositories.getAvatarUrl()) | ||||
| @ -244,7 +244,7 @@ public class ReposListAdapter extends RecyclerView.Adapter<RecyclerView.ViewHold | |||
repoLastUpdated.setVisibility(View.GONE); | ||||
} | ||||
| ||||
if (!repositories.getDescription().equals("")) { | ||||
if (!repositories.getDescription().isEmpty()) { | ||||
repoDescription.setVisibility(View.VISIBLE); | ||||
repoDescription.setText(repositories.getDescription()); | ||||
spacerView.setVisibility(View.GONE); | ||||
| |
| @ -44,7 +44,7 @@ public class SSHKeysAdapter extends RecyclerView.Adapter<SSHKeysAdapter.KeysView | |||
return keysList.size(); | ||||
} | ||||
| ||||
static class KeysViewHolder extends RecyclerView.ViewHolder { | ||||
public static class KeysViewHolder extends RecyclerView.ViewHolder { | ||||
| ||||
private final TextView keyName; | ||||
private final TextView key; | ||||
| |
| @ -99,8 +99,7 @@ public class UserAccountsAdapter | |||
} | ||||
| ||||
@NonNull @Override | ||||
public UserAccountsAdapter.UserAccountsViewHolder onCreateViewHolder( | ||||
@NonNull ViewGroup parent, int viewType) { | ||||
public UserAccountsViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { | ||||
| ||||
View v = | ||||
LayoutInflater.from(parent.getContext()) | ||||
| @ -110,8 +109,7 @@ public class UserAccountsAdapter | |||
| ||||
@SuppressLint("DefaultLocale") | ||||
@Override | ||||
public void onBindViewHolder( | ||||
@NonNull UserAccountsAdapter.UserAccountsViewHolder holder, int position) { | ||||
public void onBindViewHolder(@NonNull UserAccountsViewHolder holder, int position) { | ||||
| ||||
UserAccount currentItem = userAccountsList.get(position); | ||||
| ||||
| @ -150,7 +148,7 @@ public class UserAccountsAdapter | |||
return userAccountsList.size(); | ||||
} | ||||
| ||||
class UserAccountsViewHolder extends RecyclerView.ViewHolder { | ||||
public class UserAccountsViewHolder extends RecyclerView.ViewHolder { | ||||
| ||||
private final TextView accountUrl; | ||||
private final TextView userId; | ||||
| |
| @ -101,7 +101,7 @@ public class UserAccountsNavAdapter | |||
dialog.show(); | ||||
} | ||||
| ||||
class UserAccountsViewHolder extends RecyclerView.ViewHolder { | ||||
public class UserAccountsViewHolder extends RecyclerView.ViewHolder { | ||||
| ||||
private final ImageView userAccountAvatar; | ||||
| ||||
| |
| @ -120,7 +120,7 @@ public class UserGridAdapter extends BaseAdapter implements Filterable { | |||
| ||||
viewHolder.userLoginId = currentItem.getLogin(); | ||||
| ||||
if (!currentItem.getFullName().equals("")) { | ||||
if (!currentItem.getFullName().isEmpty()) { | ||||
| ||||
viewHolder.memberName.setText(Html.fromHtml(currentItem.getFullName())); | ||||
viewHolder.userName.setText( | ||||
| |
| @ -129,7 +129,7 @@ public class UsersAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> | |||
this.userInfo = userInfo; | ||||
int imgRadius = AppUtil.getPixelsFromDensity(context, 3); | ||||
| ||||
if (!userInfo.getFullName().equals("")) { | ||||
if (!userInfo.getFullName().isEmpty()) { | ||||
userFullName.setText(Html.fromHtml(userInfo.getFullName())); | ||||
userName.setText( | ||||
context.getResources() | ||||
| |
| @ -35,6 +35,7 @@ import org.mian.gitnex.databinding.CustomEditAvatarDialogBinding; | |||
import org.mian.gitnex.databinding.CustomEditProfileBinding; | ||||
import org.mian.gitnex.databinding.FragmentProfileDetailBinding; | ||||
import org.mian.gitnex.helpers.AlertDialogs; | ||||
import org.mian.gitnex.helpers.AppDatabaseSettings; | ||||
import org.mian.gitnex.helpers.AppUtil; | ||||
import org.mian.gitnex.helpers.ClickListener; | ||||
import org.mian.gitnex.helpers.Markdown; | ||||
| @ -62,6 +63,7 @@ public class DetailFragment extends Fragment { | |||
private int imgRadius; | ||||
private static Uri avatarUri = null; | ||||
public static boolean refProfile = false; | ||||
public boolean itsMe = false; | ||||
| ||||
public DetailFragment() {} | ||||
| ||||
| @ -109,6 +111,7 @@ public class DetailFragment extends Fragment { | |||
| ||||
if (username.equals(((BaseActivity) context).getAccount().getAccount().getUserName())) { | ||||
binding.metaProfile.setVisibility(View.VISIBLE); | ||||
itsMe = true; | ||||
} else { | ||||
binding.metaProfile.setVisibility(View.GONE); | ||||
} | ||||
| @ -366,7 +369,19 @@ public class DetailFragment extends Fragment { | |||
getString( | ||||
R.string.usernameWithAt, | ||||
response.body().getLogin())); | ||||
binding.userEmail.setText(email); | ||||
| ||||
if (Boolean.parseBoolean( | ||||
AppDatabaseSettings.getSettingsValue( | ||||
context, | ||||
AppDatabaseSettings | ||||
.APP_USER_PROFILE_HIDE_EMAIL_LANGUAGE_KEY)) | ||||
&& itsMe) { | ||||
binding.userEmail.setText( | ||||
getString(R.string.strPrivate).toUpperCase()); | ||||
binding.userEmail.setAlpha(.4F); | ||||
} else { | ||||
binding.userEmail.setText(email); | ||||
} | ||||
| ||||
binding.userFollowersCount.setText( | ||||
String.format( | ||||
| @ -389,13 +404,25 @@ public class DetailFragment extends Fragment { | |||
String[] userLanguageCodes = | ||||
response.body().getLanguage().split("-"); | ||||
| ||||
if (userLanguageCodes.length >= 2) { | ||||
Locale locale = | ||||
new Locale( | ||||
userLanguageCodes[0], userLanguageCodes[1]); | ||||
binding.userLang.setText(locale.getDisplayLanguage()); | ||||
if (Boolean.parseBoolean( | ||||
AppDatabaseSettings.getSettingsValue( | ||||
context, | ||||
AppDatabaseSettings | ||||
.APP_USER_PROFILE_HIDE_EMAIL_LANGUAGE_KEY)) | ||||
&& itsMe) { | ||||
binding.userLang.setText( | ||||
getString(R.string.strPrivate).toUpperCase()); | ||||
binding.userLang.setAlpha(.4F); | ||||
} else { | ||||
binding.userLang.setText(locale.getDisplayLanguage()); | ||||
if (userLanguageCodes.length >= 2) { | ||||
Locale locale = | ||||
new Locale( | ||||
userLanguageCodes[0], | ||||
userLanguageCodes[1]); | ||||
binding.userLang.setText(locale.getDisplayLanguage()); | ||||
} else { | ||||
binding.userLang.setText(locale.getDisplayLanguage()); | ||||
} | ||||
} | ||||
| ||||
PicassoService.getInstance(context) | ||||
| @ -415,6 +442,30 @@ public class DetailFragment extends Fragment { | |||
TimeHelper.customDateFormatForToastDateFormat( | ||||
response.body().getCreated()), | ||||
context)); | ||||
if (!response.body().getWebsite().isEmpty()) { | ||||
binding.website.setText(response.body().getWebsite()); | ||||
} else { | ||||
binding.website.setText(R.string.noDataWebsite); | ||||
binding.website.setAlpha(.4F); | ||||
} | ||||
| ||||
if (!response.body().getLocation().isEmpty()) { | ||||
binding.location.setText(response.body().getLocation()); | ||||
} else { | ||||
binding.location.setText(R.string.noDataLocation); | ||||
binding.location.setAlpha(.4F); | ||||
} | ||||
| ||||
if (!response.body().getDescription().isEmpty()) { | ||||
Markdown.render( | ||||
context, | ||||
response.body().getDescription(), | ||||
binding.bio); | ||||
} else { | ||||
binding.bio.setText(R.string.noDataBio); | ||||
binding.bio.setAlpha(.4F); | ||||
} | ||||
| ||||
break; | ||||
| ||||
case 401: | ||||
| |
| @ -62,6 +62,11 @@ public class AppDatabaseSettings { | |||
public static String APP_IMAGES_CACHE_DEFAULT = "1"; | ||||
public static String APP_IMAGES_CACHE_SIZE_KEY = "app_images_cache_size"; | ||||
public static String APP_IMAGES_CACHE_SIZE_DEFAULT = "100 MB"; | ||||
public static String APP_USER_PROFILE_HIDE_EMAIL_LANGUAGE_KEY = | ||||
"app_user_profile_hide_email_lang"; | ||||
public static String APP_USER_PROFILE_HIDE_EMAIL_LANGUAGE_DEFAULT = "false"; | ||||
public static String APP_USER_HIDE_EMAIL_IN_NAV_KEY = "app_user_hide_email_nav"; | ||||
public static String APP_USER_HIDE_EMAIL_IN_NAV_DEFAULT = "false"; | ||||
| ||||
public static void initDefaultSettings(Context ctx) { | ||||
| ||||
| @ -190,6 +195,18 @@ public class AppDatabaseSettings { | |||
APP_IMAGES_CACHE_SIZE_DEFAULT, | ||||
APP_IMAGES_CACHE_SIZE_DEFAULT); | ||||
} | ||||
if (appSettingsApi.fetchSettingCountByKey(APP_USER_PROFILE_HIDE_EMAIL_LANGUAGE_KEY) == 0) { | ||||
appSettingsApi.insertNewSetting( | ||||
APP_USER_PROFILE_HIDE_EMAIL_LANGUAGE_KEY, | ||||
APP_USER_PROFILE_HIDE_EMAIL_LANGUAGE_DEFAULT, | ||||
APP_USER_PROFILE_HIDE_EMAIL_LANGUAGE_DEFAULT); | ||||
} | ||||
if (appSettingsApi.fetchSettingCountByKey(APP_USER_HIDE_EMAIL_IN_NAV_KEY) == 0) { | ||||
appSettingsApi.insertNewSetting( | ||||
APP_USER_HIDE_EMAIL_IN_NAV_KEY, | ||||
APP_USER_HIDE_EMAIL_IN_NAV_DEFAULT, | ||||
APP_USER_HIDE_EMAIL_IN_NAV_DEFAULT); | ||||
} | ||||
| ||||
if (appSettingsApi.fetchSettingCountByKey("prefsMigration") == 0) { | ||||
appSettingsApi.insertNewSetting("prefsMigration", "true", "true"); | ||||
| |
| @ -34,7 +34,7 @@ public class SnackBar { | |||
View sbView = snackBar.getView(); | ||||
TextView textView = sbView.findViewById(R.id.snackbar_text); | ||||
snackBar.setBackgroundTint(context.getColor(R.color.cardBackground)); | ||||
textView.setTextColor(context.getColor(R.color.warningColor)); | ||||
textView.setTextColor(context.getColor(R.color.colorWhite)); | ||||
snackBar.show(); | ||||
} | ||||
| ||||
| @ -43,7 +43,7 @@ public class SnackBar { | |||
View sbView = snackBar.getView(); | ||||
TextView textView = sbView.findViewById(R.id.snackbar_text); | ||||
snackBar.setBackgroundTint(context.getColor(R.color.cardBackground)); | ||||
textView.setTextColor(context.getColor(R.color.darkRed)); | ||||
textView.setTextColor(context.getColor(R.color.colorWhite)); | ||||
snackBar.show(); | ||||
} | ||||
} | ||||
| |
| @ -106,9 +106,17 @@ public class Notifications { | |||
MaterialAlertDialogBuilder materialAlertDialogBuilder = | ||||
new MaterialAlertDialogBuilder(context) | ||||
.setTitle(R.string.pageTitleNotifications) | ||||
.setCancelable(false) | ||||
.setMessage(context.getString(R.string.openAppSettings)) | ||||
.setNeutralButton( | ||||
R.string.cancelButton, (dialog, which) -> dialog.dismiss()) | ||||
R.string.cancelButton, | ||||
(dialog, which) -> { | ||||
dialog.dismiss(); | ||||
AppDatabaseSettings.updateSettingsValue( | ||||
context, | ||||
"false", | ||||
AppDatabaseSettings.APP_NOTIFICATIONS_KEY); | ||||
}) | ||||
.setPositiveButton( | ||||
R.string.isOpen, | ||||
(dialog, which) -> { | ||||
| |
34 app/src/main/res/drawable/ic_file_description.xml Normal file
34
app/src/main/res/drawable/ic_file_description.xml Normal file | @ -0,0 +1,34 @@ | |||
<vector xmlns:android="http://schemas.android.com/apk/res/android" | ||||
android:width="24dp" | ||||
android:height="24dp" | ||||
android:viewportWidth="24" | ||||
android:viewportHeight="24"> | ||||
<path | ||||
android:pathData="M14,3v4a1,1 0,0 0,1 1h4" | ||||
android:strokeLineJoin="round" | ||||
android:strokeWidth="2" | ||||
android:fillColor="#00000000" | ||||
android:strokeColor="?attr/iconsColor" | ||||
android:strokeLineCap="round"/> | ||||
<path | ||||
android:pathData="M17,21h-10a2,2 0,0 1,-2 -2v-14a2,2 0,0 1,2 -2h7l5,5v11a2,2 0,0 1,-2 2z" | ||||
android:strokeLineJoin="round" | ||||
android:strokeWidth="2" | ||||
android:fillColor="#00000000" | ||||
android:strokeColor="?attr/iconsColor" | ||||
android:strokeLineCap="round"/> | ||||
<path | ||||
android:pathData="M9,17h6" | ||||
android:strokeLineJoin="round" | ||||
android:strokeWidth="2" | ||||
android:fillColor="#00000000" | ||||
android:strokeColor="?attr/iconsColor" | ||||
android:strokeLineCap="round"/> | ||||
<path | ||||
android:pathData="M9,13h6" | ||||
android:strokeLineJoin="round" | ||||
android:strokeWidth="2" | ||||
android:fillColor="#00000000" | ||||
android:strokeColor="?attr/iconsColor" | ||||
android:strokeLineCap="round"/> | ||||
</vector> |
| @ -90,7 +90,7 @@ | |||
android:paddingStart="@dimen/dimen8dp" | ||||
android:paddingTop="@dimen/dimen2dp" | ||||
android:paddingEnd="@dimen/dimen8dp" | ||||
android:paddingBottom="@dimen/dimen104dp"> | ||||
android:paddingBottom="@dimen/dimen120dp"> | ||||
| ||||
<com.google.android.material.card.MaterialCardView | ||||
android:id="@+id/titleCard" | ||||
| @ -391,6 +391,29 @@ | |||
android:background="?attr/primaryBackgroundColor" | ||||
android:padding="@dimen/dimen8dp"> | ||||
| ||||
<com.google.android.material.card.MaterialCardView | ||||
style="?attr/materialCardViewFilledStyle" | ||||
android:id="@+id/attachmentsCard" | ||||
android:layout_width="@dimen/dimen38dp" | ||||
android:layout_height="@dimen/dimen38dp" | ||||
android:layout_gravity="center_vertical" | ||||
android:backgroundTint="?attr/fabColor" | ||||
android:layout_marginEnd="@dimen/dimen8dp" | ||||
app:cardCornerRadius="@dimen/dimen36dp"> | ||||
| ||||
<ImageView | ||||
android:id="@+id/attachments" | ||||
android:layout_width="@dimen/dimen24dp" | ||||
android:layout_height="@dimen/dimen24dp" | ||||
android:layout_gravity="center_vertical|center_horizontal" | ||||
android:contentDescription="@string/generalImgContentText" | ||||
app:tint="?attr/materialCardBackgroundColor" | ||||
android:clickable="true" | ||||
android:src="@drawable/ic_attachment" | ||||
android:focusable="true" /> | ||||
| ||||
</com.google.android.material.card.MaterialCardView> | ||||
| ||||
<EditText | ||||
android:id="@+id/comment_reply" | ||||
android:layout_width="0dp" | ||||
| @ -420,8 +443,8 @@ | |||
| ||||
<ImageView | ||||
android:id="@+id/send" | ||||
android:layout_width="@dimen/dimen26dp" | ||||
android:layout_height="@dimen/dimen26dp" | ||||
android:layout_width="@dimen/dimen24dp" | ||||
android:layout_height="@dimen/dimen24dp" | ||||
android:layout_gravity="center_vertical|center_horizontal" | ||||
android:contentDescription="@string/generalImgContentText" | ||||
app:tint="?attr/materialCardBackgroundColor" | ||||
| |
| @ -254,6 +254,102 @@ | |||
| ||||
</LinearLayout> | ||||
| ||||
<LinearLayout | ||||
android:id="@+id/hideEmailLangInProfileFrame" | ||||
android:layout_width="match_parent" | ||||
android:layout_height="wrap_content" | ||||
android:layout_marginTop="@dimen/dimen32dp" | ||||
android:orientation="vertical"> | ||||
| ||||
<LinearLayout | ||||
android:layout_width="match_parent" | ||||
android:layout_height="wrap_content" | ||||
android:orientation="horizontal"> | ||||
| ||||
<TextView | ||||
android:id="@+id/tvHideEmailLangInProfileHeader" | ||||
android:layout_width="wrap_content" | ||||
android:layout_height="wrap_content" | ||||
android:layout_weight=".90" | ||||
android:text="@string/hideEmailLangInProfileHeader" | ||||
android:layout_marginTop="@dimen/dimen4dp" | ||||
android:textColor="?attr/primaryTextColor" | ||||
android:textSize="@dimen/dimen18sp" /> | ||||
| ||||
<com.google.android.material.materialswitch.MaterialSwitch | ||||
android:id="@+id/switchHideEmailLangInProfile" | ||||
android:layout_width="wrap_content" | ||||
android:layout_height="wrap_content" | ||||
android:contentDescription="@string/hideEmailLangInProfileHeader" | ||||
android:layout_weight=".10" /> | ||||
| ||||
</LinearLayout> | ||||
| ||||
<LinearLayout | ||||
android:layout_width="match_parent" | ||||
android:layout_height="wrap_content" | ||||
android:orientation="vertical"> | ||||
| ||||
<TextView | ||||
android:layout_width="wrap_content" | ||||
android:layout_height="wrap_content" | ||||
android:layout_marginEnd="@dimen/dimen96dp" | ||||
android:text="@string/hideEmailLangInProfileHint" | ||||
android:textColor="?attr/hintColor" | ||||
android:textSize="@dimen/dimen12sp" /> | ||||
| ||||
</LinearLayout> | ||||
| ||||
</LinearLayout> | ||||
| ||||
<LinearLayout | ||||
android:id="@+id/hideEmailNavDrawerFrame" | ||||
android:layout_width="match_parent" | ||||
android:layout_height="wrap_content" | ||||
android:layout_marginTop="@dimen/dimen32dp" | ||||
android:orientation="vertical"> | ||||
| ||||
<LinearLayout | ||||
android:layout_width="match_parent" | ||||
android:layout_height="wrap_content" | ||||
android:orientation="horizontal"> | ||||
| ||||
<TextView | ||||
android:id="@+id/tvHideEmailNavDrawerHeader" | ||||
android:layout_width="wrap_content" | ||||
android:layout_height="wrap_content" | ||||
android:layout_weight=".90" | ||||
android:text="@string/hideEmailNavDrawerHeader" | ||||
android:layout_marginTop="@dimen/dimen4dp" | ||||
android:textColor="?attr/primaryTextColor" | ||||
android:textSize="@dimen/dimen18sp" /> | ||||
| ||||
<com.google.android.material.materialswitch.MaterialSwitch | ||||
android:id="@+id/switchHideEmailNavDrawer" | ||||
android:layout_width="wrap_content" | ||||
android:layout_height="wrap_content" | ||||
android:contentDescription="@string/hideEmailNavDrawerHeader" | ||||
android:layout_weight=".10" /> | ||||
| ||||
</LinearLayout> | ||||
| ||||
<LinearLayout | ||||
android:layout_width="match_parent" | ||||
android:layout_height="wrap_content" | ||||
android:orientation="vertical"> | ||||
| ||||
<TextView | ||||
android:layout_width="wrap_content" | ||||
android:layout_height="wrap_content" | ||||
android:layout_marginEnd="@dimen/dimen96dp" | ||||
android:text="@string/hideEmailNavDrawerHint" | ||||
android:textColor="?attr/hintColor" | ||||
android:textSize="@dimen/dimen12sp" /> | ||||
| ||||
</LinearLayout> | ||||
| ||||
</LinearLayout> | ||||
| ||||
<LinearLayout | ||||
android:id="@+id/langFrame" | ||||
android:layout_width="match_parent" | ||||
| |
| @ -157,6 +157,7 @@ | |||
android:textColor="?attr/materialCardBackgroundColor" | ||||
app:iconTint="?attr/materialCardBackgroundColor" | ||||
android:backgroundTint="?attr/fabColor" | ||||
android:visibility="gone" | ||||
app:icon="@drawable/ic_bmc" /> | ||||
| ||||
</LinearLayout> | ||||
| |
| @ -51,12 +51,12 @@ | |||
tools:ignore="UseCompoundDrawables"> | ||||
| ||||
<com.google.android.material.card.MaterialCardView | ||||
android:layout_width="@dimen/dimen80dp" | ||||
android:layout_height="@dimen/dimen80dp" | ||||
android:layout_width="@dimen/dimen96dp" | ||||
android:layout_height="@dimen/dimen96dp" | ||||
style="?attr/materialCardViewFilledStyle" | ||||
android:layout_marginBottom="@dimen/dimen8dp" | ||||
app:cardElevation="@dimen/dimen0dp" | ||||
app:cardCornerRadius="@dimen/dimen40dp"> | ||||
app:cardCornerRadius="@dimen/dimen16dp"> | ||||
| ||||
<ImageView | ||||
android:id="@+id/userAvatar" | ||||
| @ -178,8 +178,8 @@ | |||
| ||||
<ImageView | ||||
android:layout_gravity="center" | ||||
android:layout_width="wrap_content" | ||||
android:layout_height="wrap_content" | ||||
android:layout_width="@dimen/dimen24dp" | ||||
android:layout_height="@dimen/dimen24dp" | ||||
android:contentDescription="@string/generalImgContentText" | ||||
app:srcCompat="@drawable/ic_email" /> | ||||
| ||||
| @ -203,6 +203,7 @@ | |||
android:layout_height="wrap_content" | ||||
android:alpha="0.9" | ||||
android:textColor="?attr/primaryTextColor" | ||||
android:autoLink="email" | ||||
android:textSize="@dimen/dimen14sp" /> | ||||
</LinearLayout> | ||||
| ||||
| @ -214,76 +215,191 @@ | |||
android:layout_marginTop="@dimen/dimen32dp" | ||||
android:orientation="horizontal"> | ||||
| ||||
<ImageView | ||||
android:layout_gravity="center" | ||||
android:layout_width="wrap_content" | ||||
android:layout_height="wrap_content" | ||||
android:contentDescription="@string/generalImgContentText" | ||||
app:srcCompat="@drawable/ic_language"/> | ||||
| ||||
<LinearLayout | ||||
android:layout_width="match_parent" | ||||
android:layout_height="wrap_content" | ||||
android:layout_marginStart="@dimen/dimen16dp" | ||||
android:gravity="center_vertical" | ||||
android:orientation="vertical"> | ||||
| ||||
<TextView | ||||
android:layout_width="wrap_content" | ||||
android:layout_height="wrap_content" | ||||
android:text="@string/settingsLanguageSelectorHeader" | ||||
android:textColor="?attr/primaryTextColor" | ||||
android:textSize="@dimen/dimen16sp" /> | ||||
| ||||
<TextView | ||||
android:id="@+id/userLang" | ||||
android:layout_width="match_parent" | ||||
android:layout_height="wrap_content" | ||||
android:alpha="0.9" | ||||
android:textColor="?attr/primaryTextColor" | ||||
android:textSize="@dimen/dimen14sp" /> | ||||
</LinearLayout> | ||||
| ||||
</LinearLayout> | ||||
<ImageView | ||||
android:layout_gravity="center" | ||||
android:layout_width="@dimen/dimen24dp" | ||||
android:layout_height="@dimen/dimen24dp" | ||||
android:contentDescription="@string/generalImgContentText" | ||||
app:srcCompat="@drawable/ic_browser" /> | ||||
| ||||
<LinearLayout | ||||
android:layout_width="match_parent" | ||||
android:layout_height="wrap_content" | ||||
android:layout_marginTop="@dimen/dimen32dp" | ||||
android:orientation="horizontal"> | ||||
android:layout_marginStart="@dimen/dimen16dp" | ||||
android:gravity="center_vertical" | ||||
android:orientation="vertical"> | ||||
| ||||
<ImageView | ||||
android:layout_gravity="center" | ||||
<TextView | ||||
android:layout_width="wrap_content" | ||||
android:layout_height="wrap_content" | ||||
android:contentDescription="@string/generalImgContentText" | ||||
app:srcCompat="@drawable/ic_calendar"/> | ||||
android:text="@string/websiteText" | ||||
android:textColor="?attr/primaryTextColor" | ||||
android:textSize="@dimen/dimen16sp" /> | ||||
| ||||
<LinearLayout | ||||
<TextView | ||||
android:id="@+id/website" | ||||
android:layout_width="match_parent" | ||||
android:layout_height="wrap_content" | ||||
android:layout_marginStart="@dimen/dimen16dp" | ||||
android:gravity="center_vertical" | ||||
android:orientation="vertical"> | ||||
| ||||
<TextView | ||||
android:layout_width="wrap_content" | ||||
android:layout_height="wrap_content" | ||||
android:text="@string/joined" | ||||
android:textColor="?attr/primaryTextColor" | ||||
android:textSize="@dimen/dimen16sp" /> | ||||
| ||||
<TextView | ||||
android:id="@+id/userJoinedOn" | ||||
android:layout_width="match_parent" | ||||
android:layout_height="wrap_content" | ||||
android:alpha="0.9" | ||||
android:textColor="?attr/primaryTextColor" | ||||
android:textSize="@dimen/dimen14sp" /> | ||||
</LinearLayout> | ||||
| ||||
android:alpha="0.9" | ||||
android:textColor="?attr/primaryTextColor" | ||||
android:autoLink="web" | ||||
android:textSize="@dimen/dimen14sp" /> | ||||
</LinearLayout> | ||||
| ||||
</LinearLayout> | ||||
| ||||
<LinearLayout | ||||
android:layout_width="match_parent" | ||||
android:layout_height="wrap_content" | ||||
android:layout_marginTop="@dimen/dimen32dp" | ||||
android:orientation="horizontal"> | ||||
| ||||
<ImageView | ||||
android:layout_gravity="center" | ||||
android:layout_width="@dimen/dimen24dp" | ||||
android:layout_height="@dimen/dimen24dp" | ||||
android:contentDescription="@string/generalImgContentText" | ||||
app:srcCompat="@drawable/ic_location" /> | ||||
| ||||
<LinearLayout | ||||
android:layout_width="match_parent" | ||||
android:layout_height="wrap_content" | ||||
android:layout_marginStart="@dimen/dimen16dp" | ||||
android:gravity="center_vertical" | ||||
android:orientation="vertical"> | ||||
| ||||
<TextView | ||||
android:layout_width="wrap_content" | ||||
android:layout_height="wrap_content" | ||||
android:text="@string/locationText" | ||||
android:textColor="?attr/primaryTextColor" | ||||
android:textSize="@dimen/dimen16sp" /> | ||||
| ||||
<TextView | ||||
android:id="@+id/location" | ||||
android:layout_width="match_parent" | ||||
android:layout_height="wrap_content" | ||||
android:alpha="0.9" | ||||
android:textColor="?attr/primaryTextColor" | ||||
android:textSize="@dimen/dimen14sp" /> | ||||
</LinearLayout> | ||||
| ||||
</LinearLayout> | ||||
| ||||
<LinearLayout | ||||
android:layout_width="match_parent" | ||||
android:layout_height="wrap_content" | ||||
android:layout_marginTop="@dimen/dimen32dp" | ||||
android:orientation="horizontal"> | ||||
| ||||
<ImageView | ||||
android:layout_gravity="center" | ||||
android:layout_width="@dimen/dimen24dp" | ||||
android:layout_height="@dimen/dimen24dp" | ||||
android:contentDescription="@string/generalImgContentText" | ||||
app:srcCompat="@drawable/ic_language"/> | ||||
| ||||
<LinearLayout | ||||
android:layout_width="match_parent" | ||||
android:layout_height="wrap_content" | ||||
android:layout_marginStart="@dimen/dimen16dp" | ||||
android:gravity="center_vertical" | ||||
android:orientation="vertical"> | ||||
| ||||
<TextView | ||||
android:layout_width="wrap_content" | ||||
android:layout_height="wrap_content" | ||||
android:text="@string/settingsLanguageSelectorHeader" | ||||
android:textColor="?attr/primaryTextColor" | ||||
android:textSize="@dimen/dimen16sp" /> | ||||
| ||||
<TextView | ||||
android:id="@+id/userLang" | ||||
android:layout_width="match_parent" | ||||
android:layout_height="wrap_content" | ||||
android:alpha="0.9" | ||||
android:textColor="?attr/primaryTextColor" | ||||
android:textSize="@dimen/dimen14sp" /> | ||||
</LinearLayout> | ||||
| ||||
</LinearLayout> | ||||
| ||||
<LinearLayout | ||||
android:layout_width="match_parent" | ||||
android:layout_height="wrap_content" | ||||
android:layout_marginTop="@dimen/dimen32dp" | ||||
android:orientation="horizontal"> | ||||
| ||||
<ImageView | ||||
android:layout_gravity="center" | ||||
android:layout_width="@dimen/dimen24dp" | ||||
android:layout_height="@dimen/dimen24dp" | ||||
android:contentDescription="@string/generalImgContentText" | ||||
app:srcCompat="@drawable/ic_file_description" /> | ||||
| ||||
<LinearLayout | ||||
android:layout_width="match_parent" | ||||
android:layout_height="wrap_content" | ||||
android:layout_marginStart="@dimen/dimen16dp" | ||||
android:gravity="center_vertical" | ||||
android:orientation="vertical"> | ||||
| ||||
<TextView | ||||
android:layout_width="wrap_content" | ||||
android:layout_height="wrap_content" | ||||
android:text="@string/bioText" | ||||
android:textColor="?attr/primaryTextColor" | ||||
android:textSize="@dimen/dimen16sp" /> | ||||
| ||||
<TextView | ||||
android:id="@+id/bio" | ||||
android:layout_width="match_parent" | ||||
android:layout_height="wrap_content" | ||||
android:alpha="0.9" | ||||
android:textColor="?attr/primaryTextColor" | ||||
android:textSize="@dimen/dimen14sp" /> | ||||
</LinearLayout> | ||||
| ||||
</LinearLayout> | ||||
| ||||
<LinearLayout | ||||
android:layout_width="match_parent" | ||||
android:layout_height="wrap_content" | ||||
android:layout_marginTop="@dimen/dimen32dp" | ||||
android:orientation="horizontal"> | ||||
| ||||
<ImageView | ||||
android:layout_gravity="center" | ||||
android:layout_width="@dimen/dimen24dp" | ||||
android:layout_height="@dimen/dimen24dp" | ||||
android:contentDescription="@string/generalImgContentText" | ||||
app:srcCompat="@drawable/ic_calendar"/> | ||||
| ||||
<LinearLayout | ||||
android:layout_width="match_parent" | ||||
android:layout_height="wrap_content" | ||||
android:layout_marginStart="@dimen/dimen16dp" | ||||
android:gravity="center_vertical" | ||||
android:orientation="vertical"> | ||||
| ||||
<TextView | ||||
android:layout_width="wrap_content" | ||||
android:layout_height="wrap_content" | ||||
android:text="@string/joined" | ||||
android:textColor="?attr/primaryTextColor" | ||||
android:textSize="@dimen/dimen16sp" /> | ||||
| ||||
<TextView | ||||
android:id="@+id/userJoinedOn" | ||||
android:layout_width="match_parent" | ||||
android:layout_height="wrap_content" | ||||
android:alpha="0.9" | ||||
android:textColor="?attr/primaryTextColor" | ||||
android:textSize="@dimen/dimen14sp" /> | ||||
</LinearLayout> | ||||
| ||||
</LinearLayout> | ||||
| ||||
</LinearLayout> | ||||
| ||||
</com.google.android.material.card.MaterialCardView> | ||||
| |
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue