Delete email from account / create ssh key / update profile details and other UI improvements (#1342) All checks were successful ci/woodpecker/push/locale Pipeline was successful ci/woodpecker/push/check Pipeline was successful ci/woodpecker/push/build 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/check Pipeline was successful
ci/woodpecker/push/build 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
Closes #135 Closes #1282 Closes #1343 Reviewed-on: #1342 Co-authored-by: M M Arif <mmarif@swatian.com> Co-committed-by: M M Arif <mmarif@swatian.com>
This commit is contained in:
parent 98e41186ec
commit e5f4d14750
46 changed files with 763 additions and 139 deletions
| @ -58,8 +58,8 @@ dependencies { | |||
implementation fileTree(include: ['*.jar'], dir: 'libs') | ||||
implementation 'androidx.appcompat:appcompat:1.6.1' | ||||
implementation 'com.google.android.material:material:1.11.0' | ||||
implementation 'androidx.compose.material3:material3:1.2.0' | ||||
implementation 'androidx.compose.material3:material3-window-size-class:1.2.0' | ||||
implementation 'androidx.compose.material3:material3:1.2.1' | ||||
implementation 'androidx.compose.material3:material3-window-size-class:1.2.1' | ||||
implementation 'androidx.viewpager2:viewpager2:1.1.0-beta02' | ||||
implementation 'androidx.constraintlayout:constraintlayout:2.1.4' | ||||
implementation "androidx.legacy:legacy-support-v4:1.0.0" | ||||
| @ -108,16 +108,16 @@ dependencies { | |||
coreLibraryDesugaring "com.android.tools:desugar_jdk_libs:1.1.5" | ||||
implementation 'androidx.biometric:biometric:1.1.0' | ||||
implementation 'com.github.chrisvest:stormpot:2.4.2' | ||||
implementation 'androidx.browser:browser:1.7.0' | ||||
implementation 'androidx.browser:browser:1.8.0' | ||||
implementation 'com.google.android.flexbox:flexbox:3.0.0' | ||||
implementation('org.codeberg.gitnex:tea4j-autodeploy:4646f53557') { | ||||
implementation('org.codeberg.gitnex:tea4j-autodeploy:5f0dc819a3') { | ||||
exclude module: 'org.apache.oltu.oauth2.common' | ||||
} | ||||
implementation 'io.github.amrdeveloper:codeview:1.3.9' | ||||
| ||||
constraints { | ||||
implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.8.0") | ||||
implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.8.0") | ||||
it.implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.8.0") | ||||
it.implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.8.0") | ||||
} | ||||
} | ||||
| ||||
| |
| @ -274,6 +274,7 @@ public class IssueDetailActivity extends BaseActivity | |||
repoOwner = issue.getRepository().getOwner(); | ||||
repoName = issue.getRepository().getName(); | ||||
issueIndex = issue.getIssueIndex(); | ||||
Boolean isLocked = issue.getIssue().isIsLocked(); | ||||
| ||||
setSupportActionBar(viewBinding.toolbar); | ||||
Objects.requireNonNull(getSupportActionBar()).setTitle(repoName); | ||||
| @ -293,6 +294,12 @@ public class IssueDetailActivity extends BaseActivity | |||
viewBinding.recyclerView.setNestedScrollingEnabled(false); | ||||
viewBinding.recyclerView.setLayoutManager(new LinearLayoutManager(ctx)); | ||||
| ||||
if (isLocked && !issue.getRepository().getPermissions().isAdmin()) { | ||||
viewBinding.addNewComment.setVisibility(View.GONE); | ||||
} else { | ||||
viewBinding.addNewComment.setVisibility(View.VISIBLE); | ||||
} | ||||
| ||||
viewBinding.addNewComment.setOnClickListener( | ||||
v -> { | ||||
BottomSheetReplyFragment bottomSheetReplyFragment = | ||||
| @ -1106,7 +1113,7 @@ public class IssueDetailActivity extends BaseActivity | |||
LinearLayout.LayoutParams.WRAP_CONTENT); | ||||
paramsLabels.setMargins(0, 0, 15, 0); | ||||
| ||||
if (issue.getIssue().getLabels().size() > 0) { | ||||
if (!issue.getIssue().getLabels().isEmpty()) { | ||||
| ||||
viewBinding.labelsScrollView.setVisibility(View.VISIBLE); | ||||
| ||||
| @ -1298,7 +1305,7 @@ public class IssueDetailActivity extends BaseActivity | |||
if (response.code() == 200) { | ||||
assert attachment != null; | ||||
| ||||
if (attachment.size() > 0) { | ||||
if (!attachment.isEmpty()) { | ||||
| ||||
viewBinding.attachmentFrame.setVisibility(View.VISIBLE); | ||||
LinearLayout.LayoutParams paramsAttachment = | ||||
| |
| @ -31,7 +31,6 @@ import retrofit2.Callback; | |||
*/ | ||||
public class RepositorySettingsActivity extends BaseActivity { | ||||
| ||||
private ActivityRepositorySettingsBinding viewBinding; | ||||
private CustomRepositoryEditPropertiesDialogBinding propBinding; | ||||
private CustomRepositoryDeleteDialogBinding deleteRepoBinding; | ||||
private CustomRepositoryTransferDialogBinding transferRepoBinding; | ||||
| @ -49,11 +48,11 @@ public class RepositorySettingsActivity extends BaseActivity { | |||
| ||||
super.onCreate(savedInstanceState); | ||||
| ||||
viewBinding = ActivityRepositorySettingsBinding.inflate(getLayoutInflater()); | ||||
org.mian.gitnex.databinding.ActivityRepositorySettingsBinding viewBinding = | ||||
ActivityRepositorySettingsBinding.inflate(getLayoutInflater()); | ||||
setContentView(viewBinding.getRoot()); | ||||
| ||||
repository = RepositoryContext.fromIntent(getIntent()); | ||||
; | ||||
| ||||
ImageView closeActivity = findViewById(R.id.close); | ||||
| ||||
| |
| @ -5,14 +5,22 @@ import android.view.LayoutInflater; | |||
import android.view.View; | ||||
import android.view.ViewGroup; | ||||
import android.widget.ImageView; | ||||
import android.widget.LinearLayout; | ||||
import android.widget.TextView; | ||||
import androidx.annotation.NonNull; | ||||
import androidx.core.content.res.ResourcesCompat; | ||||
import androidx.recyclerview.widget.RecyclerView; | ||||
import com.amulyakhare.textdrawable.TextDrawable; | ||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder; | ||||
import java.util.List; | ||||
import org.gitnex.tea4j.v2.models.DeleteEmailOption; | ||||
import org.gitnex.tea4j.v2.models.Email; | ||||
import org.mian.gitnex.R; | ||||
import org.mian.gitnex.clients.RetrofitClient; | ||||
import org.mian.gitnex.helpers.Toasty; | ||||
import retrofit2.Call; | ||||
import retrofit2.Callback; | ||||
import retrofit2.Response; | ||||
| ||||
/** | ||||
* @author M M Arif | ||||
| @ -29,23 +37,28 @@ public class AccountSettingsEmailsAdapter | |||
} | ||||
| ||||
@NonNull @Override | ||||
public AccountSettingsEmailsAdapter.EmailsViewHolder onCreateViewHolder( | ||||
@NonNull ViewGroup parent, int viewType) { | ||||
public EmailsViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { | ||||
View v = | ||||
LayoutInflater.from(parent.getContext()) | ||||
.inflate(R.layout.list_account_settings_emails, parent, false); | ||||
return new AccountSettingsEmailsAdapter.EmailsViewHolder(v); | ||||
return new EmailsViewHolder(v); | ||||
} | ||||
| ||||
@Override | ||||
public void onBindViewHolder( | ||||
@NonNull AccountSettingsEmailsAdapter.EmailsViewHolder holder, int position) { | ||||
public void onBindViewHolder(@NonNull EmailsViewHolder holder, int position) { | ||||
| ||||
Email currentItem = emailsList.get(position); | ||||
| ||||
holder.userEmail.setText(currentItem.getEmail()); | ||||
| ||||
LinearLayout.LayoutParams param = | ||||
new LinearLayout.LayoutParams( | ||||
ViewGroup.LayoutParams.WRAP_CONTENT, | ||||
ViewGroup.LayoutParams.WRAP_CONTENT, | ||||
.1f); | ||||
| ||||
if (currentItem.isPrimary()) { | ||||
holder.primaryFrame.setVisibility(View.VISIBLE); | ||||
TextDrawable drawable = | ||||
TextDrawable.builder() | ||||
.beginConfig() | ||||
| @ -64,9 +77,15 @@ public class AccountSettingsEmailsAdapter | |||
null), | ||||
8); | ||||
holder.emailPrimary.setImageDrawable(drawable); | ||||
holder.deleteFrame.setVisibility(View.GONE); | ||||
holder.primaryFrame.setLayoutParams(param); | ||||
} else { | ||||
holder.emailPrimary.setVisibility(View.GONE); | ||||
holder.primaryFrame.setVisibility(View.GONE); | ||||
holder.deleteFrame.setVisibility(View.VISIBLE); | ||||
} | ||||
| ||||
holder.deleteFrame.setOnClickListener( | ||||
delEmail -> deleteEmailAddress(currentItem.getEmail(), position)); | ||||
} | ||||
| ||||
@Override | ||||
| @ -74,16 +93,88 @@ public class AccountSettingsEmailsAdapter | |||
return emailsList.size(); | ||||
} | ||||
| ||||
static class EmailsViewHolder extends RecyclerView.ViewHolder { | ||||
public static class EmailsViewHolder extends RecyclerView.ViewHolder { | ||||
| ||||
private final ImageView emailPrimary; | ||||
private final TextView userEmail; | ||||
private final LinearLayout deleteFrame; | ||||
private final LinearLayout primaryFrame; | ||||
| ||||
private EmailsViewHolder(View itemView) { | ||||
super(itemView); | ||||
| ||||
emailPrimary = itemView.findViewById(R.id.emailPrimary); | ||||
userEmail = itemView.findViewById(R.id.userEmail); | ||||
deleteFrame = itemView.findViewById(R.id.deleteFrame); | ||||
primaryFrame = itemView.findViewById(R.id.primaryFrame); | ||||
} | ||||
} | ||||
| ||||
private void updateAdapter(int position) { | ||||
emailsList.remove(position); | ||||
notifyItemRemoved(position); | ||||
notifyItemRangeChanged(position, emailsList.size()); | ||||
} | ||||
| ||||
private void deleteEmailAddress(final String emailAddress, int position) { | ||||
| ||||
MaterialAlertDialogBuilder materialAlertDialogBuilder = | ||||
new MaterialAlertDialogBuilder( | ||||
context, R.style.ThemeOverlay_Material3_Dialog_Alert); | ||||
| ||||
DeleteEmailOption deleteEmailOption = new DeleteEmailOption(); | ||||
deleteEmailOption.addEmailsItem(emailAddress); | ||||
| ||||
materialAlertDialogBuilder | ||||
.setMessage( | ||||
String.format( | ||||
context.getString(R.string.deleteEmailPopupText), emailAddress)) | ||||
.setPositiveButton( | ||||
R.string.menuDeleteText, | ||||
(dialog, whichButton) -> | ||||
RetrofitClient.getApiInterface(context) | ||||
.userDeleteEmailWithBody(deleteEmailOption) | ||||
.enqueue( | ||||
new Callback<>() { | ||||
| ||||
@Override | ||||
public void onResponse( | ||||
@NonNull Call<Void> call, | ||||
@NonNull Response<Void> response) { | ||||
| ||||
if (response.isSuccessful()) { | ||||
updateAdapter(position); | ||||
Toasty.success( | ||||
context, | ||||
context.getString( | ||||
R.string | ||||
.deleteEmailSuccess)); | ||||
} else if (response.code() == 403) { | ||||
Toasty.error( | ||||
context, | ||||
context.getString( | ||||
R.string | ||||
.authorizeError)); | ||||
} else { | ||||
Toasty.error( | ||||
context, | ||||
context.getString( | ||||
R.string.genericError)); | ||||
} | ||||
} | ||||
| ||||
@Override | ||||
public void onFailure( | ||||
@NonNull Call<Void> call, | ||||
@NonNull Throwable t) { | ||||
| ||||
Toasty.error( | ||||
context, | ||||
context.getString( | ||||
R.string.genericError)); | ||||
} | ||||
})) | ||||
.setNeutralButton(R.string.cancelButton, null) | ||||
.show(); | ||||
} | ||||
} | ||||
| |
| @ -191,7 +191,7 @@ public class MilestonesAdapter extends RecyclerView.Adapter<RecyclerView.ViewHol | |||
| ||||
Markdown.render(context, dataModel.getTitle(), msTitle); | ||||
| ||||
if (!dataModel.getDescription().equals("")) { | ||||
if (!dataModel.getDescription().isEmpty()) { | ||||
| ||||
Markdown.render( | ||||
context, | ||||
| |
| @ -69,7 +69,7 @@ public class TagsAdapter extends RecyclerView.Adapter<TagsAdapter.TagsViewHolder | |||
| ||||
holder.tagName.setText(currentItem.getName()); | ||||
| ||||
if (!currentItem.getMessage().equals("")) { | ||||
if (!currentItem.getMessage().isEmpty()) { | ||||
Markdown.render(context, currentItem.getMessage(), holder.tagBody); | ||||
} else { | ||||
holder.tagBody.setVisibility(View.GONE); | ||||
| @ -228,7 +228,7 @@ public class TagsAdapter extends RecyclerView.Adapter<TagsAdapter.TagsViewHolder | |||
void onLoadFinished(); | ||||
} | ||||
| ||||
protected class TagsViewHolder extends RecyclerView.ViewHolder { | ||||
public class TagsViewHolder extends RecyclerView.ViewHolder { | ||||
| ||||
private final TextView tagName; | ||||
private final TextView tagBody; | ||||
| |
| @ -2,6 +2,7 @@ package org.mian.gitnex.clients; | |||
| ||||
import android.content.Context; | ||||
import android.util.Log; | ||||
import androidx.annotation.NonNull; | ||||
import com.google.gson.GsonBuilder; | ||||
import java.io.File; | ||||
import java.lang.annotation.Annotation; | ||||
| @ -230,7 +231,9 @@ public class RetrofitClient { | |||
| ||||
@Override | ||||
public Converter<?, String> stringConverter( | ||||
@NotNull Type type, @NotNull Annotation[] annotations, @NotNull Retrofit retrofit) { | ||||
@NotNull Type type, | ||||
@NonNull @NotNull Annotation[] annotations, | ||||
@NotNull Retrofit retrofit) { | ||||
if (type == Date.class) { | ||||
return DateQueryConverter.INSTANCE; | ||||
} | ||||
| |
| @ -127,7 +127,9 @@ public class PullRequestCommitsFragment extends Fragment { | |||
issue.getRepository().getName(), | ||||
(long) issue.getIssueIndex(), | ||||
1, | ||||
resultLimit); | ||||
resultLimit, | ||||
null, | ||||
null); | ||||
| ||||
call.enqueue( | ||||
new Callback<>() { | ||||
| @ -184,7 +186,9 @@ public class PullRequestCommitsFragment extends Fragment { | |||
issue.getRepository().getName(), | ||||
(long) issue.getIssueIndex(), | ||||
page, | ||||
resultLimit); | ||||
resultLimit, | ||||
null, | ||||
null); | ||||
| ||||
call.enqueue( | ||||
new Callback<>() { | ||||
| |
| @ -9,12 +9,23 @@ import android.view.LayoutInflater; | |||
import android.view.View; | ||||
import android.view.ViewGroup; | ||||
import androidx.annotation.NonNull; | ||||
import androidx.appcompat.app.AlertDialog; | ||||
import androidx.fragment.app.Fragment; | ||||
import androidx.lifecycle.ViewModelProvider; | ||||
import androidx.recyclerview.widget.LinearLayoutManager; | ||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder; | ||||
import java.util.Objects; | ||||
import org.gitnex.tea4j.v2.models.CreateKeyOption; | ||||
import org.gitnex.tea4j.v2.models.PublicKey; | ||||
import org.mian.gitnex.R; | ||||
import org.mian.gitnex.adapters.SSHKeysAdapter; | ||||
import org.mian.gitnex.clients.RetrofitClient; | ||||
import org.mian.gitnex.databinding.CustomAccountSettingsAddSshKeyBinding; | ||||
import org.mian.gitnex.databinding.FragmentAccountSettingsSshKeysBinding; | ||||
import org.mian.gitnex.helpers.Toasty; | ||||
import org.mian.gitnex.viewmodels.AccountSettingsSSHKeysViewModel; | ||||
import retrofit2.Call; | ||||
import retrofit2.Callback; | ||||
| ||||
/** | ||||
* @author M M Arif | ||||
| @ -25,6 +36,9 @@ public class SSHKeysFragment extends Fragment { | |||
private Context context; | ||||
private SSHKeysAdapter adapter; | ||||
private AccountSettingsSSHKeysViewModel accountSettingsSSHKeysViewModel; | ||||
private CustomAccountSettingsAddSshKeyBinding newSSHKeyBinding; | ||||
private MaterialAlertDialogBuilder materialAlertDialogBuilder; | ||||
private AlertDialog dialogSaveKey; | ||||
| ||||
@Override | ||||
public View onCreateView( | ||||
| @ -33,6 +47,11 @@ public class SSHKeysFragment extends Fragment { | |||
viewBinding = FragmentAccountSettingsSshKeysBinding.inflate(inflater, container, false); | ||||
context = getContext(); | ||||
| ||||
assert context != null; | ||||
materialAlertDialogBuilder = | ||||
new MaterialAlertDialogBuilder( | ||||
context, R.style.ThemeOverlay_Material3_Dialog_Alert); | ||||
| ||||
accountSettingsSSHKeysViewModel = | ||||
new ViewModelProvider(this).get(AccountSettingsSSHKeysViewModel.class); | ||||
| ||||
| @ -51,9 +70,90 @@ public class SSHKeysFragment extends Fragment { | |||
| ||||
fetchDataAsync(); | ||||
| ||||
viewBinding.addNewSSHKey.setOnClickListener(editProperties -> showNewSSHKeyDialog()); | ||||
| ||||
return viewBinding.getRoot(); | ||||
} | ||||
| ||||
private void showNewSSHKeyDialog() { | ||||
| ||||
newSSHKeyBinding = | ||||
CustomAccountSettingsAddSshKeyBinding.inflate(LayoutInflater.from(context)); | ||||
| ||||
View view = newSSHKeyBinding.getRoot(); | ||||
materialAlertDialogBuilder.setView(view); | ||||
| ||||
newSSHKeyBinding.keyStatus.setOnCheckedChangeListener( | ||||
(buttonView, isChecked) -> { | ||||
if (isChecked) { | ||||
newSSHKeyBinding.keyStatus.setText( | ||||
getString(R.string.sshKeyStatusReadWrite)); | ||||
} else { | ||||
newSSHKeyBinding.keyStatus.setText( | ||||
getString(R.string.sshKeyStatusReadOnly)); | ||||
} | ||||
}); | ||||
| ||||
newSSHKeyBinding.save.setOnClickListener( | ||||
saveKey -> { | ||||
if (Objects.requireNonNull(newSSHKeyBinding.keyTitle.getText()) | ||||
.toString() | ||||
.isEmpty() | ||||
|| Objects.requireNonNull(newSSHKeyBinding.key.getText()) | ||||
.toString() | ||||
.isEmpty()) { | ||||
Toasty.error(context, getString(R.string.emptyFields)); | ||||
} else { | ||||
saveSSHKey( | ||||
String.valueOf(newSSHKeyBinding.keyTitle.getText()), | ||||
String.valueOf(newSSHKeyBinding.key.getText()), | ||||
newSSHKeyBinding.keyStatus.isChecked()); | ||||
} | ||||
}); | ||||
| ||||
dialogSaveKey = materialAlertDialogBuilder.show(); | ||||
} | ||||
| ||||
private void saveSSHKey(String title, String key, boolean keyStatus) { | ||||
| ||||
CreateKeyOption createKeyOption = new CreateKeyOption(); | ||||
createKeyOption.setTitle(title); | ||||
createKeyOption.setKey(key); | ||||
createKeyOption.setReadOnly(keyStatus); | ||||
| ||||
Call<PublicKey> saveNewKey = | ||||
RetrofitClient.getApiInterface(context).userCurrentPostKey(createKeyOption); | ||||
| ||||
saveNewKey.enqueue( | ||||
new Callback<>() { | ||||
| ||||
@Override | ||||
public void onResponse( | ||||
@NonNull Call<PublicKey> call, | ||||
@NonNull retrofit2.Response<PublicKey> response) { | ||||
| ||||
if (response.code() == 202 || response.code() == 201) { | ||||
| ||||
dialogSaveKey.dismiss(); | ||||
accountSettingsSSHKeysViewModel.loadKeysList(context); | ||||
Toasty.success(context, getString(R.string.sshKeySuccess)); | ||||
} else if (response.code() == 422) { | ||||
| ||||
Toasty.error(context, getString(R.string.sshKeyError)); | ||||
} else { | ||||
| ||||
Toasty.error(context, getString(R.string.genericError)); | ||||
} | ||||
} | ||||
| ||||
@Override | ||||
public void onFailure(@NonNull Call<PublicKey> call, @NonNull Throwable t) { | ||||
| ||||
Toasty.error(context, getString(R.string.genericServerResponseError)); | ||||
} | ||||
}); | ||||
} | ||||
| ||||
@SuppressLint("NotifyDataSetChanged") | ||||
private void fetchDataAsync() { | ||||
| ||||
| |
| @ -6,16 +6,22 @@ import android.view.LayoutInflater; | |||
import android.view.View; | ||||
import android.view.ViewGroup; | ||||
import androidx.annotation.NonNull; | ||||
import androidx.appcompat.app.AlertDialog; | ||||
import androidx.fragment.app.Fragment; | ||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder; | ||||
import java.io.IOException; | ||||
import java.util.Locale; | ||||
import okhttp3.ResponseBody; | ||||
import org.gitnex.tea4j.v2.models.Repository; | ||||
import org.gitnex.tea4j.v2.models.User; | ||||
import org.gitnex.tea4j.v2.models.UserSettings; | ||||
import org.gitnex.tea4j.v2.models.UserSettingsOptions; | ||||
import org.mian.gitnex.R; | ||||
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.CustomEditProfileBinding; | ||||
import org.mian.gitnex.databinding.FragmentProfileDetailBinding; | ||||
import org.mian.gitnex.helpers.AlertDialogs; | ||||
import org.mian.gitnex.helpers.AppUtil; | ||||
| @ -39,6 +45,9 @@ public class DetailFragment extends Fragment { | |||
private Context context; | ||||
private FragmentProfileDetailBinding binding; | ||||
private String username; | ||||
private CustomEditProfileBinding customEditProfileBinding; | ||||
private MaterialAlertDialogBuilder materialAlertDialogBuilder; | ||||
private AlertDialog dialogEditSettings; | ||||
| ||||
public DetailFragment() {} | ||||
| ||||
| @ -70,6 +79,10 @@ public class DetailFragment extends Fragment { | |||
getProfileDetail(username); | ||||
getProfileRepository(username); | ||||
| ||||
materialAlertDialogBuilder = | ||||
new MaterialAlertDialogBuilder( | ||||
context, R.style.ThemeOverlay_Material3_Dialog_Alert); | ||||
| ||||
binding.userFollowersCount.setOnClickListener( | ||||
metaFollowersFrame -> | ||||
((ProfileActivity) requireActivity()).viewPager.setCurrentItem(4)); | ||||
| @ -80,9 +93,135 @@ public class DetailFragment extends Fragment { | |||
metaStarredReposFrame -> | ||||
((ProfileActivity) requireActivity()).viewPager.setCurrentItem(2)); | ||||
| ||||
if (username.equals(((BaseActivity) context).getAccount().getAccount().getUserName())) { | ||||
binding.editProfile.setVisibility(View.VISIBLE); | ||||
} else { | ||||
binding.editProfile.setVisibility(View.GONE); | ||||
} | ||||
| ||||
binding.editProfile.setOnClickListener( | ||||
editProfileSettings -> { | ||||
customEditProfileBinding = | ||||
CustomEditProfileBinding.inflate(LayoutInflater.from(context)); | ||||
showEditProfileDialog(); | ||||
}); | ||||
| ||||
return binding.getRoot(); | ||||
} | ||||
| ||||
private void showEditProfileDialog() { | ||||
| ||||
View view = customEditProfileBinding.getRoot(); | ||||
materialAlertDialogBuilder.setView(view); | ||||
| ||||
customEditProfileBinding.save.setOnClickListener( | ||||
saveKey -> | ||||
saveUserProfile( | ||||
String.valueOf(customEditProfileBinding.fullname.getText()), | ||||
String.valueOf(customEditProfileBinding.description.getText()), | ||||
String.valueOf(customEditProfileBinding.location.getText()), | ||||
String.valueOf(customEditProfileBinding.website.getText()), | ||||
customEditProfileBinding.hideEmail.isChecked(), | ||||
customEditProfileBinding.hideActivity.isChecked())); | ||||
| ||||
dialogEditSettings = materialAlertDialogBuilder.show(); | ||||
| ||||
getUserProfileSettings(); | ||||
} | ||||
| ||||
private void saveUserProfile( | ||||
String fullname, | ||||
String description, | ||||
String location, | ||||
String website, | ||||
boolean hideEmail, | ||||
boolean hideActivity) { | ||||
| ||||
UserSettingsOptions userSettings = new UserSettingsOptions(); | ||||
userSettings.setFullName(fullname); | ||||
userSettings.setDescription(description); | ||||
userSettings.setLocation(location); | ||||
userSettings.setWebsite(website); | ||||
userSettings.setHideEmail(hideEmail); | ||||
userSettings.setHideActivity(hideActivity); | ||||
| ||||
Call<UserSettings> saveUserSettings = | ||||
RetrofitClient.getApiInterface(context).customUpdateUserSettings(userSettings); | ||||
| ||||
saveUserSettings.enqueue( | ||||
new Callback<>() { | ||||
| ||||
@Override | ||||
public void onResponse( | ||||
@NonNull Call<UserSettings> call, | ||||
@NonNull retrofit2.Response<UserSettings> response) { | ||||
| ||||
if (response.code() == 200) { | ||||
| ||||
dialogEditSettings.dismiss(); | ||||
getProfileDetail(username); | ||||
Toasty.success(context, getString(R.string.settingsSave)); | ||||
} else { | ||||
| ||||
Toasty.error(context, getString(R.string.genericError)); | ||||
} | ||||
} | ||||
| ||||
@Override | ||||
public void onFailure(@NonNull Call<UserSettings> call, @NonNull Throwable t) { | ||||
| ||||
Toasty.error(context, getString(R.string.genericServerResponseError)); | ||||
} | ||||
}); | ||||
} | ||||
| ||||
public void getUserProfileSettings() { | ||||
| ||||
Call<UserSettings> call1 = RetrofitClient.getApiInterface(context).customGetUserSettings(); | ||||
| ||||
call1.enqueue( | ||||
new Callback<>() { | ||||
| ||||
@Override | ||||
public void onResponse( | ||||
@NonNull Call<UserSettings> call, | ||||
@NonNull retrofit2.Response<UserSettings> response) { | ||||
| ||||
if (response.isSuccessful() && response.body() != null) { | ||||
if (response.code() == 200) { | ||||
| ||||
if (!response.body().getFullName().isEmpty()) { | ||||
customEditProfileBinding.fullname.setText( | ||||
response.body().getFullName()); | ||||
} | ||||
if (!response.body().getDescription().isEmpty()) { | ||||
customEditProfileBinding.fullname.setText( | ||||
response.body().getDescription()); | ||||
} | ||||
if (!response.body().getLocation().isEmpty()) { | ||||
customEditProfileBinding.fullname.setText( | ||||
response.body().getLocation()); | ||||
} | ||||
if (!response.body().getWebsite().isEmpty()) { | ||||
customEditProfileBinding.fullname.setText( | ||||
response.body().getWebsite()); | ||||
} | ||||
customEditProfileBinding.hideEmail.setChecked( | ||||
response.body().isHideEmail()); | ||||
customEditProfileBinding.hideActivity.setChecked( | ||||
response.body().isHideActivity()); | ||||
} | ||||
} | ||||
} | ||||
| ||||
@Override | ||||
public void onFailure(@NonNull Call<UserSettings> call, @NonNull Throwable t) { | ||||
Toasty.error( | ||||
context, context.getResources().getString(R.string.genericError)); | ||||
} | ||||
}); | ||||
} | ||||
| ||||
public void getProfileDetail(String username) { | ||||
| ||||
Call<User> call = RetrofitClient.getApiInterface(context).userGet(username); | ||||
| @ -117,19 +256,19 @@ public class DetailFragment extends Fragment { | |||
binding.userEmail.setText(email); | ||||
| ||||
binding.userFollowersCount.setText( | ||||
String.valueOf( | ||||
String.format( | ||||
response.body().getFollowersCount() | ||||
+ " " | ||||
+ getString( | ||||
R.string.profileTabFollowers))); | ||||
binding.userFollowingCount.setText( | ||||
String.valueOf( | ||||
String.format( | ||||
response.body().getFollowingCount() | ||||
+ " " | ||||
+ getString( | ||||
R.string.profileTabFollowing))); | ||||
binding.userStarredReposCount.setText( | ||||
String.valueOf( | ||||
String.format( | ||||
response.body().getStarredReposCount() | ||||
+ " " | ||||
+ getString(R.string.starredRepos))); | ||||
| |
| @ -41,7 +41,7 @@ public class FileDiffView implements Serializable { | |||
| ||||
public String getFileName() { | ||||
| ||||
if (fileOldName.length() != 0 && !fileOldName.equals(fileNewName)) { | ||||
if (!fileOldName.isEmpty() && !fileOldName.equals(fileNewName)) { | ||||
return fileOldName + " -> " + fileNewName; | ||||
} | ||||
return fileNewName; | ||||
| |
| @ -10,7 +10,6 @@ import android.text.Spanned; | |||
import android.text.method.LinkMovementMethod; | ||||
import android.widget.TextView; | ||||
import androidx.annotation.NonNull; | ||||
import androidx.core.content.res.ResourcesCompat; | ||||
import androidx.recyclerview.widget.LinearLayoutManager; | ||||
import androidx.recyclerview.widget.RecyclerView; | ||||
import io.noties.markwon.AbstractMarkwonPlugin; | ||||
| @ -170,7 +169,6 @@ public class Markdown { | |||
private Context context; | ||||
private String markdown; | ||||
private TextView textView; | ||||
TinyDB tinyDB = TinyDB.getInstance(null); | ||||
| ||||
public Renderer(Slot slot) { | ||||
| ||||
| @ -233,42 +231,25 @@ public class Markdown { | |||
public void configureTheme( | ||||
@NonNull MarkwonTheme.Builder builder) { | ||||
| ||||
builder.codeBlockTypeface( | ||||
Typeface.createFromAsset( | ||||
context.getAssets(), | ||||
"fonts/sourcecodeproregular.ttf")); | ||||
builder.codeBlockMargin( | ||||
(int) | ||||
(context.getResources() | ||||
.getDisplayMetrics() | ||||
.density | ||||
* 10)); | ||||
builder.blockMargin( | ||||
(int) | ||||
(context.getResources() | ||||
.getDisplayMetrics() | ||||
.density | ||||
* 10)); | ||||
| ||||
builder.codeTextSize( | ||||
(int) | ||||
(context.getResources() | ||||
.getDisplayMetrics() | ||||
.scaledDensity | ||||
* 13)); | ||||
builder.codeTypeface( | ||||
Typeface.createFromAsset( | ||||
context.getAssets(), | ||||
"fonts/sourcecodeproregular.ttf")); | ||||
builder.linkColor( | ||||
ResourcesCompat.getColor( | ||||
context.getResources(), | ||||
R.color.lightBlue, | ||||
null)); | ||||
| ||||
if (tf == null) { | ||||
tf = AppUtil.getTypeface(context); | ||||
} | ||||
builder.headingTypeface(tf); | ||||
builder.headingTypeface( | ||||
Typeface.create(tf, Typeface.BOLD)); | ||||
} | ||||
}); | ||||
| ||||
| @ -320,7 +301,6 @@ public class Markdown { | |||
private RecyclerView recyclerView; | ||||
private MarkwonAdapter adapter; | ||||
private RepositoryContext repository; | ||||
TinyDB tinyDB = TinyDB.getInstance(null); | ||||
| ||||
private LinkPostProcessor linkPostProcessor; | ||||
| ||||
| @ -350,7 +330,7 @@ public class Markdown { | |||
Markwon.builder(context) | ||||
.usePlugin(CorePlugin.create()) | ||||
.usePlugin(HtmlPlugin.create()) | ||||
.usePlugin(LinkifyPlugin.create(true)) // TODO not working | ||||
.usePlugin(LinkifyPlugin.create(true)) | ||||
.usePlugin(SoftBreakAddsNewLinePlugin.create()) | ||||
.usePlugin(TableEntryPlugin.create(context)) | ||||
.usePlugin( | ||||
| @ -410,37 +390,19 @@ public class Markdown { | |||
public void configureTheme( | ||||
@NonNull MarkwonTheme.Builder builder) { | ||||
| ||||
builder.codeBlockTypeface( | ||||
Typeface.createFromAsset( | ||||
context.getAssets(), | ||||
"fonts/sourcecodeproregular.ttf")); | ||||
builder.codeBlockMargin( | ||||
(int) | ||||
(context.getResources() | ||||
.getDisplayMetrics() | ||||
.density | ||||
* 10)); | ||||
builder.blockMargin( | ||||
(int) | ||||
(context.getResources() | ||||
.getDisplayMetrics() | ||||
.density | ||||
* 10)); | ||||
| ||||
builder.codeTextSize( | ||||
(int) | ||||
(context.getResources() | ||||
.getDisplayMetrics() | ||||
.scaledDensity | ||||
* 13)); | ||||
builder.codeTypeface( | ||||
Typeface.createFromAsset( | ||||
context.getAssets(), | ||||
"fonts/sourcecodeproregular.ttf")); | ||||
builder.linkColor( | ||||
ResourcesCompat.getColor( | ||||
context.getResources(), | ||||
R.color.lightBlue, | ||||
null)); | ||||
| ||||
if (tf == null) { | ||||
tf = AppUtil.getTypeface(context); | ||||
| |
| @ -1,5 +1,6 @@ | |||
package org.mian.gitnex.helpers; | ||||
| ||||
import androidx.annotation.NonNull; | ||||
import java.io.Serializable; | ||||
| ||||
/** | ||||
| @ -31,7 +32,7 @@ public class MergePullRequestSpinner implements Serializable { | |||
this.mergerMethod = mergerMethod; | ||||
} | ||||
| ||||
@Override | ||||
@NonNull @Override | ||||
public String toString() { | ||||
return mergerMethod; | ||||
} | ||||
| |
| @ -29,7 +29,6 @@ public class PicassoCache implements Cache { | |||
private final int CACHE_SIZE; | ||||
private final File cachePath; | ||||
private final HashMap<String, String> cacheMap; | ||||
private final Context ctx; | ||||
| ||||
public PicassoCache(File cachePath, Context ctx) throws IOException, ClassNotFoundException { | ||||
| ||||
| @ -41,7 +40,6 @@ public class PicassoCache implements Cache { | |||
* 1024; | ||||
this.cachePath = cachePath; | ||||
cacheMap = new HashMap<>(); | ||||
this.ctx = ctx; | ||||
| ||||
if (cacheMapExists(cachePath)) { | ||||
| ||||
| @ -104,7 +102,8 @@ public class PicassoCache implements Cache { | |||
| ||||
for (String key : cacheMap.keySet()) { | ||||
| ||||
currentSize += new File(cachePath, Objects.requireNonNull(cacheMap.get(key))).length(); | ||||
currentSize += | ||||
(int) new File(cachePath, Objects.requireNonNull(cacheMap.get(key))).length(); | ||||
} | ||||
| ||||
return currentSize; | ||||
| |
| @ -1,7 +1,9 @@ | |||
package org.mian.gitnex.helpers; | ||||
| ||||
import android.util.Log; | ||||
import java.net.URI; | ||||
import java.net.URISyntaxException; | ||||
import java.util.Objects; | ||||
| ||||
/** | ||||
* @author M M Arif | ||||
| @ -14,7 +16,7 @@ public class UrlHelper { | |||
try { | ||||
uri = new URI(url); | ||||
} catch (URISyntaxException e) { | ||||
e.printStackTrace(); | ||||
Log.e("UrlHelper", Objects.requireNonNull(e.getMessage())); | ||||
} | ||||
| ||||
assert uri != null; | ||||
| |
| @ -28,8 +28,8 @@ public class Version { | |||
/** | ||||
* valid return true if string is a valid version | ||||
* | ||||
* @param value | ||||
* @return | ||||
* @param value String | ||||
* @return true/false | ||||
*/ | ||||
public static boolean valid(String value) { | ||||
| ||||
| @ -37,15 +37,13 @@ public class Version { | |||
return false; | ||||
} | ||||
final Pattern patternValid = | ||||
Pattern.compile("^[vV]?(\\d+)+(\\.(\\d+))*([_\\-+][\\w\\-+\\.]*)?$"); | ||||
Pattern.compile("^[vV]?(\\d+)+(\\.(\\d+))*([_\\-+][\\w\\-+.]*)?$"); | ||||
return value.equals("main") || patternValid.matcher(value).find(); | ||||
} | ||||
| ||||
/** | ||||
* init parse and store values for other functions of an Version instance it use the raw | ||||
* variable as base | ||||
* | ||||
* @return if parse was successfully | ||||
*/ | ||||
private void init() { | ||||
| ||||
| @ -84,7 +82,7 @@ public class Version { | |||
/** | ||||
* equal return true if version is the same | ||||
* | ||||
* @param value | ||||
* @param value String | ||||
* @return true/false | ||||
*/ | ||||
public boolean equal(String value) { | ||||
| @ -95,8 +93,8 @@ public class Version { | |||
/** | ||||
* equal return true if version is the same | ||||
* | ||||
* @param v | ||||
* @return | ||||
* @param v Version | ||||
* @return true/false | ||||
*/ | ||||
public boolean equal(@NonNull Version v) { | ||||
| ||||
| @ -116,7 +114,7 @@ public class Version { | |||
/** | ||||
* less return true if version is less | ||||
* | ||||
* @param value | ||||
* @param value String | ||||
* @return true/false | ||||
*/ | ||||
public boolean less(String value) { | ||||
| @ -127,8 +125,8 @@ public class Version { | |||
/** | ||||
* less return true if version is less | ||||
* | ||||
* @param v | ||||
* @return | ||||
* @param v Version | ||||
* @return true/false | ||||
*/ | ||||
public boolean less(@NonNull Version v) { | ||||
| ||||
| @ -138,7 +136,7 @@ public class Version { | |||
/** | ||||
* higher return true if version is higher | ||||
* | ||||
* @param value | ||||
* @param value String | ||||
* @return true/false | ||||
*/ | ||||
public boolean higher(String value) { | ||||
| @ -149,8 +147,8 @@ public class Version { | |||
/** | ||||
* higher return true if version is higher | ||||
* | ||||
* @param v | ||||
* @return | ||||
* @param v Version | ||||
* @return true/false | ||||
*/ | ||||
public boolean higher(@NonNull Version v) { | ||||
| ||||
| @ -180,7 +178,7 @@ public class Version { | |||
/** | ||||
* lessOrEqual return true if version is less or equal | ||||
* | ||||
* @param value | ||||
* @param value String | ||||
* @return true/false | ||||
*/ | ||||
public boolean lessOrEqual(String value) { | ||||
| @ -191,8 +189,8 @@ public class Version { | |||
/** | ||||
* lessOrEqual return true if version is less or equal | ||||
* | ||||
* @param v | ||||
* @return | ||||
* @param v Version | ||||
* @return true/false | ||||
*/ | ||||
public boolean lessOrEqual(@NonNull Version v) { | ||||
| ||||
| @ -202,7 +200,7 @@ public class Version { | |||
/** | ||||
* higherOrEqual return true if version is higher or equal | ||||
* | ||||
* @param value | ||||
* @param value String | ||||
* @return true/false | ||||
*/ | ||||
public boolean higherOrEqual(String value) { | ||||
| @ -213,8 +211,8 @@ public class Version { | |||
/** | ||||
* higherOrEqual return true if version is higher or equal | ||||
* | ||||
* @param v | ||||
* @return | ||||
* @param v Version | ||||
* @return true/false | ||||
*/ | ||||
public boolean higherOrEqual(@NonNull Version v) { | ||||
| ||||
| |
| @ -4,10 +4,12 @@ import android.content.Context; | |||
import android.database.Cursor; | ||||
import android.net.Uri; | ||||
import android.provider.OpenableColumns; | ||||
import android.util.Log; | ||||
import java.io.File; | ||||
import java.io.FileOutputStream; | ||||
import java.io.InputStream; | ||||
import java.io.OutputStream; | ||||
import java.util.Objects; | ||||
| ||||
/** | ||||
* @author M M Arif | ||||
| @ -22,13 +24,13 @@ public class AttachmentUtils { | |||
new File( | ||||
ctx.getFilesDir().getPath() + File.separatorChar + queryName(ctx, uri)); | ||||
} catch (AssertionError e) { | ||||
destinationFilename = new File(uri.getPath()); | ||||
destinationFilename = new File(Objects.requireNonNull(uri.getPath())); | ||||
} | ||||
| ||||
try (InputStream ins = ctx.getContentResolver().openInputStream(uri)) { | ||||
createFileFromStream(ins, destinationFilename); | ||||
} catch (Exception ex) { | ||||
ex.printStackTrace(); | ||||
Log.e("AttachmentUtils", Objects.requireNonNull(ex.getMessage())); | ||||
} | ||||
return destinationFilename; | ||||
} | ||||
| @ -54,7 +56,7 @@ public class AttachmentUtils { | |||
} | ||||
os.flush(); | ||||
} catch (Exception ex) { | ||||
ex.printStackTrace(); | ||||
Log.e("AttachmentUtils", Objects.requireNonNull(ex.getMessage())); | ||||
} | ||||
} | ||||
} | ||||
| |
| @ -4,6 +4,7 @@ import android.text.Layout; | |||
import android.view.View; | ||||
import android.view.accessibility.AccessibilityEvent; | ||||
import android.widget.EditText; | ||||
import androidx.annotation.NonNull; | ||||
| ||||
/** | ||||
* @author AmrDeveloper | ||||
| @ -18,7 +19,7 @@ public class SourcePositionListener { | |||
new View.AccessibilityDelegate() { | ||||
| ||||
@Override | ||||
public void sendAccessibilityEvent(View host, int eventType) { | ||||
public void sendAccessibilityEvent(@NonNull View host, int eventType) { | ||||
super.sendAccessibilityEvent(host, eventType); | ||||
if (eventType == AccessibilityEvent.TYPE_VIEW_TEXT_SELECTION_CHANGED | ||||
&& onPositionChanged != null) { | ||||
| |
| @ -74,7 +74,7 @@ public class AccountContext implements Serializable { | |||
| ||||
public String getFullName() { | ||||
return userInfo != null | ||||
? !userInfo.getFullName().equals("") ? userInfo.getFullName() : userInfo.getLogin() | ||||
? !userInfo.getFullName().isEmpty() ? userInfo.getFullName() : userInfo.getLogin() | ||||
: account.getUserName(); | ||||
} | ||||
| ||||
| |
| @ -38,7 +38,7 @@ public class LanguageStatisticsBar extends androidx.appcompat.widget.AppCompatSe | |||
| ||||
protected void onDraw(Canvas canvas) { | ||||
| ||||
if (progressItemsList.size() > 0) { | ||||
if (!progressItemsList.isEmpty()) { | ||||
| ||||
int progressBarWidth = getWidth(); | ||||
int progressBarHeight = getHeight(); | ||||
| |
| @ -77,7 +77,7 @@ public class AdminGetUsersViewModel extends ViewModel { | |||
assert list != null; | ||||
assert response.body() != null; | ||||
| ||||
if (response.body().size() != 0) { | ||||
if (!response.body().isEmpty()) { | ||||
list.addAll(response.body()); | ||||
adapter.updateList(list); | ||||
} else { | ||||
| |
| @ -91,7 +91,7 @@ public class DashboardViewModel extends ViewModel { | |||
assert list != null; | ||||
assert response.body() != null; | ||||
| ||||
if (response.body().size() != 0) { | ||||
if (!response.body().isEmpty()) { | ||||
list.addAll(response.body()); | ||||
adapter.updateList(list); | ||||
} else { | ||||
| |
| @ -101,7 +101,7 @@ public class IssueCommentsViewModel extends ViewModel { | |||
assert list != null; | ||||
assert response.body() != null; | ||||
| ||||
if (response.body().size() != 0) { | ||||
if (!response.body().isEmpty()) { | ||||
list.addAll(response.body()); | ||||
adapter.updateList(list); | ||||
} else { | ||||
| |
| @ -135,7 +135,7 @@ public class IssuesViewModel extends ViewModel { | |||
assert list != null; | ||||
assert response.body() != null; | ||||
| ||||
if (response.body().size() != 0) { | ||||
if (!response.body().isEmpty()) { | ||||
list.addAll(response.body()); | ||||
adapter.updateList(list); | ||||
} else { | ||||
| |
| @ -50,7 +50,7 @@ public class MembersByOrgViewModel extends ViewModel { | |||
} | ||||
| ||||
@Override | ||||
public void onFailure(@NonNull Call<List<User>> call, Throwable t) { | ||||
public void onFailure(@NonNull Call<List<User>> call, @NonNull Throwable t) { | ||||
| ||||
Toasty.error(ctx, ctx.getString(R.string.genericServerResponseError)); | ||||
} | ||||
| |
| @ -92,7 +92,7 @@ public class MilestonesViewModel extends ViewModel { | |||
assert list != null; | ||||
assert response.body() != null; | ||||
| ||||
if (response.body().size() != 0) { | ||||
if (!response.body().isEmpty()) { | ||||
list.addAll(response.body()); | ||||
adapter.updateList(list); | ||||
} else { | ||||
| |
| @ -78,7 +78,7 @@ public class OrganizationsViewModel extends ViewModel { | |||
assert list != null; | ||||
assert response.body() != null; | ||||
| ||||
if (response.body().size() != 0) { | ||||
if (!response.body().isEmpty()) { | ||||
list.addAll(response.body()); | ||||
adapter.updateList(list); | ||||
} else { | ||||
| |
| @ -39,7 +39,7 @@ public class ReleasesViewModel extends ViewModel { | |||
| ||||
Call<List<Release>> call = | ||||
RetrofitClient.getApiInterface(ctx) | ||||
.repoListReleases(owner, repo, null, null, null, 1, resultLimit); | ||||
.repoListReleases(owner, repo, null, null, 1, resultLimit); | ||||
| ||||
call.enqueue( | ||||
new Callback<>() { | ||||
| @ -69,7 +69,7 @@ public class ReleasesViewModel extends ViewModel { | |||
| ||||
Call<List<Release>> call = | ||||
RetrofitClient.getApiInterface(ctx) | ||||
.repoListReleases(owner, repo, null, null, null, page, resultLimit); | ||||
.repoListReleases(owner, repo, null, null, page, resultLimit); | ||||
| ||||
call.enqueue( | ||||
new Callback<>() { | ||||
| @ -84,7 +84,7 @@ public class ReleasesViewModel extends ViewModel { | |||
assert list != null; | ||||
assert response.body() != null; | ||||
| ||||
if (response.body().size() != 0) { | ||||
if (!response.body().isEmpty()) { | ||||
list.addAll(response.body()); | ||||
adapter.updateList(list); | ||||
} else { | ||||
| @ -157,7 +157,7 @@ public class ReleasesViewModel extends ViewModel { | |||
assert list != null; | ||||
assert response.body() != null; | ||||
| ||||
if (response.body().size() != 0) { | ||||
if (!response.body().isEmpty()) { | ||||
list.addAll(response.body()); | ||||
adapter.updateList(list); | ||||
} else { | ||||
| |
| @ -51,7 +51,7 @@ public class RepoWatchersViewModel extends ViewModel { | |||
} | ||||
| ||||
@Override | ||||
public void onFailure(@NonNull Call<List<User>> call, Throwable t) { | ||||
public void onFailure(@NonNull Call<List<User>> call, @NonNull Throwable t) { | ||||
| ||||
Toasty.error(ctx, ctx.getString(R.string.genericServerResponseError)); | ||||
} | ||||
| |
| @ -157,7 +157,7 @@ public class RepositoriesViewModel extends ViewModel { | |||
assert list != null; | ||||
assert response.body() != null; | ||||
| ||||
if (response.body().size() != 0) { | ||||
if (!response.body().isEmpty()) { | ||||
list.addAll(response.body()); | ||||
adapter.updateList(list); | ||||
} else { | ||||
| |
| @ -82,7 +82,7 @@ public class RepositoryForksViewModel extends ViewModel { | |||
assert list != null; | ||||
assert response.body() != null; | ||||
| ||||
if (response.body().size() != 0) { | ||||
if (!response.body().isEmpty()) { | ||||
list.addAll(response.body()); | ||||
adapter.updateList(list); | ||||
} else { | ||||
| |
| @ -116,7 +116,7 @@ public class WikiViewModel extends ViewModel { | |||
assert list != null; | ||||
assert response.body() != null; | ||||
| ||||
if (response.body().size() != 0) { | ||||
if (!response.body().isEmpty()) { | ||||
list.addAll(response.body()); | ||||
adapter.updateList(list); | ||||
} else { | ||||
| |
| @ -20,6 +20,7 @@ import java.util.ArrayList; | |||
import java.util.HashMap; | ||||
import java.util.List; | ||||
import java.util.Map; | ||||
import java.util.Objects; | ||||
import java.util.stream.Collectors; | ||||
import org.gitnex.tea4j.v2.models.Reaction; | ||||
import org.gitnex.tea4j.v2.models.User; | ||||
| @ -108,8 +109,9 @@ public class ReactionList extends HorizontalScrollView { | |||
| ||||
if (sortedReactions.containsKey( | ||||
issueReaction.getContent())) { | ||||
sortedReactions | ||||
.get(issueReaction.getContent()) | ||||
Objects.requireNonNull( | ||||
sortedReactions.get( | ||||
issueReaction.getContent())) | ||||
.add(issueReaction); | ||||
} else { | ||||
List<Reaction> issueReactions = new ArrayList<>(); | ||||
| @ -135,6 +137,7 @@ public class ReactionList extends HorizontalScrollView { | |||
this, | ||||
false); | ||||
| ||||
assert issueReactions != null; | ||||
for (Reaction issueReaction : issueReactions) { | ||||
if (issueReaction | ||||
.getUser() | ||||
| |
| @ -100,7 +100,7 @@ public class SyntaxHighlightedArea extends LinearLayout { | |||
| ||||
public void setContent(@NonNull String source, @NonNull String extension) { | ||||
| ||||
if (source.length() > 0) { | ||||
if (!source.isEmpty()) { | ||||
| ||||
Thread highlightingThread = | ||||
new Thread( | ||||
| |
| @ -91,7 +91,7 @@ | |||
android:layout_height="wrap_content" | ||||
android:layout_marginTop="@dimen/dimen8dp" | ||||
android:layout_marginBottom="@dimen/dimen8dp" | ||||
android:hint="@string/newIssueDescriptionTitle" | ||||
android:hint="@string/description" | ||||
android:textColorHint="?attr/hintColor" | ||||
app:boxStrokeErrorColor="@color/darkRed" | ||||
app:endIconMode="clear_text" | ||||
| |
| @ -79,7 +79,7 @@ | |||
android:layout_height="wrap_content" | ||||
android:layout_marginTop="@dimen/dimen8dp" | ||||
android:layout_marginBottom="@dimen/dimen8dp" | ||||
android:hint="@string/newMilestoneDescription" | ||||
android:hint="@string/description" | ||||
android:textColorHint="?attr/hintColor" | ||||
app:boxStrokeErrorColor="@color/darkRed" | ||||
app:counterEnabled="true" | ||||
| |
| @ -91,7 +91,7 @@ | |||
android:layout_height="wrap_content" | ||||
android:layout_marginTop="@dimen/dimen8dp" | ||||
android:layout_marginBottom="@dimen/dimen8dp" | ||||
android:hint="@string/newIssueDescriptionTitle" | ||||
android:hint="@string/description" | ||||
android:textColorHint="?attr/hintColor" | ||||
app:boxStrokeErrorColor="@color/darkRed" | ||||
app:endIconMode="clear_text" | ||||
| |
| @ -79,7 +79,7 @@ | |||
android:layout_height="wrap_content" | ||||
android:layout_marginTop="@dimen/dimen8dp" | ||||
android:layout_marginBottom="@dimen/dimen8dp" | ||||
android:hint="@string/newTeamDesc" | ||||
android:hint="@string/description" | ||||
android:textColorHint="?attr/hintColor" | ||||
app:boxStrokeErrorColor="@color/darkRed" | ||||
app:counterEnabled="true" | ||||
| |
| @ -79,7 +79,7 @@ | |||
android:layout_height="wrap_content" | ||||
android:layout_marginTop="@dimen/dimen8dp" | ||||
android:layout_marginBottom="@dimen/dimen8dp" | ||||
android:hint="@string/newIssueDescriptionTitle" | ||||
android:hint="@string/description" | ||||
android:textColorHint="?attr/hintColor" | ||||
app:boxStrokeErrorColor="@color/darkRed" | ||||
app:endIconMode="clear_text" | ||||
| |
| @ -0,0 +1,97 @@ | |||
<?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:orientation="vertical"> | ||||
| ||||
<com.google.android.material.textfield.TextInputLayout | ||||
android:id="@+id/keyTitleLayout" | ||||
android:layout_width="match_parent" | ||||
android:layout_height="wrap_content" | ||||
android:layout_marginBottom="8dp" | ||||
android:hint="@string/title" | ||||
android:textColorHint="?attr/hintColor" | ||||
app:boxBackgroundColor="?attr/inputBackgroundColor" | ||||
app:boxStrokeErrorColor="@color/darkRed" | ||||
app:hintTextColor="?attr/hintColor"> | ||||
| ||||
<com.google.android.material.textfield.TextInputEditText | ||||
android:id="@+id/keyTitle" | ||||
android:layout_width="match_parent" | ||||
android:layout_height="wrap_content" | ||||
android:textColor="?attr/inputTextColor" | ||||
android:textColorHighlight="?attr/hintColor" | ||||
android:textColorHint="?attr/hintColor" | ||||
android:textSize="16sp" /> | ||||
| ||||
</com.google.android.material.textfield.TextInputLayout> | ||||
| ||||
<com.google.android.material.textfield.TextInputLayout | ||||
android:id="@+id/keyLayout" | ||||
android:layout_width="match_parent" | ||||
android:layout_height="wrap_content" | ||||
android:layout_marginTop="8dp" | ||||
android:layout_marginBottom="8dp" | ||||
android:hint="@string/sshKey" | ||||
android:textColorHint="?attr/hintColor" | ||||
app:boxBackgroundColor="?attr/inputBackgroundColor" | ||||
app:boxStrokeErrorColor="@color/darkRed" | ||||
app:hintTextColor="?attr/hintColor"> | ||||
| ||||
<com.google.android.material.textfield.TextInputEditText | ||||
android:id="@+id/key" | ||||
android:layout_width="match_parent" | ||||
android:layout_height="wrap_content" | ||||
android:gravity="top|start" | ||||
android:minHeight="@dimen/dimen180dp" | ||||
android:singleLine="false" | ||||
android:textColor="?attr/inputTextColor" | ||||
android:textColorHighlight="?attr/hintColor" | ||||
android:textColorHint="?attr/hintColor" | ||||
android:textSize="16sp" /> | ||||
| ||||
</com.google.android.material.textfield.TextInputLayout> | ||||
| ||||
<com.google.android.material.materialswitch.MaterialSwitch | ||||
android:id="@+id/keyStatus" | ||||
android:layout_width="match_parent" | ||||
android:layout_height="match_parent" | ||||
android:checked="false" | ||||
android:text="@string/sshKeyStatusReadOnly" | ||||
android:textColor="?attr/primaryTextColor" /> | ||||
| ||||
</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> |
149 app/src/main/res/layout/custom_edit_profile.xml Normal file
149
app/src/main/res/layout/custom_edit_profile.xml Normal file | @ -0,0 +1,149 @@ | |||
<?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:orientation="vertical"> | ||||
| ||||
<com.google.android.material.textfield.TextInputLayout | ||||
android:id="@+id/fullnameLayout" | ||||
android:layout_width="match_parent" | ||||
android:layout_height="wrap_content" | ||||
android:layout_marginBottom="8dp" | ||||
android:hint="@string/userFullNameText" | ||||
android:textColorHint="?attr/hintColor" | ||||
app:boxBackgroundColor="?attr/inputBackgroundColor" | ||||
app:boxStrokeErrorColor="@color/darkRed" | ||||
app:hintTextColor="?attr/hintColor"> | ||||
| ||||
<com.google.android.material.textfield.TextInputEditText | ||||
android:id="@+id/fullname" | ||||
android:layout_width="match_parent" | ||||
android:layout_height="wrap_content" | ||||
android:textColor="?attr/inputTextColor" | ||||
android:textColorHighlight="?attr/hintColor" | ||||
android:textColorHint="?attr/hintColor" | ||||
android:textSize="16sp" /> | ||||
| ||||
</com.google.android.material.textfield.TextInputLayout> | ||||
| ||||
<com.google.android.material.textfield.TextInputLayout | ||||
android:id="@+id/descriptionLayout" | ||||
android:layout_width="match_parent" | ||||
android:layout_height="wrap_content" | ||||
android:layout_marginTop="8dp" | ||||
android:layout_marginBottom="8dp" | ||||
android:hint="@string/description" | ||||
android:textColorHint="?attr/hintColor" | ||||
app:boxBackgroundColor="?attr/inputBackgroundColor" | ||||
app:boxStrokeErrorColor="@color/darkRed" | ||||
app:hintTextColor="?attr/hintColor"> | ||||
| ||||
<com.google.android.material.textfield.TextInputEditText | ||||
android:id="@+id/description" | ||||
android:layout_width="match_parent" | ||||
android:layout_height="wrap_content" | ||||
android:gravity="top|start" | ||||
android:minHeight="@dimen/dimen80dp" | ||||
android:singleLine="false" | ||||
android:textColor="?attr/inputTextColor" | ||||
android:textColorHighlight="?attr/hintColor" | ||||
android:textColorHint="?attr/hintColor" | ||||
android:textSize="16sp" /> | ||||
| ||||
</com.google.android.material.textfield.TextInputLayout> | ||||
| ||||
<com.google.android.material.textfield.TextInputLayout | ||||
android:id="@+id/locationLayout" | ||||
android:layout_width="match_parent" | ||||
android:layout_height="wrap_content" | ||||
android:layout_marginBottom="8dp" | ||||
android:hint="@string/locationText" | ||||
android:textColorHint="?attr/hintColor" | ||||
app:boxBackgroundColor="?attr/inputBackgroundColor" | ||||
app:boxStrokeErrorColor="@color/darkRed" | ||||
app:hintTextColor="?attr/hintColor"> | ||||
| ||||
<com.google.android.material.textfield.TextInputEditText | ||||
android:id="@+id/location" | ||||
android:layout_width="match_parent" | ||||
android:layout_height="wrap_content" | ||||
android:textColor="?attr/inputTextColor" | ||||
android:textColorHighlight="?attr/hintColor" | ||||
android:textColorHint="?attr/hintColor" | ||||
android:textSize="16sp" /> | ||||
| ||||
</com.google.android.material.textfield.TextInputLayout> | ||||
| ||||
<com.google.android.material.textfield.TextInputLayout | ||||
android:id="@+id/websiteLayout" | ||||
android:layout_width="match_parent" | ||||
android:layout_height="wrap_content" | ||||
android:layout_marginBottom="8dp" | ||||
android:hint="@string/websiteText" | ||||
android:textColorHint="?attr/hintColor" | ||||
app:boxBackgroundColor="?attr/inputBackgroundColor" | ||||
app:boxStrokeErrorColor="@color/darkRed" | ||||
app:hintTextColor="?attr/hintColor"> | ||||
| ||||
<com.google.android.material.textfield.TextInputEditText | ||||
android:id="@+id/website" | ||||
android:layout_width="match_parent" | ||||
android:layout_height="wrap_content" | ||||
android:textColor="?attr/inputTextColor" | ||||
android:textColorHighlight="?attr/hintColor" | ||||
android:textColorHint="?attr/hintColor" | ||||
android:textSize="16sp" /> | ||||
| ||||
</com.google.android.material.textfield.TextInputLayout> | ||||
| ||||
<com.google.android.material.materialswitch.MaterialSwitch | ||||
android:id="@+id/hideEmail" | ||||
android:layout_width="match_parent" | ||||
android:layout_height="match_parent" | ||||
android:checked="true" | ||||
android:text="@string/hideEmail" | ||||
android:textColor="?attr/primaryTextColor" /> | ||||
| ||||
<com.google.android.material.materialswitch.MaterialSwitch | ||||
android:id="@+id/hideActivity" | ||||
android:layout_width="match_parent" | ||||
android:layout_height="match_parent" | ||||
android:checked="true" | ||||
android:text="@string/hideActivity" | ||||
android:textColor="?attr/primaryTextColor" /> | ||||
| ||||
</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> |
| @ -105,7 +105,7 @@ | |||
android:layout_marginTop="@dimen/dimen16dp" | ||||
android:backgroundTint="@color/darkRed" | ||||
android:text="@string/repoTransferText" | ||||
android:textColor="?attr/materialCardBackgroundColor" | ||||
android:textColor="@color/colorWhite" | ||||
android:textSize="16sp" | ||||
android:textStyle="bold"/> | ||||
| ||||
| |
| @ -44,4 +44,17 @@ | |||
android:textSize="@dimen/dimen20sp" | ||||
android:visibility="gone" /> | ||||
| ||||
<com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton | ||||
android:id="@+id/addNewSSHKey" | ||||
android:layout_width="wrap_content" | ||||
android:layout_height="wrap_content" | ||||
android:layout_gravity="end|bottom" | ||||
android:layout_margin="@dimen/dimen16dp" | ||||
android:text="@string/addSSHKey" | ||||
android:contentDescription="@string/addSSHKey" | ||||
android:textColor="?attr/materialCardBackgroundColor" | ||||
app:iconTint="?attr/materialCardBackgroundColor" | ||||
android:backgroundTint="?attr/fabColor" | ||||
app:icon="@drawable/ic_add" /> | ||||
| ||||
</androidx.coordinatorlayout.widget.CoordinatorLayout> | ||||
| |
| @ -91,6 +91,18 @@ | |||
android:textIsSelectable="true" | ||||
android:textSize="@dimen/dimen14sp" /> | ||||
| ||||
<com.google.android.material.button.MaterialButton | ||||
android:id="@+id/editProfile" | ||||
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" /> | ||||
| ||||
</LinearLayout> | ||||
| ||||
</LinearLayout> | ||||
| |
| @ -18,30 +18,62 @@ | |||
style="?attr/materialCardViewFilledStyle"> | ||||
| ||||
<LinearLayout | ||||
android:layout_width="match_parent" | ||||
android:layout_height="wrap_content" | ||||
android:background="?attr/materialCardBackgroundColor" | ||||
android:padding="@dimen/dimen12dp" | ||||
android:layout_width="match_parent" | ||||
android:layout_height="wrap_content" | ||||
android:layout_weight="1" | ||||
android:orientation="horizontal"> | ||||
| ||||
<TextView | ||||
android:id="@+id/userEmail" | ||||
android:layout_width="wrap_content" | ||||
android:layout_height="wrap_content" | ||||
android:layout_weight=".7" | ||||
android:text="@string/accountEmailTitle" | ||||
android:textColor="?attr/primaryTextColor" | ||||
android:textSize="@dimen/dimen16sp"/> | ||||
| ||||
<ImageView | ||||
android:id="@+id/emailPrimary" | ||||
<LinearLayout | ||||
android:id="@+id/primaryFrame" | ||||
android:layout_width="wrap_content" | ||||
android:layout_height="wrap_content" | ||||
android:layout_gravity="start" | ||||
android:layout_marginTop="@dimen/dimen2dp" | ||||
android:contentDescription="@string/emailTypeText" | ||||
android:paddingStart="@dimen/dimen20dp" | ||||
android:paddingEnd="@dimen/dimen2dp" | ||||
tools:src="@drawable/ic_verified_user"/> | ||||
android:layout_weight=".2" | ||||
android:gravity="end" | ||||
android:visibility="gone"> | ||||
| ||||
<ImageView | ||||
android:id="@+id/emailPrimary" | ||||
android:layout_width="wrap_content" | ||||
android:layout_height="wrap_content" | ||||
android:paddingTop="@dimen/dimen2dp" | ||||
android:contentDescription="@string/emailTypeText" | ||||
android:paddingStart="@dimen/dimen2dp" | ||||
android:paddingEnd="@dimen/dimen2dp" | ||||
tools:src="@drawable/ic_verified_user" /> | ||||
| ||||
</LinearLayout> | ||||
| ||||
<LinearLayout | ||||
android:id="@+id/deleteFrame" | ||||
android:layout_width="wrap_content" | ||||
android:layout_height="wrap_content" | ||||
android:layout_weight=".1" | ||||
android:gravity="end" | ||||
android:visibility="gone"> | ||||
| ||||
<ImageView | ||||
android:id="@+id/deleteEmail" | ||||
android:layout_width="@dimen/dimen18dp" | ||||
android:layout_height="@dimen/dimen18dp" | ||||
android:layout_marginStart="@dimen/dimen4dp" | ||||
android:layout_marginEnd="@dimen/dimen2dp" | ||||
android:layout_marginTop="@dimen/dimen2dp" | ||||
android:contentDescription="@string/generalImgContentText" | ||||
app:srcCompat="@drawable/ic_delete" | ||||
app:tint="?attr/iconsColor" /> | ||||
| ||||
</LinearLayout> | ||||
| ||||
</LinearLayout> | ||||
| ||||
| |
| @ -176,7 +176,6 @@ | |||
<string name="noReleaseBodyContent">Release notes are not provided by the publisher.</string> | ||||
| ||||
<string name="newMilestoneTitle">Title</string> | ||||
<string name="newMilestoneDescription">Description</string> | ||||
<string name="newMilestoneDueDate">Due Date</string> | ||||
<string name="setDueDate" translatable="false">%1$d-%2$d-%3$d</string> | ||||
<string name="milestoneNameErrorEmpty">Milestone title is empty</string> | ||||
| @ -193,7 +192,6 @@ | |||
<string name="newIssueSelectLabelsListTitle">Select Labels</string> | ||||
<string name="newIssueTitle">Title</string> | ||||
<string name="newIssueAssigneesListTitle">Assignees</string> | ||||
<string name="newIssueDescriptionTitle">Description</string> | ||||
<string name="newIssueDueDateTitle">Due Date</string> | ||||
<string name="newIssueMilestoneTitle">Milestone</string> | ||||
<string name="newIssueLabelsTitle">Labels</string> | ||||
| @ -323,7 +321,6 @@ | |||
| ||||
<!-- create team --> | ||||
<string name="newTeamTitle">Team Name</string> | ||||
<string name="newTeamDesc">Description</string> | ||||
<string name="newTeamPermission">Permission</string> | ||||
<string name="newTeamAccessControls">Access Controls</string> | ||||
<string name="newTeamPermissionRead">Members can view and clone team repositories</string> | ||||
| @ -360,6 +357,9 @@ | |||
<string name="profileTabFollowers">Followers</string> | ||||
<string name="profileTabFollowing">Following</string> | ||||
<string name="usernameWithAt" translatable="false">\u0040%1$s</string> | ||||
<string name="editSettings">Edit Profile</string> | ||||
<string name="hideActivity">Hide Activity from profile page</string> | ||||
<string name="hideEmail">Hide Email</string> | ||||
<!-- profile section --> | ||||
| ||||
<!-- account settings --> | ||||
| @ -371,6 +371,14 @@ | |||
<string name="emailErrorInUse">Email address is already in use</string> | ||||
<string name="emailTypeText">Primary</string> | ||||
<string name="sshKeys">SSH Keys</string> | ||||
<string name="deleteEmailPopupText">This action will permanently delete email %s from your account.</string> | ||||
<string name="deleteEmailSuccess">Email deleted successfully</string> | ||||
<string name="addSSHKey">Add SSH Key</string> | ||||
<string name="sshKeyStatusReadOnly">Read-only Access</string> | ||||
<string name="sshKeyStatusReadWrite">Read-write Access</string> | ||||
<string name="sshKey">SSH Key</string> | ||||
<string name="sshKeySuccess">New SSH key added successfully</string> | ||||
<string name="sshKeyError">Invalid SSH key or SSH key already exists</string> | ||||
<!-- account settings --> | ||||
| ||||
<!-- single issue section --> | ||||
| @ -539,6 +547,8 @@ | |||
<string name="none">None</string> | ||||
<string name="main">main</string> | ||||
<string name="license">License</string> | ||||
<string name="title">Title</string> | ||||
<string name="description">Description</string> | ||||
<!-- generic copy --> | ||||
| ||||
<string name="exploreUsers">Explore users</string> | ||||
| |
Loading…
Add table
Add a link
Reference in a new issue