Add support for tags (#1002)

Closes #983

TODO

- [x] create tags
- [x] view tags
- [x] apply pagination
- [x] delete tags

Co-authored-by: qwerty287 <ndev@web.de>
Co-authored-by: M M Arif <mmarif@noreply.codeberg.org>
Co-authored-by: M M Arif <mmarif@swatian.com>
Reviewed-on: https://codeberg.org/gitnex/GitNex/pulls/1002
Reviewed-by: M M Arif <mmarif@noreply.codeberg.org>
Co-authored-by: qwerty287 <qwerty287@noreply.codeberg.org>
Co-committed-by: qwerty287 <qwerty287@noreply.codeberg.org>
This commit is contained in:
qwerty287 2022-02-12 14:41:11 +01:00 committed by M M Arif
parent 8cfd4b6a31
commit f79d227bff
15 changed files with 908 additions and 30 deletions

View File

@ -109,7 +109,7 @@ dependencies {
implementation "androidx.work:work-runtime:$work_version" implementation "androidx.work:work-runtime:$work_version"
implementation "io.mikael:urlbuilder:2.0.9" implementation "io.mikael:urlbuilder:2.0.9"
implementation "org.codeberg.gitnex-garage:emoji-java:v5.1.2" implementation "org.codeberg.gitnex-garage:emoji-java:v5.1.2"
implementation "org.codeberg.gitnex:tea4j:1.0.29" implementation "org.codeberg.gitnex:tea4j:1.0.30"
coreLibraryDesugaring "com.android.tools:desugar_jdk_libs:1.1.5" coreLibraryDesugaring "com.android.tools:desugar_jdk_libs:1.1.5"
implementation 'androidx.biometric:biometric:1.1.0' implementation 'androidx.biometric:biometric:1.1.0'
implementation 'com.github.chrisvest:stormpot:2.4.2' implementation 'com.github.chrisvest:stormpot:2.4.2'

View File

@ -15,6 +15,8 @@ import android.widget.EditText;
import android.widget.ImageView; import android.widget.ImageView;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import org.gitnex.tea4j.models.Branches; import org.gitnex.tea4j.models.Branches;
import org.gitnex.tea4j.models.CreateTagOptions;
import org.gitnex.tea4j.models.GitTag;
import org.gitnex.tea4j.models.Releases; import org.gitnex.tea4j.models.Releases;
import org.mian.gitnex.R; import org.mian.gitnex.R;
import org.mian.gitnex.clients.RetrofitClient; import org.mian.gitnex.clients.RetrofitClient;
@ -44,6 +46,7 @@ public class CreateReleaseActivity extends BaseActivity {
private CheckBox releaseDraft; private CheckBox releaseDraft;
private Button createNewRelease; private Button createNewRelease;
private String selectedBranch; private String selectedBranch;
private Button createNewTag;
private String repoOwner; private String repoOwner;
private String repoName; private String repoName;
@ -97,6 +100,7 @@ public class CreateReleaseActivity extends BaseActivity {
getBranches(Authorization.get(ctx), repoOwner, repoName); getBranches(Authorization.get(ctx), repoOwner, repoName);
createNewRelease = activityCreateReleaseBinding.createNewRelease; createNewRelease = activityCreateReleaseBinding.createNewRelease;
createNewTag = activityCreateReleaseBinding.createNewTag;
disableProcessButton(); disableProcessButton();
if(!connToInternet) { if(!connToInternet) {
@ -108,6 +112,79 @@ public class CreateReleaseActivity extends BaseActivity {
createNewRelease.setOnClickListener(createReleaseListener); createNewRelease.setOnClickListener(createReleaseListener);
} }
createNewTag.setOnClickListener(v -> createNewTag());
}
private void createNewTag() {
boolean connToInternet = AppUtil.hasNetworkConnection(appCtx);
String tagName = releaseTagName.getText().toString();
String message = releaseTitle.getText().toString() + "\n\n" + releaseContent.getText().toString();
if(!connToInternet) {
Toasty.error(ctx, getResources().getString(R.string.checkNetConnection));
return;
}
if(tagName.equals("")) {
Toasty.error(ctx, getString(R.string.tagNameErrorEmpty));
return;
}
if(selectedBranch == null) {
Toasty.error(ctx, getString(R.string.selectBranchError));
return;
}
disableProcessButton();
CreateTagOptions createReleaseJson = new CreateTagOptions(message, tagName, selectedBranch);
Call<GitTag> call = RetrofitClient
.getApiInterface(ctx)
.createTag(Authorization.get(ctx), repoOwner, repoName, createReleaseJson);
call.enqueue(new Callback<GitTag>() {
@Override
public void onResponse(@NonNull Call<GitTag> call, @NonNull retrofit2.Response<GitTag> response) {
if (response.code() == 201) {
tinyDB.putBoolean("updateReleases", true);
Toasty.success(ctx, getString(R.string.tagCreated));
finish();
}
else if(response.code() == 401) {
enableProcessButton();
AlertDialogs.authorizationTokenRevokedDialog(ctx, ctx.getResources().getString(R.string.alertDialogTokenRevokedTitle),
ctx.getResources().getString(R.string.alertDialogTokenRevokedMessage),
ctx.getResources().getString(R.string.cancelButton),
ctx.getResources().getString(R.string.navLogout));
}
else if(response.code() == 403) {
enableProcessButton();
Toasty.error(ctx, ctx.getString(R.string.authorizeError));
}
else if(response.code() == 404) {
enableProcessButton();
Toasty.warning(ctx, ctx.getString(R.string.apiNotFound));
}
else {
enableProcessButton();
Toasty.error(ctx, ctx.getString(R.string.genericError));
}
}
@Override
public void onFailure(@NonNull Call<GitTag> call, @NonNull Throwable t) {
Log.e("onFailure", t.toString());
enableProcessButton();
}
});
} }
private final View.OnClickListener createReleaseListener = v -> processNewRelease(); private final View.OnClickListener createReleaseListener = v -> processNewRelease();
@ -268,12 +345,12 @@ public class CreateReleaseActivity extends BaseActivity {
} }
private void disableProcessButton() { private void disableProcessButton() {
createNewTag.setEnabled(false);
createNewRelease.setEnabled(false); createNewRelease.setEnabled(false);
} }
private void enableProcessButton() { private void enableProcessButton() {
createNewTag.setEnabled(true);
createNewRelease.setEnabled(true); createNewRelease.setEnabled(true);
} }

View File

@ -39,6 +39,7 @@ import org.mian.gitnex.database.models.UserAccount;
import org.mian.gitnex.fragments.BottomSheetIssuesFilterFragment; import org.mian.gitnex.fragments.BottomSheetIssuesFilterFragment;
import org.mian.gitnex.fragments.BottomSheetMilestonesFilterFragment; import org.mian.gitnex.fragments.BottomSheetMilestonesFilterFragment;
import org.mian.gitnex.fragments.BottomSheetPullRequestFilterFragment; import org.mian.gitnex.fragments.BottomSheetPullRequestFilterFragment;
import org.mian.gitnex.fragments.BottomSheetReleasesTagsFragment;
import org.mian.gitnex.fragments.BottomSheetRepoFragment; import org.mian.gitnex.fragments.BottomSheetRepoFragment;
import org.mian.gitnex.fragments.CollaboratorsFragment; import org.mian.gitnex.fragments.CollaboratorsFragment;
import org.mian.gitnex.fragments.FilesFragment; import org.mian.gitnex.fragments.FilesFragment;
@ -76,6 +77,7 @@ public class RepoDetailActivity extends BaseActivity implements BottomSheetListe
private FragmentRefreshListener fragmentRefreshListenerMilestone; private FragmentRefreshListener fragmentRefreshListenerMilestone;
private FragmentRefreshListener fragmentRefreshListenerFiles; private FragmentRefreshListener fragmentRefreshListenerFiles;
private FragmentRefreshListener fragmentRefreshListenerFilterIssuesByMilestone; private FragmentRefreshListener fragmentRefreshListenerFilterIssuesByMilestone;
private FragmentRefreshListener fragmentRefreshListenerReleases;
private String repositoryOwner; private String repositoryOwner;
private String repositoryName; private String repositoryName;
@ -388,6 +390,11 @@ public class RepoDetailActivity extends BaseActivity implements BottomSheetListe
ctx.startActivity(intent); ctx.startActivity(intent);
return true; return true;
} }
else if(id == R.id.filterReleases && new Version(tinyDB.getString("giteaVersion")).higherOrEqual("1.15.0")) {
BottomSheetReleasesTagsFragment bottomSheetReleasesTagsFragment = new BottomSheetReleasesTagsFragment();
bottomSheetReleasesTagsFragment.show(getSupportFragmentManager(), "repoFilterReleasesMenuBottomSheet");
return true;
}
return super.onOptionsItemSelected(item); return super.onOptionsItemSelected(item);
@ -498,6 +505,16 @@ public class RepoDetailActivity extends BaseActivity implements BottomSheetListe
startActivity(new Intent(RepoDetailActivity.this, CreatePullRequestActivity.class)); startActivity(new Intent(RepoDetailActivity.this, CreatePullRequestActivity.class));
break; break;
case "tags":
if(getFragmentRefreshListenerReleases() != null) {
getFragmentRefreshListenerReleases().onRefresh("tags");
}
break;
case "releases":
if(getFragmentRefreshListenerReleases() != null) {
getFragmentRefreshListenerReleases().onRefresh("releases");
}
break;
} }
} }
@ -819,4 +836,9 @@ public class RepoDetailActivity extends BaseActivity implements BottomSheetListe
public void setFragmentRefreshListenerFiles(FragmentRefreshListener fragmentRefreshListenerFiles) { this.fragmentRefreshListenerFiles = fragmentRefreshListenerFiles; } public void setFragmentRefreshListenerFiles(FragmentRefreshListener fragmentRefreshListenerFiles) { this.fragmentRefreshListenerFiles = fragmentRefreshListenerFiles; }
//Releases interface
public FragmentRefreshListener getFragmentRefreshListenerReleases() { return fragmentRefreshListenerReleases; }
public void setFragmentRefreshListenerReleases(FragmentRefreshListener fragmentRefreshListener) { this.fragmentRefreshListenerReleases = fragmentRefreshListener; }
} }

View File

@ -32,9 +32,12 @@ import java.util.Locale;
public class ReleasesAdapter extends RecyclerView.Adapter<ReleasesAdapter.ReleasesViewHolder> { public class ReleasesAdapter extends RecyclerView.Adapter<ReleasesAdapter.ReleasesViewHolder> {
private final List<Releases> releasesList; private List<Releases> releasesList;
private final Context context; private final Context context;
private OnLoadMoreListener loadMoreListener;
private boolean isLoading = false, isMoreDataAvailable = true;
static class ReleasesViewHolder extends RecyclerView.ViewHolder { static class ReleasesViewHolder extends RecyclerView.ViewHolder {
private Releases releases; private Releases releases;
@ -172,6 +175,11 @@ public class ReleasesAdapter extends RecyclerView.Adapter<ReleasesAdapter.Releas
ReleasesDownloadsAdapter adapter = new ReleasesDownloadsAdapter(currentItem.getAssets()); ReleasesDownloadsAdapter adapter = new ReleasesDownloadsAdapter(currentItem.getAssets());
holder.downloadList.setAdapter(adapter); holder.downloadList.setAdapter(adapter);
if(position >= getItemCount() - 1 && isMoreDataAvailable && !isLoading && loadMoreListener != null) {
isLoading = true;
loadMoreListener.onLoadMore();
}
} }
@Override @Override
@ -179,4 +187,32 @@ public class ReleasesAdapter extends RecyclerView.Adapter<ReleasesAdapter.Releas
return releasesList.size(); return releasesList.size();
} }
public void setMoreDataAvailable(boolean moreDataAvailable) {
isMoreDataAvailable = moreDataAvailable;
if(!isMoreDataAvailable) {
loadMoreListener.onLoadFinished();
}
}
public void notifyDataChanged() {
notifyDataSetChanged();
isLoading = false;
loadMoreListener.onLoadFinished();
}
public interface OnLoadMoreListener {
void onLoadMore();
void onLoadFinished();
}
public void setLoadMoreListener(OnLoadMoreListener loadMoreListener) {
this.loadMoreListener = loadMoreListener;
}
public void updateList(List<Releases> list) {
releasesList = list;
notifyDataChanged();
}
} }

View File

@ -0,0 +1,174 @@
package org.mian.gitnex.adapters;
import android.annotation.SuppressLint;
import android.content.Context;
import android.text.method.LinkMovementMethod;
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.text.HtmlCompat;
import androidx.recyclerview.widget.RecyclerView;
import com.google.android.material.bottomsheet.BottomSheetDialog;
import org.gitnex.tea4j.models.GitTag;
import org.mian.gitnex.R;
import org.mian.gitnex.helpers.AlertDialogs;
import org.mian.gitnex.helpers.Markdown;
import org.mian.gitnex.helpers.TinyDB;
import java.util.List;
/**
* Author qwerty287
*/
public class TagsAdapter extends RecyclerView.Adapter<TagsAdapter.TagsViewHolder> {
private List<GitTag> tags;
private final Context context;
private final String repo;
private final String owner;
private Context ctx;
private OnLoadMoreListener loadMoreListener;
private boolean isLoading = false, isMoreDataAvailable = true;
static class TagsViewHolder extends RecyclerView.ViewHolder {
private final TextView tagName;
private final TextView tagBody;
private final LinearLayout downloadFrame;
private final LinearLayout downloads;
private final TextView releaseZipDownload;
private final TextView releaseTarDownload;
private final ImageView downloadDropdownIcon;
private final ImageView options;
private TagsViewHolder(View itemView) {
super(itemView);
tagName = itemView.findViewById(R.id.tagName);
tagBody = itemView.findViewById(R.id.tagBodyContent);
downloadFrame = itemView.findViewById(R.id.downloadFrame);
downloads = itemView.findViewById(R.id.downloads);
releaseZipDownload = itemView.findViewById(R.id.releaseZipDownload);
releaseTarDownload = itemView.findViewById(R.id.releaseTarDownload);
downloadDropdownIcon = itemView.findViewById(R.id.downloadDropdownIcon);
options = itemView.findViewById(R.id.tagsOptionsMenu);
}
}
public TagsAdapter(Context ctx, List<GitTag> releasesMain, String repoOwner, String repoName) {
this.context = ctx;
this.tags = releasesMain;
owner = repoOwner;
repo = repoName;
}
@NonNull
@Override
public TagsViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.list_tags, parent, false);
return new TagsViewHolder(v);
}
@Override
public void onBindViewHolder(@NonNull TagsViewHolder holder, int position) {
GitTag currentItem = tags.get(position);
holder.tagName.setText(currentItem.getName());
if(!currentItem.getMessage().equals("")) {
Markdown.render(context, currentItem.getMessage(), holder.tagBody);
}
else {
holder.tagBody.setVisibility(View.GONE);
}
holder.downloadFrame.setOnClickListener(v -> {
if(holder.downloads.getVisibility() == View.GONE) {
holder.downloadDropdownIcon.setImageResource(R.drawable.ic_chevron_down);
holder.downloads.setVisibility(View.VISIBLE);
}
else {
holder.downloadDropdownIcon.setImageResource(R.drawable.ic_chevron_right);
holder.downloads.setVisibility(View.GONE);
}
});
if(!TinyDB.getInstance(ctx).getBoolean("isRepoAdmin")) {
holder.options.setVisibility(View.GONE);
}
holder.options.setOnClickListener(v -> {
final Context context = v.getContext();
@SuppressLint("InflateParams")
View view = LayoutInflater.from(context).inflate(R.layout.bottom_sheet_tag_in_list, null);
TextView delete = view.findViewById(R.id.tagMenuDelete);
BottomSheetDialog dialog = new BottomSheetDialog(context);
dialog.setContentView(view);
dialog.show();
delete.setOnClickListener(v1 -> {
AlertDialogs.tagDeleteDialog(context, currentItem.getName(), owner, repo);
dialog.dismiss();
});
});
holder.releaseZipDownload.setText(
HtmlCompat.fromHtml("<a href='" + currentItem.getZipballUrl() + "'>" + context.getResources().getString(R.string.zipArchiveDownloadReleasesTab) + "</a> ", HtmlCompat.FROM_HTML_MODE_LEGACY));
holder.releaseZipDownload.setMovementMethod(LinkMovementMethod.getInstance());
holder.releaseTarDownload.setText(
HtmlCompat.fromHtml("<a href='" + currentItem.getTarballUrl() + "'>" + context.getResources().getString(R.string.tarArchiveDownloadReleasesTab) + "</a> ", HtmlCompat.FROM_HTML_MODE_LEGACY));
holder.releaseTarDownload.setMovementMethod(LinkMovementMethod.getInstance());
if(position >= getItemCount() - 1 && isMoreDataAvailable && !isLoading && loadMoreListener != null) {
isLoading = true;
loadMoreListener.onLoadMore();
}
}
@Override
public int getItemCount() {
return tags.size();
}
public void setMoreDataAvailable(boolean moreDataAvailable) {
isMoreDataAvailable = moreDataAvailable;
if(!isMoreDataAvailable) {
loadMoreListener.onLoadFinished();
}
}
public void notifyDataChanged() {
notifyDataSetChanged();
isLoading = false;
loadMoreListener.onLoadFinished();
}
public interface OnLoadMoreListener {
void onLoadMore();
void onLoadFinished();
}
public void setLoadMoreListener(OnLoadMoreListener loadMoreListener) {
this.loadMoreListener = loadMoreListener;
}
public void updateList(List<GitTag> list) {
tags = list;
notifyDataChanged();
}
}

View File

@ -0,0 +1,55 @@
package org.mian.gitnex.fragments;
import android.content.Context;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.google.android.material.bottomsheet.BottomSheetDialogFragment;
import org.mian.gitnex.databinding.BottomSheetReleasesTagsBinding;
import org.mian.gitnex.structs.BottomSheetListener;
/**
* Author opyale
*/
public class BottomSheetReleasesTagsFragment extends BottomSheetDialogFragment {
private BottomSheetListener bmListener;
@Override
public void onAttach(@NonNull Context context) {
super.onAttach(context);
try {
bmListener = (BottomSheetListener) context;
}
catch(ClassCastException e) {
throw new ClassCastException(context.toString() + " must implement BottomSheetListener");
}
}
@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
BottomSheetReleasesTagsBinding binding = BottomSheetReleasesTagsBinding.inflate(inflater, container, false);
binding.tags.setOnClickListener(v1 -> {
bmListener.onButtonClicked("tags");
dismiss();
});
binding.releases.setOnClickListener(v12 -> {
bmListener.onButtonClicked("releases");
dismiss();
});
return binding.getRoot();
}
}

View File

@ -5,6 +5,8 @@ import android.os.Bundle;
import android.os.Handler; import android.os.Handler;
import android.os.Looper; import android.os.Looper;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.widget.ProgressBar; import android.widget.ProgressBar;
@ -19,10 +21,14 @@ import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView; import androidx.recyclerview.widget.RecyclerView;
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout; import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
import org.gitnex.tea4j.models.Releases; import org.gitnex.tea4j.models.Releases;
import org.mian.gitnex.R;
import org.mian.gitnex.activities.RepoDetailActivity;
import org.mian.gitnex.adapters.ReleasesAdapter; import org.mian.gitnex.adapters.ReleasesAdapter;
import org.mian.gitnex.adapters.TagsAdapter;
import org.mian.gitnex.databinding.FragmentReleasesBinding; import org.mian.gitnex.databinding.FragmentReleasesBinding;
import org.mian.gitnex.helpers.Authorization; import org.mian.gitnex.helpers.Authorization;
import org.mian.gitnex.helpers.TinyDB; import org.mian.gitnex.helpers.TinyDB;
import org.mian.gitnex.helpers.Version;
import org.mian.gitnex.viewmodels.ReleasesViewModel; import org.mian.gitnex.viewmodels.ReleasesViewModel;
import java.util.List; import java.util.List;
@ -34,6 +40,7 @@ public class ReleasesFragment extends Fragment {
private ProgressBar mProgressBar; private ProgressBar mProgressBar;
private ReleasesAdapter adapter; private ReleasesAdapter adapter;
private TagsAdapter tagsAdapter;
private RecyclerView mRecyclerView; private RecyclerView mRecyclerView;
private TextView noDataReleases; private TextView noDataReleases;
private static String repoNameF = "param2"; private static String repoNameF = "param2";
@ -42,6 +49,9 @@ public class ReleasesFragment extends Fragment {
private String repoName; private String repoName;
private String repoOwner; private String repoOwner;
private String releaseTag; private String releaseTag;
private boolean viewTypeIsTags = false;
private int page = 1;
private int pageReleases = 1;
private OnFragmentInteractionListener mListener; private OnFragmentInteractionListener mListener;
@ -90,12 +100,30 @@ public class ReleasesFragment extends Fragment {
swipeRefresh.setOnRefreshListener(() -> new Handler(Looper.getMainLooper()).postDelayed(() -> { swipeRefresh.setOnRefreshListener(() -> new Handler(Looper.getMainLooper()).postDelayed(() -> {
swipeRefresh.setRefreshing(false); swipeRefresh.setRefreshing(false);
if(viewTypeIsTags) {
ReleasesViewModel.loadTagsList(Authorization.get(getContext()), repoOwner, repoName, getContext());
} else {
ReleasesViewModel.loadReleasesList(Authorization.get(getContext()), repoOwner, repoName, getContext()); ReleasesViewModel.loadReleasesList(Authorization.get(getContext()), repoOwner, repoName, getContext());
}
mProgressBar.setVisibility(View.VISIBLE);
}, 50)); }, 50));
fetchDataAsync(Authorization.get(getContext()), repoOwner, repoName); fetchDataAsync(Authorization.get(getContext()), repoOwner, repoName);
setHasOptionsMenu(true);
((RepoDetailActivity) requireActivity()).setFragmentRefreshListenerReleases(type -> {
viewTypeIsTags = type.equals("tags");
page = 1;
pageReleases = 1;
if(viewTypeIsTags) {
ReleasesViewModel.loadTagsList(Authorization.get(getContext()), repoOwner, repoName, getContext());
} else {
ReleasesViewModel.loadReleasesList(Authorization.get(getContext()), repoOwner, repoName, getContext());
}
mProgressBar.setVisibility(View.VISIBLE);
});
return fragmentReleasesBinding.getRoot(); return fragmentReleasesBinding.getRoot();
} }
@ -107,10 +135,14 @@ public class ReleasesFragment extends Fragment {
TinyDB tinyDb = TinyDB.getInstance(getContext()); TinyDB tinyDb = TinyDB.getInstance(getContext());
if(tinyDb.getBoolean("updateReleases")) { if(tinyDb.getBoolean("updateReleases")) {
if(viewTypeIsTags) {
ReleasesViewModel.loadTagsList(Authorization.get(getContext()), repoOwner, repoName, getContext());
} else {
ReleasesViewModel.loadReleasesList(Authorization.get(getContext()), repoOwner, repoName, getContext()); ReleasesViewModel.loadReleasesList(Authorization.get(getContext()), repoOwner, repoName, getContext());
}
mProgressBar.setVisibility(View.VISIBLE);
tinyDb.putBoolean("updateReleases", false); tinyDb.putBoolean("updateReleases", false);
} }
} }
public void onButtonPressed(Uri uri) { public void onButtonPressed(Uri uri) {
@ -136,7 +168,22 @@ public class ReleasesFragment extends Fragment {
releasesModel.getReleasesList(instanceToken, owner, repo, getContext()).observe(getViewLifecycleOwner(), new Observer<List<Releases>>() { releasesModel.getReleasesList(instanceToken, owner, repo, getContext()).observe(getViewLifecycleOwner(), new Observer<List<Releases>>() {
@Override @Override
public void onChanged(@Nullable List<Releases> releasesListMain) { public void onChanged(@Nullable List<Releases> releasesListMain) {
if(!viewTypeIsTags) {
adapter = new ReleasesAdapter(getContext(), releasesListMain); adapter = new ReleasesAdapter(getContext(), releasesListMain);
adapter.setLoadMoreListener(new ReleasesAdapter.OnLoadMoreListener() {
@Override
public void onLoadMore() {
pageReleases += 1;
ReleasesViewModel.loadMoreReleases(instanceToken, owner, repo , pageReleases, getContext(), adapter);
mProgressBar.setVisibility(View.VISIBLE);
}
@Override
public void onLoadFinished() {
mProgressBar.setVisibility(View.GONE);
}
});
if(adapter.getItemCount() > 0) { if(adapter.getItemCount() > 0) {
mRecyclerView.setAdapter(adapter); mRecyclerView.setAdapter(adapter);
if(releasesListMain != null && releaseTag != null) { if(releasesListMain != null && releaseTag != null) {
@ -155,6 +202,37 @@ public class ReleasesFragment extends Fragment {
} }
mProgressBar.setVisibility(View.GONE); mProgressBar.setVisibility(View.GONE);
} }
}
});
releasesModel.getTagsList(instanceToken, owner, repo, getContext()).observe(getViewLifecycleOwner(), tagList -> {
if(viewTypeIsTags) {
tagsAdapter = new TagsAdapter(getContext(), tagList, owner, repo);
tagsAdapter.setLoadMoreListener(new TagsAdapter.OnLoadMoreListener() {
@Override
public void onLoadMore() {
page += 1;
ReleasesViewModel.loadMoreTags(instanceToken, owner, repo , page, getContext(), tagsAdapter);
mProgressBar.setVisibility(View.VISIBLE);
}
@Override
public void onLoadFinished() {
mProgressBar.setVisibility(View.GONE);
}
});
if(tagsAdapter.getItemCount() > 0) {
mRecyclerView.setAdapter(tagsAdapter);
noDataReleases.setVisibility(View.GONE);
}
else {
tagsAdapter.notifyDataSetChanged();
mRecyclerView.setAdapter(tagsAdapter);
noDataReleases.setVisibility(View.VISIBLE);
}
mProgressBar.setVisibility(View.GONE);
}
}); });
} }
@ -168,4 +246,11 @@ public class ReleasesFragment extends Fragment {
return -1; return -1;
} }
@Override
public void onCreateOptionsMenu(@NonNull Menu menu, @NonNull MenuInflater inflater) {
if(new Version(TinyDB.getInstance(requireContext()).getString("giteaVersion")).less("1.15.0"))
return;
inflater.inflate(R.menu.filter_menu_releases, menu);
super.onCreateOptionsMenu(menu, inflater);
}
} }

View File

@ -6,6 +6,7 @@ import android.content.Intent;
import android.graphics.Color; import android.graphics.Color;
import android.graphics.drawable.ColorDrawable; import android.graphics.drawable.ColorDrawable;
import android.widget.Button; import android.widget.Button;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AlertDialog; import androidx.appcompat.app.AlertDialog;
import org.mian.gitnex.R; import org.mian.gitnex.R;
import org.mian.gitnex.actions.CollaboratorActions; import org.mian.gitnex.actions.CollaboratorActions;
@ -13,6 +14,10 @@ import org.mian.gitnex.actions.PullRequestActions;
import org.mian.gitnex.actions.TeamActions; import org.mian.gitnex.actions.TeamActions;
import org.mian.gitnex.activities.CreateLabelActivity; import org.mian.gitnex.activities.CreateLabelActivity;
import org.mian.gitnex.activities.LoginActivity; import org.mian.gitnex.activities.LoginActivity;
import org.mian.gitnex.clients.RetrofitClient;
import retrofit2.Call;
import retrofit2.Callback;
import retrofit2.Response;
/** /**
* Author M M Arif * Author M M Arif
@ -87,6 +92,34 @@ public class AlertDialogs {
} }
public static void tagDeleteDialog(final Context context, final String tagName, final String owner, final String repo) {
new AlertDialog.Builder(context)
.setTitle(String.format(context.getString(R.string.deleteTagTitle), tagName))
.setMessage(R.string.deleteTagConfirmation)
.setIcon(R.drawable.ic_delete)
.setPositiveButton(R.string.menuDeleteText, (dialog, whichButton) -> RetrofitClient.getApiInterface(context).deleteTag(Authorization.get(context), owner, repo, tagName).enqueue(new Callback<Void>() {
@Override
public void onResponse(@NonNull Call<Void> call, @NonNull Response<Void> response) {
if(response.isSuccessful()) {
Toasty.success(context, context.getString(R.string.tagDeleted));
}
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();
}
public static void collaboratorRemoveDialog(final Context context, final String userNameMain, String title, String message, String positiveButton, String negativeButton, final String searchKeyword) { public static void collaboratorRemoveDialog(final Context context, final String userNameMain, String title, String message, String positiveButton, String negativeButton, final String searchKeyword) {
new AlertDialog.Builder(context) new AlertDialog.Builder(context)

View File

@ -6,8 +6,14 @@ import androidx.annotation.NonNull;
import androidx.lifecycle.LiveData; import androidx.lifecycle.LiveData;
import androidx.lifecycle.MutableLiveData; import androidx.lifecycle.MutableLiveData;
import androidx.lifecycle.ViewModel; import androidx.lifecycle.ViewModel;
import org.gitnex.tea4j.models.GitTag;
import org.gitnex.tea4j.models.Releases; import org.gitnex.tea4j.models.Releases;
import org.mian.gitnex.adapters.ReleasesAdapter;
import org.mian.gitnex.adapters.TagsAdapter;
import org.mian.gitnex.clients.RetrofitClient; import org.mian.gitnex.clients.RetrofitClient;
import org.mian.gitnex.helpers.Constants;
import org.mian.gitnex.helpers.TinyDB;
import org.mian.gitnex.helpers.Version;
import java.util.List; import java.util.List;
import retrofit2.Call; import retrofit2.Call;
import retrofit2.Callback; import retrofit2.Callback;
@ -20,10 +26,17 @@ import retrofit2.Response;
public class ReleasesViewModel extends ViewModel { public class ReleasesViewModel extends ViewModel {
private static MutableLiveData<List<Releases>> releasesList; private static MutableLiveData<List<Releases>> releasesList;
private static int resultLimit = Constants.resultLimitOldGiteaInstances;
public LiveData<List<Releases>> getReleasesList(String token, String owner, String repo, Context ctx) { public LiveData<List<Releases>> getReleasesList(String token, String owner, String repo, Context ctx) {
releasesList = new MutableLiveData<>(); releasesList = new MutableLiveData<>();
// if gitea is 1.12 or higher use the new limit
if(new Version(TinyDB.getInstance(ctx).getString("giteaVersion")).higherOrEqual("1.12.0")) {
resultLimit = Constants.resultLimitNewGiteaInstances;
}
loadReleasesList(token, owner, repo, ctx); loadReleasesList(token, owner, repo, ctx);
return releasesList; return releasesList;
@ -33,7 +46,7 @@ public class ReleasesViewModel extends ViewModel {
Call<List<Releases>> call = RetrofitClient Call<List<Releases>> call = RetrofitClient
.getApiInterface(ctx) .getApiInterface(ctx)
.getReleases(token, owner, repo); .getReleases(token, owner, repo, 1, resultLimit);
call.enqueue(new Callback<List<Releases>>() { call.enqueue(new Callback<List<Releases>>() {
@ -42,18 +55,130 @@ public class ReleasesViewModel extends ViewModel {
if (response.isSuccessful()) { if (response.isSuccessful()) {
releasesList.postValue(response.body()); releasesList.postValue(response.body());
} else { }
else {
Log.i("onResponse", String.valueOf(response.code())); Log.i("onResponse", String.valueOf(response.code()));
} }
} }
@Override @Override
public void onFailure(@NonNull Call<List<Releases>> call, Throwable t) { public void onFailure(@NonNull Call<List<Releases>> call, Throwable t) {
Log.i("onFailure", t.toString()); Log.i("onFailure", t.toString());
} }
}); });
} }
public static void loadMoreReleases(String token, String owner, String repo, int page, Context ctx, ReleasesAdapter adapter) {
Call<List<Releases>> call = RetrofitClient
.getApiInterface(ctx)
.getReleases(token, owner, repo, page, resultLimit);
call.enqueue(new Callback<List<Releases>>() {
@Override
public void onResponse(@NonNull Call<List<Releases>> call, @NonNull Response<List<Releases>> response) {
if (response.isSuccessful()) {
List<Releases> list = releasesList.getValue();
assert list != null;
assert response.body() != null;
if(response.body().size() != 0) {
list.addAll(response.body());
adapter.updateList(list);
}
else {
adapter.setMoreDataAvailable(false);
}
}
else {
Log.i("onResponse", String.valueOf(response.code()));
}
}
@Override
public void onFailure(@NonNull Call<List<Releases>> call, @NonNull Throwable t) {
Log.i("onFailure", t.toString());
}
});
}
private static MutableLiveData<List<GitTag>> tagsList;
public LiveData<List<GitTag>> getTagsList(String token, String owner, String repo, Context ctx) {
tagsList = new MutableLiveData<>();
// if gitea is 1.12 or higher use the new limit
if(new Version(TinyDB.getInstance(ctx).getString("giteaVersion")).higherOrEqual("1.12.0")) {
resultLimit = Constants.resultLimitNewGiteaInstances;
}
loadTagsList(token, owner, repo, ctx);
return tagsList;
}
public static void loadTagsList(String token, String owner, String repo, Context ctx) {
Call<List<GitTag>> call = RetrofitClient
.getApiInterface(ctx)
.getTags(token, owner, repo, 1, resultLimit);
call.enqueue(new Callback<List<GitTag>>() {
@Override
public void onResponse(@NonNull Call<List<GitTag>> call, @NonNull Response<List<GitTag>> response) {
if (response.isSuccessful()) {
tagsList.postValue(response.body());
}
else {
Log.i("onResponse", String.valueOf(response.code()));
}
}
@Override
public void onFailure(@NonNull Call<List<GitTag>> call, @NonNull Throwable t) {
Log.i("onFailure", t.toString());
}
});
}
public static void loadMoreTags(String token, String owner, String repo, int page, Context ctx, TagsAdapter adapter) {
Call<List<GitTag>> call = RetrofitClient
.getApiInterface(ctx)
.getTags(token, owner, repo, page, resultLimit);
call.enqueue(new Callback<List<GitTag>>() {
@Override
public void onResponse(@NonNull Call<List<GitTag>> call, @NonNull Response<List<GitTag>> response) {
if (response.isSuccessful()) {
List<GitTag> list = tagsList.getValue();
assert list != null;
assert response.body() != null;
if(response.body().size() != 0) {
list.addAll(response.body());
adapter.updateList(list);
}
else {
adapter.setMoreDataAvailable(false);
}
}
else {
Log.i("onResponse", String.valueOf(response.code()));
}
}
@Override
public void onFailure(@NonNull Call<List<GitTag>> call, @NonNull Throwable t) {
Log.i("onFailure", t.toString());
}
});
}
} }

View File

@ -193,6 +193,14 @@
android:text="@string/newCreateButtonCopy" android:text="@string/newCreateButtonCopy"
android:textColor="@color/btnTextColor" /> android:textColor="@color/btnTextColor" />
<Button
android:id="@+id/createNewTag"
android:layout_width="match_parent"
android:layout_height="60dp"
android:layout_marginTop="8dp"
android:text="@string/create_tag"
android:textColor="@color/btnTextColor" />
</LinearLayout> </LinearLayout>
</ScrollView> </ScrollView>

View File

@ -0,0 +1,53 @@
<?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:paddingTop="6dp"
android:paddingBottom="12dp"
android:background="?attr/primaryBackgroundColor">
<androidx.core.widget.NestedScrollView
android:layout_width="match_parent"
android:layout_height="wrap_content">
<LinearLayout
android:layout_width="match_parent"
android:orientation="vertical"
android:layout_height="wrap_content">
<TextView
android:id="@+id/releases"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:background="?android:attr/selectableItemBackground"
android:focusable="true"
android:clickable="true"
android:drawablePadding="24dp"
android:padding="12dp"
android:text="@string/tabTextReleases"
android:textColor="?attr/primaryTextColor"
android:textSize="16sp"
app:drawableStartCompat="@drawable/ic_release" />
<TextView
android:id="@+id/tags"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:background="?android:attr/selectableItemBackground"
android:focusable="true"
android:clickable="true"
android:drawablePadding="24dp"
android:padding="12dp"
android:text="@string/tags"
android:textColor="?attr/primaryTextColor"
android:textSize="16sp"
app:drawableStartCompat="@drawable/ic_label" />
</LinearLayout>
</androidx.core.widget.NestedScrollView>
</LinearLayout>

View File

@ -0,0 +1,40 @@
<?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:paddingTop="6dp"
android:paddingBottom="12dp"
android:background="?attr/primaryBackgroundColor">
<androidx.core.widget.NestedScrollView
android:layout_width="match_parent"
android:layout_height="wrap_content">
<LinearLayout
android:layout_width="match_parent"
android:orientation="vertical"
android:layout_height="wrap_content">
<TextView
android:id="@+id/tagMenuDelete"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:background="?android:attr/selectableItemBackground"
android:focusable="true"
android:clickable="true"
android:drawablePadding="24dp"
android:padding="12dp"
android:text="@string/menuDeleteText"
android:textColor="?attr/primaryTextColor"
android:textSize="16sp"
app:drawableStartCompat="@drawable/ic_delete" />
</LinearLayout>
</androidx.core.widget.NestedScrollView>
</LinearLayout>

View File

@ -0,0 +1,150 @@
<?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"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="16dp">
<LinearLayout
android:id="@+id/headerFrame"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal">
<TextView
android:id="@+id/tagName"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="0.9"
android:singleLine="true"
android:textColor="?attr/primaryTextColor"
android:textSize="16sp"
android:layout_marginBottom="10dp"
android:textStyle="bold" />
<ImageView
android:id="@+id/tagsOptionsMenu"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical|end"
android:contentDescription="@string/labelMenuContentDesc"
android:background="?android:attr/selectableItemBackgroundBorderless"
android:src="@drawable/ic_dotted_menu_horizontal" />
</LinearLayout>
<LinearLayout
android:id="@+id/bodyFrame"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginTop="10dp"
android:orientation="vertical">
<TextView
android:id="@+id/tagBodyContent"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:autoLink="web|email"
android:textColorLink="@color/lightBlue"
android:textColor="?attr/primaryTextColor"
android:textSize="14sp" />
</LinearLayout>
<LinearLayout
android:id="@+id/downloadFrame"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
android:orientation="vertical">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_vertical"
android:orientation="horizontal"
tools:ignore="UseCompoundDrawables">
<ImageView
android:id="@+id/downloadDropdownIcon"
android:layout_width="15dp"
android:layout_height="wrap_content"
android:contentDescription="@string/generalImgContentText"
app:tint="?attr/primaryTextColor"
android:src="@drawable/ic_chevron_right"
app:srcCompat="@drawable/ic_chevron_right" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="3dp"
android:textColor="?attr/primaryTextColor"
android:textSize="14sp"
android:text="@string/releaseDownloadText" />
</LinearLayout>
<LinearLayout
android:id="@+id/downloads"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_vertical"
android:orientation="vertical"
android:visibility="gone"
android:paddingTop="8dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_vertical"
android:orientation="horizontal"
android:layout_marginStart="8dp"
tools:ignore="UseCompoundDrawables">
<ImageView
android:layout_width="18dp"
android:layout_height="18dp"
app:srcCompat="@drawable/ic_download"
android:contentDescription="@string/generalImgContentText" />
<TextView
android:id="@+id/releaseZipDownload"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:textColor="?attr/primaryTextColor"
android:textSize="12sp" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_vertical"
android:orientation="horizontal"
android:layout_marginStart="8dp"
tools:ignore="UseCompoundDrawables">
<ImageView
android:layout_width="18dp"
android:layout_height="18dp"
app:srcCompat="@drawable/ic_download"
android:contentDescription="@string/generalImgContentText" />
<TextView
android:id="@+id/releaseTarDownload"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:textColor="?attr/primaryTextColor"
android:textSize="12sp" />
</LinearLayout>
</LinearLayout>
</LinearLayout>
</LinearLayout>

View File

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<item
android:id="@+id/filterReleases"
android:icon="@drawable/ic_filter"
android:title="@string/releasesTags"
android:orderInCategory="0"
app:showAsAction="ifRoom" />
</menu>

View File

@ -753,6 +753,14 @@
<string name="closePr">Close Pull Request</string> <string name="closePr">Close Pull Request</string>
<string name="reopenPr">Reopen Pull Request</string> <string name="reopenPr">Reopen Pull Request</string>
<string name="userAvatar">Avatar</string> <string name="userAvatar">Avatar</string>
<string name="tags">Tags</string>
<string name="releasesTags">Releases/Tags</string>
<string name="create_tag">Create Tag Only</string>
<string name="tagCreated">Tag created</string>
<string name="asRef">Use as reference</string>
<string name="deleteTagConfirmation">Do you really want to delete this tag?</string>
<string name="deleteTagTitle">Delete tag %s</string>
<string name="tagDeleted">Tag deleted</string>
<string name="useCustomTabs">Use Custom Tabs</string> <string name="useCustomTabs">Use Custom Tabs</string>
<string name="browserOpenFailed">No application found to open this link. SSH URLs and URLs with another prefix the http:// or https:// are not supported by most browser</string> <string name="browserOpenFailed">No application found to open this link. SSH URLs and URLs with another prefix the http:// or https:// are not supported by most browser</string>
</resources> </resources>