Fixing bug where path separators would be URL encoded. (#832)

Merge branch 'master' of https://codeberg.org/gitnex/GitNex into fix-path-encoding-issues

Fixing bug where path separators would be URL encoded.

Reviewed-on: https://codeberg.org/gitnex/GitNex/pulls/832
Reviewed-by: M M Arif <mmarif@noreply.codeberg.org>
Co-Authored-By: opyale <opyale@noreply.codeberg.org>
Co-Committed-By: opyale <opyale@noreply.codeberg.org>
This commit is contained in:
opyale 2021-02-17 14:51:20 +01:00 committed by M M Arif
parent d8a14105c9
commit 57ce453661
5 changed files with 242 additions and 98 deletions

View File

@ -110,7 +110,7 @@ dependencies {
implementation "com.eightbitlab:blurview:1.6.4"
implementation "io.mikael:urlbuilder:2.0.9"
implementation "org.codeberg.gitnex-garage:emoji-java:v5.1.2"
implementation "org.codeberg.gitnex:tea4j:1.0.0"
implementation "org.codeberg.gitnex:tea4j:1.0.1"
coreLibraryDesugaring "com.android.tools:desugar_jdk_libs:1.1.1"
}

View File

@ -23,25 +23,26 @@ import java.util.List;
public class FilesAdapter extends RecyclerView.Adapter<FilesAdapter.FilesViewHolder> implements Filterable {
private List<Files> filesList;
private Context mCtx;
private List<Files> filesListFull;
private final List<Files> originalFiles = new ArrayList<>();
private final List<Files> alteredFiles = new ArrayList<>();
private FilesAdapterListener filesListener;
private Context mCtx;
private final FilesAdapterListener filesListener;
public interface FilesAdapterListener {
void onClickDir(String str);
void onClickFile(String str);
}
class FilesViewHolder extends RecyclerView.ViewHolder {
class FilesViewHolder extends RecyclerView.ViewHolder {
private ImageView fileTypeImage;
private ImageView dirTypeImage;
private ImageView unknownTypeImage;
private TextView fileName;
private TextView fileType;
private TextView fileInfo;
private final ImageView fileTypeImage;
private final ImageView dirTypeImage;
private final ImageView unknownTypeImage;
private final TextView fileName;
private final TextView fileType;
private final TextView fileInfo;
private FilesViewHolder(View itemView) {
@ -138,11 +139,24 @@ public class FilesAdapter extends RecyclerView.Adapter<FilesAdapter.FilesViewHol
}
}
public FilesAdapter(Context mCtx, List<Files> filesListMain, FilesAdapterListener filesListener) {
public FilesAdapter(Context mCtx, FilesAdapterListener filesListener) {
this.mCtx = mCtx;
this.filesList = filesListMain;
filesListFull = new ArrayList<>(filesList);
this.filesListener = filesListener;
}
public List<Files> getOriginalFiles() {
return originalFiles;
}
public void notifyOriginalDataSetChanged() {
alteredFiles.clear();
alteredFiles.addAll(originalFiles);
notifyDataSetChanged();
}
@NonNull
@ -155,7 +169,7 @@ public class FilesAdapter extends RecyclerView.Adapter<FilesAdapter.FilesViewHol
@Override
public void onBindViewHolder(@NonNull FilesAdapter.FilesViewHolder holder, int position) {
Files currentItem = filesList.get(position);
Files currentItem = alteredFiles.get(position);
holder.fileType.setText(currentItem.getType());
holder.fileName.setText(currentItem.getName());
@ -183,7 +197,7 @@ public class FilesAdapter extends RecyclerView.Adapter<FilesAdapter.FilesViewHol
@Override
public int getItemCount() {
return filesList.size();
return alteredFiles.size();
}
@Override
@ -191,17 +205,19 @@ public class FilesAdapter extends RecyclerView.Adapter<FilesAdapter.FilesViewHol
return filesFilter;
}
private Filter filesFilter = new Filter() {
private final Filter filesFilter = new Filter() {
@Override
protected FilterResults performFiltering(CharSequence constraint) {
List<Files> filteredList = new ArrayList<>();
if (constraint == null || constraint.length() == 0) {
filteredList.addAll(filesListFull);
filteredList.addAll(originalFiles);
} else {
String filterPattern = constraint.toString().toLowerCase().trim();
for (Files item : filesListFull) {
for (Files item : originalFiles) {
if (item.getName().toLowerCase().contains(filterPattern) || item.getPath().toLowerCase().contains(filterPattern)) {
filteredList.add(item);
}
@ -216,10 +232,14 @@ public class FilesAdapter extends RecyclerView.Adapter<FilesAdapter.FilesViewHol
@Override
protected void publishResults(CharSequence constraint, FilterResults results) {
filesList.clear();
filesList.addAll((List) results.values);
alteredFiles.clear();
alteredFiles.addAll((List) results.values);
notifyDataSetChanged();
}
};
}

View File

@ -27,6 +27,7 @@ import org.mian.gitnex.adapters.FilesAdapter;
import org.mian.gitnex.databinding.FragmentFilesBinding;
import org.mian.gitnex.helpers.AppUtil;
import org.mian.gitnex.helpers.Authorization;
import org.mian.gitnex.helpers.Path;
import org.mian.gitnex.viewmodels.FilesViewModel;
import java.util.ArrayList;
import java.util.Collections;
@ -41,34 +42,38 @@ import moe.feng.common.view.breadcrumbs.model.BreadcrumbItem;
public class FilesFragment extends Fragment implements FilesAdapter.FilesAdapterListener {
private ProgressBar mProgressBar;
private FilesAdapter adapter;
private RecyclerView mRecyclerView;
private TextView noDataFiles;
private LinearLayout filesFrame;
private TextView fileStructure;
private static String repoNameF = "param2";
private static String repoOwnerF = "param1";
private static String repoRefF = "param3";
private static final String repoNameF = "param2";
private static final String repoOwnerF = "param1";
private static final String repoRefF = "param3";
private BreadcrumbsView mBreadcrumbsView;
private String repoName;
private String repoOwner;
private String ref;
private final Path path = new Path();
private FilesAdapter filesAdapter;
private OnFragmentInteractionListener mListener;
public FilesFragment() {
}
public FilesFragment() {}
public static FilesFragment newInstance(String param1, String param2, String param3) {
FilesFragment fragment = new FilesFragment();
Bundle args = new Bundle();
args.putString(repoOwnerF, param1);
args.putString(repoNameF, param2);
args.putString(repoRefF, param3);
fragment.setArguments(args);
return fragment;
}
@ -76,6 +81,7 @@ public class FilesFragment extends Fragment implements FilesAdapter.FilesAdapter
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if(getArguments() != null) {
repoName = getArguments().getString(repoNameF);
repoOwner = getArguments().getString(repoOwnerF);
@ -84,7 +90,7 @@ public class FilesFragment extends Fragment implements FilesAdapter.FilesAdapter
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
FragmentFilesBinding fragmentFilesBinding = FragmentFilesBinding.inflate(inflater, container, false);
setHasOptionsMenu(true);
@ -92,10 +98,12 @@ public class FilesFragment extends Fragment implements FilesAdapter.FilesAdapter
noDataFiles = fragmentFilesBinding.noDataFiles;
filesFrame = fragmentFilesBinding.filesFrame;
fileStructure = fragmentFilesBinding.fileStructure;
filesAdapter = new FilesAdapter(getContext(), this);
mRecyclerView = fragmentFilesBinding.recyclerView;
mRecyclerView.setHasFixedSize(true);
mRecyclerView.setLayoutManager(new LinearLayoutManager(getContext()));
mRecyclerView.setAdapter(filesAdapter);
DividerItemDecoration dividerItemDecoration = new DividerItemDecoration(mRecyclerView.getContext(), DividerItemDecoration.VERTICAL);
mRecyclerView.addItemDecoration(dividerItemDecoration);
@ -104,10 +112,33 @@ public class FilesFragment extends Fragment implements FilesAdapter.FilesAdapter
mBreadcrumbsView = fragmentFilesBinding.breadcrumbsView;
mBreadcrumbsView.setItems(new ArrayList<>(Collections.singletonList(BreadcrumbItem.createSimpleItem(getResources().getString(R.string.filesBreadcrumbRoot) + getResources().getString(R.string.colonDivider) + ref))));
// noinspection unchecked
mBreadcrumbsView.setCallback(new DefaultBreadcrumbsCallback<BreadcrumbItem>() {
@SuppressLint("SetTextI18n")
@Override
public void onNavigateBack(BreadcrumbItem item, int position) {
if(position == 0) {
path.clear();
fetchDataAsync(Authorization.get(getContext()), repoOwner, repoName, ref);
return;
}
path.pop(path.size() - position);
fetchDataAsyncSub(Authorization.get(getContext()), repoOwner, repoName, path.toString(), ref);
}
@Override public void onNavigateNewLocation(BreadcrumbItem newItem, int changedPosition) {}
});
((RepoDetailActivity) requireActivity()).setFragmentRefreshListenerFiles(repoBranch -> {
fileStructure.setText("");
path.clear();
ref = repoBranch;
mBreadcrumbsView.setItems(new ArrayList<>(Collections.singletonList(BreadcrumbItem.createSimpleItem(getResources().getString(R.string.filesBreadcrumbRoot) + getResources().getString(R.string.colonDivider) + ref))));
fetchDataAsync(Authorization.get(getContext()), repoOwner, repoName, repoBranch);
@ -128,48 +159,10 @@ public class FilesFragment extends Fragment implements FilesAdapter.FilesAdapter
@Override
public void onClickDir(String dirName) {
StringBuilder breadcrumbBuilder = new StringBuilder();
breadcrumbBuilder.append(fileStructure.getText().toString()).append("/").append(dirName);
fileStructure.setText(breadcrumbBuilder);
String dirName_ = fileStructure.getText().toString();
dirName_ = dirName_.startsWith("/") ? dirName_.substring(1) : dirName_;
final String finalDirName_ = dirName_;
path.add(dirName);
mBreadcrumbsView.addItem(new BreadcrumbItem(Collections.singletonList(dirName)));
//noinspection unchecked
mBreadcrumbsView.setCallback(new DefaultBreadcrumbsCallback<BreadcrumbItem>() {
@SuppressLint("SetTextI18n")
@Override
public void onNavigateBack(BreadcrumbItem item, int position) {
if(position == 0) {
fetchDataAsync(Authorization.get(getContext()), repoOwner, repoName, ref);
fileStructure.setText("");
return;
}
String filterDir = fileStructure.getText().toString();
String result = filterDir.substring(0, filterDir.indexOf(item.getSelectedItem()));
fileStructure.setText(result + item.getSelectedItem());
String currentIndex = (result + item.getSelectedItem()).substring(1);
fetchDataAsyncSub(Authorization.get(getContext()), repoOwner, repoName, currentIndex, ref);
}
@Override
public void onNavigateNewLocation(BreadcrumbItem newItem, int changedPosition) {
}
});
fetchDataAsyncSub(Authorization.get(getContext()), repoOwner, repoName, finalDirName_, ref);
fetchDataAsyncSub(Authorization.get(getContext()), repoOwner, repoName, path.toString(), ref);
}
@ -178,9 +171,9 @@ public class FilesFragment extends Fragment implements FilesAdapter.FilesAdapter
Intent intent = new Intent(getContext(), FileViewActivity.class);
if(!fileStructure.getText().toString().equals("Root")) {
if(path.size() != 0) {
intent.putExtra("singleFileName", fileStructure.getText().toString() + "/" + fileName);
intent.putExtra("singleFileName", path.toString() + "/" + fileName);
}
else {
@ -199,24 +192,23 @@ public class FilesFragment extends Fragment implements FilesAdapter.FilesAdapter
filesModel.getFilesList(instanceToken, owner, repo, ref, getContext(), mProgressBar, noDataFiles).observe(getViewLifecycleOwner(), filesListMain -> {
adapter = new FilesAdapter(getContext(), filesListMain, FilesFragment.this);
mBreadcrumbsView.removeItemAfter(1);
filesAdapter.getOriginalFiles().clear();
filesAdapter.getOriginalFiles().addAll(filesListMain);
filesAdapter.notifyOriginalDataSetChanged();
if(adapter.getItemCount() > 0) {
if(filesListMain.size() > 0) {
mRecyclerView.setAdapter(adapter);
AppUtil.setMultiVisibility(View.VISIBLE, mRecyclerView, filesFrame);
noDataFiles.setVisibility(View.GONE);
}
else {
adapter.notifyDataSetChanged();
mRecyclerView.setAdapter(adapter);
AppUtil.setMultiVisibility(View.VISIBLE, mRecyclerView, filesFrame, noDataFiles);
}
filesFrame.setVisibility(View.VISIBLE);
mProgressBar.setVisibility(View.GONE);
});
}
@ -230,16 +222,16 @@ public class FilesFragment extends Fragment implements FilesAdapter.FilesAdapter
filesModel2.getFilesList2(instanceToken, owner, repo, filesDir, ref, getContext(), mProgressBar, noDataFiles).observe(this, filesListMain2 -> {
adapter = new FilesAdapter(getContext(), filesListMain2, FilesFragment.this);
filesAdapter.getOriginalFiles().clear();
filesAdapter.getOriginalFiles().addAll(filesListMain2);
filesAdapter.notifyOriginalDataSetChanged();
if(filesListMain2.size() > 0) {
if(adapter.getItemCount() > 0) {
mRecyclerView.setAdapter(adapter);
AppUtil.setMultiVisibility(View.VISIBLE, mRecyclerView, filesFrame);
noDataFiles.setVisibility(View.GONE);
}
else {
adapter.notifyDataSetChanged();
mRecyclerView.setAdapter(adapter);
AppUtil.setMultiVisibility(View.VISIBLE, mRecyclerView, filesFrame, noDataFiles);
}
@ -268,7 +260,7 @@ public class FilesFragment extends Fragment implements FilesAdapter.FilesAdapter
public boolean onQueryTextChange(String newText) {
if(mRecyclerView.getAdapter() != null) {
adapter.getFilter().filter(newText);
filesAdapter.getFilter().filter(newText);
}
return false;

View File

@ -0,0 +1,137 @@
package org.mian.gitnex.helpers;
import androidx.annotation.NonNull;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.StringJoiner;
/**
* @author opyale
*/
public class Path {
private final List<String> segments;
private final List<OnChangedListener> onChangedListeners;
public Path(String... segments) {
this.segments = new ArrayList<>(Arrays.asList(segments));
this.onChangedListeners = new ArrayList<>();
}
public interface OnChangedListener { void onChanged(); }
public Path addListener(OnChangedListener onChangedListener) {
onChangedListeners.add(onChangedListener);
return this;
}
public Path removeListener(OnChangedListener onChangedListener) {
onChangedListeners.remove(onChangedListener);
return this;
}
private void pathChanged() {
for(OnChangedListener onChangedListener : onChangedListeners) {
onChangedListener.onChanged();
}
}
public Path add(String segment) {
if(segment != null && !segment.trim().isEmpty()) {
try {
segments.add(URLEncoder.encode(segment, "UTF-8"));
} catch(UnsupportedEncodingException ignored) {}
}
pathChanged();
return this;
}
public int size() {
return segments.size();
}
public Path join(Path path) {
this.segments.addAll(path.segments);
pathChanged();
return this;
}
public Path pop(int count) {
for(int i=0; i<count; i++)
segments.remove(segments.size() - 1);
pathChanged();
return this;
}
public Path remove(int index) {
segments.remove(index);
pathChanged();
return this;
}
public Path clear() {
segments.clear();
pathChanged();
return this;
}
public String[] segments() {
return segments.toArray(new String[]{});
}
public static Path of(String path) {
String[] parsed_segments = path.split("/");
return new Path(
Arrays
.stream(parsed_segments)
.filter(s -> !s.trim().isEmpty())
.toArray(String[]::new)
);
}
@NonNull
@Override
public String toString() {
StringJoiner stringJoiner = new StringJoiner("/");
for(String segment : segments)
stringJoiner.add(segment);
return stringJoiner.toString();
}
}

View File

@ -9,25 +9,20 @@
<LinearLayout
android:id="@+id/filesFrame"
android:visibility="gone"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<TextView
android:id="@+id/fileStructure"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:visibility="gone" />
android:orientation="vertical"
android:visibility="gone"
tools:visibility="visible">
<moe.feng.common.view.breadcrumbs.BreadcrumbsView
android:id="@+id/breadcrumbs_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/filesBreadcrumbRoot"
app:CustomTextSize="16sp"
app:SelectedTextColor="?attr/primaryTextColor"
app:UnSelectedTextColor="@color/lightGray"
android:text="@string/filesBreadcrumbRoot" />
app:UnSelectedTextColor="@color/lightGray" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recyclerView"