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 "com.eightbitlab:blurview:1.6.4"
implementation "io.mikael:urlbuilder:2.0.9" implementation "io.mikael:urlbuilder:2.0.9"
implementation "org.codeberg.gitnex-garage:emoji-java:v5.1.2" implementation "org.codeberg.gitnex-garage:emoji-java:v5.1.2"
implementation "org.codeberg.gitnex:tea4j:1.0.0" implementation "org.codeberg.gitnex:tea4j:1.0.1"
coreLibraryDesugaring "com.android.tools:desugar_jdk_libs:1.1.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 { public class FilesAdapter extends RecyclerView.Adapter<FilesAdapter.FilesViewHolder> implements Filterable {
private List<Files> filesList; private final List<Files> originalFiles = new ArrayList<>();
private Context mCtx; private final List<Files> alteredFiles = new ArrayList<>();
private List<Files> filesListFull;
private FilesAdapterListener filesListener; private Context mCtx;
private final FilesAdapterListener filesListener;
public interface FilesAdapterListener { public interface FilesAdapterListener {
void onClickDir(String str); void onClickDir(String str);
void onClickFile(String str); void onClickFile(String str);
} }
class FilesViewHolder extends RecyclerView.ViewHolder { class FilesViewHolder extends RecyclerView.ViewHolder {
private ImageView fileTypeImage; private final ImageView fileTypeImage;
private ImageView dirTypeImage; private final ImageView dirTypeImage;
private ImageView unknownTypeImage; private final ImageView unknownTypeImage;
private TextView fileName; private final TextView fileName;
private TextView fileType; private final TextView fileType;
private TextView fileInfo; private final TextView fileInfo;
private FilesViewHolder(View itemView) { 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.mCtx = mCtx;
this.filesList = filesListMain;
filesListFull = new ArrayList<>(filesList);
this.filesListener = filesListener; this.filesListener = filesListener;
}
public List<Files> getOriginalFiles() {
return originalFiles;
}
public void notifyOriginalDataSetChanged() {
alteredFiles.clear();
alteredFiles.addAll(originalFiles);
notifyDataSetChanged();
} }
@NonNull @NonNull
@ -155,7 +169,7 @@ public class FilesAdapter extends RecyclerView.Adapter<FilesAdapter.FilesViewHol
@Override @Override
public void onBindViewHolder(@NonNull FilesAdapter.FilesViewHolder holder, int position) { 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.fileType.setText(currentItem.getType());
holder.fileName.setText(currentItem.getName()); holder.fileName.setText(currentItem.getName());
@ -183,7 +197,7 @@ public class FilesAdapter extends RecyclerView.Adapter<FilesAdapter.FilesViewHol
@Override @Override
public int getItemCount() { public int getItemCount() {
return filesList.size(); return alteredFiles.size();
} }
@Override @Override
@ -191,17 +205,19 @@ public class FilesAdapter extends RecyclerView.Adapter<FilesAdapter.FilesViewHol
return filesFilter; return filesFilter;
} }
private Filter filesFilter = new Filter() { private final Filter filesFilter = new Filter() {
@Override @Override
protected FilterResults performFiltering(CharSequence constraint) { protected FilterResults performFiltering(CharSequence constraint) {
List<Files> filteredList = new ArrayList<>(); List<Files> filteredList = new ArrayList<>();
if (constraint == null || constraint.length() == 0) { if (constraint == null || constraint.length() == 0) {
filteredList.addAll(filesListFull); filteredList.addAll(originalFiles);
} else { } else {
String filterPattern = constraint.toString().toLowerCase().trim(); 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)) { if (item.getName().toLowerCase().contains(filterPattern) || item.getPath().toLowerCase().contains(filterPattern)) {
filteredList.add(item); filteredList.add(item);
} }
@ -216,10 +232,14 @@ public class FilesAdapter extends RecyclerView.Adapter<FilesAdapter.FilesViewHol
@Override @Override
protected void publishResults(CharSequence constraint, FilterResults results) { protected void publishResults(CharSequence constraint, FilterResults results) {
filesList.clear();
filesList.addAll((List) results.values); alteredFiles.clear();
alteredFiles.addAll((List) results.values);
notifyDataSetChanged(); notifyDataSetChanged();
} }
}; };
} }

View File

@ -27,6 +27,7 @@ import org.mian.gitnex.adapters.FilesAdapter;
import org.mian.gitnex.databinding.FragmentFilesBinding; import org.mian.gitnex.databinding.FragmentFilesBinding;
import org.mian.gitnex.helpers.AppUtil; import org.mian.gitnex.helpers.AppUtil;
import org.mian.gitnex.helpers.Authorization; import org.mian.gitnex.helpers.Authorization;
import org.mian.gitnex.helpers.Path;
import org.mian.gitnex.viewmodels.FilesViewModel; import org.mian.gitnex.viewmodels.FilesViewModel;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
@ -41,34 +42,38 @@ import moe.feng.common.view.breadcrumbs.model.BreadcrumbItem;
public class FilesFragment extends Fragment implements FilesAdapter.FilesAdapterListener { public class FilesFragment extends Fragment implements FilesAdapter.FilesAdapterListener {
private ProgressBar mProgressBar; private ProgressBar mProgressBar;
private FilesAdapter adapter;
private RecyclerView mRecyclerView; private RecyclerView mRecyclerView;
private TextView noDataFiles; private TextView noDataFiles;
private LinearLayout filesFrame; private LinearLayout filesFrame;
private TextView fileStructure;
private static String repoNameF = "param2"; private static final String repoNameF = "param2";
private static String repoOwnerF = "param1"; private static final String repoOwnerF = "param1";
private static String repoRefF = "param3"; private static final String repoRefF = "param3";
private BreadcrumbsView mBreadcrumbsView; private BreadcrumbsView mBreadcrumbsView;
private String repoName; private String repoName;
private String repoOwner; private String repoOwner;
private String ref; private String ref;
private final Path path = new Path();
private FilesAdapter filesAdapter;
private OnFragmentInteractionListener mListener; private OnFragmentInteractionListener mListener;
public FilesFragment() { public FilesFragment() {}
}
public static FilesFragment newInstance(String param1, String param2, String param3) { public static FilesFragment newInstance(String param1, String param2, String param3) {
FilesFragment fragment = new FilesFragment(); FilesFragment fragment = new FilesFragment();
Bundle args = new Bundle(); Bundle args = new Bundle();
args.putString(repoOwnerF, param1); args.putString(repoOwnerF, param1);
args.putString(repoNameF, param2); args.putString(repoNameF, param2);
args.putString(repoRefF, param3); args.putString(repoRefF, param3);
fragment.setArguments(args); fragment.setArguments(args);
return fragment; return fragment;
} }
@ -76,6 +81,7 @@ public class FilesFragment extends Fragment implements FilesAdapter.FilesAdapter
public void onCreate(Bundle savedInstanceState) { public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
if(getArguments() != null) { if(getArguments() != null) {
repoName = getArguments().getString(repoNameF); repoName = getArguments().getString(repoNameF);
repoOwner = getArguments().getString(repoOwnerF); repoOwner = getArguments().getString(repoOwnerF);
@ -84,7 +90,7 @@ public class FilesFragment extends Fragment implements FilesAdapter.FilesAdapter
} }
@Override @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); FragmentFilesBinding fragmentFilesBinding = FragmentFilesBinding.inflate(inflater, container, false);
setHasOptionsMenu(true); setHasOptionsMenu(true);
@ -92,10 +98,12 @@ public class FilesFragment extends Fragment implements FilesAdapter.FilesAdapter
noDataFiles = fragmentFilesBinding.noDataFiles; noDataFiles = fragmentFilesBinding.noDataFiles;
filesFrame = fragmentFilesBinding.filesFrame; filesFrame = fragmentFilesBinding.filesFrame;
fileStructure = fragmentFilesBinding.fileStructure; filesAdapter = new FilesAdapter(getContext(), this);
mRecyclerView = fragmentFilesBinding.recyclerView; mRecyclerView = fragmentFilesBinding.recyclerView;
mRecyclerView.setHasFixedSize(true); mRecyclerView.setHasFixedSize(true);
mRecyclerView.setLayoutManager(new LinearLayoutManager(getContext())); mRecyclerView.setLayoutManager(new LinearLayoutManager(getContext()));
mRecyclerView.setAdapter(filesAdapter);
DividerItemDecoration dividerItemDecoration = new DividerItemDecoration(mRecyclerView.getContext(), DividerItemDecoration.VERTICAL); DividerItemDecoration dividerItemDecoration = new DividerItemDecoration(mRecyclerView.getContext(), DividerItemDecoration.VERTICAL);
mRecyclerView.addItemDecoration(dividerItemDecoration); mRecyclerView.addItemDecoration(dividerItemDecoration);
@ -104,10 +112,33 @@ public class FilesFragment extends Fragment implements FilesAdapter.FilesAdapter
mBreadcrumbsView = fragmentFilesBinding.breadcrumbsView; mBreadcrumbsView = fragmentFilesBinding.breadcrumbsView;
mBreadcrumbsView.setItems(new ArrayList<>(Collections.singletonList(BreadcrumbItem.createSimpleItem(getResources().getString(R.string.filesBreadcrumbRoot) + getResources().getString(R.string.colonDivider) + ref)))); 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 -> { ((RepoDetailActivity) requireActivity()).setFragmentRefreshListenerFiles(repoBranch -> {
fileStructure.setText(""); path.clear();
ref = repoBranch; ref = repoBranch;
mBreadcrumbsView.setItems(new ArrayList<>(Collections.singletonList(BreadcrumbItem.createSimpleItem(getResources().getString(R.string.filesBreadcrumbRoot) + getResources().getString(R.string.colonDivider) + ref)))); 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); fetchDataAsync(Authorization.get(getContext()), repoOwner, repoName, repoBranch);
@ -128,48 +159,10 @@ public class FilesFragment extends Fragment implements FilesAdapter.FilesAdapter
@Override @Override
public void onClickDir(String dirName) { public void onClickDir(String dirName) {
StringBuilder breadcrumbBuilder = new StringBuilder(); path.add(dirName);
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_;
mBreadcrumbsView.addItem(new BreadcrumbItem(Collections.singletonList(dirName))); mBreadcrumbsView.addItem(new BreadcrumbItem(Collections.singletonList(dirName)));
//noinspection unchecked
mBreadcrumbsView.setCallback(new DefaultBreadcrumbsCallback<BreadcrumbItem>() {
@SuppressLint("SetTextI18n") fetchDataAsyncSub(Authorization.get(getContext()), repoOwner, repoName, path.toString(), ref);
@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);
} }
@ -178,9 +171,9 @@ public class FilesFragment extends Fragment implements FilesAdapter.FilesAdapter
Intent intent = new Intent(getContext(), FileViewActivity.class); 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 { 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 -> { filesModel.getFilesList(instanceToken, owner, repo, ref, getContext(), mProgressBar, noDataFiles).observe(getViewLifecycleOwner(), filesListMain -> {
adapter = new FilesAdapter(getContext(), filesListMain, FilesFragment.this); filesAdapter.getOriginalFiles().clear();
mBreadcrumbsView.removeItemAfter(1); filesAdapter.getOriginalFiles().addAll(filesListMain);
filesAdapter.notifyOriginalDataSetChanged();
if(adapter.getItemCount() > 0) { if(filesListMain.size() > 0) {
mRecyclerView.setAdapter(adapter);
AppUtil.setMultiVisibility(View.VISIBLE, mRecyclerView, filesFrame); AppUtil.setMultiVisibility(View.VISIBLE, mRecyclerView, filesFrame);
noDataFiles.setVisibility(View.GONE); noDataFiles.setVisibility(View.GONE);
} }
else { else {
adapter.notifyDataSetChanged();
mRecyclerView.setAdapter(adapter);
AppUtil.setMultiVisibility(View.VISIBLE, mRecyclerView, filesFrame, noDataFiles); AppUtil.setMultiVisibility(View.VISIBLE, mRecyclerView, filesFrame, noDataFiles);
} }
filesFrame.setVisibility(View.VISIBLE); filesFrame.setVisibility(View.VISIBLE);
mProgressBar.setVisibility(View.GONE); 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 -> { 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); AppUtil.setMultiVisibility(View.VISIBLE, mRecyclerView, filesFrame);
noDataFiles.setVisibility(View.GONE); noDataFiles.setVisibility(View.GONE);
} }
else { else {
adapter.notifyDataSetChanged();
mRecyclerView.setAdapter(adapter);
AppUtil.setMultiVisibility(View.VISIBLE, mRecyclerView, filesFrame, noDataFiles); AppUtil.setMultiVisibility(View.VISIBLE, mRecyclerView, filesFrame, noDataFiles);
} }
@ -268,7 +260,7 @@ public class FilesFragment extends Fragment implements FilesAdapter.FilesAdapter
public boolean onQueryTextChange(String newText) { public boolean onQueryTextChange(String newText) {
if(mRecyclerView.getAdapter() != null) { if(mRecyclerView.getAdapter() != null) {
adapter.getFilter().filter(newText); filesAdapter.getFilter().filter(newText);
} }
return false; 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 <LinearLayout
android:id="@+id/filesFrame" android:id="@+id/filesFrame"
android:visibility="gone"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:orientation="vertical"> android:orientation="vertical"
android:visibility="gone"
<TextView tools:visibility="visible">
android:id="@+id/fileStructure"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:visibility="gone" />
<moe.feng.common.view.breadcrumbs.BreadcrumbsView <moe.feng.common.view.breadcrumbs.BreadcrumbsView
android:id="@+id/breadcrumbs_view" android:id="@+id/breadcrumbs_view"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:text="@string/filesBreadcrumbRoot"
app:CustomTextSize="16sp" app:CustomTextSize="16sp"
app:SelectedTextColor="?attr/primaryTextColor" app:SelectedTextColor="?attr/primaryTextColor"
app:UnSelectedTextColor="@color/lightGray" app:UnSelectedTextColor="@color/lightGray" />
android:text="@string/filesBreadcrumbRoot" />
<androidx.recyclerview.widget.RecyclerView <androidx.recyclerview.widget.RecyclerView
android:id="@+id/recyclerView" android:id="@+id/recyclerView"