From 9f5eceff0e0c7dad755ce90d048c3842b5de49a5 Mon Sep 17 00:00:00 2001 From: M M Arif Date: Sun, 24 Nov 2019 12:42:57 +0000 Subject: [PATCH] Repository PR List (#198) --- app/build.gradle | 2 +- app/src/main/AndroidManifest.xml | 1 + .../gitnex/activities/EditIssueActivity.java | 16 +- .../gitnex/activities/FileDiffActivity.java | 212 +++++ .../activities/ReplyToIssueActivity.java | 1 + .../gitnex/activities/RepoDetailActivity.java | 16 +- .../gitnex/adapters/ClosedIssuesAdapter.java | 22 +- .../gitnex/adapters/FilesDiffAdapter.java | 138 +++ .../mian/gitnex/adapters/IssuesAdapter.java | 22 +- .../gitnex/adapters/MyReposListAdapter.java | 5 + .../gitnex/adapters/PullRequestsAdapter.java | 279 ++++++ .../gitnex/adapters/ReposListAdapter.java | 6 + .../adapters/RepositoriesByOrgAdapter.java | 5 + .../adapters/StarredReposListAdapter.java | 5 + .../mian/gitnex/clients/IssuesService.java | 2 +- .../gitnex/clients/PullRequestsService.java | 61 ++ .../mian/gitnex/clients/RetrofitClient.java | 2 +- .../fragments/ClosedIssuesFragment.java | 4 - .../mian/gitnex/fragments/IssuesFragment.java | 4 - .../fragments/PullRequestsFragment.java | 293 +++++++ .../SingleIssueBottomSheetFragment.java | 74 +- .../mian/gitnex/interfaces/ApiInterface.java | 10 + .../org/mian/gitnex/models/FileDiffView.java | 39 + .../java/org/mian/gitnex/models/Issues.java | 4 +- .../org/mian/gitnex/models/PullRequests.java | 823 ++++++++++++++++++ .../java/org/mian/gitnex/util/AppUtil.java | 3 +- .../main/res/layout/activity_file_diff.xml | 72 ++ .../main/res/layout/activity_repo_detail.xml | 6 + app/src/main/res/layout/files_diffs_list.xml | 73 ++ .../res/layout/fragment_pull_requests.xml | 45 + app/src/main/res/layout/my_repos_list.xml | 6 + .../res/layout/repo_detail_issues_list.xml | 28 +- app/src/main/res/layout/repo_pr_list.xml | 91 ++ app/src/main/res/layout/repos_list.xml | 6 + .../res/layout/repositories_by_org_list.xml | 6 + .../single_issue_bottom_sheet_layout.xml | 13 + .../main/res/layout/starred_repos_list.xml | 7 +- app/src/main/res/values/colors.xml | 4 + app/src/main/res/values/strings.xml | 23 +- 39 files changed, 2345 insertions(+), 84 deletions(-) create mode 100644 app/src/main/java/org/mian/gitnex/activities/FileDiffActivity.java create mode 100644 app/src/main/java/org/mian/gitnex/adapters/FilesDiffAdapter.java create mode 100644 app/src/main/java/org/mian/gitnex/adapters/PullRequestsAdapter.java create mode 100644 app/src/main/java/org/mian/gitnex/clients/PullRequestsService.java create mode 100644 app/src/main/java/org/mian/gitnex/fragments/PullRequestsFragment.java create mode 100644 app/src/main/java/org/mian/gitnex/models/FileDiffView.java create mode 100644 app/src/main/java/org/mian/gitnex/models/PullRequests.java create mode 100644 app/src/main/res/layout/activity_file_diff.xml create mode 100644 app/src/main/res/layout/files_diffs_list.xml create mode 100644 app/src/main/res/layout/fragment_pull_requests.xml create mode 100644 app/src/main/res/layout/repo_pr_list.xml diff --git a/app/build.gradle b/app/build.gradle index bcd7572a..118beb5a 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -23,7 +23,7 @@ android { } dependencies { - def lifecycle_version = "2.2.0-rc01" + def lifecycle_version = "2.2.0-rc02" final def markwon_version = "4.1.1" implementation fileTree(include: ['*.jar'], dir: 'libs') diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index b2bef5fa..3d0157b8 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -66,6 +66,7 @@ + diff --git a/app/src/main/java/org/mian/gitnex/activities/EditIssueActivity.java b/app/src/main/java/org/mian/gitnex/activities/EditIssueActivity.java index d98fd0ad..7b7887be 100644 --- a/app/src/main/java/org/mian/gitnex/activities/EditIssueActivity.java +++ b/app/src/main/java/org/mian/gitnex/activities/EditIssueActivity.java @@ -100,7 +100,13 @@ public class EditIssueActivity extends AppCompatActivity implements View.OnClick editIssueButton.setOnClickListener(this); if(!tinyDb.getString("issueNumber").isEmpty()) { - toolbar_title.setText(getString(R.string.editIssueNavHeader, String.valueOf(issueIndex))); + + if(tinyDb.getString("issueType").equals("pr")) { + toolbar_title.setText(getString(R.string.editPrNavHeader, String.valueOf(issueIndex))); + } + else { + toolbar_title.setText(getString(R.string.editIssueNavHeader, String.valueOf(issueIndex))); + } } disableProcessButton(); @@ -240,7 +246,13 @@ public class EditIssueActivity extends AppCompatActivity implements View.OnClick if(response.code() == 201) { - Toasty.info(getApplicationContext(), getString(R.string.editIssueSuccessMessage)); + if(tinyDb.getString("issueType").equals("pr")) { + Toasty.info(getApplicationContext(), getString(R.string.editPrSuccessMessage)); + } + else { + Toasty.info(getApplicationContext(), getString(R.string.editIssueSuccessMessage)); + } + tinyDb.putBoolean("issueEdited", true); tinyDb.putBoolean("resumeIssues", true); finish(); diff --git a/app/src/main/java/org/mian/gitnex/activities/FileDiffActivity.java b/app/src/main/java/org/mian/gitnex/activities/FileDiffActivity.java new file mode 100644 index 00000000..660656fe --- /dev/null +++ b/app/src/main/java/org/mian/gitnex/activities/FileDiffActivity.java @@ -0,0 +1,212 @@ +package org.mian.gitnex.activities; + +import android.os.Bundle; +import android.util.Log; +import android.view.View; +import android.widget.ImageView; +import android.widget.ProgressBar; +import android.widget.TextView; +import androidx.annotation.NonNull; +import androidx.appcompat.app.AppCompatActivity; +import androidx.appcompat.widget.Toolbar; +import androidx.recyclerview.widget.LinearLayoutManager; +import androidx.recyclerview.widget.RecyclerView; +import org.apache.commons.io.FilenameUtils; +import org.mian.gitnex.R; +import org.mian.gitnex.adapters.FilesDiffAdapter; +import org.mian.gitnex.clients.RetrofitClient; +import org.mian.gitnex.helpers.AlertDialogs; +import org.mian.gitnex.helpers.Toasty; +import org.mian.gitnex.models.FileDiffView; +import org.mian.gitnex.util.AppUtil; +import org.mian.gitnex.util.TinyDB; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import okhttp3.ResponseBody; +import retrofit2.Call; +import retrofit2.Callback; + +/** + * Author M M Arif + */ + +public class FileDiffActivity extends AppCompatActivity { + + private View.OnClickListener onClickListener; + private TextView toolbar_title; + private RecyclerView mRecyclerView; + private ProgressBar mProgressBar; + + @Override + protected void onCreate(Bundle savedInstanceState) { + + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_file_diff); + Toolbar toolbar = findViewById(R.id.toolbar); + setSupportActionBar(toolbar); + + final TinyDB tinyDb = new TinyDB(getApplicationContext()); + String repoFullName = tinyDb.getString("repoFullName"); + String[] parts = repoFullName.split("/"); + final String repoOwner = parts[0]; + final String repoName = parts[1]; + final String instanceUrl = tinyDb.getString("instanceUrl"); + final String loginUid = tinyDb.getString("loginUid"); + final String instanceToken = "token " + tinyDb.getString(loginUid + "-token"); + + ImageView closeActivity = findViewById(R.id.close); + toolbar_title = findViewById(R.id.toolbar_title); + mRecyclerView = findViewById(R.id.recyclerView); + mProgressBar = findViewById(R.id.progress_bar); + + mRecyclerView.setHasFixedSize(true); + mRecyclerView.setLayoutManager(new LinearLayoutManager(getApplicationContext())); + + toolbar_title.setText(R.string.processingText); + initCloseListener(); + closeActivity.setOnClickListener(onClickListener); + + mProgressBar.setVisibility(View.VISIBLE); + + String fileDiffName = tinyDb.getString("issueNumber")+".diff"; + + getFileContents(tinyDb.getString("instanceUrlWithProtocol"), repoOwner, repoName, fileDiffName); + + } + + private void getFileContents(String instanceUrl, String owner, String repo, String filename) { + + Call call = RetrofitClient + .getInstance(instanceUrl, getApplicationContext()) + .getApiInterface() + .getFileDiffContents(owner, repo, filename); + + call.enqueue(new Callback() { + + @Override + public void onResponse(@NonNull Call call, @NonNull retrofit2.Response response) { + + if (response.code() == 200) { + + try { + assert response.body() != null; + + AppUtil appUtil = new AppUtil(); + List fileContentsArray = new ArrayList<>(); + + String[] lines = response.body().string().split("diff"); + + if(lines.length > 0) { + + for (int i = 1; i < lines.length; i++) { + + if(lines[i].contains("@@ -")) { + + String[] level2nd = lines[i].split("@@ -"); // main content part of single diff view + + String[] fileName_ = level2nd[0].split("\\+\\+\\+ b/"); // filename part + String fileNameFinal = fileName_[1]; + + String[] fileContents_ = level2nd[1].split("@@"); // file info / content part + String fileInfoFinal = fileContents_[0]; + String fileContentsFinal = (fileContents_[1]); + + if(level2nd.length > 2) { + for (int j = 2; j < level2nd.length; j++) { + fileContentsFinal += (level2nd[j]); + } + } + + String fileExtension = FilenameUtils.getExtension(fileNameFinal); + + String fileContentsFinalWithBlankLines = fileContentsFinal.replaceAll( ".*@@.*", "" ); + String fileContentsFinalWithoutBlankLines = fileContentsFinal.replaceAll( ".*@@.*(\r?\n|\r)?", "" ); + fileContentsFinalWithoutBlankLines = fileContentsFinalWithoutBlankLines.replaceAll( ".*\\ No newline at end of file.*(\r?\n|\r)?", "" ); + + fileContentsArray.add(new FileDiffView(fileNameFinal, appUtil.imageExtension(fileExtension), fileInfoFinal, fileContentsFinalWithoutBlankLines)); + } + else { + + String[] getFileName = lines[i].split("--git a/"); + + String[] getFileName_ = getFileName[1].split("b/"); + String getFileNameFinal = getFileName_[0].trim(); + + String[] binaryFile = getFileName_[1].split("GIT binary patch"); + String binaryFileRaw = binaryFile[1].substring(binaryFile[1].indexOf('\n')+1); + String binaryFileFinal = binaryFile[1].substring(binaryFileRaw.indexOf('\n')+1); + + String fileExtension = FilenameUtils.getExtension(getFileNameFinal); + + fileContentsArray.add(new FileDiffView(getFileNameFinal, appUtil.imageExtension(fileExtension),"", binaryFileFinal)); + } + + } + + } + + int filesCount = fileContentsArray.size(); + if(filesCount > 1) { + toolbar_title.setText(getResources().getString(R.string.fileDiffViewHeader, Integer.toString(filesCount))); + } + else { + toolbar_title.setText(getResources().getString(R.string.fileDiffViewHeaderSingle, Integer.toString(filesCount))); + } + + FilesDiffAdapter adapter = new FilesDiffAdapter(fileContentsArray, getApplicationContext()); + mRecyclerView.setAdapter(adapter); + + mProgressBar.setVisibility(View.GONE); + + } catch (IOException e) { + e.printStackTrace(); + } + + } + else if(response.code() == 401) { + + AlertDialogs.authorizationTokenRevokedDialog(getApplicationContext(), getResources().getString(R.string.alertDialogTokenRevokedTitle), + getResources().getString(R.string.alertDialogTokenRevokedMessage), + getResources().getString(R.string.alertDialogTokenRevokedCopyNegativeButton), + getResources().getString(R.string.alertDialogTokenRevokedCopyPositiveButton)); + + } + else if(response.code() == 403) { + + Toasty.info(getApplicationContext(), getApplicationContext().getString(R.string.authorizeError)); + + } + else if(response.code() == 404) { + + Toasty.info(getApplicationContext(), getApplicationContext().getString(R.string.apiNotFound)); + + } + else { + + Toasty.info(getApplicationContext(), getString(R.string.labelGeneralError)); + + } + + } + + @Override + public void onFailure(@NonNull Call call, @NonNull Throwable t) { + Log.e("onFailure", t.toString()); + } + }); + + } + + private void initCloseListener() { + onClickListener = new View.OnClickListener() { + @Override + public void onClick(View view) { + getIntent().removeExtra("singleFileName"); + finish(); + } + }; + } + + +} diff --git a/app/src/main/java/org/mian/gitnex/activities/ReplyToIssueActivity.java b/app/src/main/java/org/mian/gitnex/activities/ReplyToIssueActivity.java index fb36b4f9..f5ac58e9 100644 --- a/app/src/main/java/org/mian/gitnex/activities/ReplyToIssueActivity.java +++ b/app/src/main/java/org/mian/gitnex/activities/ReplyToIssueActivity.java @@ -226,6 +226,7 @@ public class ReplyToIssueActivity extends AppCompatActivity { Toasty.info(getApplicationContext(), getString(R.string.commentSuccess)); tinyDb.putBoolean("commentPosted", true); tinyDb.putBoolean("resumeIssues", true); + tinyDb.putBoolean("resumePullRequests", true); finish(); } diff --git a/app/src/main/java/org/mian/gitnex/activities/RepoDetailActivity.java b/app/src/main/java/org/mian/gitnex/activities/RepoDetailActivity.java index 7959080a..1bc60fd9 100644 --- a/app/src/main/java/org/mian/gitnex/activities/RepoDetailActivity.java +++ b/app/src/main/java/org/mian/gitnex/activities/RepoDetailActivity.java @@ -31,6 +31,7 @@ import org.mian.gitnex.fragments.FilesFragment; import org.mian.gitnex.fragments.IssuesFragment; import org.mian.gitnex.fragments.LabelsFragment; import org.mian.gitnex.fragments.MilestonesFragment; +import org.mian.gitnex.fragments.PullRequestsFragment; import org.mian.gitnex.fragments.ReleasesFragment; import org.mian.gitnex.fragments.RepoBottomSheetFragment; import org.mian.gitnex.fragments.RepoInfoFragment; @@ -213,15 +214,18 @@ public class RepoDetailActivity extends AppCompatActivity implements RepoBottomS case 3: // closed issues fragment = new ClosedIssuesFragment(); break; - case 4: // milestones + case 4: // pull requests + fragment = new PullRequestsFragment(); + break; + case 5: // milestones return MilestonesFragment.newInstance(repoOwner, repoName); - case 5: // labels + case 6: // labels return LabelsFragment.newInstance(repoOwner, repoName); - case 6: // branches + case 7: // branches return BranchesFragment.newInstance(repoOwner, repoName); - case 7: // releases + case 8: // releases return ReleasesFragment.newInstance(repoOwner, repoName); - case 8: // collaborators + case 9: // collaborators return CollaboratorsFragment.newInstance(repoOwner, repoName); } return fragment; @@ -229,7 +233,7 @@ public class RepoDetailActivity extends AppCompatActivity implements RepoBottomS @Override public int getCount() { - return 9; + return 10; } } diff --git a/app/src/main/java/org/mian/gitnex/adapters/ClosedIssuesAdapter.java b/app/src/main/java/org/mian/gitnex/adapters/ClosedIssuesAdapter.java index 19d7a05a..d8859ff8 100644 --- a/app/src/main/java/org/mian/gitnex/adapters/ClosedIssuesAdapter.java +++ b/app/src/main/java/org/mian/gitnex/adapters/ClosedIssuesAdapter.java @@ -11,6 +11,7 @@ import android.widget.Filter; import android.widget.Filterable; import android.widget.ImageView; import android.widget.LinearLayout; +import android.widget.RelativeLayout; import android.widget.TextView; import com.squareup.picasso.Picasso; import org.mian.gitnex.R; @@ -109,7 +110,7 @@ public class ClosedIssuesAdapter extends RecyclerView.Adapter" + context.getResources().getString(R.string.hash) + issuesModel.getNumber() + ""; issueTitle.setText(Html.fromHtml(issueNumber_ + " " + issuesModel.getTitle())); diff --git a/app/src/main/java/org/mian/gitnex/adapters/FilesDiffAdapter.java b/app/src/main/java/org/mian/gitnex/adapters/FilesDiffAdapter.java new file mode 100644 index 00000000..5cd20c7a --- /dev/null +++ b/app/src/main/java/org/mian/gitnex/adapters/FilesDiffAdapter.java @@ -0,0 +1,138 @@ +package org.mian.gitnex.adapters; + +import android.content.Context; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.HorizontalScrollView; +import android.widget.ImageView; +import android.widget.LinearLayout; +import android.widget.TextView; +import androidx.annotation.NonNull; +import androidx.recyclerview.widget.RecyclerView; +import org.mian.gitnex.R; +import org.mian.gitnex.models.FileDiffView; +import java.util.List; + +/** + * Author M M Arif + */ + +public class FilesDiffAdapter extends RecyclerView.Adapter { + + private List dataList; + private Context ctx; + + static class FilesDiffViewHolder extends RecyclerView.ViewHolder { + + private TextView fileContents; + private TextView fileName; + private TextView fileInfo; + private ImageView fileImage; + private HorizontalScrollView fileContentsView; + private LinearLayout allLines; + + private FilesDiffViewHolder(View itemView) { + super(itemView); + + fileContents = itemView.findViewById(R.id.fileContents); + fileName = itemView.findViewById(R.id.fileName); + fileInfo = itemView.findViewById(R.id.fileInfo); + fileImage = itemView.findViewById(R.id.fileImage); + fileContentsView = itemView.findViewById(R.id.fileContentsView); + allLines = itemView.findViewById(R.id.allLinesLayout); + + } + } + + public FilesDiffAdapter(List dataListMain, Context ctx) { + this.dataList = dataListMain; + this.ctx = ctx; + } + + @NonNull + @Override + public FilesDiffAdapter.FilesDiffViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { + View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.files_diffs_list, parent, false); + return new FilesDiffAdapter.FilesDiffViewHolder(v); + } + + @Override + public void onBindViewHolder(@NonNull FilesDiffViewHolder holder, int position) { + + FileDiffView data = dataList.get(position); + + if(data.isFileType()) { + + holder.fileName.setText(data.getFileName()); + + holder.fileInfo.setVisibility(View.GONE); + + //byte[] imageData = Base64.decode(data.getFileContents(), Base64.DEFAULT); + //Drawable imageDrawable = new BitmapDrawable(ctx.getResources(), BitmapFactory.decodeByteArray(imageData, 0, imageData.length)); + //holder.fileImage.setImageDrawable(imageDrawable); + holder.fileContentsView.setVisibility(View.GONE); + + } + else { + + String[] splitData = data.getFileContents().split("\\R"); + + for (String eachSplit : splitData) { + + TextView textLine = new TextView(ctx); + textLine.setLayoutParams(new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT)); + + if (eachSplit.startsWith("+")) { + + textLine.setText(eachSplit); + holder.allLines.addView(textLine); + + textLine.setTextColor(ctx.getResources().getColor(R.color.colorPrimary)); + textLine.setPadding(5, 5, 5, 5); + textLine.setBackgroundColor(ctx.getResources().getColor(R.color.diffAddedColor)); + + } + else if (eachSplit.startsWith("-")) { + + textLine.setText(eachSplit); + holder.allLines.addView(textLine); + + textLine.setTextColor(ctx.getResources().getColor(R.color.colorPrimary)); + textLine.setPadding(5, 5, 5, 5); + textLine.setBackgroundColor(ctx.getResources().getColor(R.color.diffRemovedColor)); + + } + else { + + if(eachSplit.length() > 0) { + textLine.setText(eachSplit); + holder.allLines.addView(textLine); + + textLine.setTextColor(ctx.getResources().getColor(R.color.colorPrimary)); + textLine.setPadding(5, 5, 5, 5); + textLine.setBackgroundColor(ctx.getResources().getColor(R.color.white)); + } + + } + + } + + holder.fileName.setText(data.getFileName()); + if(!data.getFileInfo().equals("")) { + holder.fileInfo.setText(ctx.getResources().getString(R.string.fileDiffInfoChanges, data.getFileInfo())); + } + else { + holder.fileInfo.setVisibility(View.GONE); + } + + } + + } + + @Override + public int getItemCount() { + return dataList.size(); + } + +} \ No newline at end of file diff --git a/app/src/main/java/org/mian/gitnex/adapters/IssuesAdapter.java b/app/src/main/java/org/mian/gitnex/adapters/IssuesAdapter.java index 5e6a8960..c91b5e51 100644 --- a/app/src/main/java/org/mian/gitnex/adapters/IssuesAdapter.java +++ b/app/src/main/java/org/mian/gitnex/adapters/IssuesAdapter.java @@ -11,6 +11,7 @@ import android.widget.Filter; import android.widget.Filterable; import android.widget.ImageView; import android.widget.LinearLayout; +import android.widget.RelativeLayout; import android.widget.TextView; import com.squareup.picasso.Picasso; import org.mian.gitnex.R; @@ -109,7 +110,7 @@ public class IssuesAdapter extends RecyclerView.Adapter private TextView issueTitle; private TextView issueCreatedTime; private TextView issueCommentsCount; - private ImageView issueType; + private RelativeLayout relativeLayoutFrame; IssuesHolder(View itemView) { @@ -121,7 +122,7 @@ public class IssuesAdapter extends RecyclerView.Adapter issueCommentsCount = itemView.findViewById(R.id.issueCommentsCount); LinearLayout frameCommentsCount = itemView.findViewById(R.id.frameCommentsCount); issueCreatedTime = itemView.findViewById(R.id.issueCreatedTime); - issueType = itemView.findViewById(R.id.issueType); + relativeLayoutFrame = itemView.findViewById(R.id.relativeLayoutFrame); issueTitle.setOnClickListener(new View.OnClickListener() { @Override @@ -135,6 +136,7 @@ public class IssuesAdapter extends RecyclerView.Adapter TinyDB tinyDb = new TinyDB(context); tinyDb.putString("issueNumber", issueNumber.getText().toString()); + tinyDb.putString("issueType", "issue"); context.startActivity(intent); } @@ -151,6 +153,7 @@ public class IssuesAdapter extends RecyclerView.Adapter TinyDB tinyDb = new TinyDB(context); tinyDb.putString("issueNumber", issueNumber.getText().toString()); + tinyDb.putString("issueType", "issue"); context.startActivity(intent); } @@ -165,6 +168,13 @@ public class IssuesAdapter extends RecyclerView.Adapter final String locale = tinyDb.getString("locale"); final String timeFormat = tinyDb.getString("dateFormat"); + if(issuesModel.getPull_request() != null) { + if (!issuesModel.getPull_request().isMerged()) { + relativeLayoutFrame.setVisibility(View.GONE); + relativeLayoutFrame.setLayoutParams(new RecyclerView.LayoutParams(0, 0)); + } + } + if (!issuesModel.getUser().getFull_name().equals("")) { issueAssigneeAvatar.setOnClickListener(new ClickListener(context.getResources().getString(R.string.issueCreator) + issuesModel.getUser().getFull_name(), context)); } else { @@ -177,14 +187,6 @@ public class IssuesAdapter extends RecyclerView.Adapter Picasso.get().load(issuesModel.getUser().getAvatar_url()).transform(new RoundedTransformation(8, 0)).resize(120, 120).centerCrop().into(issueAssigneeAvatar); } - if (issuesModel.getPull_request() == null) { - issueType.setImageResource(R.drawable.ic_issues); - issueType.setOnClickListener(new ClickListener(context.getResources().getString(R.string.issueTypeIssue), context)); - } else { - issueType.setImageResource(R.drawable.ic_merge); - issueType.setOnClickListener(new ClickListener(context.getResources().getString(R.string.issueTypePullRequest), context)); - } - String issueNumber_ = "" + context.getResources().getString(R.string.hash) + issuesModel.getNumber() + ""; issueTitle.setText(Html.fromHtml(issueNumber_ + " " + issuesModel.getTitle())); diff --git a/app/src/main/java/org/mian/gitnex/adapters/MyReposListAdapter.java b/app/src/main/java/org/mian/gitnex/adapters/MyReposListAdapter.java index 35ac7f09..2304d6cb 100644 --- a/app/src/main/java/org/mian/gitnex/adapters/MyReposListAdapter.java +++ b/app/src/main/java/org/mian/gitnex/adapters/MyReposListAdapter.java @@ -50,6 +50,7 @@ public class MyReposListAdapter extends RecyclerView.Adapter implements Filterable { + + private Context context; + private final int TYPE_LOAD = 0; + private List prList; + private List prListFull; + private PullRequestsAdapter.OnLoadMoreListener loadMoreListener; + private boolean isLoading = false, isMoreDataAvailable = true; + + public PullRequestsAdapter(Context context, List prListMain) { + + this.context = context; + this.prList = prListMain; + prListFull = new ArrayList<>(prList); + + } + + @NonNull + @Override + public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { + + LayoutInflater inflater = LayoutInflater.from(context); + + if(viewType == TYPE_LOAD){ + return new PullRequestsAdapter.PullRequestsHolder(inflater.inflate(R.layout.repo_pr_list, parent,false)); + } + else { + return new PullRequestsAdapter.LoadHolder(inflater.inflate(R.layout.row_load,parent,false)); + } + + } + + @Override + public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) { + + if(position >= getItemCount()-1 && isMoreDataAvailable && !isLoading && loadMoreListener!=null) { + + isLoading = true; + loadMoreListener.onLoadMore(); + + } + + if(getItemViewType(position) == TYPE_LOAD) { + + ((PullRequestsAdapter.PullRequestsHolder)holder).bindData(prList.get(position)); + + } + + } + + @Override + public int getItemViewType(int position) { + + if(prList.get(position).getTitle() != null) { + return TYPE_LOAD; + } + else { + return 1; + } + + } + + @Override + public int getItemCount() { + + return prList.size(); + + } + + class PullRequestsHolder extends RecyclerView.ViewHolder { + + private TextView prNumber; + private ImageView assigneeAvatar; + private TextView prTitle; + private TextView prCreatedTime; + private TextView prCommentsCount; + + PullRequestsHolder(View itemView) { + + super(itemView); + + prNumber = itemView.findViewById(R.id.prNumber); + assigneeAvatar = itemView.findViewById(R.id.assigneeAvatar); + prTitle = itemView.findViewById(R.id.prTitle); + prCommentsCount = itemView.findViewById(R.id.prCommentsCount); + LinearLayout frameCommentsCount = itemView.findViewById(R.id.frameCommentsCount); + prCreatedTime = itemView.findViewById(R.id.prCreatedTime); + + prTitle.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + + Context context = v.getContext(); + + Intent intent = new Intent(context, IssueDetailActivity.class); + intent.putExtra("issueNumber", prNumber.getText()); + + TinyDB tinyDb = new TinyDB(context); + tinyDb.putString("issueNumber", prNumber.getText().toString()); + tinyDb.putString("issueType", "pr"); + context.startActivity(intent); + + } + }); + frameCommentsCount.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + + Context context = v.getContext(); + + Intent intent = new Intent(context, IssueDetailActivity.class); + intent.putExtra("issueNumber", prNumber.getText()); + + TinyDB tinyDb = new TinyDB(context); + tinyDb.putString("issueNumber", prNumber.getText().toString()); + tinyDb.putString("issueType", "pr"); + context.startActivity(intent); + + } + }); + + } + + @SuppressLint("SetTextI18n") + void bindData(PullRequests prModel){ + + final TinyDB tinyDb = new TinyDB(context); + final String locale = tinyDb.getString("locale"); + final String timeFormat = tinyDb.getString("dateFormat"); + + if (!prModel.getUser().getFull_name().equals("")) { + assigneeAvatar.setOnClickListener(new ClickListener(context.getResources().getString(R.string.prCreator) + prModel.getUser().getFull_name(), context)); + } else { + assigneeAvatar.setOnClickListener(new ClickListener(context.getResources().getString(R.string.prCreator) + prModel.getUser().getLogin(), context)); + } + + if (prModel.getUser().getAvatar_url() != null) { + Picasso.get().load(prModel.getUser().getAvatar_url()).transform(new RoundedTransformation(8, 0)).resize(120, 120).centerCrop().into(assigneeAvatar); + } else { + Picasso.get().load(prModel.getUser().getAvatar_url()).transform(new RoundedTransformation(8, 0)).resize(120, 120).centerCrop().into(assigneeAvatar); + } + + String prNumber_ = "" + context.getResources().getString(R.string.hash) + prModel.getNumber() + ""; + prTitle.setText(Html.fromHtml(prNumber_ + " " + prModel.getTitle())); + + prNumber.setText(String.valueOf(prModel.getNumber())); + prCommentsCount.setText(String.valueOf(prModel.getComments())); + + switch (timeFormat) { + case "pretty": { + PrettyTime prettyTime = new PrettyTime(new Locale(locale)); + String createdTime = prettyTime.format(prModel.getCreated_at()); + prCreatedTime.setText(createdTime); + prCreatedTime.setOnClickListener(new ClickListener(TimeHelper.customDateFormatForToastDateFormat(prModel.getCreated_at()), context)); + break; + } + case "normal": { + DateFormat formatter = new SimpleDateFormat("yyyy-MM-dd '" + context.getResources().getString(R.string.timeAtText) + "' HH:mm", new Locale(locale)); + String createdTime = formatter.format(prModel.getCreated_at()); + prCreatedTime.setText(createdTime); + break; + } + case "normal1": { + DateFormat formatter = new SimpleDateFormat("dd-MM-yyyy '" + context.getResources().getString(R.string.timeAtText) + "' HH:mm", new Locale(locale)); + String createdTime = formatter.format(prModel.getCreated_at()); + prCreatedTime.setText(createdTime); + break; + } + } + + } + + } + + static class LoadHolder extends RecyclerView.ViewHolder { + + LoadHolder(View itemView) { + super(itemView); + } + + } + + public void setMoreDataAvailable(boolean moreDataAvailable) { + + isMoreDataAvailable = moreDataAvailable; + + } + + public void notifyDataChanged() { + + notifyDataSetChanged(); + isLoading = false; + + } + + public interface OnLoadMoreListener { + + void onLoadMore(); + + } + + public void setLoadMoreListener(PullRequestsAdapter.OnLoadMoreListener loadMoreListener) { + + this.loadMoreListener = loadMoreListener; + + } + + @Override + public Filter getFilter() { + return prFilter; + } + + private Filter prFilter = new Filter() { + @Override + protected FilterResults performFiltering(CharSequence constraint) { + List filteredList = new ArrayList<>(); + + if (constraint == null || constraint.length() == 0) { + filteredList.addAll(prList); + } else { + String filterPattern = constraint.toString().toLowerCase().trim(); + + for (PullRequests item : prList) { + if (item.getTitle().toLowerCase().contains(filterPattern) || item.getBody().toLowerCase().contains(filterPattern)) { + filteredList.add(item); + } + } + } + + FilterResults results = new FilterResults(); + results.values = filteredList; + + return results; + } + + @Override + protected void publishResults(CharSequence constraint, FilterResults results) { + prList.clear(); + prList.addAll((List) results.values); + notifyDataSetChanged(); + } + }; + +} diff --git a/app/src/main/java/org/mian/gitnex/adapters/ReposListAdapter.java b/app/src/main/java/org/mian/gitnex/adapters/ReposListAdapter.java index 2432f44d..29668264 100644 --- a/app/src/main/java/org/mian/gitnex/adapters/ReposListAdapter.java +++ b/app/src/main/java/org/mian/gitnex/adapters/ReposListAdapter.java @@ -50,6 +50,7 @@ public class ReposListAdapter extends RecyclerView.Adapter S createService(Class serviceClass, String instanceURL, Context ctx) { + + final boolean connToInternet = AppUtil.haveNetworkConnection(ctx); + File httpCacheDirectory = new File(ctx.getCacheDir(), "responses"); + int cacheSize = 50 * 1024 * 1024; // 50MB + Cache cache = new Cache(httpCacheDirectory, cacheSize); + + HttpLoggingInterceptor logging = new HttpLoggingInterceptor(); + logging.setLevel(HttpLoggingInterceptor.Level.BODY); + + OkHttpClient okHttpClient = new OkHttpClient.Builder() + .cache(cache) + //.addInterceptor(logging) + .addInterceptor(new Interceptor() { + @NonNull + @Override public Response intercept(@NonNull Chain chain) throws IOException { + Request request = chain.request(); + if (connToInternet) { + request = request.newBuilder().header("Cache-Control", "public, max-age=" + 60).build(); + } else { + request = request.newBuilder().header("Cache-Control", "public, only-if-cached, max-stale=" + 60 * 60 * 24 * 30).build(); + } + return chain.proceed(request); + } + }) + .build(); + + Retrofit.Builder builder = new Retrofit.Builder() + .baseUrl(instanceURL) + .client(okHttpClient) + .addConverterFactory(GsonConverterFactory.create()); + + Retrofit retrofit = builder.build(); + + return retrofit.create(serviceClass); + + } + +} diff --git a/app/src/main/java/org/mian/gitnex/clients/RetrofitClient.java b/app/src/main/java/org/mian/gitnex/clients/RetrofitClient.java index 1f3857e6..c127c63c 100644 --- a/app/src/main/java/org/mian/gitnex/clients/RetrofitClient.java +++ b/app/src/main/java/org/mian/gitnex/clients/RetrofitClient.java @@ -27,7 +27,7 @@ public class RetrofitClient { private RetrofitClient(String instanceUrl, Context ctx) { final boolean connToInternet = AppUtil.haveNetworkConnection(ctx); - int cacheSize = 20 * 1024 * 1024; + int cacheSize = 50 * 1024 * 1024; // 50MB File httpCacheDirectory = new File(ctx.getCacheDir(), "responses"); Cache cache = new Cache(httpCacheDirectory, cacheSize); diff --git a/app/src/main/java/org/mian/gitnex/fragments/ClosedIssuesFragment.java b/app/src/main/java/org/mian/gitnex/fragments/ClosedIssuesFragment.java index 30b8cbb5..895c8940 100644 --- a/app/src/main/java/org/mian/gitnex/fragments/ClosedIssuesFragment.java +++ b/app/src/main/java/org/mian/gitnex/fragments/ClosedIssuesFragment.java @@ -28,7 +28,6 @@ import java.util.Objects; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.fragment.app.Fragment; -import androidx.recyclerview.widget.DividerItemDecoration; import androidx.recyclerview.widget.LinearLayoutManager; import androidx.recyclerview.widget.RecyclerView; import androidx.swiperefreshlayout.widget.SwipeRefreshLayout; @@ -120,10 +119,7 @@ public class ClosedIssuesFragment extends Fragment { } }); - DividerItemDecoration dividerItemDecoration = new DividerItemDecoration(recyclerViewClosed.getContext(), - DividerItemDecoration.VERTICAL); recyclerViewClosed.setHasFixedSize(true); - recyclerViewClosed.addItemDecoration(dividerItemDecoration); recyclerViewClosed.setLayoutManager(new LinearLayoutManager(context)); recyclerViewClosed.setAdapter(adapterClosed); diff --git a/app/src/main/java/org/mian/gitnex/fragments/IssuesFragment.java b/app/src/main/java/org/mian/gitnex/fragments/IssuesFragment.java index cc47dc9f..60230f18 100644 --- a/app/src/main/java/org/mian/gitnex/fragments/IssuesFragment.java +++ b/app/src/main/java/org/mian/gitnex/fragments/IssuesFragment.java @@ -5,7 +5,6 @@ import android.os.Bundle; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.fragment.app.Fragment; -import androidx.recyclerview.widget.DividerItemDecoration; import androidx.recyclerview.widget.LinearLayoutManager; import androidx.recyclerview.widget.RecyclerView; import androidx.swiperefreshlayout.widget.SwipeRefreshLayout; @@ -119,10 +118,7 @@ public class IssuesFragment extends Fragment { } }); - DividerItemDecoration dividerItemDecoration = new DividerItemDecoration(recyclerView.getContext(), - DividerItemDecoration.VERTICAL); recyclerView.setHasFixedSize(true); - recyclerView.addItemDecoration(dividerItemDecoration); recyclerView.setLayoutManager(new LinearLayoutManager(context)); recyclerView.setAdapter(adapter); diff --git a/app/src/main/java/org/mian/gitnex/fragments/PullRequestsFragment.java b/app/src/main/java/org/mian/gitnex/fragments/PullRequestsFragment.java new file mode 100644 index 00000000..a7f868d8 --- /dev/null +++ b/app/src/main/java/org/mian/gitnex/fragments/PullRequestsFragment.java @@ -0,0 +1,293 @@ +package org.mian.gitnex.fragments; + +import android.content.Context; +import android.os.Bundle; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.fragment.app.Fragment; +import androidx.recyclerview.widget.DividerItemDecoration; +import androidx.recyclerview.widget.LinearLayoutManager; +import androidx.recyclerview.widget.RecyclerView; +import androidx.swiperefreshlayout.widget.SwipeRefreshLayout; +import android.os.Handler; +import android.util.Log; +import android.view.LayoutInflater; +import android.view.Menu; +import android.view.MenuInflater; +import android.view.MenuItem; +import android.view.View; +import android.view.ViewGroup; +import android.view.inputmethod.EditorInfo; +import android.widget.ProgressBar; +import android.widget.TextView; +import org.mian.gitnex.R; +import org.mian.gitnex.adapters.PullRequestsAdapter; +import org.mian.gitnex.clients.PullRequestsService; +import org.mian.gitnex.helpers.Authorization; +import org.mian.gitnex.helpers.Toasty; +import org.mian.gitnex.interfaces.ApiInterface; +import org.mian.gitnex.models.PullRequests; +import org.mian.gitnex.util.AppUtil; +import org.mian.gitnex.util.TinyDB; +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; +import retrofit2.Call; +import retrofit2.Callback; +import retrofit2.Response; + +/** + * Author M M Arif + */ + +public class PullRequestsFragment extends Fragment { + + private ProgressBar mProgressBar; + private RecyclerView recyclerView; + private List prList; + private PullRequestsAdapter adapter; + private ApiInterface apiPR; + private String TAG = "PullRequestsListFragment - "; + private Context context; + private int pageSize = 1; + private TextView noData; + private String prState = "open"; + + @Nullable + @Override + public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { + + final View v = inflater.inflate(R.layout.fragment_pull_requests, container, false); + setHasOptionsMenu(true); + + TinyDB tinyDb = new TinyDB(getContext()); + String repoFullName = tinyDb.getString("repoFullName"); + //Log.i("repoFullName", tinyDb.getString("repoFullName")); + String[] parts = repoFullName.split("/"); + final String repoOwner = parts[0]; + final String repoName = parts[1]; + final String instanceUrl = tinyDb.getString("instanceUrl"); + final String loginUid = tinyDb.getString("loginUid"); + final String instanceToken = "token " + tinyDb.getString(loginUid + "-token"); + + final SwipeRefreshLayout swipeRefresh = v.findViewById(R.id.pullToRefresh); + + context = getContext(); + recyclerView = v.findViewById(R.id.recyclerView); + prList = new ArrayList<>(); + + mProgressBar = v.findViewById(R.id.progress_bar); + noData = v.findViewById(R.id.noData); + + swipeRefresh.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() { + @Override + public void onRefresh() { + new Handler().postDelayed(new Runnable() { + @Override + public void run() { + + swipeRefresh.setRefreshing(false); + loadInitial(instanceToken, repoOwner, repoName, pageSize, prState); + adapter.notifyDataChanged(); + + } + }, 200); + } + }); + + adapter = new PullRequestsAdapter(getContext(), prList); + adapter.setLoadMoreListener(new PullRequestsAdapter.OnLoadMoreListener() { + @Override + public void onLoadMore() { + + recyclerView.post(new Runnable() { + @Override + public void run() { + if(prList.size() == 10 || pageSize == 10) { + + int page = (prList.size() + 10) / 10; + loadMore(Authorization.returnAuthentication(getContext(), loginUid, instanceToken), repoOwner, repoName, page, prState); + + } + /*else { + + Toasty.info(context, getString(R.string.noMoreData)); + + }*/ + } + }); + + } + }); + + DividerItemDecoration dividerItemDecoration = new DividerItemDecoration(recyclerView.getContext(), + DividerItemDecoration.VERTICAL); + recyclerView.setHasFixedSize(true); + recyclerView.addItemDecoration(dividerItemDecoration); + recyclerView.setLayoutManager(new LinearLayoutManager(context)); + recyclerView.setAdapter(adapter); + + apiPR = PullRequestsService.createService(ApiInterface.class, instanceUrl, getContext()); + loadInitial(Authorization.returnAuthentication(getContext(), loginUid, instanceToken), repoOwner, repoName, pageSize, prState); + + return v; + + } + + @Override + public void onResume() { + + super.onResume(); + TinyDB tinyDb = new TinyDB(getContext()); + final String loginUid = tinyDb.getString("loginUid"); + String repoFullName = tinyDb.getString("repoFullName"); + String[] parts = repoFullName.split("/"); + final String repoOwner = parts[0]; + final String repoName = parts[1]; + final String instanceToken = "token " + tinyDb.getString(loginUid + "-token"); + + if(tinyDb.getBoolean("resumePullRequests")) { + + loadInitial(Authorization.returnAuthentication(getContext(), loginUid, instanceToken), repoOwner, repoName, pageSize, prState); + tinyDb.putBoolean("resumePullRequests", false); + + } + + } + + private void loadInitial(String token, String repoOwner, String repoName, int page, String prState) { + + Call> call = apiPR.getPullRequests(token, repoOwner, repoName, page, prState); + + call.enqueue(new Callback>() { + + @Override + public void onResponse(@NonNull Call> call, @NonNull Response> response) { + + if(response.isSuccessful()) { + + assert response.body() != null; + if(response.body().size() > 0) { + + prList.clear(); + prList.addAll(response.body()); + adapter.notifyDataChanged(); + noData.setVisibility(View.GONE); + + } + else { + prList.clear(); + adapter.notifyDataChanged(); + noData.setVisibility(View.VISIBLE); + } + mProgressBar.setVisibility(View.GONE); + } + else { + Log.i(TAG, String.valueOf(response.code())); + } + + Log.i("http", String.valueOf(response.code())); + + } + + @Override + public void onFailure(@NonNull Call> call, @NonNull Throwable t) { + Log.e(TAG, t.toString()); + } + + }); + + } + + private void loadMore(String token, String repoOwner, String repoName, int page, String prState){ + + //add loading progress view + prList.add(new PullRequests("load")); + adapter.notifyItemInserted((prList.size() - 1)); + + Call> call = apiPR.getPullRequests(token, repoOwner, repoName, page, prState); + + call.enqueue(new Callback>() { + + @Override + public void onResponse(@NonNull Call> call, @NonNull Response> response) { + + if(response.isSuccessful()){ + + //remove loading view + prList.remove(prList.size()-1); + + List result = response.body(); + + assert result != null; + if(result.size() > 0) { + + pageSize = result.size(); + prList.addAll(result); + + } + else { + + Toasty.info(context, getString(R.string.noMoreData)); + adapter.setMoreDataAvailable(false); + + } + + adapter.notifyDataChanged(); + + } + else { + + Log.e(TAG, String.valueOf(response.code())); + + } + + } + + @Override + public void onFailure(@NonNull Call> call, @NonNull Throwable t) { + + Log.e(TAG, t.toString()); + + } + + }); + } + + @Override + public void onCreateOptionsMenu(@NonNull Menu menu, @NonNull MenuInflater inflater) { + + boolean connToInternet = AppUtil.haveNetworkConnection(Objects.requireNonNull(getContext())); + + inflater.inflate(R.menu.search_menu, menu); + super.onCreateOptionsMenu(menu, inflater); + + MenuItem searchItem = menu.findItem(R.id.action_search); + androidx.appcompat.widget.SearchView searchView = (androidx.appcompat.widget.SearchView) searchItem.getActionView(); + searchView.setImeOptions(EditorInfo.IME_ACTION_DONE); + //searchView.setQueryHint(getContext().getString(R.string.strFilter)); + + /*if(!connToInternet) { + return; + }*/ + + searchView.setOnQueryTextListener(new androidx.appcompat.widget.SearchView.OnQueryTextListener() { + + @Override + public boolean onQueryTextSubmit(String query) { + return false; + } + + @Override + public boolean onQueryTextChange(String newText) { + + adapter.getFilter().filter(newText); + return false; + + } + + }); + + } + +} diff --git a/app/src/main/java/org/mian/gitnex/fragments/SingleIssueBottomSheetFragment.java b/app/src/main/java/org/mian/gitnex/fragments/SingleIssueBottomSheetFragment.java index 872d8027..3e4016b2 100644 --- a/app/src/main/java/org/mian/gitnex/fragments/SingleIssueBottomSheetFragment.java +++ b/app/src/main/java/org/mian/gitnex/fragments/SingleIssueBottomSheetFragment.java @@ -12,6 +12,7 @@ import org.mian.gitnex.actions.IssueActions; import org.mian.gitnex.activities.AddRemoveAssigneesActivity; import org.mian.gitnex.activities.AddRemoveLabelsActivity; import org.mian.gitnex.activities.EditIssueActivity; +import org.mian.gitnex.activities.FileDiffActivity; import org.mian.gitnex.activities.ReplyToIssueActivity; import org.mian.gitnex.helpers.Toasty; import org.mian.gitnex.util.TinyDB; @@ -42,6 +43,7 @@ public class SingleIssueBottomSheetFragment extends BottomSheetDialogFragment { TextView reOpenIssue = v.findViewById(R.id.reOpenIssue); TextView addRemoveAssignees = v.findViewById(R.id.addRemoveAssignees); TextView copyIssueUrl = v.findViewById(R.id.copyIssueUrl); + TextView openFilesDiff = v.findViewById(R.id.openFilesDiff); replyToIssue.setOnClickListener(new View.OnClickListener() { @Override @@ -53,6 +55,26 @@ public class SingleIssueBottomSheetFragment extends BottomSheetDialogFragment { } }); + if(tinyDB.getString("issueType").equals("pr")) { + editIssue.setText(R.string.editPrText); + copyIssueUrl.setText(R.string.copyPrUrlText); + + if(tinyDB.getString("repoType").equals("public")) { + openFilesDiff.setVisibility(View.VISIBLE); + } + + } + + openFilesDiff.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + + startActivity(new Intent(getContext(), FileDiffActivity.class)); + dismiss(); + + } + }); + editIssue.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { @@ -90,7 +112,7 @@ public class SingleIssueBottomSheetFragment extends BottomSheetDialogFragment { // get url of repo String repoFullName = tinyDB.getString("repoFullName"); String instanceUrlWithProtocol = "https://" + tinyDB.getString("instanceUrlRaw"); - if(!tinyDB.getString("instanceUrlWithProtocol").isEmpty()) { + if (!tinyDB.getString("instanceUrlWithProtocol").isEmpty()) { instanceUrlWithProtocol = tinyDB.getString("instanceUrlWithProtocol"); } @@ -100,6 +122,7 @@ public class SingleIssueBottomSheetFragment extends BottomSheetDialogFragment { // copy to clipboard ClipboardManager clipboard = (ClipboardManager) Objects.requireNonNull(getContext()).getSystemService(android.content.Context.CLIPBOARD_SERVICE); ClipData clip = ClipData.newPlainText("issueUrl", issueUrl); + assert clipboard != null; clipboard.setPrimaryClip(clip); dismiss(); @@ -109,35 +132,44 @@ public class SingleIssueBottomSheetFragment extends BottomSheetDialogFragment { } }); - if(tinyDB.getString("issueState").equals("open")) { // close issue + if(tinyDB.getString("issueType").equals("issue")) { - reOpenIssue.setVisibility(View.GONE); + if (tinyDB.getString("issueState").equals("open")) { // close issue - closeIssue.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { + reOpenIssue.setVisibility(View.GONE); - IssueActions.closeReopenIssue(getContext(), Integer.valueOf(tinyDB.getString("issueNumber")), "closed"); - dismiss(); + closeIssue.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { - } - }); + IssueActions.closeReopenIssue(getContext(), Integer.valueOf(tinyDB.getString("issueNumber")), "closed"); + dismiss(); + + } + }); + + } else if (tinyDB.getString("issueState").equals("closed")) { + + closeIssue.setVisibility(View.GONE); + + reOpenIssue.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + + IssueActions.closeReopenIssue(getContext(), Integer.valueOf(tinyDB.getString("issueNumber")), "open"); + dismiss(); + + } + }); + + } } - else if(tinyDB.getString("issueState").equals("closed")) { + else { + reOpenIssue.setVisibility(View.GONE); closeIssue.setVisibility(View.GONE); - reOpenIssue.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - - IssueActions.closeReopenIssue(getContext(), Integer.valueOf(tinyDB.getString("issueNumber")), "open"); - dismiss(); - - } - }); - } return v; diff --git a/app/src/main/java/org/mian/gitnex/interfaces/ApiInterface.java b/app/src/main/java/org/mian/gitnex/interfaces/ApiInterface.java index 5fe3e52a..61bd89a2 100644 --- a/app/src/main/java/org/mian/gitnex/interfaces/ApiInterface.java +++ b/app/src/main/java/org/mian/gitnex/interfaces/ApiInterface.java @@ -6,6 +6,7 @@ import org.mian.gitnex.models.Branches; import org.mian.gitnex.models.ExploreRepositories; import org.mian.gitnex.models.Files; import org.mian.gitnex.models.NewFile; +import org.mian.gitnex.models.PullRequests; import org.mian.gitnex.models.UpdateIssueAssignee; import org.mian.gitnex.models.UpdateIssueState; import org.mian.gitnex.models.Collaborators; @@ -30,6 +31,8 @@ import org.mian.gitnex.models.UserSearch; import org.mian.gitnex.models.UserTokens; import org.mian.gitnex.models.WatchRepository; import java.util.List; + +import okhttp3.ResponseBody; import retrofit2.Call; import retrofit2.http.Body; import retrofit2.http.DELETE; @@ -247,4 +250,11 @@ public interface ApiInterface { @DELETE("repos/{owner}/{repo}/subscription") // un watch a repository Call unWatchRepository(@Header("Authorization") String token, @Path("owner") String ownerName, @Path("repo") String repoName); + + @GET("repos/{owner}/{repo}/pulls") // get repository pull requests + Call> getPullRequests(@Header("Authorization") String token, @Path("owner") String owner, @Path("repo") String repo, @Query("page") int page, @Query("state") String state); + + @GET("{owner}/{repo}/pulls/{filename}") // get pull diff file contents + Call getFileDiffContents(@Path("owner") String owner, @Path("repo") String repo, @Path("filename") String fileName); + } diff --git a/app/src/main/java/org/mian/gitnex/models/FileDiffView.java b/app/src/main/java/org/mian/gitnex/models/FileDiffView.java new file mode 100644 index 00000000..bee37451 --- /dev/null +++ b/app/src/main/java/org/mian/gitnex/models/FileDiffView.java @@ -0,0 +1,39 @@ +package org.mian.gitnex.models; + +/** + * Author M M Arif + */ + +public class FileDiffView { + + private String fileName; + private boolean fileType; + private String fileInfo; + private String fileContents; + + public FileDiffView(String fileName, boolean fileType, String fileInfo, String fileContents) + { + + this.fileName = fileName; + this.fileType = fileType; + this.fileInfo = fileInfo; + this.fileContents = fileContents; + + } + + public String getFileName() { + return fileName; + } + + public boolean isFileType() { + return fileType; + } + + public String getFileInfo() { + return fileInfo; + } + + public String getFileContents() { + return fileContents; + } +} diff --git a/app/src/main/java/org/mian/gitnex/models/Issues.java b/app/src/main/java/org/mian/gitnex/models/Issues.java index 4cf00858..234242b7 100644 --- a/app/src/main/java/org/mian/gitnex/models/Issues.java +++ b/app/src/main/java/org/mian/gitnex/models/Issues.java @@ -98,10 +98,10 @@ public class Issues { public class pullRequestObject { - private String merged; + private boolean merged; private String merged_at; - public String getMerged() { + public boolean isMerged() { return merged; } diff --git a/app/src/main/java/org/mian/gitnex/models/PullRequests.java b/app/src/main/java/org/mian/gitnex/models/PullRequests.java new file mode 100644 index 00000000..9b479551 --- /dev/null +++ b/app/src/main/java/org/mian/gitnex/models/PullRequests.java @@ -0,0 +1,823 @@ +package org.mian.gitnex.models; + +import com.google.gson.annotations.SerializedName; +import java.util.Date; +import java.util.List; + +/** + * Author M M Arif + */ + +public class PullRequests { + + private int id; + private String body; + private int comments; + private String diff_url; + private String html_url; + private String merge_base; + private String merge_commit_sha; + private boolean mergeable; + private boolean merged; + private int number; + private String patch_url; + private String state; + private String title; + private String url; + private Date closed_at; + private Date created_at; + private Date due_date; + private Date merged_at; + private Date updated_at; + + private userObject user; + private List labels; + private List assignees; + private mergedByObject merged_by; + private milestoneObject milestone; + private baseObject base; + private headObject head; + + public PullRequests(String body) { + this.body = body; + } + + public class headObject { + + private int repo_id; + private String label; + private String ref; + private String sha; + + private repoObject repo; + + public class repoObject { + + private int repo_id; + private boolean allow_merge_commits; + private boolean allow_rebase; + private boolean allow_rebase_explicit; + private boolean allow_squash_merge; + private boolean archived; + private boolean empty; + private boolean fork; + private boolean has_issues; + private boolean has_pull_requests; + private boolean has_wiki; + private boolean ignore_whitespace_conflicts; + @SerializedName("private") + private boolean privateFlag; + private boolean mirror; + private String avatar_url; + private String clone_url; + private String default_branch; + private String description; + private String full_name; + private String html_url; + private String name; + private String ssh_url; + private String website; + private int forks_count; + private int id; + private int open_issues_count; + private int size; + private int stars_count; + private int watchers_count; + private Date created_at; + private Date updated_at; + + private ownerObject owner; + private permissionsObject permissions; + + public class ownerObject { + + private int repo_id; + private boolean is_admin; + private String avatar_url; + private String email; + private String full_name; + private String language; + private String login; + private Date created; + + public int getRepo_id() { + return repo_id; + } + + public boolean isIs_admin() { + return is_admin; + } + + public String getAvatar_url() { + return avatar_url; + } + + public String getEmail() { + return email; + } + + public String getFull_name() { + return full_name; + } + + public String getLanguage() { + return language; + } + + public String getLogin() { + return login; + } + + public Date getCreated() { + return created; + } + } + + public class permissionsObject { + + private boolean admin; + private boolean pull; + private boolean push; + + public boolean isAdmin() { + return admin; + } + + public boolean isPull() { + return pull; + } + + public boolean isPush() { + return push; + } + } + + public int getRepo_id() { + return repo_id; + } + + public boolean isAllow_merge_commits() { + return allow_merge_commits; + } + + public boolean isAllow_rebase() { + return allow_rebase; + } + + public boolean isAllow_rebase_explicit() { + return allow_rebase_explicit; + } + + public boolean isAllow_squash_merge() { + return allow_squash_merge; + } + + public boolean isArchived() { + return archived; + } + + public boolean isEmpty() { + return empty; + } + + public boolean isFork() { + return fork; + } + + public boolean isHas_issues() { + return has_issues; + } + + public boolean isHas_pull_requests() { + return has_pull_requests; + } + + public boolean isHas_wiki() { + return has_wiki; + } + + public boolean isIgnore_whitespace_conflicts() { + return ignore_whitespace_conflicts; + } + + public boolean isPrivateFlag() { + return privateFlag; + } + + public boolean isMirror() { + return mirror; + } + + public String getAvatar_url() { + return avatar_url; + } + + public String getClone_url() { + return clone_url; + } + + public String getDefault_branch() { + return default_branch; + } + + public String getDescription() { + return description; + } + + public String getFull_name() { + return full_name; + } + + public String getHtml_url() { + return html_url; + } + + public String getName() { + return name; + } + + public String getSsh_url() { + return ssh_url; + } + + public String getWebsite() { + return website; + } + + public int getForks_count() { + return forks_count; + } + + public int getId() { + return id; + } + + public int getOpen_issues_count() { + return open_issues_count; + } + + public int getSize() { + return size; + } + + public int getStars_count() { + return stars_count; + } + + public int getWatchers_count() { + return watchers_count; + } + + public Date getCreated_at() { + return created_at; + } + + public Date getUpdated_at() { + return updated_at; + } + + public ownerObject getOwner() { + return owner; + } + + public permissionsObject getPermissions() { + return permissions; + } + } + + } + + public class baseObject { + + private int repo_id; + private String label; + private String ref; + private String sha; + + private repoObject repo; + + public class repoObject { + + private int repo_id; + private boolean allow_merge_commits; + private boolean allow_rebase; + private boolean allow_rebase_explicit; + private boolean allow_squash_merge; + private boolean archived; + private boolean empty; + private boolean fork; + private boolean has_issues; + private boolean has_pull_requests; + private boolean has_wiki; + private boolean ignore_whitespace_conflicts; + @SerializedName("private") + private boolean privateFlag; + private boolean mirror; + private String avatar_url; + private String clone_url; + private String default_branch; + private String description; + private String full_name; + private String html_url; + private String name; + private String ssh_url; + private String website; + private int forks_count; + private int id; + private int open_issues_count; + private int size; + private int stars_count; + private int watchers_count; + private Date created_at; + private Date updated_at; + + private ownerObject owner; + private permissionsObject permissions; + + public class ownerObject { + + private int repo_id; + private boolean is_admin; + private String avatar_url; + private String email; + private String full_name; + private String language; + private String login; + private Date created; + + public int getRepo_id() { + return repo_id; + } + + public boolean isIs_admin() { + return is_admin; + } + + public String getAvatar_url() { + return avatar_url; + } + + public String getEmail() { + return email; + } + + public String getFull_name() { + return full_name; + } + + public String getLanguage() { + return language; + } + + public String getLogin() { + return login; + } + + public Date getCreated() { + return created; + } + } + + public class permissionsObject { + + private boolean admin; + private boolean pull; + private boolean push; + + public boolean isAdmin() { + return admin; + } + + public boolean isPull() { + return pull; + } + + public boolean isPush() { + return push; + } + } + + public int getRepo_id() { + return repo_id; + } + + public boolean isAllow_merge_commits() { + return allow_merge_commits; + } + + public boolean isAllow_rebase() { + return allow_rebase; + } + + public boolean isAllow_rebase_explicit() { + return allow_rebase_explicit; + } + + public boolean isAllow_squash_merge() { + return allow_squash_merge; + } + + public boolean isArchived() { + return archived; + } + + public boolean isEmpty() { + return empty; + } + + public boolean isFork() { + return fork; + } + + public boolean isHas_issues() { + return has_issues; + } + + public boolean isHas_pull_requests() { + return has_pull_requests; + } + + public boolean isHas_wiki() { + return has_wiki; + } + + public boolean isIgnore_whitespace_conflicts() { + return ignore_whitespace_conflicts; + } + + public boolean isPrivateFlag() { + return privateFlag; + } + + public boolean isMirror() { + return mirror; + } + + public String getAvatar_url() { + return avatar_url; + } + + public String getClone_url() { + return clone_url; + } + + public String getDefault_branch() { + return default_branch; + } + + public String getDescription() { + return description; + } + + public String getFull_name() { + return full_name; + } + + public String getHtml_url() { + return html_url; + } + + public String getName() { + return name; + } + + public String getSsh_url() { + return ssh_url; + } + + public String getWebsite() { + return website; + } + + public int getForks_count() { + return forks_count; + } + + public int getId() { + return id; + } + + public int getOpen_issues_count() { + return open_issues_count; + } + + public int getSize() { + return size; + } + + public int getStars_count() { + return stars_count; + } + + public int getWatchers_count() { + return watchers_count; + } + + public Date getCreated_at() { + return created_at; + } + + public Date getUpdated_at() { + return updated_at; + } + + public ownerObject getOwner() { + return owner; + } + + public permissionsObject getPermissions() { + return permissions; + } + } + + } + + public class userObject { + + private int id; + private String login; + private String full_name; + private String email; + private String avatar_url; + private String language; + private boolean is_admin; + + public int getId() { + return id; + } + + public String getLogin() { + return login; + } + + public String getFull_name() { + return full_name; + } + + public String getEmail() { + return email; + } + + public String getAvatar_url() { + return avatar_url; + } + + public String getLanguage() { + return language; + } + + public boolean isIs_admin() { + return is_admin; + } + } + + public class labelsObject { + + private int id; + private String name; + private String color; + private String url; + + public int getId() { + return id; + } + + public String getName() { + return name; + } + + public String getColor() { + return color; + } + + public String getUrl() { + return url; + } + } + + public class assigneesObject { + + private int id; + private String login; + private String full_name; + private String email; + private String avatar_url; + private String language; + private boolean is_admin; + + public int getId() { + return id; + } + + public String getLogin() { + return login; + } + + public String getFull_name() { + return full_name; + } + + public String getEmail() { + return email; + } + + public String getAvatar_url() { + return avatar_url; + } + + public String getLanguage() { + return language; + } + + public boolean isIs_admin() { + return is_admin; + } + } + + public class mergedByObject { + + private int id; + private String login; + private String full_name; + private String email; + private String avatar_url; + private String language; + private boolean is_admin; + + public int getId() { + return id; + } + + public String getLogin() { + return login; + } + + public String getFull_name() { + return full_name; + } + + public String getEmail() { + return email; + } + + public String getAvatar_url() { + return avatar_url; + } + + public String getLanguage() { + return language; + } + + public boolean isIs_admin() { + return is_admin; + } + } + + public class milestoneObject { + + private int id; + private String title; + private String description; + private String state; + private String open_issues; + private String closed_issues; + private String closed_at; + private String due_on; + + public int getId() { + return id; + } + + public String getTitle() { + return title; + } + + public String getDescription() { + return description; + } + + public String getState() { + return state; + } + + public String getOpen_issues() { + return open_issues; + } + + public String getClosed_issues() { + return closed_issues; + } + + public String getClosed_at() { + return closed_at; + } + + public String getDue_on() { + return due_on; + } + } + + public int getId() { + return id; + } + + public String getBody() { + return body; + } + + public int getComments() { + return comments; + } + + public String getDiff_url() { + return diff_url; + } + + public String getHtml_url() { + return html_url; + } + + public String getMerge_base() { + return merge_base; + } + + public String getMerge_commit_sha() { + return merge_commit_sha; + } + + public boolean isMergeable() { + return mergeable; + } + + public boolean isMerged() { + return merged; + } + + public int getNumber() { + return number; + } + + public String getPatch_url() { + return patch_url; + } + + public String getState() { + return state; + } + + public String getTitle() { + return title; + } + + public String getUrl() { + return url; + } + + public Date getClosed_at() { + return closed_at; + } + + public Date getCreated_at() { + return created_at; + } + + public Date getDue_date() { + return due_date; + } + + public Date getMerged_at() { + return merged_at; + } + + public Date getUpdated_at() { + return updated_at; + } + + public userObject getUser() { + return user; + } + + public List getLabels() { + return labels; + } + + public List getAssignees() { + return assignees; + } + + public mergedByObject getMerged_by() { + return merged_by; + } + + public milestoneObject getMilestone() { + return milestone; + } + + public baseObject getBase() { + return base; + } + + public headObject getHead() { + return head; + } +} diff --git a/app/src/main/java/org/mian/gitnex/util/AppUtil.java b/app/src/main/java/org/mian/gitnex/util/AppUtil.java index 7fee1811..4c7ee546 100644 --- a/app/src/main/java/org/mian/gitnex/util/AppUtil.java +++ b/app/src/main/java/org/mian/gitnex/util/AppUtil.java @@ -217,7 +217,8 @@ public class AppUtil { "cs", "bash", "sh", "bsh", "cv", "python", "perl", "pm", "rb", "ruby", "javascript", "coffee", "rc", "rs", "rust", "basic", "clj", "css", "dart", "lisp", "erl", "hs", "lsp", "rkt", "ss", "llvm", "ll", "lua", "matlab", "pascal", "r", "scala", "sql", "latex", "tex", "vb", "vbs", - "vhd", "tcl", "wiki.meta", "yaml", "yml", "markdown", "xml", "proto", "regex", "py", "pl", "js"}; + "vhd", "tcl", "wiki.meta", "yaml", "yml", "markdown", "xml", "proto", "regex", "py", "pl", "js", + "html", "htm", "volt", "ini", "htaccess", "conf", "gitignore"}; return Arrays.asList(extValues).contains(ext); diff --git a/app/src/main/res/layout/activity_file_diff.xml b/app/src/main/res/layout/activity_file_diff.xml new file mode 100644 index 00000000..af6d9e2b --- /dev/null +++ b/app/src/main/res/layout/activity_file_diff.xml @@ -0,0 +1,72 @@ + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_repo_detail.xml b/app/src/main/res/layout/activity_repo_detail.xml index 2e4123b1..c2a660b7 100644 --- a/app/src/main/res/layout/activity_repo_detail.xml +++ b/app/src/main/res/layout/activity_repo_detail.xml @@ -57,6 +57,12 @@ android:layout_height="wrap_content" android:text="@string/tabItemCloseIssues" /> + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/fragment_pull_requests.xml b/app/src/main/res/layout/fragment_pull_requests.xml new file mode 100644 index 00000000..87ba9c16 --- /dev/null +++ b/app/src/main/res/layout/fragment_pull_requests.xml @@ -0,0 +1,45 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/my_repos_list.xml b/app/src/main/res/layout/my_repos_list.xml index 2a281252..6a76758b 100644 --- a/app/src/main/res/layout/my_repos_list.xml +++ b/app/src/main/res/layout/my_repos_list.xml @@ -4,6 +4,12 @@ android:layout_height="wrap_content" android:background="@color/backgroundColor" > + + @@ -39,28 +41,14 @@ - - - - + + \ No newline at end of file diff --git a/app/src/main/res/layout/repo_pr_list.xml b/app/src/main/res/layout/repo_pr_list.xml new file mode 100644 index 00000000..331a2b24 --- /dev/null +++ b/app/src/main/res/layout/repo_pr_list.xml @@ -0,0 +1,91 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/repos_list.xml b/app/src/main/res/layout/repos_list.xml index 5c76207d..8aa5ea25 100644 --- a/app/src/main/res/layout/repos_list.xml +++ b/app/src/main/res/layout/repos_list.xml @@ -4,6 +4,12 @@ android:layout_height="wrap_content" android:background="@color/backgroundColor" > + + + + + + - + + #e74c3c #3faef7 #b6bbbf + #368f73 + #63fdd9 + #ffe0e0 + #d6fcd6 diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 275aa2de..ebb157d4 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -13,7 +13,7 @@ https://liberapay.com/mmarif/donate https://www.patreon.com/mmarif %1$s / build %2$d - GitNex is a free, open-source Android client for Git repository management tool Gitea. GitNex is Licensed under GPLv3.\n\nThanks to all the contributors and sponsors for your generous work and donations. + GitNex is a free, open-source Android client for Git repository management tool Gitea. GitNex is Licensed under GPLv3.\n\nThanks to all the contributors and donators for your generous work and donations. https://crowdin.com/project/gitnex Report issues at Gitea @@ -52,7 +52,7 @@ New Label Credits Update Label - Sponsors + Donators Starred Repositories New Team Add Email Address @@ -143,6 +143,7 @@ Labels Settings Collaborators + Pull Requests No issues found @@ -406,7 +407,7 @@ Edit Issue #%1$s - Issue updated. + Issue updated @@ -459,7 +460,7 @@ Filter Copy Issue URL - Issue URL copied to clipboard + URL copied to clipboard %1$d\uFF05 completed @@ -495,6 +496,9 @@ All fields are required Continue Token + - + private + public Translate GitNex with Crowdin @@ -523,4 +527,15 @@ Instance has returned an error - Unauthorized. Check your credentials and try again Please enter the correct token + No pull requests found + Creator :\u0020 + Edit Pull Request + Copy Pull Request URL + Edit Pull Request #%1$s + Pull Request updated + %1$s Files Changed + %1$s File Changed + -%1$s + Files Changed +