diff --git a/app/build.gradle b/app/build.gradle index d6ea30b5..ae5aaab8 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -34,6 +34,7 @@ configurations { dependencies { def lifecycle_version = "2.2.0" final def markwon_version = '4.2.1' + def paging_version = "2.1.2" implementation fileTree(include: ['*.jar'], dir: 'libs') implementation "androidx.appcompat:appcompat:1.1.0" @@ -79,4 +80,5 @@ dependencies { implementation "com.github.chrisbanes:PhotoView:2.3.0" implementation "com.pddstudio:highlightjs-android:1.5.0" implementation "com.github.barteksc:android-pdf-viewer:3.2.0-beta.1" + implementation "androidx.paging:paging-runtime:$paging_version" } 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 3259bbbf..dbbf34cb 100644 --- a/app/src/main/java/org/mian/gitnex/activities/RepoDetailActivity.java +++ b/app/src/main/java/org/mian/gitnex/activities/RepoDetailActivity.java @@ -27,6 +27,7 @@ import org.mian.gitnex.R; import org.mian.gitnex.clients.RetrofitClient; import org.mian.gitnex.fragments.BottomSheetRepoFragment; import org.mian.gitnex.fragments.BranchesFragment; +import org.mian.gitnex.fragments.CommitsFragment; import org.mian.gitnex.fragments.IssuesClosedFragment; import org.mian.gitnex.fragments.CollaboratorsFragment; import org.mian.gitnex.fragments.FilesFragment; @@ -270,15 +271,18 @@ public class RepoDetailActivity extends BaseActivity implements BottomSheetRepoF case 4: // pull requests fragment = new PullRequestsFragment(); break; - case 5: // milestones + case 5: // commits + fragment = new CommitsFragment(); + break; + case 6: // milestones return MilestonesFragment.newInstance(repoOwner, repoName); - case 6: // labels + case 7: // labels return LabelsFragment.newInstance(repoOwner, repoName); - case 7: // branches + case 8: // branches return BranchesFragment.newInstance(repoOwner, repoName); - case 8: // releases + case 9: // releases return ReleasesFragment.newInstance(repoOwner, repoName); - case 9: // collaborators + case 10: // collaborators return CollaboratorsFragment.newInstance(repoOwner, repoName); } return fragment; @@ -286,7 +290,7 @@ public class RepoDetailActivity extends BaseActivity implements BottomSheetRepoF @Override public int getCount() { - return 10; + return 11; } } diff --git a/app/src/main/java/org/mian/gitnex/adapters/CommitsAdapter.java b/app/src/main/java/org/mian/gitnex/adapters/CommitsAdapter.java new file mode 100644 index 00000000..a79e342c --- /dev/null +++ b/app/src/main/java/org/mian/gitnex/adapters/CommitsAdapter.java @@ -0,0 +1,78 @@ +package org.mian.gitnex.adapters; + +import android.annotation.SuppressLint; +import android.content.Context; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.TextView; +import androidx.annotation.NonNull; +import androidx.paging.PagedListAdapter; +import androidx.recyclerview.widget.DiffUtil; +import androidx.recyclerview.widget.RecyclerView; +import org.mian.gitnex.R; +import org.mian.gitnex.models.Commits; + +/** + * Author M M Arif + */ + +public class CommitsAdapter extends PagedListAdapter { + + private Context mCtx; + + public CommitsAdapter(Context mCtx) { + + super(DIFF_CALLBACK); + this.mCtx = mCtx; + + } + + @NonNull + @Override + public CommitsViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { + + View view = LayoutInflater.from(mCtx).inflate(R.layout.list_commits, parent, false); + return new CommitsViewHolder(view); + + } + + @Override + public void onBindViewHolder(@NonNull CommitsViewHolder holder, int position) { + + Commits commit_ = getItem(position); + + if (commit_ != null) { + holder.commitTitle.setText(commit_.getCommit().getMessage()); + } + + } + + private static DiffUtil.ItemCallback DIFF_CALLBACK = + new DiffUtil.ItemCallback() { + @Override + public boolean areItemsTheSame(Commits oldCommit, Commits newCommit) { + return oldCommit.getSha().equals(newCommit.getSha()); + } + + @SuppressLint("DiffUtilEquals") + @Override + public boolean areContentsTheSame(Commits oldCommit, @NonNull Commits newCommit) { + return oldCommit.equals(newCommit); + } + }; + + static class CommitsViewHolder extends RecyclerView.ViewHolder { + + TextView commitTitle; + + CommitsViewHolder(View itemView) { + + super(itemView); + commitTitle = itemView.findViewById(R.id.commitTitle); + + } + + } + +} diff --git a/app/src/main/java/org/mian/gitnex/datasource/CommitsDataSource.java b/app/src/main/java/org/mian/gitnex/datasource/CommitsDataSource.java new file mode 100644 index 00000000..05725175 --- /dev/null +++ b/app/src/main/java/org/mian/gitnex/datasource/CommitsDataSource.java @@ -0,0 +1,138 @@ +package org.mian.gitnex.datasource; + +import android.content.Context; +import android.util.Log; +import androidx.annotation.NonNull; +import androidx.paging.PageKeyedDataSource; +import org.mian.gitnex.clients.RetrofitClient; +import org.mian.gitnex.models.Commits; +import java.util.List; +import java.util.Objects; +import retrofit2.Call; +import retrofit2.Callback; +import retrofit2.Response; + +/** + * Author M M Arif + */ + +public class CommitsDataSource extends PageKeyedDataSource { + + private String TAG = "CommitsDataSource"; + private Context ctx; + + private static final int FIRST_PAGE = 1; + + private String instanceUrl; + private String instanceToken; + private String owner; + private String repo; + + CommitsDataSource(Context ctx, String instanceUrl, String instanceToken, String owner, String repo) { + + this.ctx = ctx; + this.instanceUrl = instanceUrl; + this.instanceToken = instanceToken; + this.owner = owner; + this.repo = repo; + + } + + @Override + public void loadInitial(@NonNull LoadInitialParams params, @NonNull final LoadInitialCallback callback) { + + RetrofitClient.getInstance(instanceUrl, ctx) + .getApiInterface() + .getRepositoryCommits(instanceToken, owner, repo, FIRST_PAGE) + .enqueue(new Callback>() { + @Override + public void onResponse(@NonNull Call> call, @NonNull Response> response) { + + if (response.body() != null) { + + callback.onResult(response.body(), null, FIRST_PAGE + 1); + + } + + } + + @Override + public void onFailure(@NonNull Call> call, @NonNull Throwable t) { + + Log.e(TAG, Objects.requireNonNull(t.getMessage())); + + } + }); + + } + + @Override + public void loadBefore(@NonNull final LoadParams params, @NonNull final LoadCallback callback) { + + RetrofitClient.getInstance(instanceUrl, ctx) + .getApiInterface() + .getRepositoryCommits(instanceToken, owner, repo, params.key) + .enqueue(new Callback>() { + + @Override + public void onResponse(@NonNull Call> call, @NonNull Response> response) { + + Integer adjacentKey = (params.key > 1) ? params.key - 1 : null; + + if (response.body() != null) { + + callback.onResult(response.body(), adjacentKey); + + } + + } + + @Override + public void onFailure(@NonNull Call> call, @NonNull Throwable t) { + + Log.e(TAG, Objects.requireNonNull(t.getMessage())); + + } + + }); + + } + + @Override + public void loadAfter(@NonNull final LoadParams params, @NonNull final LoadCallback callback) { + + RetrofitClient.getInstance(instanceUrl, ctx) + .getApiInterface() + .getRepositoryCommits(instanceToken, owner, repo, params.key) + .enqueue(new Callback>() { + + @Override + public void onResponse(@NonNull Call> call, @NonNull Response> response) { + + if (response.body() != null) { + + boolean next = false; + if(response.body().size() > 0) { + next = true; + } + + Integer key = next ? params.key + 1 : null; + + callback.onResult(response.body(), key); + + } + + } + + @Override + public void onFailure(@NonNull Call> call, @NonNull Throwable t) { + + Log.e(TAG, Objects.requireNonNull(t.getMessage())); + + } + + }); + + } + +} diff --git a/app/src/main/java/org/mian/gitnex/datasource/CommitsDataSourceFactory.java b/app/src/main/java/org/mian/gitnex/datasource/CommitsDataSourceFactory.java new file mode 100644 index 00000000..5e0e8093 --- /dev/null +++ b/app/src/main/java/org/mian/gitnex/datasource/CommitsDataSourceFactory.java @@ -0,0 +1,52 @@ +package org.mian.gitnex.datasource; + +import android.content.Context; +import androidx.annotation.NonNull; +import androidx.lifecycle.MutableLiveData; +import androidx.paging.DataSource; +import androidx.paging.PageKeyedDataSource; +import org.mian.gitnex.models.Commits; + +/** + * Author M M Arif + */ + +public class CommitsDataSourceFactory extends DataSource.Factory { + + private Context ctx; + private String instanceUrl; + private String instanceToken; + private String repoOwner; + private String repoName; + private int listLimit; + + public CommitsDataSourceFactory(Context ctx, String instanceUrl, String instanceToken, String repoOwner, String repoName, int listLimit) { + + this.ctx = ctx; + this.instanceUrl = instanceUrl; + this.instanceToken = instanceToken; + this.repoOwner = repoOwner; + this.repoName = repoName; + this.listLimit = listLimit; + + } + + private MutableLiveData> itemLiveDataSource = new MutableLiveData<>(); + + @NonNull + @Override + public DataSource create() { + + CommitsDataSource itemDataSource = new CommitsDataSource(ctx, instanceUrl, instanceToken, repoOwner, repoName); + itemLiveDataSource.postValue(itemDataSource); + return itemDataSource; + + } + + public MutableLiveData> getItemLiveDataSource() { + + return itemLiveDataSource; + + } + +} diff --git a/app/src/main/java/org/mian/gitnex/fragments/CommitsFragment.java b/app/src/main/java/org/mian/gitnex/fragments/CommitsFragment.java new file mode 100644 index 00000000..3c52b23f --- /dev/null +++ b/app/src/main/java/org/mian/gitnex/fragments/CommitsFragment.java @@ -0,0 +1,98 @@ +package org.mian.gitnex.fragments; + +import android.os.Bundle; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ProgressBar; +import android.widget.TextView; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.fragment.app.Fragment; +import androidx.lifecycle.Observer; +import androidx.lifecycle.ViewModel; +import androidx.lifecycle.ViewModelProvider; +import androidx.lifecycle.ViewModelProviders; +import androidx.paging.PagedList; +import androidx.recyclerview.widget.LinearLayoutManager; +import androidx.recyclerview.widget.RecyclerView; +import androidx.swiperefreshlayout.widget.SwipeRefreshLayout; +import org.mian.gitnex.R; +import org.mian.gitnex.adapters.CommitsAdapter; +import org.mian.gitnex.models.Commits; +import org.mian.gitnex.util.TinyDB; +import org.mian.gitnex.viewmodels.CommitsViewModel; + +/** + * Author M M Arif + */ + +public class CommitsFragment extends Fragment { + + private CommitsViewModel commitsViewModel; + private CommitsAdapter commitsAdapter; + private SwipeRefreshLayout swipeRefreshLayout; + + private int listLimit = 50; + + @Nullable + @Override + public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { + + final View v = inflater.inflate(R.layout.fragment_commits, container, false); + setHasOptionsMenu(true); + + TinyDB tinyDb = new TinyDB(getContext()); + final String instanceUrl = tinyDb.getString("instanceUrl"); + final String loginUid = tinyDb.getString("loginUid"); + final String instanceToken = "token " + tinyDb.getString(loginUid + "-token"); + String repoFullName = tinyDb.getString("repoFullName"); + String[] parts = repoFullName.split("/"); + final String repoOwner = parts[0]; + final String repoName = parts[1]; + + TextView noDataCommits = v.findViewById(R.id.noDataCommits); + ProgressBar progressBar = v.findViewById(R.id.progress_bar); + swipeRefreshLayout = v.findViewById(R.id.pullToRefresh); + progressBar.setVisibility(View.GONE); + + RecyclerView recyclerView = v.findViewById(R.id.recyclerView); + recyclerView.setLayoutManager(new LinearLayoutManager(getContext())); + recyclerView.setHasFixedSize(true); + + //CommitsViewModel commitsViewModel = new ViewModelProvider(this).get(CommitsViewModel.class); + + commitsAdapter = new CommitsAdapter(getContext()); + + commitsViewModel = ViewModelProviders.of(this, new ViewModelProvider.Factory() { + + @NonNull + @Override + public VM create(@NonNull Class modelClass) { + //noinspection unchecked + return (VM) new CommitsViewModel (getContext(), instanceUrl, instanceToken, repoOwner, repoName, listLimit); + } + + }).get(CommitsViewModel.class); + + swipeRefreshLayout.setOnRefreshListener(() -> commitsViewModel.refresh()); + + //noinspection unchecked + commitsViewModel.itemPagedList.observe(getViewLifecycleOwner(), new Observer>() { + + @Override + public void onChanged(@Nullable PagedList items) { + + commitsAdapter.submitList(items); + swipeRefreshLayout.setRefreshing(false); + + } + + }); + + recyclerView.setAdapter(commitsAdapter); + return v; + + } + +} diff --git a/app/src/main/java/org/mian/gitnex/fragments/FilesFragment.java b/app/src/main/java/org/mian/gitnex/fragments/FilesFragment.java index 8c3da1b2..458e4978 100644 --- a/app/src/main/java/org/mian/gitnex/fragments/FilesFragment.java +++ b/app/src/main/java/org/mian/gitnex/fragments/FilesFragment.java @@ -1,5 +1,6 @@ package org.mian.gitnex.fragments; +import android.annotation.SuppressLint; import android.content.Intent; import android.net.Uri; import android.os.Bundle; @@ -145,7 +146,9 @@ public class FilesFragment extends Fragment implements FilesAdapter.FilesAdapter final String finalDirName_ = dirName_; mBreadcrumbsView.addItem(createItem(dirName)); + //noinspection unchecked mBreadcrumbsView.setCallback(new DefaultBreadcrumbsCallback() { + @SuppressLint("SetTextI18n") @Override public void onNavigateBack(BreadcrumbItem item, int position) { 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 e00149c3..06752497 100644 --- a/app/src/main/java/org/mian/gitnex/interfaces/ApiInterface.java +++ b/app/src/main/java/org/mian/gitnex/interfaces/ApiInterface.java @@ -3,6 +3,7 @@ package org.mian.gitnex.interfaces; import com.google.gson.JsonElement; import org.mian.gitnex.models.AddEmail; import org.mian.gitnex.models.Branches; +import org.mian.gitnex.models.Commits; import org.mian.gitnex.models.ExploreRepositories; import org.mian.gitnex.models.Files; import org.mian.gitnex.models.MergePullRequest; @@ -259,4 +260,7 @@ public interface ApiInterface { @POST("repos/{owner}/{repo}/pulls/{index}/merge") // merge a pull request Call mergePullRequest(@Header("Authorization") String token, @Path("owner") String ownerName, @Path("repo") String repoName, @Path("index") int index, @Body MergePullRequest jsonStr); + + @GET("repos/{owner}/{repo}/commits") // get all commits + Call> getRepositoryCommits(@Header("Authorization") String token, @Path("owner") String owner, @Path("repo") String repo, @Query("page") int page); } diff --git a/app/src/main/java/org/mian/gitnex/models/Commits.java b/app/src/main/java/org/mian/gitnex/models/Commits.java new file mode 100644 index 00000000..b166a861 --- /dev/null +++ b/app/src/main/java/org/mian/gitnex/models/Commits.java @@ -0,0 +1,257 @@ +package org.mian.gitnex.models; + +import java.util.List; + +/** + * Author M M Arif + */ + +public class Commits { + + private String url; + private String sha; + private String html_url; + private commitObject commit; + private authorObject author; + private committerObject committer; + private List parent; + + public String getUrl() { + return url; + } + + public String getSha() { + return sha; + } + + public String getHtml_url() { + return html_url; + } + + public static class commitObject { + + private String url; + private CommitAuthor author; + private CommitCommitter committer; + private String message; + private CommitTree tree; + + public static class CommitAuthor { + + String name; + String email; + String date; + + public String getName() { + return name; + } + + public String getEmail() { + return email; + } + + public String getDate() { + return date; + } + + } + + public static class CommitCommitter { + + String name; + String email; + String date; + + public String getName() { + return name; + } + + public String getEmail() { + return email; + } + + public String getDate() { + return date; + } + + } + + public static class CommitTree { + + String url; + String sha; + + public String getUrl() { + return url; + } + + public String getSha() { + return sha; + } + + } + + public String getUrl() { + return url; + } + + public String getMessage() { + return message; + } + + public CommitAuthor getAuthor() { + return author; + } + + public CommitCommitter getCommitter() { + return committer; + } + + public CommitTree getTree() { + return tree; + } + } + + public static class authorObject { + + private int id; + private String login; + private String full_name; + private String email; + private String avatar_url; + private String language; + private Boolean is_admin; + private String last_login; + private String created; + private String username; + + 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 getIs_admin() { + return is_admin; + } + + public String getLast_login() { + return last_login; + } + + public String getCreated() { + return created; + } + + public String getUsername() { + return username; + } + } + + public static class committerObject { + + private int id; + private String login; + private String full_name; + private String email; + private String avatar_url; + private String language; + private Boolean is_admin; + private String last_login; + private String created; + private String username; + + 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 getIs_admin() { + return is_admin; + } + + public String getLast_login() { + return last_login; + } + + public String getCreated() { + return created; + } + + public String getUsername() { + return username; + } + + } + + public static class parentObject { + + private String url; + private String sha; + + public String getUrl() { + return url; + } + + public String getSha() { + return sha; + } + } + + public commitObject getCommit() { + return commit; + } + + public authorObject getAuthor() { + return author; + } + + public committerObject getCommitter() { + return committer; + } + + public List getParent() { + return parent; + } + +} + + + diff --git a/app/src/main/java/org/mian/gitnex/viewmodels/CommitsViewModel.java b/app/src/main/java/org/mian/gitnex/viewmodels/CommitsViewModel.java new file mode 100644 index 00000000..7545c607 --- /dev/null +++ b/app/src/main/java/org/mian/gitnex/viewmodels/CommitsViewModel.java @@ -0,0 +1,43 @@ +package org.mian.gitnex.viewmodels; + +import android.content.Context; +import androidx.lifecycle.LiveData; +import androidx.lifecycle.ViewModel; +import androidx.paging.LivePagedListBuilder; +import androidx.paging.PageKeyedDataSource; +import androidx.paging.PagedList; +import org.mian.gitnex.datasource.CommitsDataSource; +import org.mian.gitnex.datasource.CommitsDataSourceFactory; +import org.mian.gitnex.models.Commits; +import java.util.Objects; + +/** + * Author M M Arif + */ + +public class CommitsViewModel extends ViewModel { + + private CommitsDataSourceFactory itemDataSourceFactory; + public LiveData itemPagedList; + + public CommitsViewModel(Context ctx, String instannceUrl, String instanceToken, String owner, String repo, int listLimit) { + + itemDataSourceFactory = new CommitsDataSourceFactory(ctx, instannceUrl, instanceToken, owner, repo, listLimit); + LiveData> liveDataSource = itemDataSourceFactory.getItemLiveDataSource(); + PagedList.Config pagedListConfig = + (new PagedList.Config.Builder()) + .setEnablePlaceholders(true) + .setPageSize(listLimit).build(); + //noinspection unchecked + itemPagedList = new LivePagedListBuilder(itemDataSourceFactory, pagedListConfig) + .build(); + + } + + public void refresh() { + + Objects.requireNonNull(itemDataSourceFactory.getItemLiveDataSource().getValue()).invalidate(); + + } + +} diff --git a/app/src/main/res/layout/activity_repo_detail.xml b/app/src/main/res/layout/activity_repo_detail.xml index ce6408e5..b482f8ba 100644 --- a/app/src/main/res/layout/activity_repo_detail.xml +++ b/app/src/main/res/layout/activity_repo_detail.xml @@ -74,6 +74,12 @@ android:layout_height="wrap_content" android:text="@string/tabPullRequests" /> + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/list_commits.xml b/app/src/main/res/layout/list_commits.xml new file mode 100644 index 00000000..65665766 --- /dev/null +++ b/app/src/main/res/layout/list_commits.xml @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index bba9dfb9..f2e5a20b 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -560,4 +560,6 @@ Share Issue Share Repository Create Repository + Commits + Commit Title