1
0
mirror of https://codeberg.org/gitnex/GitNex synced 2025-02-16 20:20:36 +01:00

Enhancements and improvements (#1403)

closes #1388

Reviewed-on: https://codeberg.org/gitnex/GitNex/pulls/1403
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 2025-01-19 16:38:07 +00:00 committed by M M Arif
parent a288555076
commit cad36de367
67 changed files with 1232 additions and 527 deletions

View File

@ -13,9 +13,16 @@ steps:
OUTPUT: "signed.apk"
INSTANCE: "https://codeberg.org"
KS_FILE: "gitnex_ci_keystore.jks"
BOT_TOKEN:
from_secret: BOT_TOKEN
KS_PASS:
from_secret: KS_PASS
KEY_PASS:
from_secret: KEY_PASS
KS_REPO:
from_secret: KS_REPO
commands:
- ./scripts/sign-build.sh
secrets: [ BOT_TOKEN, KS_PASS, KEY_PASS, KS_REPO ]
when:
event: [ push, tag ]
branch: main
@ -27,9 +34,10 @@ steps:
WEBDAV_USERNAME: "GitNexBot"
PLUGIN_FILE: "signed.apk"
PLUGIN_DESTINATION: "https://cloud.swatian.com/remote.php/dav/files/GitNexBot/gitnex/builds/latest.apk"
WEBDAV_PASSWORD:
from_secret: WEBDAV_PASSWORD
commands:
- curl -T "$PLUGIN_FILE" -u "$WEBDAV_USERNAME":"$WEBDAV_PASSWORD" "$PLUGIN_DESTINATION"
secrets: [ WEBDAV_PASSWORD ]
when:
event: [ push, tag ]
branch: main
@ -40,9 +48,10 @@ steps:
environment:
WEBDAV_USERNAME: "GitNexBot"
PLUGIN_FILE: "signed.apk"
WEBDAV_PASSWORD:
from_secret: WEBDAV_PASSWORD
commands:
- "[[ $CI_COMMIT_TAG == *'-rc'* ]] && echo 'Upload blocked. Build seems to be a release candidate.' && exit 0"
- curl -T "$PLUGIN_FILE" -u "$WEBDAV_USERNAME":"$WEBDAV_PASSWORD" 'https://cloud.swatian.com/remote.php/dav/files/GitNexBot/gitnex/releases/'"$CI_COMMIT_TAG"'.apk'
secrets: [ WEBDAV_PASSWORD ]
when:
event: [ tag ]

View File

@ -3,7 +3,6 @@ depends_on:
- locale
- check
run_on: [ success, failure ]
skip_clone: true
steps:

View File

@ -4,7 +4,9 @@ steps:
commands:
- cp crowdin.example.yml crowdin.yml
- sed -i 's/-removed-/'"$CROWDIN_TOKEN"'/' crowdin.yml
secrets: [ CROWDIN_TOKEN ]
environment:
CROWDIN_TOKEN:
from_secret: CROWDIN_TOKEN
when:
event: [ push, tag, cron ]
branch: main
@ -40,7 +42,8 @@ steps:
- GITEA_BRANCH=main
# Token that should be used to authenticate against the gitea instance
# - BOT_TOKEN=secret
secrets: [ BOT_TOKEN ]
BOT_TOKEN:
from_secret: BOT_TOKEN
commands:
# Setup git credentials and checkout target branch
- git config user.name "$${GIT_AUTHOR_NAME}"

View File

@ -7,12 +7,12 @@ android {
defaultConfig {
applicationId "org.mian.gitnex"
minSdkVersion 23
targetSdkVersion 34
targetSdkVersion 35
versionCode 695
versionName "7.0.0-dev"
multiDexEnabled true
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
compileSdk 34
compileSdk 35
}
flavorDimensions = ["default"]
productFlavors {
@ -106,7 +106,7 @@ dependencies {
implementation 'ch.acra:acra-notification:5.11.3'
implementation 'androidx.room:room-runtime:2.6.1'
annotationProcessor 'androidx.room:room-compiler:2.6.1'
implementation "androidx.work:work-runtime:2.9.1"
implementation "androidx.work:work-runtime:2.10.0"
implementation "io.mikael:urlbuilder:2.0.9"
implementation "org.codeberg.gitnex-garage:emoji-java:v5.1.2"
//noinspection GradleDependency

View File

@ -61,7 +61,7 @@ public class AddCollaboratorToRepositoryActivity extends BaseActivity {
activityAddCollaboratorToRepositoryBinding
.addCollaboratorSearch.getText())
.toString()
.equals("")) {
.isEmpty()) {
activityAddCollaboratorToRepositoryBinding.progressBar.setVisibility(
View.VISIBLE);
@ -128,7 +128,9 @@ public class AddCollaboratorToRepositoryActivity extends BaseActivity {
activityAddCollaboratorToRepositoryBinding.noData.setVisibility(View.GONE);
} else {
activityAddCollaboratorToRepositoryBinding.recyclerViewUserSearch.setAdapter(null);
activityAddCollaboratorToRepositoryBinding.noData.setVisibility(View.VISIBLE);
adapter.clearAdapter();
}
activityAddCollaboratorToRepositoryBinding.progressBar.setVisibility(View.GONE);

View File

@ -118,7 +118,7 @@ public class AddNewTeamMemberActivity extends BaseActivity {
if (response.isSuccessful()) {
assert response.body() != null;
if (response.body().getData().size() > 0) {
if (!response.body().getData().isEmpty()) {
dataList.clear();
dataList.addAll(response.body().getData());
@ -126,7 +126,9 @@ public class AddNewTeamMemberActivity extends BaseActivity {
noData.setVisibility(View.GONE);
} else {
dataList.clear();
noData.setVisibility(View.VISIBLE);
mRecyclerView.setAdapter(null);
}
mProgressBar.setVisibility(View.GONE);

View File

@ -61,6 +61,7 @@ public class CreateNoteActivity extends BaseActivity {
binding.close.setOnClickListener(close -> finish());
binding.toolbarTitle.setMovementMethod(new ScrollingMovementMethod());
assert action != null;
if (action.equalsIgnoreCase("edit")) {
noteId = getIntent().getIntExtra("noteId", 0);

View File

@ -1,22 +1,47 @@
package org.mian.gitnex.activities;
import android.annotation.SuppressLint;
import android.app.Activity;
import android.app.NotificationManager;
import android.content.Context;
import android.content.Intent;
import android.graphics.Bitmap;
import android.graphics.Color;
import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.MenuItem;
import android.view.MotionEvent;
import android.view.View;
import android.widget.ArrayAdapter;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;
import androidx.activity.result.ActivityResultLauncher;
import androidx.activity.result.contract.ActivityResultContracts;
import androidx.annotation.NonNull;
import androidx.core.app.NotificationCompat;
import androidx.recyclerview.widget.LinearLayoutManager;
import com.bumptech.glide.Glide;
import com.bumptech.glide.load.engine.DiskCacheStrategy;
import com.bumptech.glide.request.target.CustomTarget;
import com.bumptech.glide.request.transition.Transition;
import com.google.android.material.bottomsheet.BottomSheetDialog;
import com.google.android.material.card.MaterialCardView;
import com.google.android.material.datepicker.MaterialDatePicker;
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import com.vdurmont.emoji.EmojiParser;
import java.io.File;
import java.io.IOException;
import java.io.OutputStream;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Date;
import java.util.LinkedHashMap;
@ -24,27 +49,41 @@ import java.util.List;
import java.util.Locale;
import java.util.Objects;
import java.util.TimeZone;
import okhttp3.MediaType;
import okhttp3.RequestBody;
import okhttp3.ResponseBody;
import org.apache.commons.io.FilenameUtils;
import org.gitnex.tea4j.v2.models.Attachment;
import org.gitnex.tea4j.v2.models.EditIssueOption;
import org.gitnex.tea4j.v2.models.Issue;
import org.gitnex.tea4j.v2.models.Milestone;
import org.mian.gitnex.R;
import org.mian.gitnex.adapters.AttachmentsAdapter;
import org.mian.gitnex.clients.RetrofitClient;
import org.mian.gitnex.databinding.ActivityEditIssueBinding;
import org.mian.gitnex.databinding.BottomSheetAttachmentsBinding;
import org.mian.gitnex.databinding.CustomImageViewDialogBinding;
import org.mian.gitnex.fragments.IssuesFragment;
import org.mian.gitnex.fragments.PullRequestsFragment;
import org.mian.gitnex.helpers.AlertDialogs;
import org.mian.gitnex.helpers.AppDatabaseSettings;
import org.mian.gitnex.helpers.AppUtil;
import org.mian.gitnex.helpers.Constants;
import org.mian.gitnex.helpers.Markdown;
import org.mian.gitnex.helpers.SnackBar;
import org.mian.gitnex.helpers.attachments.AttachmentUtils;
import org.mian.gitnex.helpers.attachments.AttachmentsModel;
import org.mian.gitnex.helpers.contexts.IssueContext;
import org.mian.gitnex.notifications.Notifications;
import retrofit2.Call;
import retrofit2.Callback;
import retrofit2.Response;
/**
* @author M M Arif
*/
public class EditIssueActivity extends BaseActivity {
public class EditIssueActivity extends BaseActivity
implements AttachmentsAdapter.AttachmentsReceiverListener {
private ActivityEditIssueBinding binding;
private final String msState = "open";
@ -52,6 +91,147 @@ public class EditIssueActivity extends BaseActivity {
private int milestoneId = 0;
private IssueContext issue;
private boolean renderMd = false;
private MaterialAlertDialogBuilder materialAlertDialogBuilder;
private String token;
private String filename;
private Long filesize;
private String filehash;
private String instanceUrlOnly;
private AttachmentsAdapter attachmentsAdapter;
private static List<AttachmentsModel> attachmentsList;
private static final List<Uri> contentUri = new ArrayList<>();
private MenuItem create;
public ActivityResultLauncher<Intent> downloadAttachmentLauncher =
registerForActivityResult(
new ActivityResultContracts.StartActivityForResult(),
result -> {
if (result.getResultCode() == Activity.RESULT_OK) {
assert result.getData() != null;
try {
OutputStream outputStream =
getContentResolver()
.openOutputStream(
Objects.requireNonNull(
result.getData().getData()));
NotificationCompat.Builder builder =
new NotificationCompat.Builder(ctx, ctx.getPackageName())
.setContentTitle(
getString(
R.string
.fileViewerNotificationTitleStarted))
.setContentText(
getString(
R.string
.fileViewerNotificationDescriptionStarted,
filename))
.setSmallIcon(R.drawable.gitnex_transparent)
.setPriority(NotificationCompat.PRIORITY_LOW)
.setChannelId(
Constants.downloadNotificationChannelId)
.setProgress(100, 0, false)
.setOngoing(true);
int notificationId = Notifications.uniqueNotificationId(ctx);
NotificationManager notificationManager =
(NotificationManager)
getSystemService(Context.NOTIFICATION_SERVICE);
notificationManager.notify(notificationId, builder.build());
Thread thread =
new Thread(
() -> {
try {
Call<ResponseBody> call =
RetrofitClient.getWebInterface(
ctx,
instanceUrlOnly)
.getAttachment(filehash);
Response<ResponseBody> response =
call.execute();
assert response.body() != null;
builder.setOngoing(false)
.setContentTitle(
getString(
R.string
.fileViewerNotificationTitleFinished))
.setContentText(
getString(
R.string
.fileViewerNotificationDescriptionFinished,
filename));
AppUtil.copyProgress(
response.body().byteStream(),
outputStream,
filesize,
progress -> {
builder.setProgress(
100, progress, false);
notificationManager.notify(
notificationId,
builder.build());
});
} catch (IOException ignored) {
builder.setOngoing(false)
.setContentTitle(
getString(
R.string
.fileViewerNotificationTitleFailed))
.setContentText(
getString(
R.string
.fileViewerNotificationDescriptionFailed,
filename));
} finally {
builder.setProgress(0, 0, false)
.setOngoing(false);
notificationManager.notify(
notificationId, builder.build());
}
});
thread.start();
} catch (IOException ignored) {
}
}
});
ActivityResultLauncher<Intent> startActivityForResult =
registerForActivityResult(
new ActivityResultContracts.StartActivityForResult(),
result -> {
if (result.getResultCode() == Activity.RESULT_OK) {
Intent data = result.getData();
assert data != null;
contentUri.add(data.getData());
attachmentsList.add(
new AttachmentsModel(
AttachmentUtils.queryName(ctx, data.getData()),
data.getData()));
attachmentsAdapter.updateList(attachmentsList);
}
});
public void onDestroy() {
AttachmentsAdapter.setAttachmentsReceiveListener(null);
super.onDestroy();
}
@SuppressLint("ClickableViewAccessibility")
@Override
@ -65,12 +245,27 @@ public class EditIssueActivity extends BaseActivity {
int resultLimit = Constants.getCurrentResultLimit(ctx);
issue = IssueContext.fromIntent(getIntent());
binding.topAppBar.setNavigationOnClickListener(v -> finish());
binding.topAppBar.setNavigationOnClickListener(
v -> {
finish();
contentUri.clear();
});
MenuItem attachment = binding.topAppBar.getMenu().getItem(0);
MenuItem create = binding.topAppBar.getMenu().getItem(2);
attachment.setVisible(false);
create.setTitle(getString(R.string.menuEditText));
materialAlertDialogBuilder =
new MaterialAlertDialogBuilder(ctx, R.style.ThemeOverlay_Material3_Dialog_Alert);
token = ((BaseActivity) ctx).getAccount().getAccount().getToken();
String instanceUrl = ((BaseActivity) ctx).getAccount().getAccount().getInstanceUrl();
instanceUrlOnly = instanceUrl.substring(0, instanceUrl.lastIndexOf("api/v1/"));
attachmentsList = new ArrayList<>();
attachmentsAdapter = new AttachmentsAdapter(attachmentsList, ctx);
AttachmentsAdapter.setAttachmentsReceiveListener(this);
create = binding.topAppBar.getMenu().getItem(2);
create.setTitle(getString(R.string.saveButton));
binding.editIssueDescription.setOnTouchListener(
(touchView, motionEvent) -> {
@ -122,7 +317,15 @@ public class EditIssueActivity extends BaseActivity {
return true;
} else if (id == R.id.create) {
create.setVisible(false);
processEditIssue();
if (!contentUri.isEmpty()) {
processAttachments();
contentUri.clear();
}
return true;
} else if (id == R.id.attachment) {
checkForAttachments();
return true;
} else {
return super.onOptionsItemSelected(menuItem);
@ -135,12 +338,116 @@ public class EditIssueActivity extends BaseActivity {
issue.getIssueIndex(),
resultLimit);
getAttachments();
if (!issue.getRepository().getPermissions().isPush()) {
findViewById(R.id.editIssueMilestoneSpinnerLayout).setVisibility(View.GONE);
findViewById(R.id.editIssueDueDateLayout).setVisibility(View.GONE);
}
}
@Override
public void setAttachmentsData(Uri filename) {
contentUri.remove(filename);
}
private void checkForAttachments() {
if (!contentUri.isEmpty()) {
BottomSheetAttachmentsBinding bottomSheetAttachmentsBinding =
BottomSheetAttachmentsBinding.inflate(getLayoutInflater());
BottomSheetDialog bottomSheetDialog = new BottomSheetDialog(ctx);
bottomSheetAttachmentsBinding.addAttachment.setOnClickListener(
v1 -> openFileAttachmentActivity());
bottomSheetAttachmentsBinding.recyclerViewAttachments.setHasFixedSize(true);
bottomSheetAttachmentsBinding.recyclerViewAttachments.setLayoutManager(
new LinearLayoutManager(ctx));
bottomSheetAttachmentsBinding.recyclerViewAttachments.setAdapter(attachmentsAdapter);
bottomSheetDialog.setContentView(bottomSheetAttachmentsBinding.getRoot());
bottomSheetDialog.show();
} else {
attachmentsAdapter.clearAdapter();
openFileAttachmentActivity();
}
}
private void openFileAttachmentActivity() {
Intent data = new Intent(Intent.ACTION_GET_CONTENT);
data.addCategory(Intent.CATEGORY_OPENABLE);
data.setType("*/*");
Intent intent = Intent.createChooser(data, "Choose a file");
startActivityForResult.launch(intent);
}
private void processAttachments() {
for (int i = 0; i < contentUri.size(); i++) {
File file = AttachmentUtils.getFile(ctx, contentUri.get(i));
RequestBody requestFile =
RequestBody.create(
file,
MediaType.parse(
Objects.requireNonNull(
getContentResolver().getType(contentUri.get(i)))));
uploadAttachments(requestFile, file.getName());
}
}
private void uploadAttachments(RequestBody requestFile, String filename1) {
Call<Attachment> call3 =
RetrofitClient.getApiInterface(ctx)
.issueCreateIssueAttachment(
requestFile,
issue.getRepository().getOwner(),
issue.getRepository().getName(),
(long) issue.getIssueIndex(),
filename1);
call3.enqueue(
new Callback<>() {
@Override
public void onResponse(
@NonNull Call<Attachment> call,
@NonNull retrofit2.Response<Attachment> response2) {
if (response2.code() == 201) {
new Handler().postDelayed(() -> finish(), 3000);
} else if (response2.code() == 401) {
AlertDialogs.authorizationTokenRevokedDialog(ctx);
} else {
create.setVisible(true);
SnackBar.error(
ctx,
findViewById(android.R.id.content),
getString(R.string.attachmentsSaveError));
}
}
@Override
public void onFailure(@NonNull Call<Attachment> call, @NonNull Throwable t) {
create.setVisible(true);
SnackBar.error(
ctx,
findViewById(android.R.id.content),
getString(R.string.genericServerResponseError));
}
});
}
private void processEditIssue() {
String editIssueTitleForm =
@ -149,7 +456,7 @@ public class EditIssueActivity extends BaseActivity {
Objects.requireNonNull(binding.editIssueDescription.getText()).toString();
String dueDate = Objects.requireNonNull(binding.editIssueDueDate.getText()).toString();
if (editIssueTitleForm.equals("")) {
if (editIssueTitleForm.isEmpty()) {
SnackBar.error(
ctx, findViewById(android.R.id.content), getString(R.string.issueTitleEmpty));
@ -229,6 +536,7 @@ public class EditIssueActivity extends BaseActivity {
AlertDialogs.authorizationTokenRevokedDialog(ctx);
} else {
create.setVisible(true);
SnackBar.error(
ctx,
findViewById(android.R.id.content),
@ -237,7 +545,9 @@ public class EditIssueActivity extends BaseActivity {
}
@Override
public void onFailure(@NonNull Call<Issue> call, @NonNull Throwable t) {}
public void onFailure(@NonNull Call<Issue> call, @NonNull Throwable t) {
create.setVisible(true);
}
});
}
@ -326,7 +636,7 @@ public class EditIssueActivity extends BaseActivity {
.issueCreatedNoMilestone));
milestonesList.put(ms.getTitle(), ms);
if (milestonesList_.size() > 0) {
if (!milestonesList_.isEmpty()) {
for (Milestone milestone :
milestonesList_) {
@ -448,6 +758,150 @@ public class EditIssueActivity extends BaseActivity {
});
}
private void getAttachments() {
Call<List<Attachment>> call =
RetrofitClient.getApiInterface(ctx)
.issueListIssueAttachments(
issue.getRepository().getOwner(),
issue.getRepository().getName(),
(long) issue.getIssueIndex());
call.enqueue(
new Callback<>() {
@Override
public void onResponse(
@NonNull Call<List<Attachment>> call,
@NonNull retrofit2.Response<List<Attachment>> response) {
List<Attachment> attachment = response.body();
if (response.code() == 200) {
assert attachment != null;
if (!attachment.isEmpty()) {
binding.attachmentFrame.setVisibility(View.VISIBLE);
LinearLayout.LayoutParams paramsAttachment =
new LinearLayout.LayoutParams(96, 96);
paramsAttachment.setMargins(0, 0, 48, 0);
for (int i = 0; i < attachment.size(); i++) {
ImageView attachmentView = new ImageView(ctx);
MaterialCardView materialCardView = new MaterialCardView(ctx);
materialCardView.setLayoutParams(paramsAttachment);
materialCardView.setStrokeWidth(0);
materialCardView.setRadius(28);
materialCardView.setCardBackgroundColor(Color.TRANSPARENT);
if (Arrays.asList(
"bmp", "gif", "jpg", "jpeg", "png", "webp",
"heic", "heif")
.contains(
FilenameUtils.getExtension(
attachment.get(i).getName())
.toLowerCase())) {
Glide.with(ctx)
.load(
attachment.get(i).getBrowserDownloadUrl()
+ "?token="
+ token)
.diskCacheStrategy(DiskCacheStrategy.ALL)
.placeholder(R.drawable.loader_animated)
.centerCrop()
.error(R.drawable.ic_close)
.into(attachmentView);
binding.attachmentsView.addView(materialCardView);
attachmentView.setLayoutParams(paramsAttachment);
materialCardView.addView(attachmentView);
int finalI1 = i;
materialCardView.setOnClickListener(
v1 ->
imageViewDialog(
attachment
.get(finalI1)
.getBrowserDownloadUrl()));
} else {
attachmentView.setImageResource(
R.drawable.ic_file_download);
attachmentView.setPadding(4, 4, 4, 4);
binding.attachmentsView.addView(materialCardView);
attachmentView.setLayoutParams(paramsAttachment);
materialCardView.addView(attachmentView);
int finalI = i;
materialCardView.setOnClickListener(
v1 -> {
filesize = attachment.get(finalI).getSize();
filename = attachment.get(finalI).getName();
filehash = attachment.get(finalI).getUuid();
requestFileDownload();
});
}
}
} else {
binding.attachmentFrame.setVisibility(View.GONE);
}
}
}
@Override
public void onFailure(
@NonNull Call<List<Attachment>> call, @NonNull Throwable t) {}
});
}
private void requestFileDownload() {
Intent intent = new Intent(Intent.ACTION_CREATE_DOCUMENT);
intent.addCategory(Intent.CATEGORY_OPENABLE);
intent.putExtra(Intent.EXTRA_TITLE, filename);
intent.setType("*/*");
downloadAttachmentLauncher.launch(intent);
}
private void imageViewDialog(String url) {
CustomImageViewDialogBinding imageViewDialogBinding =
CustomImageViewDialogBinding.inflate(LayoutInflater.from(ctx));
View view = imageViewDialogBinding.getRoot();
materialAlertDialogBuilder.setView(view);
materialAlertDialogBuilder.setNeutralButton(getString(R.string.close), null);
Glide.with(ctx)
.asBitmap()
.load(url + "?token=" + token)
.diskCacheStrategy(DiskCacheStrategy.ALL)
.placeholder(R.drawable.loader_animated)
.centerCrop()
.error(R.drawable.ic_close)
.into(
new CustomTarget<Bitmap>() {
@Override
public void onResourceReady(
@NonNull Bitmap resource,
Transition<? super Bitmap> transition) {
imageViewDialogBinding.imageView.setImageBitmap(resource);
imageViewDialogBinding.imageView.buildDrawingCache();
}
@Override
public void onLoadCleared(Drawable placeholder) {}
});
materialAlertDialogBuilder.create().show();
}
@Override
public void onResume() {
super.onResume();

View File

@ -535,7 +535,7 @@ public class IssueDetailActivity extends BaseActivity
viewBinding.commentReply.setText(null);
viewBinding.commentReply.clearFocus();
imm.toggleSoftInput(
InputMethodManager.HIDE_IMPLICIT_ONLY, 0);
InputMethodManager.SHOW_IMPLICIT, 0);
} else {
Toasty.error(ctx, getString(R.string.genericError));
@ -1785,7 +1785,7 @@ public class IssueDetailActivity extends BaseActivity
viewBinding.send.setEnabled(false);
viewBinding.commentReply.setText(null);
viewBinding.commentReply.clearFocus();
imm.toggleSoftInput(InputMethodManager.HIDE_IMPLICIT_ONLY, 0);
imm.toggleSoftInput(InputMethodManager.SHOW_IMPLICIT, 0);
} else if (response.code() == 401) {

View File

@ -10,6 +10,7 @@ import android.view.MenuItem;
import android.view.View;
import android.widget.ImageView;
import android.widget.TextView;
import androidx.activity.EdgeToEdge;
import androidx.activity.OnBackPressedCallback;
import androidx.annotation.NonNull;
import androidx.appcompat.app.ActionBarDrawerToggle;
@ -93,6 +94,7 @@ public class MainActivity extends BaseActivity
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
EdgeToEdge.enable(this);
ActivityMainBinding activityMainBinding = ActivityMainBinding.inflate(getLayoutInflater());
setContentView(activityMainBinding.getRoot());

View File

@ -222,11 +222,11 @@ public class RepoDetailActivity extends BaseActivity implements BottomSheetListe
filterMilestoneBottomSheet.show(
getSupportFragmentManager(), "repoFilterMenuMilestoneBottomSheet");
return true;
} else if (id == R.id.switchBranches) {
} /*else if (id == R.id.switchBranches) {
chooseBranch();
return true;
} else if (id == R.id.branchCommits) {
}*/ else if (id == R.id.branchCommits) {
Intent intent = repository.getIntent(ctx, CommitsActivity.class);

View File

@ -1,5 +1,6 @@
package org.mian.gitnex.adapters;
import android.annotation.SuppressLint;
import android.content.Context;
import android.content.Intent;
import android.os.Handler;
@ -99,7 +100,7 @@ public class CollaboratorSearchAdapter
currentItem.getLogin());
call.enqueue(
new Callback<Void>() {
new Callback<>() {
@Override
public void onResponse(
@ -138,6 +139,12 @@ public class CollaboratorSearchAdapter
return usersSearchList.size();
}
@SuppressLint("NotifyDataSetChanged")
public void clearAdapter() {
usersSearchList.clear();
notifyDataSetChanged();
}
public class CollaboratorSearchViewHolder extends RecyclerView.ViewHolder {
private final ImageView userAvatar;

View File

@ -18,6 +18,7 @@ import com.amulyakhare.textdrawable.TextDrawable;
import com.amulyakhare.textdrawable.util.ColorGenerator;
import com.bumptech.glide.Glide;
import com.bumptech.glide.load.engine.DiskCacheStrategy;
import com.google.android.material.card.MaterialCardView;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
@ -164,6 +165,7 @@ public class ReposListAdapter extends RecyclerView.Adapter<RecyclerView.ViewHold
private final View spacerView;
private org.gitnex.tea4j.v2.models.Repository userRepositories;
private CheckBox isRepoAdmin;
private MaterialCardView isRepoArchivedFrame;
ReposHolder(View itemView) {
@ -176,6 +178,7 @@ public class ReposListAdapter extends RecyclerView.Adapter<RecyclerView.ViewHold
repoStars = itemView.findViewById(R.id.repoStars);
repoLastUpdated = itemView.findViewById(R.id.repoLastUpdated);
spacerView = itemView.findViewById(R.id.spacerView);
isRepoArchivedFrame = itemView.findViewById(R.id.repo_is_archived_frame);
itemView.setOnClickListener(
v -> {
@ -254,6 +257,12 @@ public class ReposListAdapter extends RecyclerView.Adapter<RecyclerView.ViewHold
isRepoAdmin = new CheckBox(context);
}
isRepoAdmin.setChecked(repositories.getPermissions().isAdmin());
if (repositories.isArchived()) {
isRepoArchivedFrame.setVisibility(View.VISIBLE);
} else {
isRepoArchivedFrame.setVisibility(View.GONE);
}
}
}
}

View File

@ -1,6 +1,7 @@
package org.mian.gitnex.fragments;
import android.annotation.SuppressLint;
import android.app.Dialog;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
@ -17,17 +18,21 @@ import androidx.appcompat.widget.SearchView;
import androidx.fragment.app.Fragment;
import androidx.lifecycle.ViewModelProvider;
import androidx.recyclerview.widget.LinearLayoutManager;
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import moe.feng.common.view.breadcrumbs.DefaultBreadcrumbsCallback;
import moe.feng.common.view.breadcrumbs.model.BreadcrumbItem;
import org.gitnex.tea4j.v2.models.Branch;
import org.gitnex.tea4j.v2.models.ContentsResponse;
import org.mian.gitnex.R;
import org.mian.gitnex.activities.CreateFileActivity;
import org.mian.gitnex.activities.FileViewActivity;
import org.mian.gitnex.activities.RepoDetailActivity;
import org.mian.gitnex.adapters.FilesAdapter;
import org.mian.gitnex.clients.RetrofitClient;
import org.mian.gitnex.database.api.BaseApi;
import org.mian.gitnex.database.api.UserAccountsApi;
import org.mian.gitnex.database.models.UserAccount;
@ -36,6 +41,9 @@ import org.mian.gitnex.helpers.AppUtil;
import org.mian.gitnex.helpers.Path;
import org.mian.gitnex.helpers.contexts.RepositoryContext;
import org.mian.gitnex.viewmodels.FilesViewModel;
import retrofit2.Call;
import retrofit2.Callback;
import retrofit2.Response;
/**
* @author M M Arif
@ -79,6 +87,8 @@ public class FilesFragment extends Fragment implements FilesAdapter.FilesAdapter
binding.recyclerView.setLayoutManager(new LinearLayoutManager(getContext()));
binding.recyclerView.setAdapter(filesAdapter);
binding.branchTitle.setText(repository.getBranchRef());
binding.breadcrumbsView.setItems(
new ArrayList<>(
Collections.singletonList(
@ -172,6 +182,8 @@ public class FilesFragment extends Fragment implements FilesAdapter.FilesAdapter
binding.newFile.setOnClickListener(
v17 -> startActivity(repository.getIntent(getContext(), CreateFileActivity.class)));
binding.switchBranch.setOnClickListener(switchBranch -> chooseBranch());
return binding.getRoot();
}
@ -220,10 +232,10 @@ public class FilesFragment extends Fragment implements FilesAdapter.FilesAdapter
for (UserAccount userAccount : userAccounts) {
Uri instanceUri = Uri.parse(userAccount.getInstanceUrl());
if (instanceUri.getHost().toLowerCase().equals(host)) {
if (Objects.requireNonNull(instanceUri.getHost()).toLowerCase().equals(host)) {
account = userAccount;
// if scheme is wrong fix it
if (!url.getScheme().equals(instanceUri.getScheme())) {
if (!Objects.equals(url.getScheme(), instanceUri.getScheme())) {
url = AppUtil.changeScheme(url, instanceUri.getScheme());
}
break;
@ -281,7 +293,7 @@ public class FilesFragment extends Fragment implements FilesAdapter.FilesAdapter
filesAdapter.getOriginalFiles().addAll(filesListMain);
filesAdapter.notifyOriginalDataSetChanged();
if (filesListMain.size() > 0) {
if (!filesListMain.isEmpty()) {
AppUtil.setMultiVisibility(
View.VISIBLE, binding.recyclerView, binding.filesFrame);
@ -322,7 +334,7 @@ public class FilesFragment extends Fragment implements FilesAdapter.FilesAdapter
filesAdapter.getOriginalFiles().addAll(filesListMain2);
filesAdapter.notifyOriginalDataSetChanged();
if (filesListMain2.size() > 0) {
if (!filesListMain2.isEmpty()) {
AppUtil.setMultiVisibility(
View.VISIBLE, binding.recyclerView, binding.filesFrame);
@ -340,6 +352,70 @@ public class FilesFragment extends Fragment implements FilesAdapter.FilesAdapter
});
}
private void chooseBranch() {
Dialog progressDialog = new Dialog(requireContext());
progressDialog.setCancelable(false);
progressDialog.setContentView(R.layout.custom_progress_loader);
progressDialog.show();
MaterialAlertDialogBuilder materialAlertDialogBuilder =
new MaterialAlertDialogBuilder(
requireContext(), R.style.ThemeOverlay_Material3_Dialog_Alert);
Call<List<Branch>> call =
RetrofitClient.getApiInterface(requireContext())
.repoListBranches(repository.getOwner(), repository.getName(), null, null);
call.enqueue(
new Callback<>() {
@Override
public void onResponse(
@NonNull Call<List<Branch>> call,
@NonNull Response<List<Branch>> response) {
progressDialog.hide();
if (response.code() == 200) {
List<String> branchesList = new ArrayList<>();
int selectedBranch = 0;
assert response.body() != null;
for (int i = 0; i < response.body().size(); i++) {
Branch branches = response.body().get(i);
branchesList.add(branches.getName());
if (repository.getBranchRef().equals(branches.getName())) {
selectedBranch = i;
}
}
materialAlertDialogBuilder
.setTitle(R.string.pageTitleChooseBranch)
.setSingleChoiceItems(
branchesList.toArray(new String[0]),
selectedBranch,
(dialogInterface, i) -> {
repository.setBranchRef(branchesList.get(i));
binding.branchTitle.setText(branchesList.get(i));
refresh();
dialogInterface.dismiss();
})
.setNeutralButton(R.string.cancelButton, null);
materialAlertDialogBuilder.create().show();
}
}
@Override
public void onFailure(@NonNull Call<List<Branch>> call, @NonNull Throwable t) {
progressDialog.hide();
}
});
}
@Override
public void onCreateOptionsMenu(@NonNull Menu menu, @NonNull MenuInflater inflater) {
@ -353,6 +429,7 @@ public class FilesFragment extends Fragment implements FilesAdapter.FilesAdapter
MenuItem searchItem = menu.findItem(R.id.action_search);
SearchView searchView = (SearchView) searchItem.getActionView();
assert searchView != null;
searchView.setImeOptions(EditorInfo.IME_ACTION_DONE);
searchView.setOnQueryTextListener(
new SearchView.OnQueryTextListener() {

View File

@ -286,19 +286,9 @@ public class RepoInfoFragment extends Fragment {
AlertDialogs.authorizationTokenRevokedDialog(ctx);
break;
case 403:
Toasty.error(ctx, ctx.getString(R.string.authorizeError));
binding.languagesStatistic.setVisibility(View.GONE);
break;
case 404:
binding.languagesStatistic.setVisibility(View.GONE);
break;
default:
Toasty.error(getContext(), getString(R.string.genericError));
binding.languagesStatistic.setVisibility(View.GONE);
break;
}
}
}
@ -529,22 +519,10 @@ public class RepoInfoFragment extends Fragment {
AlertDialogs.authorizationTokenRevokedDialog(ctx);
break;
case 403:
Toasty.error(ctx, ctx.getString(R.string.authorizeError));
binding.fileContentsFrameHeader.setVisibility(View.GONE);
binding.fileContentsFrame.setVisibility(View.GONE);
break;
case 404:
binding.fileContentsFrameHeader.setVisibility(View.GONE);
binding.fileContentsFrame.setVisibility(View.GONE);
break;
default:
Toasty.error(getContext(), getString(R.string.genericError));
binding.fileContentsFrameHeader.setVisibility(View.GONE);
binding.fileContentsFrame.setVisibility(View.GONE);
break;
}
}
}

View File

@ -1,10 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout android:layout_width="match_parent"
<androidx.coordinatorlayout.widget.CoordinatorLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:orientation="vertical"
android:background="?attr/primaryBackgroundColor"
xmlns:android="http://schemas.android.com/apk/res/android"
android:background="?attr/primaryBackgroundColor">
xmlns:app="http://schemas.android.com/apk/res-auto"
android:fitsSystemWindows="true">
<com.google.android.material.appbar.AppBarLayout
android:id="@+id/appbar"
@ -55,66 +56,69 @@
android:visibility="gone"
app:indicatorColor="?attr/progressIndicatorColor"/>
<LinearLayout
<androidx.core.widget.NestedScrollView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/dimen10dp"
android:layout_marginEnd="@dimen/dimen10dp"
android:layout_marginTop="@dimen/dimen10dp"
android:layout_marginBottom="@dimen/dimen10dp">
android:layout_height="match_parent"
app:layout_behavior="@string/appbar_scrolling_view_behavior">
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/addCollaboratorSearchLayout"
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textColorHint="?attr/hintColor"
app:hintTextColor="?attr/hintColor"
app:boxStrokeErrorColor="@color/darkRed"
android:layout_marginTop="@dimen/dimen8dp"
android:layout_marginBottom="@dimen/dimen8dp"
app:startIconDrawable="@drawable/ic_search"
app:startIconTint="?attr/iconsColor"
app:endIconMode="clear_text"
app:endIconTint="?attr/iconsColor"
android:hint="@string/addCollaboratorSearchHint">
android:orientation="vertical"
android:padding="@dimen/dimen16dp">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/addCollaboratorSearch"
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/addCollaboratorSearchLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textColor="?attr/inputTextColor"
android:textColorHint="?attr/hintColor"
android:singleLine="true"
android:imeOptions="actionSend"
android:inputType="text"
android:textSize="@dimen/dimen16sp"/>
app:hintTextColor="?attr/hintColor"
app:boxStrokeErrorColor="@color/darkRed"
android:layout_marginBottom="@dimen/dimen8dp"
app:startIconDrawable="@drawable/ic_search"
app:startIconTint="?attr/iconsColor"
app:endIconMode="clear_text"
app:endIconTint="?attr/iconsColor"
android:hint="@string/addCollaboratorSearchHint">
</com.google.android.material.textfield.TextInputLayout>
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/addCollaboratorSearch"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textColor="?attr/inputTextColor"
android:textColorHint="?attr/hintColor"
android:singleLine="true"
android:imeOptions="actionSend"
android:inputType="text"
android:textSize="@dimen/dimen16sp"/>
</LinearLayout>
</com.google.android.material.textfield.TextInputLayout>
<TextView
android:id="@+id/noData"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_margin="@dimen/dimen16dp"
android:gravity="center"
android:text="@string/noDataFound"
android:textColor="?attr/primaryTextColor"
android:textSize="@dimen/dimen20sp"
android:visibility="gone"/>
<TextView
android:id="@+id/noData"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_margin="@dimen/dimen16dp"
android:gravity="center"
android:text="@string/noDataFound"
android:textColor="?attr/primaryTextColor"
android:textSize="@dimen/dimen20sp"
android:visibility="gone"/>
<FrameLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="?attr/primaryBackgroundColor"
android:padding="@dimen/dimen8dp">
<FrameLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="?attr/primaryBackgroundColor">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recyclerViewUserSearch"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recyclerViewUserSearch"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
</FrameLayout>
</FrameLayout>
</LinearLayout>
</LinearLayout>
</androidx.core.widget.NestedScrollView>
</androidx.coordinatorlayout.widget.CoordinatorLayout>

View File

@ -4,14 +4,14 @@
android:layout_height="match_parent"
android:background="?attr/primaryBackgroundColor"
xmlns:android="http://schemas.android.com/apk/res/android"
android:fitsSystemWindows="true"
xmlns:app="http://schemas.android.com/apk/res-auto">
<com.google.android.material.appbar.AppBarLayout
android:id="@+id/appBarLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?attr/primaryBackgroundColor"
android:fitsSystemWindows="true">
android:background="?attr/primaryBackgroundColor">
<com.google.android.material.appbar.CollapsingToolbarLayout
style="?attr/collapsingToolbarLayoutLargeStyle"

View File

@ -1,10 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout android:layout_width="match_parent"
<androidx.coordinatorlayout.widget.CoordinatorLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:orientation="vertical"
android:background="?attr/primaryBackgroundColor"
xmlns:android="http://schemas.android.com/apk/res/android"
android:background="?attr/primaryBackgroundColor">
xmlns:app="http://schemas.android.com/apk/res-auto"
android:fitsSystemWindows="true">
<com.google.android.material.appbar.AppBarLayout
android:id="@+id/appbar"
@ -55,15 +56,18 @@
style="@style/Widget.MaterialComponents.LinearProgressIndicator"
app:indicatorColor="?attr/progressIndicatorColor"/>
<LinearLayout
<androidx.core.widget.NestedScrollView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/dimen10dp"
android:layout_marginEnd="@dimen/dimen10dp"
android:layout_marginTop="@dimen/dimen10dp&