mirror of
https://github.com/gitnex-org/gitnex.git
synced 2026-03-26 08:18:03 -05:00
Search in files (#1414)
Closes #1249 Reviewed-on: https://codeberg.org/gitnex/GitNex/pulls/1414
This commit is contained in:
@@ -24,6 +24,7 @@
|
||||
<activity
|
||||
android:name=".activities.FileViewActivity"
|
||||
android:configChanges="orientation|screenSize|smallestScreenSize|density|screenLayout|keyboard|keyboardHidden|navigation"
|
||||
android:windowSoftInputMode="adjustResize"
|
||||
android:theme="@style/AppTheme.NoActionBar"/>
|
||||
<activity
|
||||
android:name=".activities.CreateFileActivity"
|
||||
|
||||
@@ -8,14 +8,18 @@ import android.graphics.Bitmap;
|
||||
import android.graphics.Typeface;
|
||||
import android.os.Bundle;
|
||||
import android.text.method.ScrollingMovementMethod;
|
||||
import android.util.TypedValue;
|
||||
import android.view.Gravity;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuInflater;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.widget.ImageButton;
|
||||
import androidx.activity.result.ActivityResultLauncher;
|
||||
import androidx.activity.result.contract.ActivityResultContracts;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.appcompat.widget.SearchView;
|
||||
import androidx.appcompat.widget.Toolbar;
|
||||
import androidx.core.app.NotificationCompat;
|
||||
import com.vdurmont.emoji.EmojiParser;
|
||||
import java.io.IOException;
|
||||
@@ -32,8 +36,10 @@ import org.mian.gitnex.fragments.BottomSheetFileViewerFragment;
|
||||
import org.mian.gitnex.helpers.AlertDialogs;
|
||||
import org.mian.gitnex.helpers.AppUtil;
|
||||
import org.mian.gitnex.helpers.Constants;
|
||||
import org.mian.gitnex.helpers.FileContentSearcher;
|
||||
import org.mian.gitnex.helpers.Images;
|
||||
import org.mian.gitnex.helpers.Markdown;
|
||||
import org.mian.gitnex.helpers.SnackBar;
|
||||
import org.mian.gitnex.helpers.Toasty;
|
||||
import org.mian.gitnex.helpers.contexts.RepositoryContext;
|
||||
import org.mian.gitnex.notifications.Notifications;
|
||||
@@ -49,6 +55,12 @@ public class FileViewActivity extends BaseActivity implements BottomSheetListene
|
||||
private ActivityFileViewBinding binding;
|
||||
private ContentsResponse file;
|
||||
private RepositoryContext repository;
|
||||
private FileContentSearcher searcher;
|
||||
private String fileContent;
|
||||
private ImageButton prevButton;
|
||||
private ImageButton nextButton;
|
||||
private boolean buttonsAdded = false;
|
||||
private String lastQuery = "";
|
||||
ActivityResultLauncher<Intent> activityResultLauncher =
|
||||
registerForActivityResult(
|
||||
new ActivityResultContracts.StartActivityForResult(),
|
||||
@@ -209,6 +221,7 @@ public class FileViewActivity extends BaseActivity implements BottomSheetListene
|
||||
binding.toolbarTitle.setMovementMethod(new ScrollingMovementMethod());
|
||||
binding.toolbarTitle.setText(file.getPath());
|
||||
|
||||
searcher = new FileContentSearcher(binding.contentScrollContainer, binding.markdown);
|
||||
getSingleFileContents(
|
||||
repository.getOwner(),
|
||||
repository.getName(),
|
||||
@@ -281,7 +294,7 @@ public class FileViewActivity extends BaseActivity implements BottomSheetListene
|
||||
}
|
||||
|
||||
processable = true;
|
||||
String text = responseBody.string();
|
||||
fileContent = responseBody.string();
|
||||
|
||||
runOnUiThread(
|
||||
() -> {
|
||||
@@ -289,16 +302,15 @@ public class FileViewActivity extends BaseActivity implements BottomSheetListene
|
||||
View.GONE);
|
||||
|
||||
binding.contents.setContent(
|
||||
text, fileExtension);
|
||||
fileContent, fileExtension);
|
||||
|
||||
if (renderMd) {
|
||||
Markdown.render(
|
||||
ctx,
|
||||
getApplicationContext(),
|
||||
EmojiParser.parseToUnicode(
|
||||
text),
|
||||
fileContent),
|
||||
binding.markdown,
|
||||
repository);
|
||||
|
||||
binding.contents.setVisibility(
|
||||
View.GONE);
|
||||
binding.markdownFrame.setVisibility(
|
||||
@@ -309,6 +321,7 @@ public class FileViewActivity extends BaseActivity implements BottomSheetListene
|
||||
binding.contents.setVisibility(
|
||||
View.VISIBLE);
|
||||
}
|
||||
invalidateOptionsMenu();
|
||||
});
|
||||
break;
|
||||
}
|
||||
@@ -395,67 +408,237 @@ public class FileViewActivity extends BaseActivity implements BottomSheetListene
|
||||
thread.start();
|
||||
}
|
||||
|
||||
private void performSearch(String query, boolean isSubmit) {
|
||||
if (fileContent != null && processable && !renderMd) {
|
||||
searcher.search(
|
||||
fileContent,
|
||||
FilenameUtils.getExtension(file.getPath()),
|
||||
query,
|
||||
binding.contents,
|
||||
binding.markdown,
|
||||
renderMd);
|
||||
int matches = searcher.getMatchCount();
|
||||
if (isSubmit || !query.equals(lastQuery)) {
|
||||
if (matches > 0) {
|
||||
SnackBar.success(
|
||||
this,
|
||||
binding.getRoot(),
|
||||
getString(R.string.search_matches_found, matches));
|
||||
} else {
|
||||
SnackBar.warning(
|
||||
this, binding.getRoot(), getString(R.string.search_no_matches));
|
||||
}
|
||||
lastQuery = query;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onCreateOptionsMenu(@NonNull Menu menu) {
|
||||
|
||||
MenuInflater inflater = getMenuInflater();
|
||||
inflater.inflate(R.menu.generic_nav_dotted_menu, menu);
|
||||
inflater.inflate(R.menu.markdown_switcher, menu);
|
||||
inflater.inflate(R.menu.search_menu, menu);
|
||||
|
||||
MenuItem markdownItem = menu.findItem(R.id.markdown);
|
||||
MenuItem searchItem = menu.findItem(R.id.action_search);
|
||||
|
||||
if (!FilenameUtils.getExtension(file.getName()).equalsIgnoreCase("md")) {
|
||||
markdownItem.setVisible(false);
|
||||
}
|
||||
searchItem.setVisible(!renderMd);
|
||||
|
||||
menu.getItem(0).setVisible(false);
|
||||
int iconsColor = getAttrColor(this, R.attr.iconsColor);
|
||||
binding.toolbar.setTitleTextColor(iconsColor);
|
||||
for (int i = 0; i < menu.size(); i++) {
|
||||
MenuItem item = menu.getItem(i);
|
||||
if (item.getIcon() != null) {
|
||||
item.getIcon().setTint(iconsColor);
|
||||
}
|
||||
}
|
||||
|
||||
SearchView searchView = (SearchView) searchItem.getActionView();
|
||||
if (searchView != null) {
|
||||
|
||||
searchView.setOnQueryTextListener(
|
||||
new SearchView.OnQueryTextListener() {
|
||||
@Override
|
||||
public boolean onQueryTextSubmit(String query) {
|
||||
performSearch(query, true);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onQueryTextChange(String newText) {
|
||||
performSearch(newText, false);
|
||||
return true;
|
||||
}
|
||||
});
|
||||
|
||||
if (prevButton == null) {
|
||||
|
||||
prevButton = new ImageButton(FileViewActivity.this);
|
||||
prevButton.setImageResource(R.drawable.ic_arrow_up);
|
||||
prevButton.setBackground(null);
|
||||
prevButton.setPadding(12, 12, 24, 12);
|
||||
prevButton.setColorFilter(iconsColor);
|
||||
prevButton.setMinimumWidth(48);
|
||||
prevButton.setMinimumHeight(48);
|
||||
prevButton.setOnClickListener(
|
||||
v -> searcher.previousMatch(binding.contents, binding.markdown, renderMd));
|
||||
}
|
||||
if (nextButton == null) {
|
||||
|
||||
nextButton = new ImageButton(FileViewActivity.this);
|
||||
nextButton.setImageResource(R.drawable.ic_arrow_down);
|
||||
nextButton.setBackground(null);
|
||||
nextButton.setPadding(12, 12, 12, 12);
|
||||
nextButton.setColorFilter(iconsColor);
|
||||
nextButton.setMinimumWidth(48);
|
||||
nextButton.setMinimumHeight(48);
|
||||
nextButton.setOnClickListener(
|
||||
v -> searcher.nextMatch(binding.contents, binding.markdown, renderMd));
|
||||
}
|
||||
|
||||
int maxWidth = (int) (getResources().getDisplayMetrics().widthPixels * 0.6f);
|
||||
searchView.setMaxWidth(maxWidth);
|
||||
|
||||
searchView.setOnSearchClickListener(
|
||||
v -> {
|
||||
if (!buttonsAdded) {
|
||||
|
||||
Toolbar.LayoutParams prevParams =
|
||||
new Toolbar.LayoutParams(
|
||||
Toolbar.LayoutParams.WRAP_CONTENT,
|
||||
Toolbar.LayoutParams.WRAP_CONTENT);
|
||||
prevParams.gravity = Gravity.END;
|
||||
prevParams.setMargins(0, 0, 4, 0);
|
||||
|
||||
Toolbar.LayoutParams nextParams =
|
||||
new Toolbar.LayoutParams(
|
||||
Toolbar.LayoutParams.WRAP_CONTENT,
|
||||
Toolbar.LayoutParams.WRAP_CONTENT);
|
||||
nextParams.gravity = Gravity.END;
|
||||
nextParams.setMargins(0, 0, 8, 0);
|
||||
|
||||
binding.toolbar.removeView(prevButton);
|
||||
binding.toolbar.removeView(nextButton);
|
||||
binding.toolbar.addView(prevButton, prevParams);
|
||||
binding.toolbar.addView(nextButton, nextParams);
|
||||
buttonsAdded = true;
|
||||
|
||||
if (FilenameUtils.getExtension(file.getName()).equalsIgnoreCase("md")) {
|
||||
markdownItem.setVisible(false);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
searchItem.setOnActionExpandListener(
|
||||
new MenuItem.OnActionExpandListener() {
|
||||
@Override
|
||||
public boolean onMenuItemActionExpand(@NonNull MenuItem item) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onMenuItemActionCollapse(@NonNull MenuItem item) {
|
||||
if (fileContent != null) {
|
||||
searcher.search(
|
||||
fileContent,
|
||||
FilenameUtils.getExtension(file.getPath()),
|
||||
"",
|
||||
binding.contents,
|
||||
binding.markdown,
|
||||
renderMd);
|
||||
}
|
||||
binding.toolbar.removeView(prevButton);
|
||||
binding.toolbar.removeView(nextButton);
|
||||
buttonsAdded = false;
|
||||
lastQuery = "";
|
||||
|
||||
if (FilenameUtils.getExtension(file.getName()).equalsIgnoreCase("md")) {
|
||||
markdownItem.setVisible(true);
|
||||
}
|
||||
searchItem.setVisible(!renderMd);
|
||||
invalidateOptionsMenu();
|
||||
return true;
|
||||
}
|
||||
});
|
||||
|
||||
searchView.setOnCloseListener(
|
||||
() -> {
|
||||
if (fileContent != null) {
|
||||
searcher.search(
|
||||
fileContent,
|
||||
FilenameUtils.getExtension(file.getPath()),
|
||||
"",
|
||||
binding.contents,
|
||||
binding.markdown,
|
||||
renderMd);
|
||||
}
|
||||
binding.toolbar.removeView(prevButton);
|
||||
binding.toolbar.removeView(nextButton);
|
||||
buttonsAdded = false;
|
||||
lastQuery = "";
|
||||
if (FilenameUtils.getExtension(file.getName()).equalsIgnoreCase("md")) {
|
||||
markdownItem.setVisible(true);
|
||||
}
|
||||
searchItem.setVisible(!renderMd);
|
||||
invalidateOptionsMenu();
|
||||
return false;
|
||||
});
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private void toggleMarkdown() {
|
||||
if (!renderMd) {
|
||||
if (binding.markdown.getAdapter() == null) {
|
||||
Markdown.render(
|
||||
ctx, EmojiParser.parseToUnicode(fileContent), binding.markdown, repository);
|
||||
}
|
||||
binding.contents.setVisibility(View.GONE);
|
||||
binding.markdownFrame.setVisibility(View.VISIBLE);
|
||||
renderMd = true;
|
||||
} else {
|
||||
binding.markdownFrame.setVisibility(View.GONE);
|
||||
binding.contents.setVisibility(View.VISIBLE);
|
||||
renderMd = false;
|
||||
if (fileContent != null) {
|
||||
binding.contents.setContent(
|
||||
fileContent, FilenameUtils.getExtension(file.getPath()));
|
||||
}
|
||||
}
|
||||
invalidateOptionsMenu();
|
||||
}
|
||||
|
||||
private int getAttrColor(Context context, int attr) {
|
||||
TypedValue outValue = new TypedValue();
|
||||
context.getTheme().resolveAttribute(attr, outValue, true);
|
||||
return outValue.data;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
|
||||
int id = item.getItemId();
|
||||
|
||||
if (id == android.R.id.home) {
|
||||
|
||||
finish();
|
||||
return true;
|
||||
|
||||
} else if (id == R.id.genericMenu) {
|
||||
|
||||
BottomSheetFileViewerFragment bottomSheet = new BottomSheetFileViewerFragment();
|
||||
Bundle opts = repository.getBundle();
|
||||
opts.putBoolean("editable", processable);
|
||||
bottomSheet.setArguments(opts);
|
||||
bottomSheet.show(getSupportFragmentManager(), "fileViewerBottomSheet");
|
||||
return true;
|
||||
|
||||
} else if (id == R.id.markdown) {
|
||||
|
||||
if (!renderMd) {
|
||||
if (binding.markdown.getAdapter() == null) {
|
||||
Markdown.render(
|
||||
ctx,
|
||||
EmojiParser.parseToUnicode(binding.contents.getContent()),
|
||||
binding.markdown,
|
||||
repository);
|
||||
}
|
||||
|
||||
binding.contents.setVisibility(View.GONE);
|
||||
binding.markdownFrame.setVisibility(View.VISIBLE);
|
||||
|
||||
renderMd = true;
|
||||
} else {
|
||||
binding.markdownFrame.setVisibility(View.GONE);
|
||||
binding.contents.setVisibility(View.VISIBLE);
|
||||
|
||||
renderMd = false;
|
||||
}
|
||||
|
||||
toggleMarkdown();
|
||||
return true;
|
||||
|
||||
} else {
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -0,0 +1,195 @@
|
||||
package org.mian.gitnex.helpers;
|
||||
|
||||
import android.content.Context;
|
||||
import android.text.Layout;
|
||||
import android.text.Spannable;
|
||||
import android.text.SpannableString;
|
||||
import android.text.style.BackgroundColorSpan;
|
||||
import android.util.TypedValue;
|
||||
import android.view.ViewTreeObserver;
|
||||
import android.widget.TextView;
|
||||
import androidx.core.content.ContextCompat;
|
||||
import androidx.core.widget.NestedScrollView;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
import org.mian.gitnex.R;
|
||||
import org.mian.gitnex.views.SyntaxHighlightedArea;
|
||||
|
||||
/**
|
||||
* @author mmarif
|
||||
*/
|
||||
public class FileContentSearcher {
|
||||
|
||||
private final List<int[]> matchPositions = new ArrayList<>();
|
||||
private int currentMatchIndex = -1;
|
||||
private String content;
|
||||
private String fileExtension;
|
||||
private final NestedScrollView scrollView;
|
||||
private final RecyclerView recyclerView;
|
||||
|
||||
public FileContentSearcher(NestedScrollView scrollView, RecyclerView recyclerView) {
|
||||
this.scrollView = scrollView;
|
||||
this.recyclerView = recyclerView;
|
||||
}
|
||||
|
||||
public void search(
|
||||
String content,
|
||||
String fileExtension,
|
||||
String query,
|
||||
SyntaxHighlightedArea codeView,
|
||||
RecyclerView markdownView,
|
||||
boolean isMarkdown) {
|
||||
|
||||
this.content = content;
|
||||
this.fileExtension = fileExtension;
|
||||
matchPositions.clear();
|
||||
currentMatchIndex = -1;
|
||||
|
||||
if (query == null || query.trim().isEmpty()) {
|
||||
clearHighlights(codeView, markdownView, isMarkdown);
|
||||
return;
|
||||
}
|
||||
|
||||
Pattern pattern = Pattern.compile(Pattern.quote(query), Pattern.CASE_INSENSITIVE);
|
||||
Matcher matcher = pattern.matcher(content);
|
||||
|
||||
while (matcher.find()) {
|
||||
matchPositions.add(new int[] {matcher.start(), matcher.end()});
|
||||
}
|
||||
|
||||
if (!matchPositions.isEmpty()) {
|
||||
currentMatchIndex = 0;
|
||||
highlightMatches(codeView, markdownView, isMarkdown);
|
||||
scrollToMatch(codeView, markdownView, isMarkdown, currentMatchIndex);
|
||||
} else {
|
||||
clearHighlights(codeView, markdownView, isMarkdown);
|
||||
}
|
||||
}
|
||||
|
||||
public void nextMatch(
|
||||
SyntaxHighlightedArea codeView, RecyclerView markdownView, boolean isMarkdown) {
|
||||
|
||||
if (matchPositions.isEmpty() || currentMatchIndex == -1) return;
|
||||
currentMatchIndex = (currentMatchIndex + 1) % matchPositions.size();
|
||||
highlightMatches(codeView, markdownView, isMarkdown);
|
||||
scrollToMatch(codeView, markdownView, isMarkdown, currentMatchIndex);
|
||||
}
|
||||
|
||||
public void previousMatch(
|
||||
SyntaxHighlightedArea codeView, RecyclerView markdownView, boolean isMarkdown) {
|
||||
|
||||
if (matchPositions.isEmpty() || currentMatchIndex == -1) return;
|
||||
currentMatchIndex = (currentMatchIndex - 1 + matchPositions.size()) % matchPositions.size();
|
||||
highlightMatches(codeView, markdownView, isMarkdown);
|
||||
scrollToMatch(codeView, markdownView, isMarkdown, currentMatchIndex);
|
||||
}
|
||||
|
||||
public int getMatchCount() {
|
||||
return matchPositions.size();
|
||||
}
|
||||
|
||||
private void highlightMatches(
|
||||
SyntaxHighlightedArea codeView, RecyclerView markdownView, boolean isMarkdown) {
|
||||
|
||||
SpannableString spannable = new SpannableString(content);
|
||||
|
||||
int searchHighlightColor = ContextCompat.getColor(codeView.getContext(), R.color.darkGreen);
|
||||
int selectedHighlightColor =
|
||||
ContextCompat.getColor(codeView.getContext(), R.color.iconPrMergedColor);
|
||||
|
||||
for (int i = 0; i < matchPositions.size(); i++) {
|
||||
int[] pos = matchPositions.get(i);
|
||||
int color = (i == currentMatchIndex) ? selectedHighlightColor : searchHighlightColor;
|
||||
spannable.setSpan(
|
||||
new BackgroundColorSpan(color),
|
||||
pos[0],
|
||||
pos[1],
|
||||
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
|
||||
}
|
||||
|
||||
if (isMarkdown && markdownView != null) {
|
||||
Markdown.renderWithHighlights(codeView.getContext(), spannable, markdownView, null);
|
||||
} else {
|
||||
codeView.setSpannable(spannable);
|
||||
}
|
||||
}
|
||||
|
||||
private void scrollToMatch(
|
||||
SyntaxHighlightedArea codeView,
|
||||
RecyclerView markdownView,
|
||||
boolean isMarkdown,
|
||||
int index) {
|
||||
|
||||
if (index < 0 || index >= matchPositions.size()) return;
|
||||
int[] pos = matchPositions.get(index);
|
||||
|
||||
if (isMarkdown && recyclerView != null) {
|
||||
|
||||
Context context = codeView.getContext();
|
||||
int avgCharHeight =
|
||||
(int)
|
||||
TypedValue.applyDimension(
|
||||
TypedValue.COMPLEX_UNIT_SP,
|
||||
14,
|
||||
context.getResources().getDisplayMetrics());
|
||||
int charsPerLine = recyclerView.getWidth() / (avgCharHeight / 2);
|
||||
int scrollY =
|
||||
(pos[0] / (charsPerLine > 0 ? charsPerLine : 1)) * (int) (avgCharHeight * 1.2);
|
||||
recyclerView.smoothScrollToPosition(scrollY / avgCharHeight);
|
||||
} else if (scrollView != null) {
|
||||
|
||||
TextView sourceView = codeView.getSourceView();
|
||||
ViewTreeObserver vto = sourceView.getViewTreeObserver();
|
||||
vto.addOnGlobalLayoutListener(
|
||||
new ViewTreeObserver.OnGlobalLayoutListener() {
|
||||
@Override
|
||||
public void onGlobalLayout() {
|
||||
|
||||
sourceView.getViewTreeObserver().removeOnGlobalLayoutListener(this);
|
||||
Layout layout = sourceView.getLayout();
|
||||
if (layout != null) {
|
||||
|
||||
int line = layout.getLineForOffset(pos[0]);
|
||||
int y = layout.getLineTop(line);
|
||||
int contentHeight = sourceView.getHeight();
|
||||
int scrollViewHeight = scrollView.getHeight();
|
||||
int maxScroll = Math.max(0, contentHeight - scrollViewHeight);
|
||||
int adjustedY = Math.min(y, maxScroll);
|
||||
|
||||
if (contentHeight > scrollViewHeight) {
|
||||
scrollView.requestLayout();
|
||||
scrollView.post(() -> scrollView.smoothScrollTo(0, adjustedY));
|
||||
}
|
||||
} else {
|
||||
|
||||
int lineHeight = sourceView.getLineHeight();
|
||||
int lines = content.substring(0, pos[0]).split("\n").length - 1;
|
||||
int fallbackY = lines * lineHeight;
|
||||
int contentHeight = sourceView.getHeight();
|
||||
int scrollViewHeight = scrollView.getHeight();
|
||||
int maxScroll = Math.max(0, contentHeight - scrollViewHeight);
|
||||
int adjustedFallbackY = Math.min(fallbackY, maxScroll);
|
||||
|
||||
if (contentHeight > scrollViewHeight) {
|
||||
scrollView.post(
|
||||
() -> scrollView.smoothScrollTo(0, adjustedFallbackY));
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private void clearHighlights(
|
||||
SyntaxHighlightedArea codeView, RecyclerView markdownView, boolean isMarkdown) {
|
||||
if (isMarkdown && markdownView != null) {
|
||||
Markdown.renderWithHighlights(
|
||||
codeView.getContext(), new SpannableString(content), markdownView, null);
|
||||
} else {
|
||||
codeView.setContent(content, fileExtension);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -6,8 +6,10 @@ import android.annotation.SuppressLint;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.graphics.Typeface;
|
||||
import android.text.Spannable;
|
||||
import android.text.Spanned;
|
||||
import android.text.method.LinkMovementMethod;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.TextView;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||
@@ -162,6 +164,21 @@ public class Markdown {
|
||||
}
|
||||
}
|
||||
|
||||
public static void renderWithHighlights(
|
||||
Context context,
|
||||
Spannable spannable,
|
||||
RecyclerView recyclerView,
|
||||
RepositoryContext repository) {
|
||||
try {
|
||||
RecyclerViewRenderer renderer = rvRendererPool.claim(OBJECT_POOL_CLAIM_TIMEOUT);
|
||||
if (renderer != null) {
|
||||
renderer.setParameters(context, spannable, recyclerView, repository);
|
||||
executorService.execute(renderer);
|
||||
}
|
||||
} catch (InterruptedException ignored) {
|
||||
}
|
||||
}
|
||||
|
||||
private static class Renderer implements Runnable, Poolable {
|
||||
|
||||
private final Slot slot;
|
||||
@@ -305,6 +322,7 @@ public class Markdown {
|
||||
|
||||
private Context context;
|
||||
private String markdown;
|
||||
private Spannable spannable;
|
||||
private RecyclerView recyclerView;
|
||||
private MarkwonAdapter adapter;
|
||||
private RepositoryContext repository;
|
||||
@@ -532,6 +550,21 @@ public class Markdown {
|
||||
.build();
|
||||
}
|
||||
|
||||
public void setParameters(
|
||||
Context context,
|
||||
Spannable spannable,
|
||||
RecyclerView recyclerView,
|
||||
RepositoryContext repository) {
|
||||
this.context = context;
|
||||
this.spannable = spannable;
|
||||
this.markdown = spannable.toString();
|
||||
this.recyclerView = recyclerView;
|
||||
this.repository = repository;
|
||||
if (linkPostProcessor != null) {
|
||||
linkPostProcessor.repository = repository;
|
||||
}
|
||||
}
|
||||
|
||||
public void setParameters(
|
||||
Context context,
|
||||
String markdown,
|
||||
@@ -577,9 +610,38 @@ public class Markdown {
|
||||
// separate ScrollViews
|
||||
}
|
||||
});
|
||||
localReference.setAdapter(localAdapter);
|
||||
|
||||
localAdapter.setMarkdown(markwon, localMd);
|
||||
if (spannable != null) {
|
||||
TextView tempTextView = new TextView(context);
|
||||
tempTextView.setText(spannable);
|
||||
tempTextView.setLayoutParams(
|
||||
new RecyclerView.LayoutParams(
|
||||
RecyclerView.LayoutParams.MATCH_PARENT,
|
||||
RecyclerView.LayoutParams.WRAP_CONTENT));
|
||||
RecyclerView.Adapter<RecyclerView.ViewHolder> customAdapter =
|
||||
new RecyclerView.Adapter<>() {
|
||||
@NonNull @Override
|
||||
public RecyclerView.ViewHolder onCreateViewHolder(
|
||||
@NonNull ViewGroup parent, int viewType) {
|
||||
return new RecyclerView.ViewHolder(tempTextView) {};
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBindViewHolder(
|
||||
@NonNull RecyclerView.ViewHolder holder,
|
||||
int position) {}
|
||||
|
||||
@Override
|
||||
public int getItemCount() {
|
||||
return 1;
|
||||
}
|
||||
};
|
||||
localReference.setAdapter(customAdapter);
|
||||
} else {
|
||||
localAdapter.setMarkdown(markwon, localMd);
|
||||
localReference.setAdapter(localAdapter);
|
||||
}
|
||||
|
||||
localAdapter.notifyDataSetChanged();
|
||||
});
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@ import android.graphics.Canvas;
|
||||
import android.graphics.Paint;
|
||||
import android.graphics.Rect;
|
||||
import android.graphics.Typeface;
|
||||
import android.text.Spannable;
|
||||
import android.util.AttributeSet;
|
||||
import android.util.TypedValue;
|
||||
import android.view.View;
|
||||
@@ -148,6 +149,16 @@ public class SyntaxHighlightedArea extends LinearLayout {
|
||||
}
|
||||
}
|
||||
|
||||
public void setSpannable(@NonNull Spannable spannable) {
|
||||
sourceView.setText(spannable);
|
||||
long lineCount = AppUtil.getLineCount(spannable.toString());
|
||||
linesView.setLineCount(lineCount);
|
||||
}
|
||||
|
||||
public TextView getSourceView() {
|
||||
return sourceView;
|
||||
}
|
||||
|
||||
private Activity getActivity() {
|
||||
return (Activity) getContext();
|
||||
}
|
||||
|
||||
@@ -59,6 +59,7 @@
|
||||
app:indicatorColor="?attr/progressIndicatorColor"/>
|
||||
|
||||
<androidx.core.widget.NestedScrollView
|
||||
android:id="@+id/contentScrollContainer"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
app:layout_behavior="@string/appbar_scrolling_view_behavior"
|
||||
|
||||
@@ -283,7 +283,7 @@
|
||||
<string name="selectBranchError">Select a branch for release</string>
|
||||
|
||||
<string name="alertDialogTokenRevokedTitle">Authorization Error</string>
|
||||
<string name="alertDialogTokenRevokedMessage">It seems that the Access Token is revoked OR your are not allowed to see these contents.\n\nIn case of revoked Token, please add the account again</string>
|
||||
<string name="alertDialogTokenRevokedMessage">It seems that the Access Token is revoked OR you are not allowed to see these contents.\n\nIn case of revoked Token, please add the account again</string>
|
||||
<string name="labelDeleteMessage">Do you really want to delete this label?</string>
|
||||
|
||||
<!-- org tabbed layout str -->
|
||||
@@ -469,7 +469,7 @@
|
||||
<string name="branch_created">Branch created successfully</string>
|
||||
<string name="branch_error_archive_mirror">Cannot create branch: Repository is archived or a mirror</string>
|
||||
<string name="branch_error_ref_not_found">Reference does not exist</string>
|
||||
<string name="branch_error_exists">"Branch %1$s already exists</string>
|
||||
<string name="branch_error_exists">Branch %1$s already exists</string>
|
||||
<string name="branch_error_repo_locked">Repository is locked</string>
|
||||
<string name="create_branch">Create Branch</string>
|
||||
|
||||
@@ -942,4 +942,7 @@
|
||||
<string name="attachment">Attachment</string>
|
||||
<string name="attachments">Attachments</string>
|
||||
<string name="attachmentsSaveError">An issue was created but cannot process attachments at this time. Check the server logs for more details.</string>
|
||||
|
||||
<string name="search_matches_found">%d matches found</string>
|
||||
<string name="search_no_matches">No matches found</string>
|
||||
</resources>
|
||||
|
||||
Reference in New Issue
Block a user