Improve biometric lock, new comment UI, create issue/pr from a note (#1356)

This PR will also remove Draft feature from the app.

Closes #1270

Closes #1346

Closes #750

Reviewed-on: https://codeberg.org/gitnex/GitNex/pulls/1356
Co-authored-by: M M Arif <mmarif@swatian.com>
Co-committed-by: M M Arif <mmarif@swatian.com>
This commit is contained in:
M M Arif 2024-05-11 15:17:08 +00:00 committed by M M Arif
parent 36b45df849
commit 4ccaeba2aa
26 changed files with 748 additions and 155 deletions

View File

@ -36,7 +36,7 @@ Option 2 - Open the terminal (Linux) and navigate to the project directory. Then
- Pull requests
- Files diff for PRs
- Notifications
- Drafts
- Notes
- Repositories / issues list
- [& more...](https://codeberg.org/gitnex/GitNex/wiki/Features)

View File

@ -82,7 +82,11 @@
<activity
android:name=".activities.IssueDetailActivity"
android:configChanges="orientation|screenSize|smallestScreenSize|density|screenLayout|keyboard|keyboardHidden|navigation"
android:windowSoftInputMode="adjustNothing"/>
android:windowSoftInputMode="adjustResize"/>
<activity
android:name=".activities.BiometricUnlock"
android:configChanges="orientation|screenSize|smallestScreenSize|density|screenLayout|keyboard|keyboardHidden|navigation"
android:theme="@style/Theme.AppCompat.DayNight.NoActionBar"/>
<activity
android:name=".activities.RepoDetailActivity"
android:configChanges="orientation|screenSize|smallestScreenSize|density|screenLayout|keyboard|keyboardHidden|navigation"

View File

@ -1,13 +1,10 @@
package org.mian.gitnex.activities;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
import androidx.biometric.BiometricPrompt;
import androidx.core.content.ContextCompat;
import java.util.Locale;
import java.util.concurrent.Executor;
import org.mian.gitnex.R;
import org.mian.gitnex.core.MainApplication;
import org.mian.gitnex.helpers.AppDatabaseSettings;
@ -128,50 +125,8 @@ public abstract class BaseActivity extends AppCompatActivity {
AppDatabaseSettings.getSettingsValue(
ctx, AppDatabaseSettings.APP_BIOMETRIC_LIFE_CYCLE_KEY))) {
Executor executor = ContextCompat.getMainExecutor(this);
BiometricPrompt biometricPrompt =
new BiometricPrompt(
this,
executor,
new BiometricPrompt.AuthenticationCallback() {
@Override
public void onAuthenticationError(
int errorCode, @NonNull CharSequence errString) {
super.onAuthenticationError(errorCode, errString);
// Authentication error, close the app
finish();
}
// Authentication succeeded, continue to app
@Override
public void onAuthenticationSucceeded(
@NonNull BiometricPrompt.AuthenticationResult result) {
super.onAuthenticationSucceeded(result);
AppDatabaseSettings.updateSettingsValue(
getApplicationContext(),
"true",
AppDatabaseSettings.APP_BIOMETRIC_LIFE_CYCLE_KEY);
}
// Authentication failed, close the app
@Override
public void onAuthenticationFailed() {
super.onAuthenticationFailed();
}
});
BiometricPrompt.PromptInfo biometricPromptBuilder =
new BiometricPrompt.PromptInfo.Builder()
.setTitle(getString(R.string.biometricAuthTitle))
.setSubtitle(getString(R.string.biometricAuthSubTitle))
.setNegativeButtonText(getString(R.string.cancelButton))
.build();
biometricPrompt.authenticate(biometricPromptBuilder);
Intent unlockIntent = new Intent(ctx, BiometricUnlock.class);
ctx.startActivity(unlockIntent);
}
}

View File

@ -0,0 +1,80 @@
package org.mian.gitnex.activities;
import android.content.Context;
import android.os.Bundle;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
import androidx.biometric.BiometricPrompt;
import androidx.core.content.ContextCompat;
import java.util.concurrent.Executor;
import org.mian.gitnex.R;
import org.mian.gitnex.databinding.ActivityUnlockBinding;
import org.mian.gitnex.helpers.AppDatabaseSettings;
/**
* @author M M Arif
*/
public class BiometricUnlock extends AppCompatActivity {
protected Context ctx = this;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
ActivityUnlockBinding activityUnlockBinding =
ActivityUnlockBinding.inflate(getLayoutInflater());
setContentView(activityUnlockBinding.getRoot());
}
public void onResume() {
super.onResume();
Executor executor = ContextCompat.getMainExecutor(this);
BiometricPrompt biometricPrompt =
new BiometricPrompt(
this,
executor,
new BiometricPrompt.AuthenticationCallback() {
@Override
public void onAuthenticationError(
int errorCode, @NonNull CharSequence errString) {
super.onAuthenticationError(errorCode, errString);
// Authentication error, close the app
finish();
}
// Authentication succeeded, continue to app
@Override
public void onAuthenticationSucceeded(
@NonNull BiometricPrompt.AuthenticationResult result) {
super.onAuthenticationSucceeded(result);
AppDatabaseSettings.updateSettingsValue(
getApplicationContext(),
"true",
AppDatabaseSettings.APP_BIOMETRIC_LIFE_CYCLE_KEY);
finish();
}
// Authentication failed, close the app
@Override
public void onAuthenticationFailed() {
super.onAuthenticationFailed();
}
});
BiometricPrompt.PromptInfo biometricPromptBuilder =
new BiometricPrompt.PromptInfo.Builder()
.setTitle(getString(R.string.biometricAuthTitle))
.setSubtitle(getString(R.string.biometricAuthSubTitle))
.setNegativeButtonText(getString(R.string.cancelButton))
.build();
biometricPrompt.authenticate(biometricPromptBuilder);
}
}

View File

@ -6,6 +6,7 @@ import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
@ -14,6 +15,7 @@ import android.widget.TextView;
import androidx.activity.result.ActivityResultLauncher;
import androidx.activity.result.contract.ActivityResultContracts;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AlertDialog;
import androidx.recyclerview.widget.LinearLayoutManager;
import com.google.android.material.bottomsheet.BottomSheetDialog;
import com.google.android.material.datepicker.MaterialDatePicker;
@ -43,10 +45,15 @@ import org.mian.gitnex.actions.LabelsActions;
import org.mian.gitnex.adapters.AssigneesListAdapter;
import org.mian.gitnex.adapters.AttachmentsAdapter;
import org.mian.gitnex.adapters.LabelsListAdapter;
import org.mian.gitnex.adapters.NotesAdapter;
import org.mian.gitnex.clients.RetrofitClient;
import org.mian.gitnex.database.api.BaseApi;
import org.mian.gitnex.database.api.NotesApi;
import org.mian.gitnex.database.models.Notes;
import org.mian.gitnex.databinding.ActivityCreateIssueBinding;
import org.mian.gitnex.databinding.BottomSheetAttachmentsBinding;
import org.mian.gitnex.databinding.CustomAssigneesSelectionDialogBinding;
import org.mian.gitnex.databinding.CustomInsertNoteBinding;
import org.mian.gitnex.databinding.CustomLabelsSelectionDialogBinding;
import org.mian.gitnex.fragments.IssuesFragment;
import org.mian.gitnex.helpers.AlertDialogs;
@ -54,6 +61,7 @@ import org.mian.gitnex.helpers.AppDatabaseSettings;
import org.mian.gitnex.helpers.Constants;
import org.mian.gitnex.helpers.Markdown;
import org.mian.gitnex.helpers.SnackBar;
import org.mian.gitnex.helpers.Toasty;
import org.mian.gitnex.helpers.attachments.AttachmentUtils;
import org.mian.gitnex.helpers.attachments.AttachmentsModel;
import org.mian.gitnex.helpers.contexts.RepositoryContext;
@ -77,6 +85,7 @@ public class CreateIssueActivity extends BaseActivity
private LabelsListAdapter labelsAdapter;
private AssigneesListAdapter assigneesAdapter;
private MaterialAlertDialogBuilder materialAlertDialogBuilder;
private MaterialAlertDialogBuilder materialAlertDialogBuilderNotes;
private List<Integer> labelsIds = new ArrayList<>();
private List<String> assigneesListData = new ArrayList<>();
private boolean renderMd = false;
@ -84,6 +93,11 @@ public class CreateIssueActivity extends BaseActivity
private static List<AttachmentsModel> attachmentsList;
private AttachmentsAdapter attachmentsAdapter;
private static final List<Uri> contentUri = new ArrayList<>();
private CustomInsertNoteBinding customInsertNoteBinding;
private NotesAdapter adapter;
private NotesApi notesApi;
private List<Notes> notesList;
public AlertDialog dialogNotes;
@SuppressLint("ClickableViewAccessibility")
@Override
@ -98,6 +112,8 @@ public class CreateIssueActivity extends BaseActivity
materialAlertDialogBuilder =
new MaterialAlertDialogBuilder(ctx, R.style.ThemeOverlay_Material3_Dialog_Alert);
materialAlertDialogBuilderNotes =
new MaterialAlertDialogBuilder(ctx, R.style.ThemeOverlay_Material3_Dialog_Alert);
repository = RepositoryContext.fromIntent(getIntent());
@ -174,6 +190,8 @@ public class CreateIssueActivity extends BaseActivity
}
});
viewBinding.insertNote.setOnClickListener(insertNote -> showAllNotes());
getMilestones(repository.getOwner(), repository.getName(), resultLimit);
viewBinding.newIssueLabels.setOnClickListener(newIssueLabels -> showLabels());
@ -189,6 +207,62 @@ public class CreateIssueActivity extends BaseActivity
}
}
private void showAllNotes() {
notesList = new ArrayList<>();
notesApi = BaseApi.getInstance(ctx, NotesApi.class);
customInsertNoteBinding = CustomInsertNoteBinding.inflate(LayoutInflater.from(ctx));
View view = customInsertNoteBinding.getRoot();
materialAlertDialogBuilderNotes.setView(view);
customInsertNoteBinding.recyclerView.setHasFixedSize(true);
customInsertNoteBinding.recyclerView.setLayoutManager(new LinearLayoutManager(ctx));
adapter = new NotesAdapter(ctx, notesList, "insert", "issue");
customInsertNoteBinding.pullToRefresh.setOnRefreshListener(
() ->
new Handler(Looper.getMainLooper())
.postDelayed(
() -> {
notesList.clear();
customInsertNoteBinding.pullToRefresh.setRefreshing(
false);
customInsertNoteBinding.progressBar.setVisibility(
View.VISIBLE);
fetchNotes();
},
250));
if (notesApi.getCount() > 0) {
fetchNotes();
dialogNotes = materialAlertDialogBuilderNotes.show();
} else {
Toasty.warning(ctx, getResources().getString(R.string.noNotes));
}
}
private void fetchNotes() {
notesApi.fetchAllNotes()
.observe(
this,
allNotes -> {
assert allNotes != null;
if (!allNotes.isEmpty()) {
notesList.clear();
notesList.addAll(allNotes);
adapter.notifyDataChanged();
customInsertNoteBinding.recyclerView.setAdapter(adapter);
}
customInsertNoteBinding.progressBar.setVisibility(View.GONE);
});
}
ActivityResultLauncher<Intent> startActivityForResult =
registerForActivityResult(
new ActivityResultContracts.StartActivityForResult(),

View File

@ -6,6 +6,7 @@ import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
@ -14,6 +15,7 @@ import android.widget.TextView;
import androidx.activity.result.ActivityResultLauncher;
import androidx.activity.result.contract.ActivityResultContracts;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AlertDialog;
import androidx.recyclerview.widget.LinearLayoutManager;
import com.google.android.material.bottomsheet.BottomSheetDialog;
import com.google.android.material.datepicker.MaterialDatePicker;
@ -41,9 +43,14 @@ import org.mian.gitnex.R;
import org.mian.gitnex.actions.LabelsActions;
import org.mian.gitnex.adapters.AttachmentsAdapter;
import org.mian.gitnex.adapters.LabelsListAdapter;
import org.mian.gitnex.adapters.NotesAdapter;
import org.mian.gitnex.clients.RetrofitClient;
import org.mian.gitnex.database.api.BaseApi;
import org.mian.gitnex.database.api.NotesApi;
import org.mian.gitnex.database.models.Notes;
import org.mian.gitnex.databinding.ActivityCreatePrBinding;
import org.mian.gitnex.databinding.BottomSheetAttachmentsBinding;
import org.mian.gitnex.databinding.CustomInsertNoteBinding;
import org.mian.gitnex.databinding.CustomLabelsSelectionDialogBinding;
import org.mian.gitnex.fragments.PullRequestsFragment;
import org.mian.gitnex.helpers.AlertDialogs;
@ -51,6 +58,7 @@ import org.mian.gitnex.helpers.AppDatabaseSettings;
import org.mian.gitnex.helpers.Constants;
import org.mian.gitnex.helpers.Markdown;
import org.mian.gitnex.helpers.SnackBar;
import org.mian.gitnex.helpers.Toasty;
import org.mian.gitnex.helpers.attachments.AttachmentUtils;
import org.mian.gitnex.helpers.attachments.AttachmentsModel;
import org.mian.gitnex.helpers.contexts.RepositoryContext;
@ -74,11 +82,17 @@ public class CreatePullRequestActivity extends BaseActivity
private RepositoryContext repository;
private LabelsListAdapter labelsAdapter;
private MaterialAlertDialogBuilder materialAlertDialogBuilder;
private MaterialAlertDialogBuilder materialAlertDialogBuilderNotes;
private boolean renderMd = false;
private RepositoryContext repositoryContext;
private static List<AttachmentsModel> attachmentsList;
private AttachmentsAdapter attachmentsAdapter;
private static final List<Uri> contentUri = new ArrayList<>();
private CustomInsertNoteBinding customInsertNoteBinding;
private NotesAdapter adapter;
private NotesApi notesApi;
private List<Notes> notesList;
public AlertDialog dialogNotes;
@SuppressLint("ClickableViewAccessibility")
@Override
@ -93,6 +107,8 @@ public class CreatePullRequestActivity extends BaseActivity
materialAlertDialogBuilder =
new MaterialAlertDialogBuilder(ctx, R.style.ThemeOverlay_Material3_Dialog_Alert);
materialAlertDialogBuilderNotes =
new MaterialAlertDialogBuilder(ctx, R.style.ThemeOverlay_Material3_Dialog_Alert);
repository = RepositoryContext.fromIntent(getIntent());
@ -162,6 +178,8 @@ public class CreatePullRequestActivity extends BaseActivity
}
});
viewBinding.insertNote.setOnClickListener(insertNote -> showAllNotes());
getMilestones(repository.getOwner(), repository.getName(), resultLimit);
getBranches(repository.getOwner(), repository.getName());
@ -190,6 +208,62 @@ public class CreatePullRequestActivity extends BaseActivity
}
});
private void showAllNotes() {
notesList = new ArrayList<>();
notesApi = BaseApi.getInstance(ctx, NotesApi.class);
customInsertNoteBinding = CustomInsertNoteBinding.inflate(LayoutInflater.from(ctx));
View view = customInsertNoteBinding.getRoot();
materialAlertDialogBuilderNotes.setView(view);
customInsertNoteBinding.recyclerView.setHasFixedSize(true);
customInsertNoteBinding.recyclerView.setLayoutManager(new LinearLayoutManager(ctx));
adapter = new NotesAdapter(ctx, notesList, "insert", "pr");
customInsertNoteBinding.pullToRefresh.setOnRefreshListener(
() ->
new Handler(Looper.getMainLooper())
.postDelayed(
() -> {
notesList.clear();
customInsertNoteBinding.pullToRefresh.setRefreshing(
false);
customInsertNoteBinding.progressBar.setVisibility(
View.VISIBLE);
fetchNotes();
},
250));
if (notesApi.getCount() > 0) {
fetchNotes();
dialogNotes = materialAlertDialogBuilderNotes.show();
} else {
Toasty.warning(ctx, getResources().getString(R.string.noNotes));
}
}
private void fetchNotes() {
notesApi.fetchAllNotes()
.observe(
this,
allNotes -> {
assert allNotes != null;
if (!allNotes.isEmpty()) {
notesList.clear();
notesList.addAll(allNotes);
adapter.notifyDataChanged();
customInsertNoteBinding.recyclerView.setAdapter(adapter);
}
customInsertNoteBinding.progressBar.setVisibility(View.GONE);
});
}
public void onDestroy() {
AttachmentsAdapter.setAttachmentsReceiveListener(null);
super.onDestroy();
@ -202,7 +276,7 @@ public class CreatePullRequestActivity extends BaseActivity
private void checkForAttachments() {
if (contentUri.size() > 0) {
if (!contentUri.isEmpty()) {
BottomSheetAttachmentsBinding bottomSheetAttachmentsBinding =
BottomSheetAttachmentsBinding.inflate(getLayoutInflater());
@ -241,7 +315,10 @@ public class CreatePullRequestActivity extends BaseActivity
RequestBody requestFile =
RequestBody.create(
file, MediaType.parse(getContentResolver().getType(contentUri.get(i))));
file,
MediaType.parse(
Objects.requireNonNull(
getContentResolver().getType(contentUri.get(i)))));
uploadAttachments(requestFile, issueIndex, file.getName());
}
@ -301,7 +378,7 @@ public class CreatePullRequestActivity extends BaseActivity
assignees.add("");
if (labelsIds.size() == 0) {
if (labelsIds.isEmpty()) {
labelsIds.add(0);
}
@ -383,7 +460,7 @@ public class CreatePullRequestActivity extends BaseActivity
PullRequestsFragment.resumePullRequests = true;
MainActivity.reloadRepos = true;
if (contentUri.size() > 0) {
if (!contentUri.isEmpty()) {
assert response.body() != null;
processAttachments(response.body().getNumber());
contentUri.clear();
@ -555,7 +632,7 @@ public class CreatePullRequestActivity extends BaseActivity
.title(getString(R.string.issueCreatedNoMilestone)));
assert milestonesList_ != null;
if (milestonesList_.size() > 0) {
if (!milestonesList_.isEmpty()) {
for (Milestone milestone : milestonesList_) {

View File

@ -3,10 +3,15 @@ package org.mian.gitnex.activities;
import android.annotation.SuppressLint;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.widget.ArrayAdapter;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AlertDialog;
import androidx.recyclerview.widget.LinearLayoutManager;
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import com.vdurmont.emoji.EmojiParser;
import java.util.ArrayList;
import java.util.List;
@ -17,11 +22,17 @@ import org.gitnex.tea4j.v2.models.CreateTagOption;
import org.gitnex.tea4j.v2.models.Release;
import org.gitnex.tea4j.v2.models.Tag;
import org.mian.gitnex.R;
import org.mian.gitnex.adapters.NotesAdapter;
import org.mian.gitnex.clients.RetrofitClient;
import org.mian.gitnex.database.api.BaseApi;
import org.mian.gitnex.database.api.NotesApi;
import org.mian.gitnex.database.models.Notes;
import org.mian.gitnex.databinding.ActivityCreateReleaseBinding;
import org.mian.gitnex.databinding.CustomInsertNoteBinding;
import org.mian.gitnex.helpers.AlertDialogs;
import org.mian.gitnex.helpers.Markdown;
import org.mian.gitnex.helpers.SnackBar;
import org.mian.gitnex.helpers.Toasty;
import org.mian.gitnex.helpers.contexts.RepositoryContext;
import retrofit2.Call;
import retrofit2.Callback;
@ -36,6 +47,12 @@ public class CreateReleaseActivity extends BaseActivity {
private String selectedBranch;
private RepositoryContext repository;
private boolean renderMd = false;
private MaterialAlertDialogBuilder materialAlertDialogBuilder;
private CustomInsertNoteBinding customInsertNoteBinding;
private NotesAdapter adapter;
private NotesApi notesApi;
private List<Notes> notesList;
public AlertDialog dialogNotes;
@SuppressLint("ClickableViewAccessibility")
@Override
@ -48,6 +65,9 @@ public class CreateReleaseActivity extends BaseActivity {
repository = RepositoryContext.fromIntent(getIntent());
materialAlertDialogBuilder =
new MaterialAlertDialogBuilder(ctx, R.style.ThemeOverlay_Material3_Dialog_Alert);
binding.releaseContent.setOnTouchListener(
(touchView, motionEvent) -> {
touchView.getParent().requestDisallowInterceptTouchEvent(true);
@ -60,10 +80,7 @@ public class CreateReleaseActivity extends BaseActivity {
return false;
});
binding.topAppBar.setNavigationOnClickListener(
v -> {
finish();
});
binding.topAppBar.setNavigationOnClickListener(v -> finish());
binding.topAppBar.setOnMenuItemClickListener(
menuItem -> {
@ -103,9 +120,67 @@ public class CreateReleaseActivity extends BaseActivity {
}
});
binding.insertNote.setOnClickListener(insertNote -> showAllNotes());
getBranches(repository.getOwner(), repository.getName());
}
private void showAllNotes() {
notesList = new ArrayList<>();
notesApi = BaseApi.getInstance(ctx, NotesApi.class);
customInsertNoteBinding = CustomInsertNoteBinding.inflate(LayoutInflater.from(ctx));
View view = customInsertNoteBinding.getRoot();
materialAlertDialogBuilder.setView(view);
customInsertNoteBinding.recyclerView.setHasFixedSize(true);
customInsertNoteBinding.recyclerView.setLayoutManager(new LinearLayoutManager(ctx));
adapter = new NotesAdapter(ctx, notesList, "insert", "release");
customInsertNoteBinding.pullToRefresh.setOnRefreshListener(
() ->
new Handler(Looper.getMainLooper())
.postDelayed(
() -> {
notesList.clear();
customInsertNoteBinding.pullToRefresh.setRefreshing(
false);
customInsertNoteBinding.progressBar.setVisibility(
View.VISIBLE);
fetchNotes();
},
250));
if (notesApi.getCount() > 0) {
fetchNotes();
dialogNotes = materialAlertDialogBuilder.show();
} else {
Toasty.warning(ctx, getResources().getString(R.string.noNotes));
}
}
private void fetchNotes() {
notesApi.fetchAllNotes()
.observe(
this,
allNotes -> {
assert allNotes != null;
if (!allNotes.isEmpty()) {
notesList.clear();
notesList.addAll(allNotes);
adapter.notifyDataChanged();
customInsertNoteBinding.recyclerView.setAdapter(adapter);
}
customInsertNoteBinding.progressBar.setVisibility(View.GONE);
});
}
private void createNewTag() {
String tagName = Objects.requireNonNull(binding.releaseTagName.getText()).toString();
@ -114,7 +189,7 @@ public class CreateReleaseActivity extends BaseActivity {
+ "\n\n"
+ Objects.requireNonNull(binding.releaseContent.getText());
if (tagName.equals("")) {
if (tagName.isEmpty()) {
SnackBar.error(
ctx, findViewById(android.R.id.content), getString(R.string.tagNameErrorEmpty));
return;
@ -187,13 +262,13 @@ public class CreateReleaseActivity extends BaseActivity {
boolean newReleaseType = binding.releaseType.isChecked();
boolean newReleaseDraft = binding.releaseDraft.isChecked();
if (newReleaseTitle.equals("")) {
if (newReleaseTitle.isEmpty()) {
SnackBar.error(
ctx, findViewById(android.R.id.content), getString(R.string.titleErrorEmpty));
return;
}
if (newReleaseTagName.equals("")) {
if (newReleaseTagName.isEmpty()) {
SnackBar.error(
ctx, findViewById(android.R.id.content), getString(R.string.tagNameErrorEmpty));
return;

View File

@ -11,6 +11,8 @@ import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.text.Editable;
import android.text.TextWatcher;
import android.util.Log;
import android.view.Gravity;
import android.view.LayoutInflater;
@ -18,6 +20,8 @@ import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.WindowManager;
import android.view.inputmethod.InputMethodManager;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.ScrollView;
@ -57,7 +61,9 @@ import org.gitnex.tea4j.v2.models.Repository;
import org.gitnex.tea4j.v2.models.User;
import org.gitnex.tea4j.v2.models.WatchInfo;
import org.mian.gitnex.R;
import org.mian.gitnex.actions.ActionResult;
import org.mian.gitnex.actions.AssigneesActions;
import org.mian.gitnex.actions.IssueActions;
import org.mian.gitnex.actions.LabelsActions;
import org.mian.gitnex.adapters.AssigneesListAdapter;
import org.mian.gitnex.adapters.IssueCommentsAdapter;
@ -82,6 +88,7 @@ import org.mian.gitnex.helpers.LabelWidthCalculator;
import org.mian.gitnex.helpers.Markdown;
import org.mian.gitnex.helpers.RoundedTransformation;
import org.mian.gitnex.helpers.TimeHelper;
import org.mian.gitnex.helpers.TinyDB;
import org.mian.gitnex.helpers.Toasty;
import org.mian.gitnex.helpers.contexts.IssueContext;
import org.mian.gitnex.notifications.Notifications;
@ -105,7 +112,7 @@ public class IssueDetailActivity extends BaseActivity
public static boolean commentPosted = false;
private final List<Label> labelsList = new ArrayList<>();
private final List<User> assigneesList = new ArrayList<>();
public boolean commentEdited = false;
public static boolean commentEdited = false;
private IssueCommentsAdapter adapter;
private String repoOwner;
private String repoName;
@ -131,6 +138,13 @@ public class IssueDetailActivity extends BaseActivity
private String instanceUrlOnly;
private String token;
private int page = 1;
private TinyDB tinyDB;
private Mode mode = Mode.SEND;
private enum Mode {
EDIT,
SEND
}
public ActivityResultLauncher<Intent> editIssueLauncher =
registerForActivityResult(
@ -275,10 +289,15 @@ public class IssueDetailActivity extends BaseActivity
repoName = issue.getRepository().getName();
issueIndex = issue.getIssueIndex();
tinyDB = TinyDB.getInstance(ctx);
setSupportActionBar(viewBinding.toolbar);
Objects.requireNonNull(getSupportActionBar()).setTitle(repoName);
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
InputMethodManager imm =
(InputMethodManager) getSystemService(Activity.INPUT_METHOD_SERVICE);
String instanceUrl = ((BaseActivity) ctx).getAccount().getAccount().getInstanceUrl();
instanceUrlOnly = instanceUrl.substring(0, instanceUrl.lastIndexOf("api/v1/"));
@ -293,31 +312,11 @@ public class IssueDetailActivity extends BaseActivity
viewBinding.recyclerView.setNestedScrollingEnabled(false);
viewBinding.recyclerView.setLayoutManager(new LinearLayoutManager(ctx));
new Handler()
.postDelayed(
() -> {
if (issue.getIssue() != null) {
if (issue.getIssue().isIsLocked() != null) {
if (issue.getIssue().isIsLocked()) {
if (issue.getRepository().getPermissions() != null
&& issue.getRepository().getPermissions().isAdmin()
!= null) {
if (issue.getRepository().getPermissions().isAdmin()) {
viewBinding.addNewComment.setVisibility(
View.VISIBLE);
} else {
viewBinding.addNewComment.setVisibility(View.GONE);
}
} else {
viewBinding.addNewComment.setVisibility(View.GONE);
}
} else {
viewBinding.addNewComment.setVisibility(View.VISIBLE);
}
}
}
},
50);
float buttonAlphaStatDisabled = .5F;
float buttonAlphaStatEnabled = 1F;
viewBinding.send.setAlpha(buttonAlphaStatDisabled);
viewBinding.send.setEnabled(false);
viewBinding.addNewComment.setOnClickListener(
v -> {
@ -360,6 +359,166 @@ public class IssueDetailActivity extends BaseActivity
&& Objects.equals(getIntent().getStringExtra("openPrDiff"), "true")) {
startActivity(issue.getIntent(ctx, DiffActivity.class));
}
viewBinding.commentReply.addTextChangedListener(
new TextWatcher() {
@Override
public void beforeTextChanged(CharSequence chr, int i, int i1, int i2) {}
@Override
public void onTextChanged(CharSequence chr, int i, int i1, int i2) {
if (chr.length() > 0) {
viewBinding.commentReply.requestFocus();
getWindow()
.setSoftInputMode(
WindowManager.LayoutParams
.SOFT_INPUT_STATE_ALWAYS_VISIBLE);
viewBinding.commentReply.setSelection(
viewBinding.commentReply.length());
viewBinding.send.setAlpha(buttonAlphaStatEnabled);
viewBinding.send.setEnabled(true);
} else {
viewBinding.send.setAlpha(buttonAlphaStatDisabled);
viewBinding.send.setEnabled(false);
}
}
@Override
public void afterTextChanged(Editable editable) {
if (editable.length() > 0) {
new Handler()
.postDelayed(
() -> {
if (issue.getIssue() != null) {
if (issue.getIssue().isIsLocked() != null) {
if (issue.getIssue().isIsLocked()) {
if (issue.getRepository()
.getPermissions()
!= null
&& issue.getRepository()
.getPermissions()
.isAdmin()
!= null) {
if (issue.getRepository()
.getPermissions()
.isAdmin()) {
viewBinding.send.setEnabled(
true);
viewBinding.send.setAlpha(
buttonAlphaStatEnabled);
} else {
viewBinding.send.setAlpha(
buttonAlphaStatDisabled);
viewBinding.send.setEnabled(
false);
}
} else {
viewBinding.send.setAlpha(
buttonAlphaStatDisabled);
viewBinding.send.setEnabled(false);
}
} else {
viewBinding.send.setEnabled(true);
viewBinding.send.setAlpha(
buttonAlphaStatEnabled);
}
}
}
},
50);
}
}
});
viewBinding.send.setOnClickListener(
v -> {
if (Objects.requireNonNull(tinyDB.getString("commentAction"))
.equalsIgnoreCase("edit")) {
mode = Mode.EDIT;
} else {
mode = Mode.SEND;
}
if (mode == Mode.SEND) {
IssueActions.reply(
ctx, viewBinding.commentReply.getText().toString(), issue)
.accept(
(status, result) -> {
if (status == ActionResult.Status.SUCCESS) {
viewBinding.scrollViewComments.post(
() ->
issueCommentsModel
.loadIssueComments(
repoOwner,
repoName,
issueIndex,
ctx,
() ->
viewBinding
.scrollViewComments
.fullScroll(
ScrollView
.FOCUS_DOWN)));
Toasty.success(
ctx, getString(R.string.commentSuccess));
viewBinding.send.setAlpha(buttonAlphaStatDisabled);
viewBinding.send.setEnabled(false);
viewBinding.commentReply.setText(null);
viewBinding.commentReply.clearFocus();
imm.toggleSoftInput(
InputMethodManager.HIDE_IMPLICIT_ONLY, 0);
} else {
Toasty.error(ctx, getString(R.string.genericError));
}
});
} else {
IssueActions.edit(
ctx,
viewBinding.commentReply.getText().toString(),
tinyDB.getInt("commentId"),
issue)
.accept(
(status, result) -> {
if (status == ActionResult.Status.SUCCESS) {
tinyDB.remove("commentId");
tinyDB.remove("commentAction");
viewBinding.scrollViewComments.post(
() ->
issueCommentsModel
.loadIssueComments(
repoOwner,
repoName,
issueIndex,
ctx,
null));
Toasty.success(
ctx,
getString(R.string.editCommentUpdatedText));
mode = Mode.SEND;
viewBinding.send.setAlpha(buttonAlphaStatDisabled);
viewBinding.send.setEnabled(false);
viewBinding.commentReply.setText(null);
viewBinding.commentReply.clearFocus();
imm.toggleSoftInput(
InputMethodManager.HIDE_IMPLICIT_ONLY, 0);
} else {
Toasty.error(ctx, getString(R.string.genericError));
}
});
}
});
}
@Override
@ -708,6 +867,10 @@ public class IssueDetailActivity extends BaseActivity
},
500);
}
tinyDB.remove("commentId");
tinyDB.remove("commentAction");
mode = Mode.SEND;
}
private void fetchDataAsync(String owner, String repo, int index) {
@ -724,12 +887,7 @@ public class IssueDetailActivity extends BaseActivity
adapter =
new IssueCommentsAdapter(
ctx,
bundle,
issueCommentsMain,
getSupportFragmentManager(),
this::onResume,
issue);
ctx, bundle, issueCommentsMain, this::onResume, issue);
adapter.setLoadMoreListener(
new IssueCommentsAdapter.OnLoadMoreListener() {

View File

@ -18,6 +18,7 @@ import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.EditText;
import android.widget.FrameLayout;
import android.widget.ImageView;
import android.widget.LinearLayout;
@ -27,7 +28,6 @@ import androidx.annotation.NonNull;
import androidx.core.content.ContextCompat;
import androidx.core.content.res.ResourcesCompat;
import androidx.core.text.HtmlCompat;
import androidx.fragment.app.FragmentManager;
import androidx.recyclerview.widget.RecyclerView;
import com.amulyakhare.textdrawable.TextDrawable;
import com.google.android.material.bottomsheet.BottomSheetDialog;
@ -52,7 +52,6 @@ import org.mian.gitnex.activities.ProfileActivity;
import org.mian.gitnex.clients.PicassoService;
import org.mian.gitnex.clients.RetrofitClient;
import org.mian.gitnex.databinding.CustomImageViewDialogBinding;
import org.mian.gitnex.fragments.BottomSheetReplyFragment;
import org.mian.gitnex.fragments.IssuesFragment;
import org.mian.gitnex.helpers.AlertDialogs;
import org.mian.gitnex.helpers.AppDatabaseSettings;
@ -79,7 +78,6 @@ public class IssueCommentsAdapter extends RecyclerView.Adapter<RecyclerView.View
private final Context context;
private final TinyDB tinyDB;
private final Bundle bundle;
private final FragmentManager fragmentManager;
private final Runnable onInteractedListener;
private final Locale locale;
private final IssueContext issue;
@ -92,14 +90,12 @@ public class IssueCommentsAdapter extends RecyclerView.Adapter<RecyclerView.View
Context ctx,
Bundle bundle,
List<TimelineComment> issuesCommentsMain,
FragmentManager fragmentManager,
Runnable onInteractedListener,
IssueContext issue) {
this.context = ctx;
this.bundle = bundle;
this.issuesComments = issuesCommentsMain;
this.fragmentManager = fragmentManager;
this.onInteractedListener = onInteractedListener;
tinyDB = TinyDB.getInstance(ctx);
locale = ctx.getResources().getConfiguration().locale;
@ -266,11 +262,7 @@ public class IssueCommentsAdapter extends RecyclerView.Adapter<RecyclerView.View
if (issueComment != null) {
new Handler()
.postDelayed(
() -> {
getAttachments(issueComment.getId(), view, token);
},
250);
.postDelayed(() -> getAttachments(issueComment.getId(), view, token), 250);
}
menu.setOnClickListener(
@ -343,18 +335,14 @@ public class IssueCommentsAdapter extends RecyclerView.Adapter<RecyclerView.View
commentMenuEdit.setOnClickListener(
v1 -> {
Bundle bundle = new Bundle();
bundle.putInt(
"commentId", Math.toIntExact(issueComment.getId()));
bundle.putString("commentAction", "edit");
bundle.putString("commentBody", issueComment.getBody());
IssueDetailActivity parentActivity =
(IssueDetailActivity) context;
EditText text = parentActivity.findViewById(R.id.comment_reply);
text.append(issueComment.getBody());
BottomSheetReplyFragment bottomSheetReplyFragment =
BottomSheetReplyFragment.newInstance(bundle, issue);
bottomSheetReplyFragment.setOnInteractedListener(
onInteractedListener);
bottomSheetReplyFragment.show(
fragmentManager, "replyBottomSheet");
tinyDB.putString("commentAction", "edit");
tinyDB.putInt(
"commentId", Math.toIntExact(issueComment.getId()));
dialog.dismiss();
});
@ -402,15 +390,14 @@ public class IssueCommentsAdapter extends RecyclerView.Adapter<RecyclerView.View
stringBuilder.append(">").append(line).append("\n");
}
stringBuilder.append("\n");
String comment = String.valueOf(stringBuilder.append("\n"));
Bundle bundle = new Bundle();
bundle.putString("commentBody", stringBuilder.toString());
bundle.putBoolean("cursorToEnd", true);
IssueDetailActivity parentActivity =
(IssueDetailActivity) context;
EditText text = parentActivity.findViewById(R.id.comment_reply);
text.setText(comment);
dialog.dismiss();
BottomSheetReplyFragment.newInstance(bundle, issue)
.show(fragmentManager, "replyBottomSheet");
});
commentMenuCopy.setOnClickListener(
@ -1489,7 +1476,7 @@ public class IssueCommentsAdapter extends RecyclerView.Adapter<RecyclerView.View
attachmentView.setLayoutParams(paramsAttachment);
materialCardView.addView(attachmentView);
int finalI = i;
// int finalI = i;
materialCardView.setOnClickListener(
v1 -> {
// filesize = attachment.get(finalI).getSize();

View File

@ -6,6 +6,7 @@ import android.content.Intent;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.EditText;
import android.widget.ImageView;
import android.widget.TextView;
import androidx.annotation.NonNull;
@ -19,7 +20,10 @@ import java.util.Locale;
import java.util.Objects;
import org.apache.commons.lang3.StringUtils;
import org.mian.gitnex.R;
import org.mian.gitnex.activities.CreateIssueActivity;
import org.mian.gitnex.activities.CreateNoteActivity;
import org.mian.gitnex.activities.CreatePullRequestActivity;
import org.mian.gitnex.activities.CreateReleaseActivity;
import org.mian.gitnex.database.api.BaseApi;
import org.mian.gitnex.database.api.NotesApi;
import org.mian.gitnex.database.models.Notes;
@ -35,14 +39,18 @@ public class NotesAdapter extends RecyclerView.Adapter<NotesAdapter.NotesViewHol
private List<Notes> notesList;
private final Context ctx;
private final Intent noteIntent;
private final String insert;
private final String source;
public NotesAdapter(Context ctx, List<Notes> notesListMain) {
public NotesAdapter(Context ctx, List<Notes> notesListMain, String insert, String source) {
this.ctx = ctx;
this.notesList = notesListMain;
noteIntent = new Intent(ctx, CreateNoteActivity.class);
this.insert = insert;
this.source = source;
}
class NotesViewHolder extends RecyclerView.ViewHolder {
public class NotesViewHolder extends RecyclerView.ViewHolder {
private Notes notes;
@ -82,6 +90,49 @@ public class NotesAdapter extends RecyclerView.Adapter<NotesAdapter.NotesViewHol
.setNeutralButton(R.string.cancelButton, null)
.show();
});
if (insert.equalsIgnoreCase("insert") && source.equalsIgnoreCase("issue")) {
deleteNote.setVisibility(View.GONE);
itemView.setOnClickListener(
view -> {
CreateIssueActivity parentActivity = (CreateIssueActivity) ctx;
EditText text = parentActivity.findViewById(R.id.newIssueDescription);
text.append(notes.getContent());
parentActivity.dialogNotes.dismiss();
});
}
if (insert.equalsIgnoreCase("insert") && source.equalsIgnoreCase("release")) {
deleteNote.setVisibility(View.GONE);
itemView.setOnClickListener(
view -> {
CreateReleaseActivity parentActivity = (CreateReleaseActivity) ctx;
EditText text = parentActivity.findViewById(R.id.releaseContent);
text.append(notes.getContent());
parentActivity.dialogNotes.dismiss();
});
}
if (insert.equalsIgnoreCase("insert") && source.equalsIgnoreCase("pr")) {
deleteNote.setVisibility(View.GONE);
itemView.setOnClickListener(
view -> {
CreatePullRequestActivity parentActivity =
(CreatePullRequestActivity) ctx;
EditText text = parentActivity.findViewById(R.id.prBody);
text.append(notes.getContent());
parentActivity.dialogNotes.dismiss();
});
}
}
}

View File

@ -35,6 +35,10 @@ public class NotesApi extends BaseApi {
return notesDao.fetchAllNotes();
}
public Integer getCount() {
return notesDao.getCount();
}
public Notes fetchNoteById(int noteId) {
return notesDao.fetchNoteById(noteId);
}

View File

@ -33,4 +33,7 @@ public interface NotesDao {
@Query("DELETE FROM Notes WHERE noteId = :noteId")
void deleteNote(int noteId);
@Query("SELECT COUNT(noteId) FROM Notes")
Integer getCount();
}

View File

@ -33,7 +33,6 @@ import org.mian.gitnex.database.api.DraftsApi;
import org.mian.gitnex.databinding.BottomSheetReplyLayoutBinding;
import org.mian.gitnex.helpers.AppDatabaseSettings;
import org.mian.gitnex.helpers.Constants;
import org.mian.gitnex.helpers.TinyDB;
import org.mian.gitnex.helpers.Toasty;
import org.mian.gitnex.helpers.contexts.IssueContext;
@ -43,7 +42,6 @@ import org.mian.gitnex.helpers.contexts.IssueContext;
public class BottomSheetReplyFragment extends BottomSheetDialogFragment {
private Mode mode = Mode.SEND;
private TinyDB tinyDB;
private DraftsApi draftsApi;
private int currentActiveAccountId;
private IssueContext issue;
@ -65,7 +63,6 @@ public class BottomSheetReplyFragment extends BottomSheetDialogFragment {
super.onAttach(context);
tinyDB = TinyDB.getInstance(context);
draftsApi = BaseApi.getInstance(context, DraftsApi.class);
currentActiveAccountId =
@ -103,7 +100,7 @@ public class BottomSheetReplyFragment extends BottomSheetDialogFragment {
if (arguments.getString("draftId") != null) {
draftId = Long.parseLong(arguments.getString("draftId"));
draftId = Long.parseLong(Objects.requireNonNull(arguments.getString("draftId")));
}
if (issue.getIssue() != null && !issue.getIssue().getTitle().isEmpty()) {
@ -235,8 +232,7 @@ public class BottomSheetReplyFragment extends BottomSheetDialogFragment {
(status, result) -> {
FragmentActivity activity = requireActivity();
if (activity instanceof IssueDetailActivity) {
((IssueDetailActivity) activity).commentEdited =
true;
IssueDetailActivity.commentEdited = true;
}
if (status == ActionResult.Status.SUCCESS) {
@ -271,6 +267,7 @@ public class BottomSheetReplyFragment extends BottomSheetDialogFragment {
private void saveDraft(String text, boolean remove) {
@SuppressLint("Recycle")
ValueAnimator valueAnimator = ValueAnimator.ofFloat(0f, 1f);
valueAnimator.setDuration(500);
valueAnimator.addUpdateListener(

View File

@ -70,7 +70,7 @@ public class NotesFragment extends Fragment {
binding.recyclerView.setPadding(0, 0, 0, 220);
binding.recyclerView.setClipToPadding(false);
adapter = new NotesAdapter(ctx, notesList);
adapter = new NotesAdapter(ctx, notesList, "", "");
binding.pullToRefresh.setOnRefreshListener(
() ->
@ -103,7 +103,7 @@ public class NotesFragment extends Fragment {
allNotes -> {
binding.pullToRefresh.setRefreshing(false);
assert allNotes != null;
if (allNotes.size() > 0) {
if (!allNotes.isEmpty()) {
notesList.clear();
binding.noData.setVisibility(View.GONE);
@ -138,7 +138,7 @@ public class NotesFragment extends Fragment {
public void deleteAllNotes() {
if (notesList.size() > 0) {
if (!notesList.isEmpty()) {
notesApi.deleteAllNotes();
notesList.clear();
@ -159,6 +159,7 @@ public class NotesFragment extends Fragment {
MenuItem searchItem = menu.findItem(R.id.action_search);
SearchView searchView = (SearchView) searchItem.getActionView();
assert searchView != null;
searchView.setImeOptions(EditorInfo.IME_ACTION_DONE);
searchView.setOnQueryTextListener(
@ -184,7 +185,7 @@ public class NotesFragment extends Fragment {
if (item.getItemId() == R.id.reset_menu_item) {
if (notesList.size() == 0) {
if (notesList.isEmpty()) {
Toasty.warning(ctx, getResources().getString(R.string.noDataFound));
} else {
new MaterialAlertDialogBuilder(ctx)

View File

@ -4,17 +4,17 @@
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="#00000000"
android:pathData="M22,2L11,13"
android:pathData="M4.698,4.034l16.302,7.966l-16.302,7.966a0.503,0.503 0,0 1,-0.546 -0.124a0.555,0.555 0,0 1,-0.12 -0.568l2.468,-7.274l-2.468,-7.274a0.555,0.555 0,0 1,0.12 -0.568a0.503,0.503 0,0 1,0.546 -0.124z"
android:strokeLineJoin="round"
android:strokeWidth="2"
android:fillColor="#00000000"
android:strokeColor="?attr/iconsColor"
android:strokeLineCap="round"
android:strokeLineJoin="round"/>
android:strokeLineCap="round"/>
<path
android:fillColor="#00000000"
android:pathData="M22,2l-7,20l-4,-9l-9,-4l20,-7z"
android:pathData="M6.5,12h14.5"
android:strokeLineJoin="round"
android:strokeWidth="2"
android:fillColor="#00000000"
android:strokeColor="?attr/iconsColor"
android:strokeLineCap="round"
android:strokeLineJoin="round"/>
android:strokeLineCap="round"/>
</vector>

View File

@ -4,11 +4,11 @@
android:shape="rectangle">
<solid
android:color="?attr/inputBackgroundColor">
android:color="?attr/materialCardBackgroundColor">
</solid>
<corners
android:radius="3dp">
android:radius="32dp">
</corners>
<padding

View File

@ -84,6 +84,17 @@
</com.google.android.material.textfield.TextInputLayout>
<TextView
android:id="@+id/insertNote"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="end"
android:layout_marginTop="@dimen/dimen8dp"
android:layout_marginBottom="@dimen/dimen0dp"
android:text="@string/insertNote"
android:textColor="?attr/primaryTextColor"
android:textSize="@dimen/dimen14sp"/>
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/newIssueDescriptionLayout"
android:layout_width="match_parent"

View File

@ -84,6 +84,17 @@
</com.google.android.material.textfield.TextInputLayout>
<TextView
android:id="@+id/insertNote"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="end"
android:layout_marginTop="@dimen/dimen8dp"
android:layout_marginBottom="@dimen/dimen0dp"
android:text="@string/insertNote"
android:textColor="?attr/primaryTextColor"
android:textSize="@dimen/dimen14sp"/>
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/prBodyLayout"
android:layout_width="match_parent"

View File

@ -99,6 +99,17 @@
</com.google.android.material.textfield.TextInputLayout>
<TextView
android:id="@+id/insertNote"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="end"
android:layout_marginTop="@dimen/dimen8dp"
android:layout_marginBottom="@dimen/dimen0dp"
android:text="@string/insertNote"
android:textColor="?attr/primaryTextColor"
android:textSize="@dimen/dimen14sp"/>
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/releaseContentLayout"
android:layout_width="match_parent"

View File

@ -63,6 +63,7 @@
android:text="@string/commentButtonText"
android:textColor="?attr/materialCardBackgroundColor"
app:iconTint="?attr/materialCardBackgroundColor"
android:visibility="gone"
app:icon="@drawable/ic_reply" />
<RelativeLayout
@ -89,7 +90,7 @@
android:paddingStart="@dimen/dimen8dp"
android:paddingTop="@dimen/dimen2dp"
android:paddingEnd="@dimen/dimen8dp"
android:paddingBottom="@dimen/dimen8dp">
android:paddingBottom="@dimen/dimen104dp">
<com.google.android.material.card.MaterialCardView
android:id="@+id/titleCard"
@ -153,7 +154,8 @@
android:layout_width="@dimen/dimen24dp"
android:layout_height="@dimen/dimen24dp"
app:cardCornerRadius="@dimen/dimen12dp"
app:cardElevation="@dimen/dimen0dp">
app:cardElevation="@dimen/dimen0dp"
tools:ignore="TooDeepLayout">
<ImageView
android:id="@+id/assigneeAvatar"
@ -371,9 +373,7 @@
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recyclerView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:clipToPadding="false"
android:paddingBottom="@dimen/dimen72dp" />
android:layout_height="wrap_content" />
</FrameLayout>
@ -383,6 +383,56 @@
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:layout_alignParentBottom="true"
android:background="?attr/primaryBackgroundColor"
android:padding="@dimen/dimen8dp">
<EditText
android:id="@+id/comment_reply"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:autofillHints="@string/commentButtonText"
android:background="@drawable/shape_inputs"
android:inputType="textMultiLine|textImeMultiLine|textCapSentences"
android:labelFor="@+id/comment_reply"
android:layout_weight="1"
android:maxLines="4"
android:paddingStart="@dimen/dimen16dp"
android:paddingEnd="@dimen/dimen16dp"
android:paddingTop="@dimen/dimen8dp"
android:paddingBottom="@dimen/dimen8dp"
android:textColor="?attr/inputTextColor"
android:layout_marginEnd="@dimen/dimen12dp"
android:textSize="@dimen/dimen14sp" />
<com.google.android.material.card.MaterialCardView
style="?attr/materialCardViewFilledStyle"
android:id="@+id/send_button"
android:layout_width="@dimen/dimen36dp"
android:layout_height="@dimen/dimen36dp"
android:layout_gravity="center_vertical"
android:backgroundTint="?attr/fabColor"
app:cardCornerRadius="@dimen/dimen36dp">
<ImageView
android:id="@+id/send"
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_send"
android:focusable="true" />
</com.google.android.material.card.MaterialCardView>
</LinearLayout>
</RelativeLayout>
</androidx.coordinatorlayout.widget.CoordinatorLayout>

View File

@ -138,6 +138,7 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/dimen32dp"
android:visibility="gone"
android:orientation="vertical">
<LinearLayout

View File

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
</androidx.coordinatorlayout.widget.CoordinatorLayout>

View File

@ -29,7 +29,6 @@
android:layout_marginBottom="8dp"
android:hint="@string/title"
android:textColorHint="?attr/hintColor"
app:boxBackgroundColor="?attr/inputBackgroundColor"
app:boxStrokeErrorColor="@color/darkRed"
app:hintTextColor="?attr/hintColor">
@ -51,7 +50,6 @@
android:layout_marginBottom="8dp"
android:hint="@string/sshKey"
android:textColorHint="?attr/hintColor"
app:boxBackgroundColor="?attr/inputBackgroundColor"
app:boxStrokeErrorColor="@color/darkRed"
app:hintTextColor="?attr/hintColor">

View File

@ -0,0 +1,35 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
xmlns:app="http://schemas.android.com/apk/res-auto">
<FrameLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="@dimen/dimen16dp">
<androidx.swiperefreshlayout.widget.SwipeRefreshLayout
android:id="@+id/pullToRefresh"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recyclerView"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
</FrameLayout>
<com.google.android.material.progressindicator.LinearProgressIndicator
android:id="@+id/progressBar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:indeterminate="true"
style="@style/Widget.Material3.LinearProgressIndicator"
app:indicatorColor="?attr/progressIndicatorColor" />
</androidx.coordinatorlayout.widget.CoordinatorLayout>

View File

@ -58,6 +58,7 @@
<item android:id="@+id/nav_comments_draft"
android:icon="@drawable/ic_drafts"
android:visible="false"
android:title="@string/titleDrafts"/>
<item android:id="@+id/nav_profile"

View File

@ -853,6 +853,8 @@
<item quantity="other">Notes deleted successfully</item>
</plurals>
<string name="notesAllDeletionMessage">This will delete all of your notes. This action cannot be undone.</string>
<string name="insertNote">Insert a Note</string>
<string name="noNotes">No notes found</string>
<!-- timeline -->
<string name="commitsText">commit</string>