|
34 | 34 | import android.webkit.WebSettings; |
35 | 35 | import android.webkit.WebView; |
36 | 36 | import android.widget.HorizontalScrollView; |
| 37 | +import android.widget.ImageButton; |
37 | 38 | import android.widget.LinearLayout; |
38 | 39 | import android.widget.ScrollView; |
39 | | -import android.widget.SearchView; |
| 40 | +import android.widget.TextView; |
40 | 41 | import android.widget.Toast; |
41 | 42 |
|
42 | 43 | import androidx.annotation.NonNull; |
43 | 44 | import androidx.appcompat.app.AppCompatActivity; |
| 45 | +import androidx.appcompat.widget.SearchView; |
44 | 46 |
|
45 | 47 | import net.gsantner.markor.ApplicationObject; |
46 | 48 | import net.gsantner.markor.BuildConfig; |
@@ -103,7 +105,6 @@ public static DocumentEditAndViewFragment newInstance(final @NonNull Document do |
103 | 105 | private DraggableScrollbarScrollView _verticalScrollView; |
104 | 106 | private HorizontalScrollView _horizontalScrollView; |
105 | 107 | private LineNumbersTextView _lineNumbersView; |
106 | | - private SearchView _menuSearchViewForViewMode; |
107 | 108 | private Document _document; |
108 | 109 | private FormatRegistry _format; |
109 | 110 | private MarkorContextUtils _cu; |
@@ -132,7 +133,6 @@ protected int getLayoutResId() { |
132 | 133 | return R.layout.document__fragment__edit; |
133 | 134 | } |
134 | 135 |
|
135 | | - @SuppressLint({"SetJavaScriptEnabled", "WrongConstant", "AddJavascriptInterface", "JavascriptInterface"}) |
136 | 136 | @Override |
137 | 137 | public void onViewCreated(@NonNull View view, Bundle savedInstanceState) { |
138 | 138 | super.onViewCreated(view, savedInstanceState); |
@@ -212,7 +212,7 @@ public void onViewCreated(@NonNull View view, Bundle savedInstanceState) { |
212 | 212 | // This works well to preserve keyboard state. |
213 | 213 | if (activity != null) { |
214 | 214 | final Window window = activity.getWindow(); |
215 | | - // Setting via a windowmanager state is much more robust than using show/hide |
| 215 | + // Setting via a window manager state is much more robust than using show/hide |
216 | 216 | final int adjustResize = WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE; |
217 | 217 | final int unchanged = WindowManager.LayoutParams.SOFT_INPUT_STATE_UNCHANGED | adjustResize; |
218 | 218 | final int hidden = WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN | adjustResize; |
@@ -328,35 +328,8 @@ public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { |
328 | 328 | menu.findItem(R.id.action_share_image).setVisible(true); |
329 | 329 | menu.findItem(R.id.action_load_epub).setVisible(isExperimentalFeaturesEnabled); |
330 | 330 |
|
331 | | - // SearchView (View Mode) |
332 | | - _menuSearchViewForViewMode = (SearchView) menu.findItem(R.id.action_search_view).getActionView(); |
333 | | - if (_menuSearchViewForViewMode != null) { |
334 | | - _menuSearchViewForViewMode.setSubmitButtonEnabled(true); |
335 | | - _menuSearchViewForViewMode.setQueryHint(getString(R.string.search)); |
336 | | - _menuSearchViewForViewMode.setOnQueryTextFocusChangeListener((v, searchHasFocus) -> { |
337 | | - if (!searchHasFocus) { |
338 | | - _menuSearchViewForViewMode.setQuery("", false); |
339 | | - _menuSearchViewForViewMode.setIconified(true); |
340 | | - } |
341 | | - }); |
342 | | - _menuSearchViewForViewMode.setOnQueryTextListener(new SearchView.OnQueryTextListener() { |
343 | | - @Override |
344 | | - public boolean onQueryTextSubmit(String text) { |
345 | | - if (_webView != null) { |
346 | | - _webView.findNext(true); |
347 | | - } |
348 | | - return true; |
349 | | - } |
350 | | - |
351 | | - @Override |
352 | | - public boolean onQueryTextChange(String text) { |
353 | | - if (_webView != null) { |
354 | | - _webView.findAllAsync(text); |
355 | | - } |
356 | | - return true; |
357 | | - } |
358 | | - }); |
359 | | - } |
| 331 | + // Setup SearchView for view-mode |
| 332 | + setupSearchView((SearchView) menu.findItem(R.id.action_search_view).getActionView()); |
360 | 333 |
|
361 | 334 | // Set various initial states |
362 | 335 | updateMenuToggleStates(_document.getFormat()); |
@@ -653,6 +626,7 @@ public void onFsViewerConfig(GsFileBrowserOptions.Options dopt) { |
653 | 626 | } |
654 | 627 | } |
655 | 628 | } |
| 629 | + |
656 | 630 | public void checkTextChangeState() { |
657 | 631 | final boolean isTextChanged = !_document.isContentSame(_hlEditor.getText()); |
658 | 632 | Drawable d; |
@@ -700,6 +674,134 @@ private void showHideActionBar() { |
700 | 674 | } |
701 | 675 | } |
702 | 676 |
|
| 677 | + /** |
| 678 | + * Setup SearchView from OptionsMenu for view-mode. |
| 679 | + * |
| 680 | + * @param searchView the SearchView form OptionsMenu |
| 681 | + */ |
| 682 | + private void setupSearchView(SearchView searchView) { |
| 683 | + if (searchView == null) { |
| 684 | + return; |
| 685 | + } |
| 686 | + // Only setup SearchView for view-mode, to avoid unnecessary setup for edit-mode |
| 687 | + if (!_isPreviewVisible || _webView == null) { |
| 688 | + return; |
| 689 | + } |
| 690 | + |
| 691 | + searchView.setQueryHint(getString(R.string.search)); |
| 692 | + searchView.setOnQueryTextListener(new SearchView.OnQueryTextListener() { |
| 693 | + private String searchText = ""; |
| 694 | + private Runnable searchTask; |
| 695 | + |
| 696 | + private boolean search(String text) { |
| 697 | + if (_webView == null) { |
| 698 | + return false; |
| 699 | + } |
| 700 | + if (searchTask == null) { |
| 701 | + searchTask = TextViewUtils.makeDebounced(_webView.getHandler(), 500, () -> _webView.findAllAsync(searchText)); |
| 702 | + } |
| 703 | + |
| 704 | + searchText = text; |
| 705 | + searchTask.run(); |
| 706 | + return true; |
| 707 | + } |
| 708 | + |
| 709 | + @Override |
| 710 | + public boolean onQueryTextSubmit(String query) { |
| 711 | + if (_webView != null) { |
| 712 | + _webView.findNext(true); |
| 713 | + return true; |
| 714 | + } |
| 715 | + return false; |
| 716 | + } |
| 717 | + |
| 718 | + @Override |
| 719 | + public boolean onQueryTextChange(String text) { |
| 720 | + return search(text); |
| 721 | + } |
| 722 | + }); |
| 723 | + searchView.addOnAttachStateChangeListener(new View.OnAttachStateChangeListener() { |
| 724 | + @Override |
| 725 | + public void onViewAttachedToWindow(@NonNull View v) { |
| 726 | + } |
| 727 | + |
| 728 | + @Override |
| 729 | + public void onViewDetachedFromWindow(@NonNull View v) { |
| 730 | + // Clear search when SearchView is closed abnormally, e.g. switch from QuickNote to To-Do when SearchView is opened |
| 731 | + if (searchView.getQuery().length() > 0) { |
| 732 | + searchView.setQuery("", false); // This will make onQueryTextChange be called back |
| 733 | + } |
| 734 | + if (!searchView.isIconified()) { |
| 735 | + searchView.setIconified(true); |
| 736 | + } |
| 737 | + } |
| 738 | + }); |
| 739 | + |
| 740 | + // Because SearchView doesn't provide a public API to add custom buttons |
| 741 | + // We must get the searchPlate (the layout containing the text field and close button) from SearchView |
| 742 | + // This approach is more robust than reflection |
| 743 | + ViewGroup searchPlate = searchView.findViewById(androidx.appcompat.R.id.search_plate); |
| 744 | + if (searchPlate == null) { |
| 745 | + // Ensure that SearchView is always available even if getting searchPlate fails |
| 746 | + searchView.setSubmitButtonEnabled(true); |
| 747 | + return; |
| 748 | + } |
| 749 | + |
| 750 | + Context searchViewContext = searchView.getContext(); |
| 751 | + LinearLayout linearLayout = new LinearLayout(searchViewContext); |
| 752 | + |
| 753 | + // Add search result TextView |
| 754 | + TextView resultTextView = new TextView(searchViewContext); |
| 755 | + resultTextView.setGravity(Gravity.CENTER); |
| 756 | + LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams( |
| 757 | + LinearLayout.LayoutParams.WRAP_CONTENT, |
| 758 | + LinearLayout.LayoutParams.MATCH_PARENT |
| 759 | + ); |
| 760 | + layoutParams.setMarginEnd(14); |
| 761 | + resultTextView.setLayoutParams(layoutParams); |
| 762 | + linearLayout.addView(resultTextView); |
| 763 | + |
| 764 | + // Add previous match Button |
| 765 | + ImageButton previousButton = new ImageButton(searchViewContext); |
| 766 | + previousButton.setImageResource(R.drawable.ic_baseline_keyboard_arrow_up_24); |
| 767 | + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { |
| 768 | + previousButton.setTooltipText(getString(R.string.previous_match)); |
| 769 | + } |
| 770 | + linearLayout.addView(previousButton); |
| 771 | + |
| 772 | + // Add next match Button |
| 773 | + ImageButton nextButton = new ImageButton(searchViewContext); |
| 774 | + nextButton.setImageResource(R.drawable.ic_baseline_keyboard_arrow_down_24); |
| 775 | + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { |
| 776 | + nextButton.setTooltipText(getString(R.string.next_match)); |
| 777 | + } |
| 778 | + linearLayout.addView(nextButton); |
| 779 | + |
| 780 | + // Apply to SearchView |
| 781 | + searchPlate.addView(linearLayout, 1); |
| 782 | + |
| 783 | + // Set listeners |
| 784 | + previousButton.setOnClickListener(v -> { |
| 785 | + if (_webView != null) { |
| 786 | + _webView.findNext(false); |
| 787 | + } |
| 788 | + }); |
| 789 | + nextButton.setOnClickListener(v -> { |
| 790 | + if (_webView != null) { |
| 791 | + _webView.findNext(true); |
| 792 | + } |
| 793 | + }); |
| 794 | + _webView.setFindListener((activeMatchOrdinal, numberOfMatches, isDoneCounting) -> { |
| 795 | + if (isDoneCounting) { |
| 796 | + String searchResult = ""; |
| 797 | + if (numberOfMatches > 0) { |
| 798 | + searchResult = (activeMatchOrdinal + 1) + "/" + numberOfMatches; |
| 799 | + } |
| 800 | + resultTextView.setText(searchResult); |
| 801 | + } |
| 802 | + }); |
| 803 | + } |
| 804 | + |
703 | 805 | private void setMarginBottom(final View view, final int marginBottom) { |
704 | 806 | final ViewGroup.MarginLayoutParams params = (ViewGroup.MarginLayoutParams) view.getLayoutParams(); |
705 | 807 | if (params != null) { |
@@ -867,6 +969,7 @@ public void setViewModeVisibility(final boolean show) { |
867 | 969 | setViewModeVisibility(show, true); |
868 | 970 | } |
869 | 971 |
|
| 972 | + @SuppressLint({"SetJavaScriptEnabled"}) |
870 | 973 | private void setupWebViewIfNeeded(final Activity activity) { |
871 | 974 | if (_webView == null) { |
872 | 975 | _webView = (WebView) _webViewStub.inflate(); |
|
0 commit comments