diff --git a/app/src/flavorAtest/res/drawable/ic_launcher_foreground.xml b/app/src/flavorAtest/res/drawable/ic_launcher_foreground.xml
index ea9854579f..a85f4f9a2a 100644
--- a/app/src/flavorAtest/res/drawable/ic_launcher_foreground.xml
+++ b/app/src/flavorAtest/res/drawable/ic_launcher_foreground.xml
@@ -2,16 +2,16 @@
xmlns:tools="http://schemas.android.com/tools"
tools:targetApi="n"
android:width="108dp"
- android:height="108dp"
- android:viewportWidth="800.7087"
- android:viewportHeight="800.7087">
+ android:height="108dp"
+ android:viewportWidth="800.7087"
+ android:viewportHeight="800.7087">
+ android:strokeAlpha="1" />
+ android:strokeAlpha="1" />
+ android:strokeAlpha="1" />
+ android:strokeAlpha="1" />
+ android:strokeWidth="0.49996799" />
+ android:strokeWidth="1.06330121" />
+ android:strokeWidth="1.07910931" />
+ android:strokeWidth="1.0553019" />
+ android:strokeWidth="0.96706837" />
+ android:strokeWidth="0.94961631" />
+ android:strokeWidth="0.9483121" />
+ android:strokeAlpha="1" />
diff --git a/app/src/flavorGplay/AndroidManifest.xml b/app/src/flavorGplay/AndroidManifest.xml
index c9a29c587d..95eb936659 100644
--- a/app/src/flavorGplay/AndroidManifest.xml
+++ b/app/src/flavorGplay/AndroidManifest.xml
@@ -14,6 +14,8 @@
tools:ignore="ObsoleteSdkInt"
tools:targetApi="jelly_bean">
-
+
diff --git a/app/src/main/java/net/gsantner/markor/activity/DocumentEditAndViewFragment.java b/app/src/main/java/net/gsantner/markor/activity/DocumentEditAndViewFragment.java
index 449e829a2b..03d49265b6 100644
--- a/app/src/main/java/net/gsantner/markor/activity/DocumentEditAndViewFragment.java
+++ b/app/src/main/java/net/gsantner/markor/activity/DocumentEditAndViewFragment.java
@@ -35,7 +35,6 @@
import android.webkit.WebView;
import android.widget.HorizontalScrollView;
import android.widget.ImageButton;
-import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.ScrollView;
import android.widget.TextView;
@@ -73,6 +72,7 @@
import net.gsantner.opoc.util.GsContextUtils;
import net.gsantner.opoc.util.GsCoolExperimentalStuff;
import net.gsantner.opoc.web.GsWebViewChromeClient;
+import net.gsantner.opoc.wrapper.GsCallback;
import net.gsantner.opoc.wrapper.GsTextWatcherAdapter;
import java.io.File;
@@ -110,6 +110,7 @@ public static DocumentEditAndViewFragment newInstance(final @NonNull Document do
private DraggableScrollbarScrollView _verticalScrollView;
private HorizontalScrollView _horizontalScrollView;
private LineNumbersView _lineNumbersView;
+ private TextView _searchResultTextView;
private Document _document;
private FormatRegistry _format;
private MarkorContextUtils _cu;
@@ -151,6 +152,10 @@ public void onViewCreated(@NonNull View view, Bundle savedInstanceState) {
_lineNumbersView = view.findViewById(R.id.document__fragment__edit__line_numbers_view);
_cu = new MarkorContextUtils(activity);
_editTextUndoRedoHelper = new TextViewUndoRedo();
+ _editorHolder.setOnClickListener(v -> {
+ _hlEditor.requestFocus();
+ _cu.showSoftKeyboard(activity, true, _hlEditor);
+ });
// Using `if (_document != null)` everywhere is dangerous
// It may cause reads or writes to _silently fail_
@@ -188,7 +193,9 @@ public void onViewCreated(@NonNull View view, Bundle savedInstanceState) {
_hlEditor.setLineSpacing(0, _appSettings.getEditorLineSpacing());
_hlEditor.setTextSize(TypedValue.COMPLEX_UNIT_SP, _appSettings.getDocumentFontSize(_document.path));
_hlEditor.setTypeface(GsFontPreferenceCompat.typeface(getContext(), _appSettings.getFontFamily(), Typeface.NORMAL));
- _hlEditor.setBackgroundColor(_appSettings.getEditorBackgroundColor());
+ final int editorBackgroundColor = _appSettings.getEditorBackgroundColor();
+ _hlEditor.setBackgroundColor(editorBackgroundColor);
+ _editorHolder.setBackgroundColor(editorBackgroundColor);
_hlEditor.setTextColor(_appSettings.getEditorForegroundColor());
_hlEditor.setGravity(_appSettings.isEditorStartEditingInCenter() ? Gravity.CENTER : Gravity.NO_GRAVITY);
_hlEditor.setHighlightingEnabled(_appSettings.getDocumentHighlightState(_document.path, _hlEditor.getText()));
@@ -238,41 +245,21 @@ public void onViewCreated(@NonNull View view, Bundle savedInstanceState) {
});
}
- // Keep this as a one-shot min-height sync. Do not use a persistent layout listener here,
- // otherwise large documents trigger repeated relayout work during startup.
- syncEditorMinHeightOnce(_verticalScrollView);
}
@Override
protected void onFragmentFirstTimeVisible() {
- // Restore cursor
- final int lastSelection = _appSettings.getLastEditPosition(_document.path, _hlEditor.length());
- _hlEditor.setSelection(lastSelection);
-
- // Restore scroll position for edit-mode
- _hlEditor.setReflowCallback(() -> {
- // Must be called after HighlightingEditor reflow to prevent scroll position being reset
- int lastEditHeight = _appSettings.getLastEditHeight(_document.path, 0);
- int lastEditScrollY = _appSettings.getLastEditScrollY(_document.path, 0);
- if (lastEditScrollY > 0 && lastEditHeight == _verticalScrollView.getHeight()) {
- // Set scroll position by scroll Y if last scroll Y is valid
- // This way is precise, not as imprecise as using line number
- _hlEditor.postDelayed(() -> {
- _verticalScrollView.scrollTo(0, lastEditScrollY);
- _hlEditor.requestFocus();
- }, 600);
- } else {
- // Set scroll position by line number if last scroll Y is invalid
- final Bundle args = getArguments();
- if (args != null && args.containsKey(Document.EXTRA_FILE_LINE_NUMBER)) {
- final int lineNumber = args.getInt(Document.EXTRA_FILE_LINE_NUMBER);
- int selection = lineNumber >= 0 ? TextViewUtils.getIndexFromLineOffset(_hlEditor.getText(), lineNumber, 0) : _hlEditor.length();
- TextViewUtils.setSelectionAndShow(_hlEditor, selection);
- } else {
- _hlEditor.requestFocus();
- }
- }
- });
+ final Bundle args = getArguments();
+ final boolean hasLineNumber = args != null && args.containsKey(Document.EXTRA_FILE_LINE_NUMBER);
+ int startPos = _appSettings.getLastEditPosition(_document.path, _hlEditor.length());
+ if (hasLineNumber) {
+ final int lineNumber = args.getInt(Document.EXTRA_FILE_LINE_NUMBER);
+ startPos = lineNumber >= 0
+ ? TextViewUtils.getIndexFromLineOffset(_hlEditor.getText(), lineNumber, 0)
+ : _hlEditor.length();
+ } else {
+ _hlEditor.setSelection(startPos);
+ }
// Restore scroll position for view-mode
if (_webView != null) {
@@ -284,13 +271,41 @@ protected void onFragmentFirstTimeVisible() {
}
_hlEditor.recomputeHighlighting();
-
- // One-shot floor for first render after content/highlighting setup.
- // Do not replace with per-layout updates; they regress big-file open performance.
- syncEditorMinHeightOnce(_editorHolder);
+ if (hasLineNumber) {
+ TextViewUtils.setSelectionAndShow(_hlEditor, startPos);
+ } else {
+ final int lastEditHeight = _appSettings.getLastEditHeight(_document.path, 0);
+ final int lastEditScrollY = _appSettings.getLastEditScrollY(_document.path, 0);
+ final int fallbackPos = startPos;
+ _verticalScrollView.post(() -> {
+ if (lastEditHeight > 0 && lastEditHeight == _verticalScrollView.getHeight()) {
+ _verticalScrollView.scrollTo(0, lastEditScrollY);
+ } else {
+ TextViewUtils.setSelectionAndShow(_hlEditor, fallbackPos);
+ }
+ });
+ }
// Fade in to hide initial jank
_hlEditor.post(() -> _hlEditor.animate().alpha(1).setDuration(250).start());
+ setupHighlightingScrollRestore();
+ }
+
+ private void setupHighlightingScrollRestore() {
+ _hlEditor.setScrollCallbacks(
+ () -> new int[]{
+ _horizontalScrollView != null ? _horizontalScrollView.getScrollX() : 0,
+ _verticalScrollView != null ? _verticalScrollView.getScrollY() : 0
+ },
+ (x, y) -> {
+ if (_horizontalScrollView != null) {
+ _horizontalScrollView.scrollTo(x, 0);
+ }
+ if (_verticalScrollView != null) {
+ _verticalScrollView.scrollTo(0, y);
+ }
+ }
+ );
}
@Override
@@ -778,20 +793,10 @@ private void setActionBarVisibility() {
return;
}
- final boolean visible = _format.getActions().loadActionBarVisible() && _textActionsBar.getChildCount() > 0;
- if (visible && parent.getVisibility() == View.VISIBLE) {
- return;
- }
-
final View bar = view.findViewById(R.id.document__fragment__edit__text_actions_bar);
if (bar != null && _verticalScrollView != null) {
+ final boolean visible = _format.getActions().loadActionBarVisible() && _textActionsBar.getChildCount() > 0;
parent.setVisibility(visible ? View.VISIBLE : View.GONE);
- final int marginBottom = visible ? (int) getResources().getDimension(R.dimen.text_actions_bar_height) : 0;
- setMarginBottom(_verticalScrollView, marginBottom);
- final View viewScroll = view.findViewById(R.id.document__fragment_view_webview);
- if (viewScroll != null) {
- setMarginBottom(viewScroll, marginBottom);
- }
}
}
@@ -804,10 +809,6 @@ private void setupSearchView(SearchView searchView) {
if (searchView == null) {
return;
}
- // Only setup SearchView for view-mode, to avoid unnecessary setup for edit-mode
- if (!_isPreviewVisible || _webView == null) {
- return;
- }
searchView.setQueryHint(getString(R.string.search));
searchView.setOnQueryTextListener(new SearchView.OnQueryTextListener() {
@@ -841,22 +842,25 @@ public boolean onQueryTextChange(String text) {
return search(text);
}
});
- searchView.addOnAttachStateChangeListener(new View.OnAttachStateChangeListener() {
- @Override
- public void onViewAttachedToWindow(@NonNull View v) {
- }
-
- @Override
- public void onViewDetachedFromWindow(@NonNull View v) {
- // Clear search when SearchView is closed abnormally, e.g. switch from QuickNote to To-Do when SearchView is opened
- if (searchView.getQuery().length() > 0) {
- searchView.setQuery("", false); // This will make onQueryTextChange be called back
+ if (searchView.getTag(R.id.action_search_view) == null) {
+ searchView.addOnAttachStateChangeListener(new View.OnAttachStateChangeListener() {
+ @Override
+ public void onViewAttachedToWindow(@NonNull View v) {
}
- if (!searchView.isIconified()) {
- searchView.setIconified(true);
+
+ @Override
+ public void onViewDetachedFromWindow(@NonNull View v) {
+ // Clear search when SearchView is closed abnormally, e.g. switch from QuickNote to To-Do when SearchView is opened
+ if (searchView.getQuery().length() > 0) {
+ searchView.setQuery("", false); // This will make onQueryTextChange be called back
+ }
+ if (!searchView.isIconified()) {
+ searchView.setIconified(true);
+ }
}
- }
- });
+ });
+ searchView.setTag(R.id.action_search_view, Boolean.TRUE);
+ }
// Because SearchView doesn't provide a public API to add custom buttons
// We must get the searchPlate (the layout containing the text field and close button) from SearchView
@@ -868,46 +872,68 @@ public void onViewDetachedFromWindow(@NonNull View v) {
return;
}
- LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.WRAP_CONTENT, LinearLayout.LayoutParams.WRAP_CONTENT);
- layoutParams.gravity = Gravity.CENTER;
+ final GsCallback.r0 makeLayoutParams = () -> {
+ final LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(
+ LinearLayout.LayoutParams.WRAP_CONTENT,
+ LinearLayout.LayoutParams.WRAP_CONTENT
+ );
+ params.gravity = Gravity.CENTER;
+ return params;
+ };
Context searchViewContext = searchView.getContext();
- LinearLayout linearLayout = new LinearLayout(searchViewContext);
- linearLayout.setLayoutParams(layoutParams);
-
- // Add search result TextView
- TextView resultTextView = new TextView(searchViewContext);
- LinearLayout.LayoutParams textViewLayoutParams = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.WRAP_CONTENT, LinearLayout.LayoutParams.MATCH_PARENT);
- textViewLayoutParams.setMarginEnd(30);
- resultTextView.setLayoutParams(textViewLayoutParams);
- resultTextView.setGravity(Gravity.CENTER);
- resultTextView.setTextSize(TypedValue.COMPLEX_UNIT_SP, 16);
- linearLayout.addView(resultTextView);
-
- // Add previous match Button
- ImageView previousButton = new ImageView(searchViewContext);
- previousButton.setImageResource(R.drawable.ic_baseline_keyboard_arrow_up_24);
- previousButton.setLayoutParams(layoutParams);
- previousButton.setPadding(24, 24, 24, 24);
- TextViewUtils.setSelectableItemBackgroundBorderless(previousButton, searchViewContext);
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
- previousButton.setTooltipText(getString(R.string.previous_match));
- }
- linearLayout.addView(previousButton);
+ LinearLayout linearLayout = searchPlate.findViewWithTag("markor_search_nav_controls");
+ TextView resultTextView;
+ ImageButton previousButton;
+ ImageButton nextButton;
+ if (linearLayout == null) {
+ linearLayout = new LinearLayout(searchViewContext);
+ linearLayout.setTag("markor_search_nav_controls");
+ linearLayout.setLayoutParams(makeLayoutParams.callback());
+
+ // Add search result TextView
+ resultTextView = new TextView(searchViewContext);
+ resultTextView.setTag("markor_search_nav_result");
+ LinearLayout.LayoutParams textViewLayoutParams = new LinearLayout.LayoutParams(
+ LinearLayout.LayoutParams.WRAP_CONTENT,
+ LinearLayout.LayoutParams.MATCH_PARENT
+ );
+ textViewLayoutParams.setMarginEnd(30);
+ resultTextView.setLayoutParams(textViewLayoutParams);
+ resultTextView.setGravity(Gravity.CENTER);
+ resultTextView.setTextSize(TypedValue.COMPLEX_UNIT_SP, 16);
+ linearLayout.addView(resultTextView);
+
+ // Add previous match Button
+ previousButton = new ImageButton(searchViewContext);
+ previousButton.setImageResource(R.drawable.ic_baseline_keyboard_arrow_up_24);
+ previousButton.setLayoutParams(makeLayoutParams.callback());
+ previousButton.setPadding(24, 24, 24, 24);
+ TextViewUtils.setSelectableItemBackgroundBorderless(previousButton, searchViewContext);
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+ previousButton.setTooltipText(getString(R.string.previous_match));
+ }
+ linearLayout.addView(previousButton);
+
+ // Add next match Button
+ nextButton = new ImageButton(searchViewContext);
+ nextButton.setImageResource(R.drawable.ic_baseline_keyboard_arrow_down_24);
+ nextButton.setLayoutParams(makeLayoutParams.callback());
+ nextButton.setPadding(24, 24, 24, 24);
+ TextViewUtils.setSelectableItemBackgroundBorderless(nextButton, searchViewContext);
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+ nextButton.setTooltipText(getString(R.string.next_match));
+ }
+ linearLayout.addView(nextButton);
- // Add next match Button
- ImageButton nextButton = new ImageButton(searchViewContext);
- nextButton.setImageResource(R.drawable.ic_baseline_keyboard_arrow_down_24);
- nextButton.setLayoutParams(layoutParams);
- nextButton.setPadding(24, 24, 24, 24);
- TextViewUtils.setSelectableItemBackgroundBorderless(nextButton, searchViewContext);
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
- nextButton.setTooltipText(getString(R.string.next_match));
+ // Apply to SearchView
+ searchPlate.addView(linearLayout, 1);
+ } else {
+ resultTextView = linearLayout.findViewWithTag("markor_search_nav_result");
+ previousButton = (ImageButton) linearLayout.getChildAt(1);
+ nextButton = (ImageButton) linearLayout.getChildAt(2);
}
- linearLayout.addView(nextButton);
-
- // Apply to SearchView
- searchPlate.addView(linearLayout, 1);
+ _searchResultTextView = resultTextView;
// Set listeners
previousButton.setOnClickListener(v -> {
@@ -920,13 +946,20 @@ public void onViewDetachedFromWindow(@NonNull View v) {
_webView.findNext(true);
}
});
+ bindWebViewSearchListener();
+ }
+
+ private void bindWebViewSearchListener() {
+ if (_webView == null || _searchResultTextView == null) {
+ return;
+ }
_webView.setFindListener((activeMatchOrdinal, numberOfMatches, isDoneCounting) -> {
if (isDoneCounting) {
String searchResult = "";
if (numberOfMatches > 0) {
searchResult = (activeMatchOrdinal + 1) + "/" + numberOfMatches;
}
- resultTextView.setText(searchResult);
+ _searchResultTextView.setText(searchResult);
}
});
}
@@ -958,26 +991,6 @@ public boolean isSearchViewIconified() {
}
}
- private void setMarginBottom(final View view, final int marginBottom) {
- final ViewGroup.MarginLayoutParams params = (ViewGroup.MarginLayoutParams) view.getLayoutParams();
- if (params != null) {
- params.setMargins(params.leftMargin, params.topMargin, params.rightMargin, marginBottom);
- view.setLayoutParams(params);
- }
- }
-
- private void syncEditorMinHeightOnce(final View parent) {
- if (parent == null) {
- return;
- }
- parent.post(() -> {
- final int parentHeight = parent.getHeight();
- if (parentHeight > 0 && parentHeight != _hlEditor.getMinHeight()) {
- _hlEditor.setMinHeight(parentHeight);
- }
- });
- }
-
private void updateMenuToggleStates(final int selectedFormatActionId) {
MenuItem mi;
if ((mi = _fragmentMenu.findItem(R.id.action_wrap_words)) != null) {
@@ -1013,7 +1026,7 @@ private ViewGroup.LayoutParams makeLinearLayoutChildParams() {
}
private ViewGroup.LayoutParams makeScrollViewChildParams() {
- return new ScrollView.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);
+ return new ScrollView.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
}
private void setWrapState(final boolean wrap) {
@@ -1045,7 +1058,6 @@ private void setWrapState(final boolean wrap) {
}
_hlEditor.requestLayout();
- syncEditorMinHeightOnce(_editorHolder);
_hlEditor.setHighlightingEnabled(hlEnabled);
_hlEditor.post(() -> {
@@ -1201,6 +1213,10 @@ private void setupWebViewIfNeeded(final Activity activity) {
return false;
});
}
+ if (_format != null) {
+ _format.getActions().setUiReferences(activity, _hlEditor, _webView);
+ }
+ bindWebViewSearchListener();
}
@SuppressLint({"AddJavascriptInterface", "SetJavaScriptEnabled"})
@@ -1261,7 +1277,7 @@ protected boolean onToolbarLongClicked(View v) {
}
@Override
- public void onDestroy() {
+ public void onDestroyView() {
if (_webView != null) {
try {
_webView.loadUrl("about:blank");
@@ -1269,7 +1285,16 @@ public void onDestroy() {
} catch (Exception ignored) {
}
}
- super.onDestroy();
+ _webView = null;
+ _webViewClient = null;
+ _searchResultTextView = null;
+ if (_hlEditor != null) {
+ _hlEditor.setScrollCallbacks(null, null);
+ }
+ if (_format != null) {
+ _format.getActions().setUiReferences(getActivity(), _hlEditor, null);
+ }
+ super.onDestroyView();
}
public Document getDocument() {
diff --git a/app/src/main/java/net/gsantner/markor/activity/DocumentShareIntoFragment.java b/app/src/main/java/net/gsantner/markor/activity/DocumentShareIntoFragment.java
index d39cb0865c..8c7df200f8 100644
--- a/app/src/main/java/net/gsantner/markor/activity/DocumentShareIntoFragment.java
+++ b/app/src/main/java/net/gsantner/markor/activity/DocumentShareIntoFragment.java
@@ -321,7 +321,7 @@ private void attachOrCopyAndClose(final File dest, final boolean show) {
_appSettings.addRecentFile(dest);
// Only if not forced link due to attachment
- if (attachment == null) {
+ if (attachment == null && _linkCheckBox != null && _linkCheckBox.getVisibility() == View.VISIBLE) {
_appSettings.setFormatShareAsLink(asLink);
}
diff --git a/app/src/main/java/net/gsantner/markor/activity/MainActivity.java b/app/src/main/java/net/gsantner/markor/activity/MainActivity.java
index 9b74acf1e1..1df6964b26 100644
--- a/app/src/main/java/net/gsantner/markor/activity/MainActivity.java
+++ b/app/src/main/java/net/gsantner/markor/activity/MainActivity.java
@@ -476,6 +476,8 @@ public void onViewPagerPageSelected(final int pos) {
} else {
_fab.hide();
}
+
+ setTitle(getPosTitle(pos));
}
private GsFileBrowserOptions.Options _filesystemDialogOptions = null;
diff --git a/app/src/main/java/net/gsantner/markor/format/ActionButtonBase.java b/app/src/main/java/net/gsantner/markor/format/ActionButtonBase.java
index 7b0deda3e7..a73b4d750e 100644
--- a/app/src/main/java/net/gsantner/markor/format/ActionButtonBase.java
+++ b/app/src/main/java/net/gsantner/markor/format/ActionButtonBase.java
@@ -898,7 +898,11 @@ protected final boolean runCommonAction(final @StringRes int action) {
return true;
}
case R.string.abid_common_web_jump_to_table_of_contents: {
- runTitleClick();
+ if (_appSettings.isMarkdownTableOfContentsEnabled() && _webView != null) {
+ _webView.loadUrl("javascript:document.getElementsByClassName('toc')[0].scrollIntoView();");
+ } else {
+ runTitleClick();
+ }
return true;
}
case R.string.abid_common_view_file_in_other_app: {
diff --git a/app/src/main/java/net/gsantner/markor/frontend/MarkorDialogFactory.java b/app/src/main/java/net/gsantner/markor/frontend/MarkorDialogFactory.java
index 9c86f61248..ee4ad5eef9 100644
--- a/app/src/main/java/net/gsantner/markor/frontend/MarkorDialogFactory.java
+++ b/app/src/main/java/net/gsantner/markor/frontend/MarkorDialogFactory.java
@@ -17,7 +17,6 @@
import android.annotation.SuppressLint;
import android.app.Activity;
-import android.app.AlertDialog;
import android.content.Context;
import android.os.Build;
import android.text.Editable;
@@ -33,6 +32,7 @@
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
+import android.view.Window;
import android.view.WindowManager;
import android.view.animation.LinearInterpolator;
import android.webkit.WebView;
@@ -46,6 +46,7 @@
import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
import androidx.annotation.StringRes;
+import androidx.appcompat.app.AlertDialog;
import androidx.core.content.ContextCompat;
import net.gsantner.markor.R;
@@ -470,12 +471,23 @@ public static DialogOptions makeSttLineSelectionDialog(
return dopt;
}
+ @Nullable
+ private static Editable getCurrentSearchText(final AlertDialog dialog) {
+ final Window window = dialog.getWindow();
+ if (window == null) {
+ return null;
+ }
+ final View view = window.getDecorView().findViewWithTag("EDIT");
+ return view instanceof EditText ? ((EditText) view).getText() : null;
+ }
+
// Search dialog for todo.txt
public static void showSttSearchDialog(final Activity activity, final EditText text) {
final DialogOptions dopt = makeSttLineSelectionDialog(activity, text, t -> true);
dopt.titleText = R.string.search_documents;
dopt.neutralButtonText = R.string.replace;
- dopt.neutralButtonCallback2 = (dialog, searchText) -> {
+ dopt.neutralButtonCallback = dialog -> {
+ final Editable searchText = getCurrentSearchText(dialog);
dialog.dismiss();
SearchAndReplaceTextDialog.showSearchReplaceDialog(activity, text.getText(), searchText, TextViewUtils.getSelection(text));
};
@@ -762,10 +774,11 @@ public static void showSearchDialog(final Activity activity, final EditText edit
dopt.dataFilter = "[^\\s]+"; // Line must have one or more non-whitespace to display
dopt.titleText = R.string.search_documents;
dopt.searchHintText = R.string.search;
- dopt.searchText = searchText;
+ dopt.state.searchText = searchText;
dopt.neutralButtonCallback = null;
- dopt.neutralButtonCallback2 = (dialog, searchText2) -> {
+ dopt.neutralButtonCallback = dialog -> {
dialog.dismiss();
+ final Editable searchText2 = getCurrentSearchText(dialog);
SearchAndReplaceTextDialog.showSearchReplaceDialog(activity, edit, searchText2, TextViewUtils.getSelection(editText));
};
dopt.neutralButtonText = R.string.replace;
diff --git a/app/src/main/java/net/gsantner/markor/frontend/SearchAndReplaceTextDialog.java b/app/src/main/java/net/gsantner/markor/frontend/SearchAndReplaceTextDialog.java
index c81e0c37a0..58f39915c7 100644
--- a/app/src/main/java/net/gsantner/markor/frontend/SearchAndReplaceTextDialog.java
+++ b/app/src/main/java/net/gsantner/markor/frontend/SearchAndReplaceTextDialog.java
@@ -75,7 +75,7 @@ public class SearchAndReplaceTextDialog {
private final List recentReplaces;
- public static void showSearchReplaceDialog(final Activity activity, final Editable edit, Editable searchText, final int[] sel) {
+ public static void showSearchReplaceDialog(final Activity activity, final Editable edit, @Nullable CharSequence searchText, final int[] sel) {
new SearchAndReplaceTextDialog(activity, edit, searchText, sel);
}
@@ -83,7 +83,7 @@ public static void showSearchReplaceDialog(final Activity activity, final Editab
showSearchReplaceDialog(activity, edit, null, sel);
}
- private SearchAndReplaceTextDialog(final Activity activity, final Editable edit, Editable searchText, final int[] sel) {
+ private SearchAndReplaceTextDialog(final Activity activity, final Editable edit, @Nullable CharSequence searchText, final int[] sel) {
_activity = activity;
_edit = edit;
@@ -404,4 +404,4 @@ public JSONObject toJson() {
return obj;
}
}
-}
\ No newline at end of file
+}
diff --git a/app/src/main/java/net/gsantner/markor/frontend/textview/HighlightingEditor.java b/app/src/main/java/net/gsantner/markor/frontend/textview/HighlightingEditor.java
index 611e4f2ca7..ad24dfadc9 100644
--- a/app/src/main/java/net/gsantner/markor/frontend/textview/HighlightingEditor.java
+++ b/app/src/main/java/net/gsantner/markor/frontend/textview/HighlightingEditor.java
@@ -79,6 +79,9 @@ public class HighlightingEditor extends AppCompatEditText {
private int _textChangedNumber;
private final Runnable _textChangedRecorder = TextViewUtils.makeDebounced(getHandler(), 1000, () -> _textChangedNumber++);
private StaticCursorDrawer _staticCursorDrawer;
+ private GsCallback.r0 _getScrollCallback;
+ private GsCallback.a2 _applyScrollCallback;
+ private int[] _savedScrollPosition;
public HighlightingEditor(Context context, AttributeSet attrs) {
super(context, attrs);
@@ -138,17 +141,27 @@ private void batch(final Runnable runnable) {
beginBatchEdit();
runnable.run();
} finally {
- endBatchEdit(); // This triggers a reflow which will bring focus back to the cursor and reset scroll position
- if (reflowCallback != null) {
- reflowCallback.callback();
- }
+ endBatchEdit(); // This can trigger reflow which will bring focus back to the cursor and reset scroll position
}
}
- private GsCallback.a0 reflowCallback;
+ public void setScrollCallbacks(final GsCallback.r0 get, final GsCallback.a2 apply) {
+ _getScrollCallback = get;
+ _applyScrollCallback = apply;
+ }
+
+ private void saveScrollPositionForLayout() {
+ if (_savedScrollPosition == null && _getScrollCallback != null) {
+ _savedScrollPosition = _getScrollCallback.callback();
+ }
+ }
- public void setReflowCallback(GsCallback.a0 callback) {
- this.reflowCallback = callback;
+ private void applySavedScrollPosition() {
+ final int[] position = _savedScrollPosition;
+ _savedScrollPosition = null;
+ if (position != null && position.length >= 2 && _applyScrollCallback != null) {
+ _applyScrollCallback.callback(position[0], position[1]);
+ }
}
private boolean isScrollSignificant() {
@@ -177,6 +190,7 @@ private void updateHighlighting() {
public void recomputeHighlighting() {
if (_hlEnabled && runHighlight(true)) {
+ this.saveScrollPositionForLayout();
batch(() -> _hl
.clearDynamic()
.clearStatic(false)
@@ -185,6 +199,7 @@ public void recomputeHighlighting() {
.applyStatic()
.applyDynamic(hlRegion())
);
+ this.applySavedScrollPosition();
}
}
diff --git a/app/src/main/java/net/gsantner/markor/frontend/textview/TextViewUtils.java b/app/src/main/java/net/gsantner/markor/frontend/textview/TextViewUtils.java
index 641252e4b2..4f257517b1 100644
--- a/app/src/main/java/net/gsantner/markor/frontend/textview/TextViewUtils.java
+++ b/app/src/main/java/net/gsantner/markor/frontend/textview/TextViewUtils.java
@@ -425,9 +425,9 @@ public static void showSelection(final EditText editText, final int startSelecti
int line = layout.getLineForOffset(startSelection);
int lineHeight = editText.getLineHeight();
if (layout.getLineTop(line) < visible.top - lineHeight) {
- showSelection(editText, visible, startSelection, startSelection, -lineHeight);
+ showSelection(editText, visible, startSelection, startSelection, -lineHeight * 3);
} else if (layout.getLineBottom(line) > visible.bottom - lineHeight) {
- showSelection(editText, visible, startSelection, startSelection, lineHeight * 4);
+ showSelection(editText, visible, startSelection, startSelection, lineHeight * 2);
}
}
diff --git a/app/src/main/java/net/gsantner/opoc/frontend/GsSearchOrCustomTextDialog.java b/app/src/main/java/net/gsantner/opoc/frontend/GsSearchOrCustomTextDialog.java
index 1406753327..8a39cd047f 100644
--- a/app/src/main/java/net/gsantner/opoc/frontend/GsSearchOrCustomTextDialog.java
+++ b/app/src/main/java/net/gsantner/opoc/frontend/GsSearchOrCustomTextDialog.java
@@ -21,7 +21,6 @@
import android.graphics.drawable.RippleDrawable;
import android.os.Build;
import android.os.Parcelable;
-import android.text.Editable;
import android.text.InputFilter;
import android.text.InputType;
import android.text.Spannable;
@@ -55,6 +54,7 @@
import androidx.appcompat.widget.AppCompatEditText;
import androidx.appcompat.widget.TooltipCompat;
import androidx.core.graphics.ColorUtils;
+import androidx.core.view.ViewCompat;
import androidx.core.view.WindowCompat;
import androidx.core.widget.TextViewCompat;
@@ -133,7 +133,6 @@ public enum SelectionMode {
public String dataFilter = null; // Regex pattern to filter data
public GsCallback.a1 highlighter = null;
public GsCallback.a1 neutralButtonCallback = null;
- public GsCallback.a2 neutralButtonCallback2 = null;
public GsCallback.a1 dismissCallback = null;
public @Nullable InputFilter searchInputFilter = null;
@@ -156,8 +155,6 @@ public enum SelectionMode {
@StyleRes
public int dialogStyle = 0;
- public String searchText;
-
/**
* Initial state of the dialog. Will be updated when the dialog is dismissed.
*/
@@ -388,22 +385,15 @@ public static void showMultiChoiceDialogWithSearchFilterUI(final Activity activi
}
};
- searchEditText.addTextChangedListener(GsTextWatcherAdapter.after(new GsCallback.a1() {
- private Runnable searchTask;
- private CharSequence searchText = new SpannableString("");
-
- @Override
- public void callback(Editable constraint) {
- if (searchTask == null) {
- searchTask = TextViewUtils.makeDebounced(searchEditText.getHandler(), 400, () -> {
- listAdapter.filter(searchText);
- setSelectAllButtonState.callback();
- });
- }
- searchText = constraint;
- searchTask.run();
- }
- }));
+ // Filter when the user stops typing if the list is long
+ final Runnable _filterList = () -> {
+ listAdapter.filter(searchEditText.getText());
+ setSelectAllButtonState.callback();
+ };
+ final Runnable _changeListener = dopt.data == null || dopt.data.size() < 1000 ?
+ _filterList :
+ TextViewUtils.makeDebounced(searchEditText.getHandler(), 400, _filterList);
+ searchEditText.addTextChangedListener(GsTextWatcherAdapter.after(e -> _changeListener.run()));
// Ok button only present under these circumstances
final boolean isSearchOk = dopt.callback != null && dopt.isSearchEnabled;
@@ -437,10 +427,24 @@ public void callback(Editable constraint) {
return false;
});
+ dialog.show();
+
final Window win = dialog.getWindow();
if (win != null) {
WindowCompat.setDecorFitsSystemWindows(win, true);
+ win.setLayout(
+ dopt.dialogWidthDp < 0 ? dopt.dialogWidthDp : GsContextUtils.instance.convertDpToPx(activity, dopt.dialogWidthDp),
+ dopt.dialogHeightDp < 0 ? dopt.dialogHeightDp : GsContextUtils.instance.convertDpToPx(activity, dopt.dialogHeightDp)
+ );
+
+ final View decorView = win.getDecorView();
+ ViewCompat.setOnApplyWindowInsetsListener(decorView, (view, insets) -> {
+ decorView.requestLayout();
+ listView.post(listView::requestLayout);
+ return insets;
+ });
+
if (dopt.isSearchEnabled) {
if (dopt.isSoftInputVisible) {
win.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE | WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE);
@@ -452,15 +456,6 @@ public void callback(Editable constraint) {
}
}
- dialog.show();
-
- if (win != null) {
- win.setLayout(
- dopt.dialogWidthDp < 0 ? dopt.dialogWidthDp : GsContextUtils.instance.convertDpToPx(activity, dopt.dialogWidthDp),
- dopt.dialogHeightDp < 0 ? dopt.dialogHeightDp : GsContextUtils.instance.convertDpToPx(activity, dopt.dialogHeightDp)
- );
- }
-
final Button neutralButton = dialog.getButton(AlertDialog.BUTTON_NEUTRAL);
if (neutralButton != null && dopt.neutralButtonText != 0) {
neutralButton.setVisibility(Button.VISIBLE);
@@ -468,9 +463,6 @@ public void callback(Editable constraint) {
if (dopt.neutralButtonCallback != null) {
neutralButton.setOnClickListener((button) -> dopt.neutralButtonCallback.callback(dialog));
- } else if (dopt.neutralButtonCallback2 != null) {
- // Open search & replace dialog with search text of current search dialog, no need to input it again
- neutralButton.setOnClickListener((button) -> dopt.neutralButtonCallback2.callback(dialog, searchEditText.getText()));
}
}
@@ -645,7 +637,6 @@ public static View makeSearchView(final Context context, final DialogOptions dop
searchEditText.setTextColor(dopt.textColor);
searchEditText.setHintTextColor(ColorUtils.setAlphaComponent(dopt.textColor, 0x99));
searchEditText.setHint(dopt.searchHintText);
- searchEditText.setText(dopt.searchText);
searchEditText.setInputType(dopt.searchInputType == 0 ? searchEditText.getInputType() : dopt.searchInputType);
searchEditText.setTag("EDIT"); // So we can easily find the search edit text
@@ -657,7 +648,7 @@ public static View makeSearchView(final Context context, final DialogOptions dop
final ImageView clearButton = new ImageView(context);
clearButton.setImageResource(dopt.clearInputIcon);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
- RippleDrawable rippleDrawable = new RippleDrawable(AppCompatResources.getColorStateList(context, R.color.accent), null, null);
+ final RippleDrawable rippleDrawable = new RippleDrawable(AppCompatResources.getColorStateList(context, R.color.accent), null, null);
rippleDrawable.setRadius(60);
clearButton.setBackground(rippleDrawable);
}
diff --git a/app/src/main/java/net/gsantner/opoc/util/AlphanumComparator.java b/app/src/main/java/net/gsantner/opoc/util/AlphanumComparator.java
index a8ff47f5e0..71f3991a57 100644
--- a/app/src/main/java/net/gsantner/opoc/util/AlphanumComparator.java
+++ b/app/src/main/java/net/gsantner/opoc/util/AlphanumComparator.java
@@ -19,7 +19,7 @@ private static boolean isDigit(char ch) {
* Find the end of a chunk starting at the given index. A chunk is either a sequence of digits
* or a sequence of non-digits.
*
- * @param s The string to scan
+ * @param s The string to scan
* @param index The index to start scanning from
* @return The index of the first character not belonging to the chunk
*/
@@ -37,9 +37,9 @@ private static int getChunkEnd(String s, int index) {
* Count the number of leading zeros in a numeric chunk, leaving at least one significant digit.
* For example, "001" has 2 leading zeros, and '0' is the single remaining.
*
- * @param s The string containing the chunk
+ * @param s The string containing the chunk
* @param start The start index of the numeric chunk
- * @param end The end index of the numeric chunk
+ * @param end The end index of the numeric chunk
* @return The number of leading zeros that can be ignored for numeric comparison
*/
private static int countLeadingZeros(String s, int start, int end) {
@@ -56,12 +56,12 @@ private static int countLeadingZeros(String s, int start, int end) {
* Compares two regions of strings for order, optionally ignoring case. Mimics String.compareTo
* and String.compareToIgnoreCase without allocations.
*
- * @param s1 First string
- * @param start1 Start index in s1
- * @param end1 End index in s1
- * @param s2 Second string
- * @param start2 Start index in s2
- * @param end2 End index in s2
+ * @param s1 First string
+ * @param start1 Start index in s1
+ * @param end1 End index in s1
+ * @param s2 Second string
+ * @param start2 Start index in s2
+ * @param end2 End index in s2
* @param ignoreCase Whether to perform case-insensitive comparison
* @return Negative if s1 region < s2 region, positive if >, zero if equal
*/
diff --git a/app/src/main/java/net/gsantner/opoc/util/GsContextUtils.java b/app/src/main/java/net/gsantner/opoc/util/GsContextUtils.java
index 1b2f1581fc..6f071636eb 100644
--- a/app/src/main/java/net/gsantner/opoc/util/GsContextUtils.java
+++ b/app/src/main/java/net/gsantner/opoc/util/GsContextUtils.java
@@ -2719,6 +2719,21 @@ public T setActivityNavigationBarBackgroundColor(fina
final Window window = context.getWindow();
window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS);
window.setNavigationBarColor(color);
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+ final View decorView = window.getDecorView();
+ final boolean useDarkNavIcons = !shouldColorOnTopBeLight(color);
+ final WindowInsetsControllerCompat controller = new WindowInsetsControllerCompat(window, decorView);
+ controller.setAppearanceLightNavigationBars(useDarkNavIcons);
+
+ // Keep the legacy flag in sync for OEMs that still depend on decor view UI flags.
+ int systemUiVisibility = decorView.getSystemUiVisibility();
+ if (useDarkNavIcons) {
+ systemUiVisibility |= View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR;
+ } else {
+ systemUiVisibility &= ~View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR;
+ }
+ decorView.setSystemUiVisibility(systemUiVisibility);
+ }
}
} catch (Exception ignored) {
}
diff --git a/app/src/main/res/layout/document__fragment__edit.xml b/app/src/main/res/layout/document__fragment__edit.xml
index 540935228a..301bceb3dc 100644
--- a/app/src/main/res/layout/document__fragment__edit.xml
+++ b/app/src/main/res/layout/document__fragment__edit.xml
@@ -19,7 +19,6 @@
@@ -29,74 +28,81 @@
android:layout_height="wrap_content"
android:layout_marginHorizontal="2.5dp" />
-
+ android:layout_height="0dp"
+ android:layout_weight="1">
-
+ android:layout_height="match_parent"
+ android:background="@android:color/transparent"
+ android:fillViewport="true">
-
-
-
-
-
-
+ android:clickable="true"
+ android:orientation="horizontal">
-
+
-
+
+
+
+
+
+
+
-
+ android:elevation="4dp"
+ android:paddingTop="4dp"
+ android:scrollbars="none"
+ android:translationZ="4dp">
-
+
+
+
-
\ No newline at end of file
+