mirror of
https://codeberg.org/gitnex/GitNex.git
synced 2024-12-16 15:48:13 +08:00
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:
parent
d8a14105c9
commit
57ce453661
@ -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"
|
||||
|
||||
}
|
||||
|
@ -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();
|
||||
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -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;
|
||||
|
137
app/src/main/java/org/mian/gitnex/helpers/Path.java
Normal file
137
app/src/main/java/org/mian/gitnex/helpers/Path.java
Normal 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();
|
||||
|
||||
}
|
||||
|
||||
}
|
@ -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"
|
||||
|
Loading…
Reference in New Issue
Block a user