diff --git a/app/build.gradle b/app/build.gradle index 06c5293a..ea2ba986 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -24,7 +24,7 @@ android { dependencies { def lifecycle_version = "2.2.0-alpha05" - final def markwon_version = "4.1.1" + final def markwon_version = "4.1.1" implementation fileTree(include: ['*.jar'], dir: 'libs') implementation 'androidx.appcompat:appcompat:1.1.0' 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 53c554b5..887b0659 100644 --- a/app/src/main/java/org/mian/gitnex/activities/RepoDetailActivity.java +++ b/app/src/main/java/org/mian/gitnex/activities/RepoDetailActivity.java @@ -26,6 +26,7 @@ import org.mian.gitnex.clients.RetrofitClient; import org.mian.gitnex.fragments.BranchesFragment; import org.mian.gitnex.fragments.ClosedIssuesFragment; import org.mian.gitnex.fragments.CollaboratorsFragment; +import org.mian.gitnex.fragments.FilesFragment; import org.mian.gitnex.fragments.IssuesFragment; import org.mian.gitnex.fragments.LabelsFragment; import org.mian.gitnex.fragments.MilestonesFragment; @@ -200,7 +201,7 @@ public class RepoDetailActivity extends AppCompatActivity implements RepoBottomS case 0: // information return RepoInfoFragment.newInstance(repoOwner, repoName); case 1: // files - //return RepoInfoFragment.newInstance(repoOwner, repoName); + return FilesFragment.newInstance(repoOwner, repoName); case 2: // issues fragment = new IssuesFragment(); break; diff --git a/app/src/main/java/org/mian/gitnex/adapters/FilesAdapter.java b/app/src/main/java/org/mian/gitnex/adapters/FilesAdapter.java new file mode 100644 index 00000000..16814cfc --- /dev/null +++ b/app/src/main/java/org/mian/gitnex/adapters/FilesAdapter.java @@ -0,0 +1,190 @@ +package org.mian.gitnex.adapters; + +import android.content.Context; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.Filter; +import android.widget.Filterable; +import android.widget.ImageView; +import android.widget.TextView; +import androidx.annotation.NonNull; +import androidx.recyclerview.widget.RecyclerView; +import org.mian.gitnex.R; +import org.mian.gitnex.models.Files; +import java.util.ArrayList; +import java.util.List; + +/** + * Author M M Arif + */ + +public class FilesAdapter extends RecyclerView.Adapter implements Filterable { + + private List filesList; + private Context mCtx; + private List filesListFull; + + static class FilesViewHolder extends RecyclerView.ViewHolder { + + private ImageView fileTypeImage; + private TextView fileName; + + private FilesViewHolder(View itemView) { + + super(itemView); + fileName = itemView.findViewById(R.id.fileName); + fileTypeImage = itemView.findViewById(R.id.fileImage); + + /*ImageView filesDropdownMenu = itemView.findViewById(R.id.filesDropdownMenu); + + itemView.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + + Context context = v.getContext(); + context.startActivity(intent); + + } + }); + + filesDropdownMenu.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + + final Context context = v.getContext(); + Context context_ = new ContextThemeWrapper(context, R.style.popupMenuStyle); + + PopupMenu popupMenu = new PopupMenu(context_, v); + popupMenu.inflate(R.menu.files_dotted_list_menu); + + Object menuHelper; + Class[] argTypes; + try { + + Field fMenuHelper = PopupMenu.class.getDeclaredField("mPopup"); + fMenuHelper.setAccessible(true); + menuHelper = fMenuHelper.get(popupMenu); + argTypes = new Class[] { boolean.class }; + menuHelper.getClass().getDeclaredMethod("setForceShowIcon", + argTypes).invoke(menuHelper, true); + + } catch (Exception e) { + + popupMenu.show(); + return; + + } + + popupMenu.setOnMenuItemClickListener(new PopupMenu.OnMenuItemClickListener() { + @Override + public boolean onMenuItemClick(MenuItem item) { + switch (item.getItemId()) { + case R.id.deleteFile: + + Intent intent = new Intent(context, DeleteFileActivity.class); + intent.putExtra("repoFullNameForDeleteFile", fullName.getText()); + context.startActivity(intent); + break; + + case R.id.editFile: + + Intent intentW = new Intent(context, EditFileActivity.class); + intentW.putExtra("repoFullNameForEditFile", fullName.getText()); + context.startActivity(intentW); + break; + + case R.id.openInBrowser: + + Intent intentOpenInBrowser = new Intent(context, OpenFileInBrowserActivity.class); + intentOpenInBrowser.putExtra("fileFullNameBrowser", fullName.getText()); + context.startActivity(intentOpenInBrowser); + break; + + } + return false; + } + }); + + popupMenu.show(); + + } + });*/ + + } + } + + public FilesAdapter(Context mCtx, List filesListMain) { + this.mCtx = mCtx; + this.filesList = filesListMain; + filesListFull = new ArrayList<>(filesList); + } + + @NonNull + @Override + public FilesAdapter.FilesViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { + View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.files_list, parent, false); + return new FilesAdapter.FilesViewHolder(v); + } + + @Override + public void onBindViewHolder(@NonNull FilesAdapter.FilesViewHolder holder, int position) { + + Files currentItem = filesList.get(position); + + holder.fileName.setText(currentItem.getName()); + + if(currentItem.getType().equals("file")) { + holder.fileTypeImage.setImageDrawable(mCtx.getResources().getDrawable(R.drawable.ic_file)); + } + else if(currentItem.getType().equals("dir")) { + holder.fileTypeImage.setImageDrawable(mCtx.getResources().getDrawable(R.drawable.ic_folder_24)); + } + else { + + } + + } + + @Override + public int getItemCount() { + return filesList.size(); + } + + @Override + public Filter getFilter() { + return filesFilter; + } + + private Filter filesFilter = new Filter() { + @Override + protected FilterResults performFiltering(CharSequence constraint) { + List filteredList = new ArrayList<>(); + + if (constraint == null || constraint.length() == 0) { + filteredList.addAll(filesListFull); + } else { + String filterPattern = constraint.toString().toLowerCase().trim(); + + for (Files item : filesListFull) { + if (item.getName().toLowerCase().contains(filterPattern) || item.getPath().toLowerCase().contains(filterPattern)) { + filteredList.add(item); + } + } + } + + FilterResults results = new FilterResults(); + results.values = filteredList; + + return results; + } + + @Override + protected void publishResults(CharSequence constraint, FilterResults results) { + filesList.clear(); + filesList.addAll((List) results.values); + notifyDataSetChanged(); + } + }; + +} diff --git a/app/src/main/java/org/mian/gitnex/fragments/FilesFragment.java b/app/src/main/java/org/mian/gitnex/fragments/FilesFragment.java new file mode 100644 index 00000000..137b43df --- /dev/null +++ b/app/src/main/java/org/mian/gitnex/fragments/FilesFragment.java @@ -0,0 +1,148 @@ +package org.mian.gitnex.fragments; + +import android.net.Uri; +import android.os.Bundle; +import androidx.annotation.Nullable; +import androidx.fragment.app.Fragment; +import androidx.lifecycle.Observer; +import androidx.lifecycle.ViewModelProvider; +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.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ProgressBar; +import android.widget.TextView; +import org.mian.gitnex.R; +import org.mian.gitnex.adapters.FilesAdapter; +import org.mian.gitnex.helpers.Authorization; +import org.mian.gitnex.models.Files; +import org.mian.gitnex.util.TinyDB; +import org.mian.gitnex.viewmodels.FilesViewModel; +import org.mian.gitnex.viewmodels.ReleasesViewModel; +import java.util.List; + +/** + * Author M M Arif + */ + +public class FilesFragment extends Fragment { + + private ProgressBar mProgressBar; + private FilesAdapter adapter; + private RecyclerView mRecyclerView; + private TextView noDataFiles; + private static String repoNameF = "param2"; + private static String repoOwnerF = "param1"; + + private String repoName; + private String repoOwner; + + private OnFragmentInteractionListener mListener; + + public FilesFragment() { + } + + public static FilesFragment newInstance(String param1, String param2) { + FilesFragment fragment = new FilesFragment(); + Bundle args = new Bundle(); + args.putString(repoOwnerF, param1); + args.putString(repoNameF, param2); + fragment.setArguments(args); + return fragment; + } + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + if (getArguments() != null) { + repoName = getArguments().getString(repoNameF); + repoOwner = getArguments().getString(repoOwnerF); + } + } + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, + Bundle savedInstanceState) { + + View v = inflater.inflate(R.layout.fragment_files, container, false); + + 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"); + + noDataFiles = v.findViewById(R.id.noDataFiles); + + final SwipeRefreshLayout swipeRefresh = v.findViewById(R.id.pullToRefresh); + + mRecyclerView = v.findViewById(R.id.recyclerView); + mRecyclerView.setHasFixedSize(true); + mRecyclerView.setLayoutManager(new LinearLayoutManager(getContext())); + + DividerItemDecoration dividerItemDecoration = new DividerItemDecoration(mRecyclerView.getContext(), + DividerItemDecoration.VERTICAL); + mRecyclerView.addItemDecoration(dividerItemDecoration); + + mProgressBar = v.findViewById(R.id.progress_bar); + + swipeRefresh.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() { + @Override + public void onRefresh() { + new Handler().postDelayed(new Runnable() { + @Override + public void run() { + swipeRefresh.setRefreshing(false); + ReleasesViewModel.loadReleasesList(instanceUrl, Authorization.returnAuthentication(getContext(), loginUid, instanceToken), repoOwner, repoName); + } + }, 50); + } + }); + + fetchDataAsync(instanceUrl, Authorization.returnAuthentication(getContext(), loginUid, instanceToken), repoOwner, repoName); + + return v; + } + + private void fetchDataAsync(String instanceUrl, String instanceToken, String owner, String repo) { + + FilesViewModel filesModel = new ViewModelProvider(this).get(FilesViewModel.class); + + filesModel.getFilesList(instanceUrl, instanceToken, owner, repo).observe(this, new Observer>() { + @Override + public void onChanged(@Nullable List filesListMain) { + adapter = new FilesAdapter(getContext(), filesListMain); + if(adapter.getItemCount() > 0) { + mRecyclerView.setAdapter(adapter); + noDataFiles.setVisibility(View.GONE); + } + else { + adapter.notifyDataSetChanged(); + mRecyclerView.setAdapter(adapter); + noDataFiles.setVisibility(View.VISIBLE); + } + mProgressBar.setVisibility(View.GONE); + } + }); + + } + + public void onButtonPressed(Uri uri) { + if (mListener != null) { + mListener.onFragmentInteraction(uri); + } + } + + @Override + public void onDetach() { + super.onDetach(); + mListener = null; + } + + public interface OnFragmentInteractionListener { + void onFragmentInteraction(Uri uri); + } +} 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 a59a64de..6b3a8c39 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.Files; import org.mian.gitnex.models.NewFile; import org.mian.gitnex.models.UpdateIssueAssignee; import org.mian.gitnex.models.UpdateIssueState; @@ -214,4 +215,7 @@ public interface ApiInterface { @POST("repos/{owner}/{repo}/contents/{file}") // create new file Call createNewFile(@Header("Authorization") String token, @Path("owner") String ownerName, @Path("repo") String repoName, @Path("file") String fileName, @Body NewFile jsonStr); + + @GET("repos/{owner}/{repo}/contents") // get all files and dirs + Call> getFiles(@Header("Authorization") String token, @Path("owner") String ownerName, @Path("repo") String repoName); } \ No newline at end of file diff --git a/app/src/main/java/org/mian/gitnex/viewmodels/FilesViewModel.java b/app/src/main/java/org/mian/gitnex/viewmodels/FilesViewModel.java new file mode 100644 index 00000000..5d209a6a --- /dev/null +++ b/app/src/main/java/org/mian/gitnex/viewmodels/FilesViewModel.java @@ -0,0 +1,59 @@ +package org.mian.gitnex.viewmodels; + +import android.util.Log; +import androidx.annotation.NonNull; +import androidx.lifecycle.LiveData; +import androidx.lifecycle.MutableLiveData; +import androidx.lifecycle.ViewModel; +import org.mian.gitnex.clients.RetrofitClient; +import org.mian.gitnex.models.Files; +import java.util.List; +import retrofit2.Call; +import retrofit2.Callback; +import retrofit2.Response; + +/** + * Author M M Arif + */ + +public class FilesViewModel extends ViewModel { + + private static MutableLiveData> filesList; + + public LiveData> getFilesList(String instanceUrl, String token, String owner, String repo) { + + filesList = new MutableLiveData<>(); + loadFilesList(instanceUrl, token, owner, repo); + + return filesList; + } + + public static void loadFilesList(String instanceUrl, String token, String owner, String repo) { + + Call> call = RetrofitClient + .getInstance(instanceUrl) + .getApiInterface() + .getFiles(token, owner, repo); + + call.enqueue(new Callback>() { + + @Override + public void onResponse(@NonNull Call> call, @NonNull Response> response) { + + if (response.isSuccessful()) { + filesList.postValue(response.body()); + } else { + Log.i("onResponse", String.valueOf(response.code())); + } + + } + + @Override + public void onFailure(@NonNull Call> call, Throwable t) { + Log.i("onFailure", t.toString()); + } + + }); + } + +} diff --git a/app/src/main/res/layout/files_list.xml b/app/src/main/res/layout/files_list.xml new file mode 100644 index 00000000..bebd9514 --- /dev/null +++ b/app/src/main/res/layout/files_list.xml @@ -0,0 +1,55 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_files.xml b/app/src/main/res/layout/fragment_files.xml new file mode 100644 index 00000000..9f0d3d45 --- /dev/null +++ b/app/src/main/res/layout/fragment_files.xml @@ -0,0 +1,45 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 6416f4d7..c3077bde 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -470,6 +470,8 @@ %1$d\uFF05 completed + No files found + OK Done