From 7cbeadca1d88d1beab2fdc6ba058c697ddd1031d Mon Sep 17 00:00:00 2001 From: Harshad Vedartham Date: Sat, 10 Sep 2022 00:03:53 -0700 Subject: [PATCH 01/40] Don't shift on resize --- .../frontend/textview/HighlightingEditor.java | 20 ++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) 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 8702ffe7ba..2e3afd7492 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 @@ -141,9 +141,12 @@ private void updateHighlighting(final boolean recompute) { // Addition of spans which require reflow can shift text on re-application of spans // we compute the resulting shift and scroll the view to compensate in order to make - // the experience smooth for the user - final int shiftTestLine = layout.getLineForVertical(_hlRect.centerY()); - final int oldOffset = layout.getLineBaseline(shiftTestLine); + // the experience smooth for the user. + int shiftTestLine = -1, oldOffset = -1; + if (_scrollView != null && _hlRect.height() == _olhHlRect.height()) { + shiftTestLine = layout.getLineForVertical(_hlRect.centerY()); + oldOffset = layout.getLineBaseline(shiftTestLine); + } final int[] newHlRegion = hlRegion(_hlRect); // Compute this _before_ clear try { @@ -159,13 +162,12 @@ private void updateHighlighting(final boolean recompute) { endBatchEdit(); } - _olhHlRect.set(_hlRect); - - final int shift = layout.getLineBaseline(shiftTestLine) - oldOffset; - if (_scrollView != null && Math.abs(shift) > 1) { - // Only apply the shift when not flicking or drag-scrolling + if (shiftTestLine >= 0) { + final int shift = layout.getLineBaseline(shiftTestLine) - oldOffset; _scrollView.slowScrollShift(shift); } + + _olhHlRect.set(_hlRect); } _isUpdatingDynamicHighlighting = false; @@ -464,7 +466,7 @@ public void insertOrReplaceTextOnCursor(final String newText) { sel[0] = Math.max(sel[0], 0); withAutoFormatDisabled(() -> edit.replace(sel[0], sel[1], finalText)); if (newCursorPos >= 0) { - setSelection(sel[0] + newCursorPos); + TextViewUtils.setSelectionAndShow(this, sel[0] + newCursorPos); } } } From 79524beaadefb4337d1212880078faab88f4bd34 Mon Sep 17 00:00:00 2001 From: Harshad Vedartham Date: Sat, 10 Sep 2022 09:01:12 -0700 Subject: [PATCH 02/40] Tighter logic --- .../markor/frontend/textview/HighlightingEditor.java | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) 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 2e3afd7492..39b2d279d0 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 @@ -55,7 +55,7 @@ public class HighlightingEditor extends AppCompatEditText { private boolean _isDynamicHighlightingEnabled = true; private Runnable _hlDebounced; // Debounced runnable which recomputes highlighting private boolean _hlEnabled; // Whether highlighting is enabled - private final Rect _olhHlRect; // Rect highlighting was previously applied to + private final Rect _oldHlRect; // Rect highlighting was previously applied to private final Rect _hlRect; // Current rect private int _hlShiftThreshold = -1; // How much to scroll before re-apply highlight private volatile boolean _hlPostQueued = false; @@ -77,7 +77,7 @@ public HighlightingEditor(Context context, AttributeSet attrs) { } _hlEnabled = false; - _olhHlRect = new Rect(); + _oldHlRect = new Rect(); _hlRect = new Rect(); addTextChangedListener(new GsTextWatcherAdapter() { @@ -109,8 +109,8 @@ public void afterTextChanged(final Editable s) { // --------------------------------------------------------------------------------------------- private boolean isScrollSignificant() { - return Math.abs(_hlRect.top - _olhHlRect.top) > _hlShiftThreshold || - Math.abs(_hlRect.bottom - _olhHlRect.bottom) > _hlShiftThreshold; + return (_oldHlRect.top - _hlRect.top) > _hlShiftThreshold || + (_hlRect.bottom - _oldHlRect.bottom) > _hlShiftThreshold; } private void updateDynamicHighlighting() { @@ -143,7 +143,7 @@ private void updateHighlighting(final boolean recompute) { // we compute the resulting shift and scroll the view to compensate in order to make // the experience smooth for the user. int shiftTestLine = -1, oldOffset = -1; - if (_scrollView != null && _hlRect.height() == _olhHlRect.height()) { + if (_scrollView != null && _hlRect.height() == _oldHlRect.height()) { shiftTestLine = layout.getLineForVertical(_hlRect.centerY()); oldOffset = layout.getLineBaseline(shiftTestLine); } @@ -167,7 +167,7 @@ private void updateHighlighting(final boolean recompute) { _scrollView.slowScrollShift(shift); } - _olhHlRect.set(_hlRect); + _oldHlRect.set(_hlRect); } _isUpdatingDynamicHighlighting = false; From 9b1a70f18dfdd5f6da9c3333c53f6d2a8eb184fe Mon Sep 17 00:00:00 2001 From: Harshad Vedartham Date: Sun, 11 Sep 2022 09:21:32 -0700 Subject: [PATCH 03/40] Reverting some changes --- .../gsantner/markor/frontend/textview/HighlightingEditor.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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 39b2d279d0..fa3c27b0ff 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 @@ -460,13 +460,13 @@ public void simulateKeyPress(int keyEvent_KEYCODE_SOMETHING) { public void insertOrReplaceTextOnCursor(final String newText) { final Editable edit = getText(); if (edit != null && newText != null) { - int newCursorPos = newText.indexOf(PLACE_CURSOR_HERE_TOKEN); + final int newCursorPos = newText.indexOf(PLACE_CURSOR_HERE_TOKEN); final String finalText = newText.replace(PLACE_CURSOR_HERE_TOKEN, ""); final int[] sel = TextViewUtils.getSelection(this); sel[0] = Math.max(sel[0], 0); withAutoFormatDisabled(() -> edit.replace(sel[0], sel[1], finalText)); if (newCursorPos >= 0) { - TextViewUtils.setSelectionAndShow(this, sel[0] + newCursorPos); + setSelection(sel[0] + newCursorPos); } } } From 3e2daea0aba18524d3116166dbbf8f3cc691025a Mon Sep 17 00:00:00 2001 From: Harshad Vedartham Date: Mon, 19 Sep 2022 20:49:46 -0700 Subject: [PATCH 04/40] Many more fixes --- .../activity/DocumentEditAndViewFragment.java | 7 +- .../markor/format/todotxt/TodoTxtFilter.java | 10 +- .../frontend/textview/HighlightingEditor.java | 4 + .../frontend/textview/TextViewUtils.java | 107 +++++++++++++++--- 4 files changed, 106 insertions(+), 22 deletions(-) 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 28b4dccc0d..141c97f691 100644 --- a/app/src/main/java/net/gsantner/markor/activity/DocumentEditAndViewFragment.java +++ b/app/src/main/java/net/gsantner/markor/activity/DocumentEditAndViewFragment.java @@ -655,6 +655,9 @@ private void setHorizontalScrollMode(final boolean wrap) { final int[] sel = TextViewUtils.getSelection(_hlEditor); + final boolean hlEnabled = _hlEditor.getHighlightingEnabled(); + _hlEditor.setHighlightingEnabled(false); + _primaryScrollView.removeAllViews(); if (_hsView != null) { _hsView.removeAllViews(); @@ -670,8 +673,10 @@ private void setHorizontalScrollMode(final boolean wrap) { _primaryScrollView.addView(_hlEditor); } + _hlEditor.setHighlightingEnabled(hlEnabled); + // Run after layout() of immediate parent completes - (wrap ? _primaryScrollView : _hsView).post(() -> TextViewUtils.setSelectionAndShow(_hlEditor, sel[0], sel[1])); + (wrap ? _primaryScrollView : _hsView).post(() -> TextViewUtils.setSelectionAndShow(_hlEditor, sel)); } } diff --git a/app/src/main/java/net/gsantner/markor/format/todotxt/TodoTxtFilter.java b/app/src/main/java/net/gsantner/markor/format/todotxt/TodoTxtFilter.java index 10cd4c1629..82f3141f02 100644 --- a/app/src/main/java/net/gsantner/markor/format/todotxt/TodoTxtFilter.java +++ b/app/src/main/java/net/gsantner/markor/format/todotxt/TodoTxtFilter.java @@ -37,6 +37,8 @@ public class TodoTxtFilter { private static final String KEYS = "keys"; private static final String TYPE = "type"; + private static final String NULL_SENTINEL = "NULL SENTINEL"; // As this has a space, it isn't a valid context etc + // For any type, return a function which maps a task -> a list of string keys public static GsCallback.r1, TodoTxtParser> keyGetter(final Context context, final String type) { switch (type) { @@ -109,7 +111,7 @@ public static void saveFilter(final Context context, final String saveTitle, fin obj.put(IS_AND, isAnd); final JSONArray keysArray = new JSONArray(); for (final String key : selKeys) { - keysArray.put(key); + keysArray.put(key != null ? key : NULL_SENTINEL); } obj.put(KEYS, keysArray); @@ -166,9 +168,9 @@ public static boolean deleteFilterIndex(final Context context, int index) { } public static List loadSavedFilters(final Context context) { + final SharedPreferences pref = context.getSharedPreferences(GsSharedPreferencesPropertyBackend.SHARED_PREF_APP, Context.MODE_PRIVATE); try { final List loadedViews = new ArrayList<>(); - final SharedPreferences pref = context.getSharedPreferences(GsSharedPreferencesPropertyBackend.SHARED_PREF_APP, Context.MODE_PRIVATE); final String jsonString = pref.getString(SAVED_TODO_VIEWS, "[]"); final JSONArray array = new JSONArray(jsonString); for (int i = 0; i < array.length(); i++) { @@ -180,13 +182,15 @@ public static List loadSavedFilters(final Context context) { gp.keys = new ArrayList<>(); final JSONArray keysArray = obj.getJSONArray(KEYS); for (int j = 0; j < keysArray.length(); j++) { - gp.keys.add(keysArray.getString(j)); + final String key = keysArray.getString(i); + gp.keys.add(NULL_SENTINEL.equals(key) ? null : key); } loadedViews.add(gp); } return loadedViews; } catch (JSONException e) { e.printStackTrace(); + pref.edit().remove(SAVED_TODO_VIEWS).apply(); } return Collections.emptyList(); } 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 fa3c27b0ff..62bc0005a9 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 @@ -210,6 +210,10 @@ public SyntaxHighlighterBase getHighlighter() { return _hl; } + public boolean getHighlightingEnabled() { + return _hlEnabled; + } + public boolean setHighlightingEnabled(final boolean enable) { final boolean prev = _hlEnabled; if (enable && !_hlEnabled) { 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 4e9d6fd6fd..f192857ae9 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 @@ -17,6 +17,9 @@ import android.text.Layout; import android.text.Selection; import android.text.TextUtils; +import android.view.View; +import android.view.ViewTreeObserver; +import android.view.animation.Animation; import android.widget.EditText; import android.widget.TextView; @@ -30,6 +33,7 @@ import java.util.Collections; import java.util.List; import java.util.Locale; +import java.util.Observer; import java.util.TreeSet; @SuppressWarnings({"CharsetObjectCanBeUsed", "WeakerAccess", "unused"}) @@ -277,29 +281,27 @@ public static void selectLines(final EditText edit, final List position } } - public static void showSelection(final TextView text) { - showSelection(text, text.getSelectionStart(), text.getSelectionEnd()); - } + public static @NonNull Rect getRegionRect(final TextView text, final int start, final int end) { - public static void showSelection(final TextView text, final int start, final int end) { + final Rect region = new Rect(); // Get view info // ------------------------------------------------------------ final Layout layout = text.getLayout(); if (layout == null) { - return; + return region; } final int _start = Math.min(start, end); final int _end = Math.max(start, end); if (start < 0 || end > text.length()) { - return; + return region; } final int lineStart = TextViewUtils.getLineStart(text.getText(), _start); final Rect viewSize = new Rect(); if (!text.getLocalVisibleRect(viewSize)) { - return; + return region; } // Region in Y @@ -309,8 +311,6 @@ public static void showSelection(final TextView text, final int start, final int final int selStartLineTop = layout.getLineTop(selStartLine); final int lineStartLineTop = layout.getLineTop(lineStartLine); - final Rect region = new Rect(); - if ((selStartLine - lineStartLine) <= 3) { // good to see the start of the line if close enough region.top = lineStartLineTop; @@ -327,23 +327,94 @@ public static void showSelection(final TextView text, final int start, final int final int startLeft = (int) layout.getPrimaryHorizontal(_start); final int halfWidth = viewSize.width() / 2; // Push the start to the middle of the screen - region.left = startLeft - halfWidth; - region.right = startLeft + halfWidth; + region.left = Math.max(startLeft - halfWidth, 0); + region.right = Math.min(startLeft + halfWidth, viewSize.width()); + + return region; + } + + // Due to dynamic highlighting we can't just requestRectangleOnScreen as + // when we scroll to the rect, the rect may no longer represent the ROI (layout changes). + // So we adopt an iterative scheme - scroll to the rect, check if rect is good and then + // repeat until we are satisfied + private static class SelectionShower implements ViewTreeObserver.OnScrollChangedListener { + final @NonNull TextView _text; + final ViewTreeObserver _observer; + final int _start, _end; + int _iterCount; + + public SelectionShower(@NonNull TextView text, int start, int end, int iterCount) { + _text = text; + _observer = text.getViewTreeObserver(); + _start = start; + _end = end; + _iterCount = iterCount; + } + + public void run() { + final Rect region = getRegionRect(_text, _start, _end); + if (!region.isEmpty()) { + if (_observer != null && _iterCount > 0) { + _observer.addOnScrollChangedListener(this); + } + _text.post(() -> _text.requestRectangleOnScreen(region)); + } + } + + @Override + public void onScrollChanged() { + final Rect region; + if (--_iterCount <= 0 || (region = getRegionRect(_text, _start, _end)).isEmpty()) { + _observer.removeOnScrollChangedListener(this); + return; + } + + if (!regionVisible(_text, region)) { + _text.post(() -> _text.requestRectangleOnScreen(region)); + } + } + } + + public static void showSelection(final TextView text, final int ... sel) { + if (sel != null && sel.length > 0) { + new SelectionShower(text, sel[0], sel.length > 1 ? sel[1] : sel[0], 5).run(); + } + } + + // Check if rect is visible + public static boolean regionVisible(final View view, final Rect region) { + if (region == null || region.isEmpty()) { + return true; + } + + final Rect visible = new Rect(); + if (!view.getLocalVisibleRect(visible) || visible.isEmpty()) { + return true; + } - // Call in post to try to make sure we run after any pending actions - text.post(() -> text.requestRectangleOnScreen(region)); + if (region.height() >= visible.height()) { + return visible.contains(region.left, region.top); + } else { + return visible.contains(region); + } } - public static void setSelectionAndShow(final EditText edit, final int start, final int... end) { - final int _end = end != null && end.length > 0 ? end[0] : start; - if (inRange(0, edit.length(), start, _end)) { + public static void setSelectionAndShow(final EditText edit, final int ... sel) { + if (sel == null || sel.length == 0) { + return; + } + + final int start = sel[0]; + final int end = sel.length > 1 ? sel[1] : start; + + if (inRange(0, edit.length(), start, end)) { edit.post(() -> { if (!edit.hasFocus()) { edit.requestFocus(); } - edit.setSelection(start, _end); - edit.post(() -> showSelection(edit, start, _end)); + edit.setSelection(start, end); + edit.post(() -> showSelection(edit, start, end)); }); } } From cc73725a1993866c3bba8dfbba51b82f2e355484 Mon Sep 17 00:00:00 2001 From: Harshad Vedartham Date: Mon, 19 Sep 2022 21:15:26 -0700 Subject: [PATCH 05/40] Updated TodoTxtParser -> TodoTxtTask --- .../activity/DocumentShareIntoFragment.java | 4 +- .../format/todotxt/TodoTxtActionButtons.java | 56 +++++++++---------- .../todotxt/TodoTxtAutoTextFormatter.java | 2 +- .../TodoTxtBasicSyntaxHighlighter.java | 26 ++++----- .../markor/format/todotxt/TodoTxtFilter.java | 27 +++++---- .../{TodoTxtParser.java => TodoTxtTask.java} | 42 +++++++------- .../format/todotxt/TodoTxtTextConverter.java | 2 +- .../markor/frontend/MarkorDialogFactory.java | 34 +++++------ .../markor/frontend/NewFileDialog.java | 6 +- .../todotxt/TodoTxtFileRecognitionTests.java | 2 +- 10 files changed, 103 insertions(+), 98 deletions(-) rename app/src/main/java/net/gsantner/markor/format/todotxt/{TodoTxtParser.java => TodoTxtTask.java} (91%) 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 c7916aa8c9..965b0571bc 100644 --- a/app/src/main/java/net/gsantner/markor/activity/DocumentShareIntoFragment.java +++ b/app/src/main/java/net/gsantner/markor/activity/DocumentShareIntoFragment.java @@ -30,7 +30,7 @@ import net.gsantner.markor.R; import net.gsantner.markor.format.FormatRegistry; import net.gsantner.markor.format.plaintext.PlaintextSyntaxHighlighter; -import net.gsantner.markor.format.todotxt.TodoTxtParser; +import net.gsantner.markor.format.todotxt.TodoTxtTask; import net.gsantner.markor.frontend.NewFileDialog; import net.gsantner.markor.frontend.filebrowser.MarkorFileBrowserFactory; import net.gsantner.markor.frontend.settings.MarkorPermissionChecker; @@ -311,7 +311,7 @@ public Boolean onPreferenceClicked(Preference preference, String key, int keyId) if (permc.doIfExtStoragePermissionGranted()) { String sep = "\n"; if (_appSettings.getDocumentAutoFormatEnabled(this._appSettings.getTodoFile().getAbsolutePath())) { - sep += TodoTxtParser.getToday() + " "; + sep += TodoTxtTask.getToday() + " "; } appendToExistingDocument(this._appSettings.getTodoFile(), sep, false); close = true; diff --git a/app/src/main/java/net/gsantner/markor/format/todotxt/TodoTxtActionButtons.java b/app/src/main/java/net/gsantner/markor/format/todotxt/TodoTxtActionButtons.java index e8b5b0b34c..a362667d87 100644 --- a/app/src/main/java/net/gsantner/markor/format/todotxt/TodoTxtActionButtons.java +++ b/app/src/main/java/net/gsantner/markor/format/todotxt/TodoTxtActionButtons.java @@ -82,13 +82,13 @@ protected int getFormatActionsKey() { @SuppressLint("NonConstantResourceId") @Override public boolean onActionClick(final @StringRes int action) { - final List selTasks = TodoTxtParser.getSelectedTasks(_hlEditor); + final List selTasks = TodoTxtTask.getSelectedTasks(_hlEditor); switch (action) { case R.string.abid_todotxt_toggle_done: { - final String doneMark = "x" + (_appSettings.isTodoAddCompletionDateEnabled() ? (" " + TodoTxtParser.getToday()) : "") + " "; + final String doneMark = "x" + (_appSettings.isTodoAddCompletionDateEnabled() ? (" " + TodoTxtTask.getToday()) : "") + " "; final String bodyWithPri = "(.*)(\\spri:([A-Z])(?=\\s|$))(.*)"; // +1 = pre, +2 = full tag, +3 = pri, +4 = post - final String doneWithDate = "^([Xx]\\s(?:" + TodoTxtParser.PT_DATE + "\\s)?)"; + final String doneWithDate = "^([Xx]\\s(?:" + TodoTxtTask.PT_DATE + "\\s)?)"; final String startingPriority = "^\\(([A-Z])\\)\\s"; runRegexReplaceAction( // If task not done and starts with a priority and contains a pri tag @@ -106,8 +106,8 @@ public boolean onActionClick(final @StringRes int action) { } case R.string.abid_todotxt_add_context: { final List contexts = new ArrayList<>(); - contexts.addAll(TodoTxtParser.getContexts(TodoTxtParser.getAllTasks(_hlEditor.getText()))); - contexts.addAll(new TodoTxtParser(_appSettings.getTodotxtAdditionalContextsAndProjects()).getContexts()); + contexts.addAll(TodoTxtTask.getContexts(TodoTxtTask.getAllTasks(_hlEditor.getText()))); + contexts.addAll(new TodoTxtTask(_appSettings.getTodotxtAdditionalContextsAndProjects()).getContexts()); MarkorDialogFactory.showSttContextDialog(getActivity(), contexts, (context) -> { insertUniqueItem((context.charAt(0) == '@') ? context : "@" + context); }); @@ -115,8 +115,8 @@ public boolean onActionClick(final @StringRes int action) { } case R.string.abid_todotxt_add_project: { final List projects = new ArrayList<>(); - projects.addAll(TodoTxtParser.getProjects(TodoTxtParser.getAllTasks(_hlEditor.getText()))); - projects.addAll(new TodoTxtParser(_appSettings.getTodotxtAdditionalContextsAndProjects()).getProjects()); + projects.addAll(TodoTxtTask.getProjects(TodoTxtTask.getAllTasks(_hlEditor.getText()))); + projects.addAll(new TodoTxtTask(_appSettings.getTodotxtAdditionalContextsAndProjects()).getProjects()); MarkorDialogFactory.showSttProjectDialog(getActivity(), projects, (project) -> { insertUniqueItem((project.charAt(0) == '+') ? project : "+" + project); }); @@ -126,10 +126,10 @@ public boolean onActionClick(final @StringRes int action) { MarkorDialogFactory.showPriorityDialog(getActivity(), selTasks.get(0).getPriority(), (priority) -> { ArrayList patterns = new ArrayList<>(); if (priority.length() > 1) { - patterns.add(new ReplacePattern(TodoTxtParser.PATTERN_PRIORITY_ANY, "")); + patterns.add(new ReplacePattern(TodoTxtTask.PATTERN_PRIORITY_ANY, "")); } else if (priority.length() == 1) { final String _priority = String.format("(%c) ", priority.charAt(0)); - patterns.add(new ReplacePattern(TodoTxtParser.PATTERN_PRIORITY_ANY, _priority)); + patterns.add(new ReplacePattern(TodoTxtTask.PATTERN_PRIORITY_ANY, _priority)); patterns.add(new ReplacePattern("^\\s*", _priority)); } runRegexReplaceAction(patterns); @@ -145,9 +145,9 @@ public boolean onActionClick(final @StringRes int action) { MarkorDialogFactory.showSttArchiveDialog(getActivity(), (callbackPayload) -> { callbackPayload = Document.normalizeFilename(callbackPayload); - final ArrayList keep = new ArrayList<>(); - final ArrayList move = new ArrayList<>(); - final List allTasks = TodoTxtParser.getAllTasks(_hlEditor.getText()); + final ArrayList keep = new ArrayList<>(); + final ArrayList move = new ArrayList<>(); + final List allTasks = TodoTxtTask.getAllTasks(_hlEditor.getText()); final int[] sel = TextViewUtils.getSelection(_hlEditor); final CharSequence text = _hlEditor.getText(); @@ -155,7 +155,7 @@ public boolean onActionClick(final @StringRes int action) { final int[] selEnd = TextViewUtils.getLineOffsetFromIndex(text, sel[1]); for (int i = 0; i < allTasks.size(); i++) { - final TodoTxtParser task = allTasks.get(i); + final TodoTxtTask task = allTasks.get(i); if (task.isDone()) { move.add(task); if (i <= selStart[0]) selStart[0]--; @@ -170,11 +170,11 @@ public boolean onActionClick(final @StringRes int action) { if (doneFile.exists() && doneFile.canRead()) { doneFileContents = GsFileUtils.readTextFileFast(doneFile).first.trim() + "\n"; } - doneFileContents += TodoTxtParser.tasksToString(move) + "\n"; + doneFileContents += TodoTxtTask.tasksToString(move) + "\n"; // Write to done file if (new Document(doneFile).saveContent(getActivity(), doneFileContents)) { - final String tasksString = TodoTxtParser.tasksToString(keep); + final String tasksString = TodoTxtTask.tasksToString(keep); _hlEditor.setText(tasksString); _hlEditor.setSelection( TextViewUtils.getIndexFromLineOffset(tasksString, selStart), @@ -190,9 +190,9 @@ public boolean onActionClick(final @StringRes int action) { MarkorDialogFactory.showSttSortDialogue(getActivity(), (orderBy, descending) -> new Thread() { @Override public void run() { - final List tasks = TodoTxtParser.getAllTasks(_hlEditor.getText()); - TodoTxtParser.sortTasks(tasks, orderBy, descending); - setEditorTextAsync(TodoTxtParser.tasksToString(tasks)); + final List tasks = TodoTxtTask.getAllTasks(_hlEditor.getText()); + TodoTxtTask.sortTasks(tasks, orderBy, descending); + setEditorTextAsync(TodoTxtTask.tasksToString(tasks)); _appSettings.setStringList(LAST_SORT_ORDER_KEY, Arrays.asList(orderBy, Boolean.toString(descending))); } }.start()); @@ -219,9 +219,9 @@ public boolean onActionLongClick(final @StringRes int action) { case R.string.abid_todotxt_sort_todo: { final List last = _appSettings.getStringList(LAST_SORT_ORDER_KEY); if (last != null && last.size() == 2) { - final List tasks = TodoTxtParser.getAllTasks(_hlEditor.getText()); - TodoTxtParser.sortTasks(tasks, last.get(0), Boolean.parseBoolean(last.get(1))); - setEditorTextAsync(TodoTxtParser.tasksToString(tasks)); + final List tasks = TodoTxtTask.getAllTasks(_hlEditor.getText()); + TodoTxtTask.sortTasks(tasks, last.get(0), Boolean.parseBoolean(last.get(1))); + setEditorTextAsync(TodoTxtTask.tasksToString(tasks)); } return true; } @@ -289,13 +289,13 @@ private void insertInline(String thing) { } private static Calendar parseDateString(final String dateString, final Calendar fallback) { - if (dateString == null || dateString.length() != TodoTxtParser.DATEF_YYYY_MM_DD_LEN) { + if (dateString == null || dateString.length() != TodoTxtTask.DATEF_YYYY_MM_DD_LEN) { return fallback; } try { Calendar calendar = Calendar.getInstance(); - calendar.setTime(TodoTxtParser.DATEF_YYYY_MM_DD.parse(dateString)); + calendar.setTime(TodoTxtTask.DATEF_YYYY_MM_DD.parse(dateString)); return calendar; } catch (ParseException e) { return fallback; @@ -311,7 +311,7 @@ private void setDate() { DatePickerDialog.OnDateSetListener listener = (_view, year, month, day) -> { Calendar fmtCal = Calendar.getInstance(); fmtCal.set(year, month, day); - final String newDate = TodoTxtParser.DATEF_YYYY_MM_DD.format(fmtCal.getTime()); + final String newDate = TodoTxtTask.DATEF_YYYY_MM_DD.format(fmtCal.getTime()); text.replace(sel[0], sel[1], newDate); }; @@ -324,24 +324,24 @@ private void setDate() { private void setDueDate(final int offset) { - final String dueString = TodoTxtParser.getSelectedTasks(_hlEditor).get(0).getDueDate(); + final String dueString = TodoTxtTask.getSelectedTasks(_hlEditor).get(0).getDueDate(); Calendar initDate = parseDateString(dueString, Calendar.getInstance()); initDate.add(Calendar.DAY_OF_MONTH, (dueString == null || dueString.isEmpty()) ? offset : 0); final DatePickerDialog.OnDateSetListener listener = (_view, year, month, day) -> { Calendar fmtCal = Calendar.getInstance(); fmtCal.set(year, month, day); - final String newDue = "due:" + TodoTxtParser.DATEF_YYYY_MM_DD.format(fmtCal.getTime()); + final String newDue = "due:" + TodoTxtTask.DATEF_YYYY_MM_DD.format(fmtCal.getTime()); runRegexReplaceAction( // Replace due date - new ReplacePattern(TodoTxtParser.PATTERN_DUE_DATE, "$1" + newDue + "$4"), + new ReplacePattern(TodoTxtTask.PATTERN_DUE_DATE, "$1" + newDue + "$4"), // Add due date to end if none already exists. Will correctly handle trailing whitespace. new ReplacePattern("\\s*$", " " + newDue) ); }; final DatePickerDialog.OnClickListener clear = (dialog, which) -> { - runRegexReplaceAction(new ReplacePattern(TodoTxtParser.PATTERN_DUE_DATE, "$4")); + runRegexReplaceAction(new ReplacePattern(TodoTxtTask.PATTERN_DUE_DATE, "$4")); }; new DateFragment() diff --git a/app/src/main/java/net/gsantner/markor/format/todotxt/TodoTxtAutoTextFormatter.java b/app/src/main/java/net/gsantner/markor/format/todotxt/TodoTxtAutoTextFormatter.java index 7010166675..1656da5ad9 100644 --- a/app/src/main/java/net/gsantner/markor/format/todotxt/TodoTxtAutoTextFormatter.java +++ b/app/src/main/java/net/gsantner/markor/format/todotxt/TodoTxtAutoTextFormatter.java @@ -31,6 +31,6 @@ public CharSequence filter(CharSequence source, int start, int end, Spanned dest } private CharSequence autoIndent(CharSequence source) { - return source + TodoTxtParser.DATEF_YYYY_MM_DD.format(new Date()) + " "; + return source + TodoTxtTask.DATEF_YYYY_MM_DD.format(new Date()) + " "; } } diff --git a/app/src/main/java/net/gsantner/markor/format/todotxt/TodoTxtBasicSyntaxHighlighter.java b/app/src/main/java/net/gsantner/markor/format/todotxt/TodoTxtBasicSyntaxHighlighter.java index f413c67fd1..1a2a2a894a 100644 --- a/app/src/main/java/net/gsantner/markor/format/todotxt/TodoTxtBasicSyntaxHighlighter.java +++ b/app/src/main/java/net/gsantner/markor/format/todotxt/TodoTxtBasicSyntaxHighlighter.java @@ -29,24 +29,24 @@ public TodoTxtBasicSyntaxHighlighter(final AppSettings as) { @Override public void generateSpans() { createSmallBlueLinkSpans(); - createColorSpanForMatches(TodoTxtParser.PATTERN_CONTEXTS, COLOR_CONTEXT); - createColorSpanForMatches(TodoTxtParser.PATTERN_PROJECTS, COLOR_CATEGORY); - createStyleSpanForMatches(TodoTxtParser.PATTERN_KEY_VALUE_PAIRS, Typeface.ITALIC); + createColorSpanForMatches(TodoTxtTask.PATTERN_CONTEXTS, COLOR_CONTEXT); + createColorSpanForMatches(TodoTxtTask.PATTERN_PROJECTS, COLOR_CATEGORY); + createStyleSpanForMatches(TodoTxtTask.PATTERN_KEY_VALUE_PAIRS, Typeface.ITALIC); // Priorities - createSpanForMatches(TodoTxtParser.PATTERN_PRIORITY_A, new HighlightSpan().setForeColor(COLOR_PRIORITY_A).setBold(true)); - createSpanForMatches(TodoTxtParser.PATTERN_PRIORITY_B, new HighlightSpan().setForeColor(COLOR_PRIORITY_B).setBold(true)); - createSpanForMatches(TodoTxtParser.PATTERN_PRIORITY_C, new HighlightSpan().setForeColor(COLOR_PRIORITY_C).setBold(true)); - createSpanForMatches(TodoTxtParser.PATTERN_PRIORITY_D, new HighlightSpan().setForeColor(COLOR_PRIORITY_D).setBold(true)); - createSpanForMatches(TodoTxtParser.PATTERN_PRIORITY_E, new HighlightSpan().setForeColor(COLOR_PRIORITY_E).setBold(true)); - createSpanForMatches(TodoTxtParser.PATTERN_PRIORITY_F, new HighlightSpan().setForeColor(COLOR_PRIORITY_F).setBold(true)); - createStyleSpanForMatches(TodoTxtParser.PATTERN_PRIORITY_G_TO_Z, Typeface.BOLD); + createSpanForMatches(TodoTxtTask.PATTERN_PRIORITY_A, new HighlightSpan().setForeColor(COLOR_PRIORITY_A).setBold(true)); + createSpanForMatches(TodoTxtTask.PATTERN_PRIORITY_B, new HighlightSpan().setForeColor(COLOR_PRIORITY_B).setBold(true)); + createSpanForMatches(TodoTxtTask.PATTERN_PRIORITY_C, new HighlightSpan().setForeColor(COLOR_PRIORITY_C).setBold(true)); + createSpanForMatches(TodoTxtTask.PATTERN_PRIORITY_D, new HighlightSpan().setForeColor(COLOR_PRIORITY_D).setBold(true)); + createSpanForMatches(TodoTxtTask.PATTERN_PRIORITY_E, new HighlightSpan().setForeColor(COLOR_PRIORITY_E).setBold(true)); + createSpanForMatches(TodoTxtTask.PATTERN_PRIORITY_F, new HighlightSpan().setForeColor(COLOR_PRIORITY_F).setBold(true)); + createStyleSpanForMatches(TodoTxtTask.PATTERN_PRIORITY_G_TO_Z, Typeface.BOLD); - createColorSpanForMatches(TodoTxtParser.PATTERN_CREATION_DATE, _isDarkMode ? COLOR_DATE_DARK : COLOR_DATE_LIGHT, 1); - createColorSpanForMatches(TodoTxtParser.PATTERN_DUE_DATE, COLOR_PRIORITY_A, 2, 3); + createColorSpanForMatches(TodoTxtTask.PATTERN_CREATION_DATE, _isDarkMode ? COLOR_DATE_DARK : COLOR_DATE_LIGHT, 1); + createColorSpanForMatches(TodoTxtTask.PATTERN_DUE_DATE, COLOR_PRIORITY_A, 2, 3); // Strike out done tasks // Note - as we now sort by start, projects, contexts, tags and due date will be highlighted for done tasks - createSpanForMatches(TodoTxtParser.PATTERN_DONE, new HighlightSpan().setForeColor(_isDarkMode ? COLOR_DONE_DARK : COLOR_DONE_LIGHT).setStrike(true)); + createSpanForMatches(TodoTxtTask.PATTERN_DONE, new HighlightSpan().setForeColor(_isDarkMode ? COLOR_DONE_DARK : COLOR_DONE_LIGHT).setStrike(true)); } } \ No newline at end of file diff --git a/app/src/main/java/net/gsantner/markor/format/todotxt/TodoTxtFilter.java b/app/src/main/java/net/gsantner/markor/format/todotxt/TodoTxtFilter.java index 82f3141f02..edfccaba9a 100644 --- a/app/src/main/java/net/gsantner/markor/format/todotxt/TodoTxtFilter.java +++ b/app/src/main/java/net/gsantner/markor/format/todotxt/TodoTxtFilter.java @@ -40,30 +40,33 @@ public class TodoTxtFilter { private static final String NULL_SENTINEL = "NULL SENTINEL"; // As this has a space, it isn't a valid context etc // For any type, return a function which maps a task -> a list of string keys - public static GsCallback.r1, TodoTxtParser> keyGetter(final Context context, final String type) { + public static GsCallback.r1, TodoTxtTask> keyGetter(final Context context, final String type) { switch (type) { case PROJECT: - return TodoTxtParser::getProjects; + return TodoTxtTask::getProjects; case CONTEXT: - return TodoTxtParser::getContexts; + return TodoTxtTask::getContexts; case PRIORITY: - return task -> task.getPriority() == TodoTxtParser.PRIORITY_NONE ? Collections.emptyList() : Collections.singletonList(Character.toString(task.getPriority())); + return task -> task.getPriority() == TodoTxtTask.PRIORITY_NONE ? Collections.emptyList() : Collections.singletonList(Character.toString(task.getPriority())); case DUE: - final Map statusMap = new HashMap<>(); - statusMap.put(TodoTxtParser.TodoDueState.TODAY, context.getString(R.string.due_today)); - statusMap.put(TodoTxtParser.TodoDueState.OVERDUE, context.getString(R.string.due_overdue)); - statusMap.put(TodoTxtParser.TodoDueState.FUTURE, context.getString(R.string.due_future)); - return task -> task.getDueStatus() == TodoTxtParser.TodoDueState.NONE ? Collections.emptyList() : Collections.singletonList(statusMap.get(task.getDueStatus())); + final Map statusMap = new HashMap<>(); + statusMap.put(TodoTxtTask.TodoDueState.TODAY, context.getString(R.string.due_today)); + statusMap.put(TodoTxtTask.TodoDueState.OVERDUE, context.getString(R.string.due_overdue)); + statusMap.put(TodoTxtTask.TodoDueState.FUTURE, context.getString(R.string.due_future)); + return task -> task.getDueStatus() == TodoTxtTask.TodoDueState.NONE ? Collections.emptyList() : Collections.singletonList(statusMap.get(task.getDueStatus())); } return null; } // For a list of keys and a task -> key mapping, return a function which selects tasks - public static GsCallback.b1 taskSelector(final Collection keys, final GsCallback.r1, TodoTxtParser> keyGetter, final boolean isAnd) { + public static GsCallback.b1 taskSelector( + final Collection keys, + final GsCallback.r1, TodoTxtTask> keyGetter, + final boolean isAnd) { - final boolean noneIncluded = keys.remove(null); - final Set searchSet = (keys instanceof HashSet) ? (HashSet) keys : new HashSet<>(keys); + final Set searchSet = new HashSet<>(keys); + final boolean noneIncluded = searchSet.remove(null); return (task) -> { final List taskKeys = keyGetter.callback(task); diff --git a/app/src/main/java/net/gsantner/markor/format/todotxt/TodoTxtParser.java b/app/src/main/java/net/gsantner/markor/format/todotxt/TodoTxtTask.java similarity index 91% rename from app/src/main/java/net/gsantner/markor/format/todotxt/TodoTxtParser.java rename to app/src/main/java/net/gsantner/markor/format/todotxt/TodoTxtTask.java index cf3d0ae315..8947c10eb1 100644 --- a/app/src/main/java/net/gsantner/markor/format/todotxt/TodoTxtParser.java +++ b/app/src/main/java/net/gsantner/markor/format/todotxt/TodoTxtTask.java @@ -26,7 +26,7 @@ import java.util.regex.Matcher; import java.util.regex.Pattern; -public class TodoTxtParser { +public class TodoTxtTask { // // Static memebers @@ -69,63 +69,63 @@ public static boolean isTodoFile(String filepath) { || TODOTXT_FILE_PATTERN.matcher(filepath).matches() && (filepath.endsWith(".txt") || filepath.endsWith(".text"))); } - public static List getTasks(final CharSequence text, final int selStart, final int selEnd) { + public static List getTasks(final CharSequence text, final int selStart, final int selEnd) { final String[] lines = text.subSequence( TextViewUtils.getLineStart(text, selStart), TextViewUtils.getLineEnd(text, selEnd) ).toString().split("\n"); - final List tasks = new ArrayList<>(); + final List tasks = new ArrayList<>(); for (final String line : lines) { - tasks.add(new TodoTxtParser(line)); + tasks.add(new TodoTxtTask(line)); } return tasks; } - public static List getSelectedTasks(final TextView view) { + public static List getSelectedTasks(final TextView view) { final int[] sel = TextViewUtils.getSelection(view); return getTasks(view.getText(), sel[0], sel[1]); } - public static List getAllTasks(final CharSequence text) { + public static List getAllTasks(final CharSequence text) { return getTasks(text, 0, text.length()); } - public static List getProjects(final List tasks) { + public static List getProjects(final List tasks) { final TreeSet set = new TreeSet<>(); - for (final TodoTxtParser task : tasks) { + for (final TodoTxtTask task : tasks) { set.addAll(task.getProjects()); } return new ArrayList<>(set); } - public static List getContexts(final List tasks) { + public static List getContexts(final List tasks) { final TreeSet set = new TreeSet<>(); - for (final TodoTxtParser task : tasks) { + for (final TodoTxtTask task : tasks) { set.addAll(task.getContexts()); } return new ArrayList<>(set); } - public static List getPriorities(final List tasks) { + public static List getPriorities(final List tasks) { final TreeSet set = new TreeSet<>(); - for (final TodoTxtParser task : tasks) { + for (final TodoTxtTask task : tasks) { set.add(task.getPriority()); } return new ArrayList<>(set); } - public static List getDueStates(final List tasks) { + public static List getDueStates(final List tasks) { final TreeSet set = new TreeSet<>(); - for (final TodoTxtParser task : tasks) { + for (final TodoTxtTask task : tasks) { set.add(task.getDueStatus()); } return new ArrayList<>(set); } - public static String tasksToString(final List tasks) { + public static String tasksToString(final List tasks) { StringBuilder builder = new StringBuilder(); - for (TodoTxtParser task : tasks) { + for (TodoTxtTask task : tasks) { builder.append(task.getLine()); builder.append('\n'); } @@ -150,7 +150,7 @@ public static String tasksToString(final List tasks) { private String description = null; private TodoDueState dueStatus = null; - public TodoTxtParser(final String line) { + public TodoTxtTask(final String line) { this.line = line; } @@ -276,12 +276,12 @@ private static boolean isPatternFindable(final String text, final Pattern patter } // Sort tasks array and return it. Changes input array. - public static List sortTasks(List tasks, final String orderBy, final boolean descending) { + public static List sortTasks(List tasks, final String orderBy, final boolean descending) { Collections.sort(tasks, new SttTaskSimpleComparator(orderBy, descending)); return tasks; } - public static class SttTaskSimpleComparator implements Comparator { + public static class SttTaskSimpleComparator implements Comparator { private final String _orderBy; private final boolean _descending; @@ -299,7 +299,7 @@ public SttTaskSimpleComparator(final String orderBy, final Boolean descending) { } @Override - public int compare(final TodoTxtParser x, final TodoTxtParser y) { + public int compare(final TodoTxtTask x, final TodoTxtTask y) { // Always push done tasks to the bottom. Note ascending is small -> big. final int doneCompare = Integer.compare(x.isDone() ? 1 : 0, y.isDone() ? 1 : 0); @@ -360,7 +360,7 @@ private int compareNull(final String x, final String y) { return Integer.compare(xi, yi); } - private int compareDone(final TodoTxtParser a, TodoTxtParser b) { + private int compareDone(final TodoTxtTask a, TodoTxtTask b) { return Integer.compare(a.isDone() ? 1 : 0, b.isDone() ? 1 : 0); } diff --git a/app/src/main/java/net/gsantner/markor/format/todotxt/TodoTxtTextConverter.java b/app/src/main/java/net/gsantner/markor/format/todotxt/TodoTxtTextConverter.java index a223f20062..77e4e56bf7 100644 --- a/app/src/main/java/net/gsantner/markor/format/todotxt/TodoTxtTextConverter.java +++ b/app/src/main/java/net/gsantner/markor/format/todotxt/TodoTxtTextConverter.java @@ -51,6 +51,6 @@ protected String getContentType() { @Override protected boolean isFileOutOfThisFormat(String filepath, String extWithDot) { - return TodoTxtParser.isTodoFile(filepath.replace(JavaPasswordbasedCryption.DEFAULT_ENCRYPTION_EXTENSION, "")); + return TodoTxtTask.isTodoFile(filepath.replace(JavaPasswordbasedCryption.DEFAULT_ENCRYPTION_EXTENSION, "")); } } 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 a52a25a566..8f0e8dd64c 100644 --- a/app/src/main/java/net/gsantner/markor/frontend/MarkorDialogFactory.java +++ b/app/src/main/java/net/gsantner/markor/frontend/MarkorDialogFactory.java @@ -9,13 +9,13 @@ #########################################################*/ package net.gsantner.markor.frontend; -import static net.gsantner.markor.format.todotxt.TodoTxtParser.SttTaskSimpleComparator.BY_CONTEXT; -import static net.gsantner.markor.format.todotxt.TodoTxtParser.SttTaskSimpleComparator.BY_CREATION_DATE; -import static net.gsantner.markor.format.todotxt.TodoTxtParser.SttTaskSimpleComparator.BY_DESCRIPTION; -import static net.gsantner.markor.format.todotxt.TodoTxtParser.SttTaskSimpleComparator.BY_DUE_DATE; -import static net.gsantner.markor.format.todotxt.TodoTxtParser.SttTaskSimpleComparator.BY_LINE; -import static net.gsantner.markor.format.todotxt.TodoTxtParser.SttTaskSimpleComparator.BY_PRIORITY; -import static net.gsantner.markor.format.todotxt.TodoTxtParser.SttTaskSimpleComparator.BY_PROJECT; +import static net.gsantner.markor.format.todotxt.TodoTxtTask.SttTaskSimpleComparator.BY_CONTEXT; +import static net.gsantner.markor.format.todotxt.TodoTxtTask.SttTaskSimpleComparator.BY_CREATION_DATE; +import static net.gsantner.markor.format.todotxt.TodoTxtTask.SttTaskSimpleComparator.BY_DESCRIPTION; +import static net.gsantner.markor.format.todotxt.TodoTxtTask.SttTaskSimpleComparator.BY_DUE_DATE; +import static net.gsantner.markor.format.todotxt.TodoTxtTask.SttTaskSimpleComparator.BY_LINE; +import static net.gsantner.markor.format.todotxt.TodoTxtTask.SttTaskSimpleComparator.BY_PRIORITY; +import static net.gsantner.markor.format.todotxt.TodoTxtTask.SttTaskSimpleComparator.BY_PROJECT; import android.annotation.SuppressLint; import android.app.Activity; @@ -40,7 +40,7 @@ import net.gsantner.markor.R; import net.gsantner.markor.format.todotxt.TodoTxtBasicSyntaxHighlighter; import net.gsantner.markor.format.todotxt.TodoTxtFilter; -import net.gsantner.markor.format.todotxt.TodoTxtParser; +import net.gsantner.markor.format.todotxt.TodoTxtTask; import net.gsantner.markor.frontend.filesearch.FileSearchDialog; import net.gsantner.markor.frontend.filesearch.FileSearchEngine; import net.gsantner.markor.frontend.filesearch.FileSearchResultSelectorDialog; @@ -275,7 +275,7 @@ public static void showSttFilteringDialog(final Activity activity, final EditTex options.add(activity.getString(R.string.completed)); icons.add(R.drawable.ic_check_black_24dp); callbacks.add(() -> { - final GsSearchOrCustomTextDialog.DialogOptions dopt2 = makeSttLineSelectionDialog(activity, text, TodoTxtParser::isDone); + final GsSearchOrCustomTextDialog.DialogOptions dopt2 = makeSttLineSelectionDialog(activity, text, TodoTxtTask::isDone); dopt2.highlighter = null; // Don't need the grey + strikeout highlighting. Makes it harder to see. dopt2.titleText = R.string.completed; GsSearchOrCustomTextDialog.showMultiChoiceDialogWithSearchFilterUI(activity, dopt2); @@ -339,12 +339,12 @@ public static void showSttKeySearchDialog(final Activity activity, final EditTex GsSearchOrCustomTextDialog.DialogOptions dopt = new GsSearchOrCustomTextDialog.DialogOptions(); baseConf(activity, dopt); - final GsCallback.r1, TodoTxtParser> getKeys = TodoTxtFilter.keyGetter(activity, queryType); - final List allTasks = TodoTxtParser.getAllTasks(text.getText()); + final GsCallback.r1, TodoTxtTask> getKeys = TodoTxtFilter.keyGetter(activity, queryType); + final List allTasks = TodoTxtTask.getAllTasks(text.getText()); final List keys = new ArrayList<>(); final int[] noneCount = {0}; // Using an array as we need a final var - for (final TodoTxtParser task : allTasks) { + for (final TodoTxtTask task : allTasks) { if (!task.isDone()) { final List taskKeys = getKeys.callback(task); noneCount[0] += (taskKeys.size() == 0) ? 1 : 0; @@ -395,7 +395,9 @@ public static void showSttKeySearchDialog(final Activity activity, final EditTex for (int i = noneIncluded ? 1 : 0; i < keyIndices.size(); i++) { selKeys.add(data.get(keyIndices.get(i))); } - selKeys.addAll(noneIncluded ? Collections.singletonList(null) : Collections.emptyList()); + if (noneIncluded) { + selKeys.add(null); + } final GsSearchOrCustomTextDialog.DialogOptions doptSel = makeSttLineSelectionDialog(activity, text, TodoTxtFilter.taskSelector(selKeys, getKeys, useAnd[0])); doptSel.messageText = activity.getString(title); @@ -422,10 +424,10 @@ public static void showSttKeySearchDialog(final Activity activity, final EditTex GsSearchOrCustomTextDialog.showMultiChoiceDialogWithSearchFilterUI(activity, dopt); } - public static GsSearchOrCustomTextDialog.DialogOptions makeSttLineSelectionDialog(final Activity activity, final EditText text, final GsCallback.b1 filter) { + public static GsSearchOrCustomTextDialog.DialogOptions makeSttLineSelectionDialog(final Activity activity, final EditText text, final GsCallback.b1 filter) { GsSearchOrCustomTextDialog.DialogOptions dopt = new GsSearchOrCustomTextDialog.DialogOptions(); baseConf(activity, dopt); - final List allTasks = TodoTxtParser.getAllTasks(text.getText()); + final List allTasks = TodoTxtTask.getAllTasks(text.getText()); final List lines = new ArrayList<>(); final List lineIndices = new ArrayList<>(); for (int i = 0; i < allTasks.size(); i++) { @@ -589,7 +591,7 @@ public static void showPriorityDialog(Activity activity, char selectedPriority, availableData.add(Character.toString((char) i)); } highlightedData.add(none); - if (selectedPriority != TodoTxtParser.PRIORITY_NONE) { + if (selectedPriority != TodoTxtTask.PRIORITY_NONE) { highlightedData.add(Character.toString(selectedPriority)); } diff --git a/app/src/main/java/net/gsantner/markor/frontend/NewFileDialog.java b/app/src/main/java/net/gsantner/markor/frontend/NewFileDialog.java index a70b509992..0dde7aa615 100644 --- a/app/src/main/java/net/gsantner/markor/frontend/NewFileDialog.java +++ b/app/src/main/java/net/gsantner/markor/frontend/NewFileDialog.java @@ -32,7 +32,7 @@ import net.gsantner.markor.ApplicationObject; import net.gsantner.markor.R; -import net.gsantner.markor.format.todotxt.TodoTxtParser; +import net.gsantner.markor.format.todotxt.TodoTxtTask; import net.gsantner.markor.format.wikitext.WikitextActionButtons; import net.gsantner.markor.model.AppSettings; import net.gsantner.markor.model.Document; @@ -143,7 +143,7 @@ private AlertDialog.Builder makeDialog(final File basedir, final boolean allowCr String prefix = null; if (pos == 3) { // Jekyll - prefix = TodoTxtParser.DATEF_YYYY_MM_DD.format(new Date()) + "-"; + prefix = TodoTxtTask.DATEF_YYYY_MM_DD.format(new Date()) + "-"; } else if (pos == 9) { //ZettelKasten prefix = new SimpleDateFormat("yyyyMMddHHmm", Locale.ROOT).format(new Date()) + "-"; } @@ -313,7 +313,7 @@ private byte[] getTemplateContent(final Spinner templateSpinner, final File base return null; } } - t = t.replace("{{ template.timestamp_date_yyyy_mm_dd }}", TodoTxtParser.DATEF_YYYY_MM_DD.format(new Date())); + t = t.replace("{{ template.timestamp_date_yyyy_mm_dd }}", TodoTxtTask.DATEF_YYYY_MM_DD.format(new Date())); if (encrypt && Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { final char[] pass = ApplicationObject.settings().getDefaultPassword(); diff --git a/app/src/test/java/net/gsantner/markor/format/todotxt/TodoTxtFileRecognitionTests.java b/app/src/test/java/net/gsantner/markor/format/todotxt/TodoTxtFileRecognitionTests.java index 65fe725482..bf95de1b87 100644 --- a/app/src/test/java/net/gsantner/markor/format/todotxt/TodoTxtFileRecognitionTests.java +++ b/app/src/test/java/net/gsantner/markor/format/todotxt/TodoTxtFileRecognitionTests.java @@ -20,7 +20,7 @@ public class TodoTxtFileRecognitionTests { @Test public void checkTodoTxtFileRecognition() { - Pattern p = TodoTxtParser.TODOTXT_FILE_PATTERN; + Pattern p = TodoTxtTask.TODOTXT_FILE_PATTERN; assertThat(ispm(p, "todo.txt")).isEqualTo(true); assertThat(ispm(p, "ToDO.txt")).isEqualTo(true); assertThat(ispm(p, "todo.archive.txt")).isEqualTo(true); From fc3a4f984aceab43d33867b5b8369aa08ef4cc9d Mon Sep 17 00:00:00 2001 From: Harshad Vedartham Date: Tue, 20 Sep 2022 21:02:09 -0700 Subject: [PATCH 06/40] Respect dotfile hidden in widget --- .../net/gsantner/markor/format/todotxt/TodoTxtFilter.java | 2 +- .../net/gsantner/markor/frontend/MarkorDialogFactory.java | 3 ++- .../java/other/writeily/widget/WrFilesWidgetFactory.java | 7 ++++--- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/app/src/main/java/net/gsantner/markor/format/todotxt/TodoTxtFilter.java b/app/src/main/java/net/gsantner/markor/format/todotxt/TodoTxtFilter.java index edfccaba9a..8e277529e9 100644 --- a/app/src/main/java/net/gsantner/markor/format/todotxt/TodoTxtFilter.java +++ b/app/src/main/java/net/gsantner/markor/format/todotxt/TodoTxtFilter.java @@ -185,7 +185,7 @@ public static List loadSavedFilters(final Context context) { gp.keys = new ArrayList<>(); final JSONArray keysArray = obj.getJSONArray(KEYS); for (int j = 0; j < keysArray.length(); j++) { - final String key = keysArray.getString(i); + final String key = keysArray.getString(j); gp.keys.add(NULL_SENTINEL.equals(key) ? null : key); } loadedViews.add(gp); 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 8f0e8dd64c..7b137a6051 100644 --- a/app/src/main/java/net/gsantner/markor/frontend/MarkorDialogFactory.java +++ b/app/src/main/java/net/gsantner/markor/frontend/MarkorDialogFactory.java @@ -399,7 +399,8 @@ public static void showSttKeySearchDialog(final Activity activity, final EditTex selKeys.add(null); } - final GsSearchOrCustomTextDialog.DialogOptions doptSel = makeSttLineSelectionDialog(activity, text, TodoTxtFilter.taskSelector(selKeys, getKeys, useAnd[0])); + final GsSearchOrCustomTextDialog.DialogOptions doptSel = makeSttLineSelectionDialog( + activity, text, TodoTxtFilter.taskSelector(selKeys, getKeys, useAnd[0])); doptSel.messageText = activity.getString(title); // Callback to save view diff --git a/app/thirdparty/java/other/writeily/widget/WrFilesWidgetFactory.java b/app/thirdparty/java/other/writeily/widget/WrFilesWidgetFactory.java index 77fe3c2ddb..c9fe16e490 100644 --- a/app/thirdparty/java/other/writeily/widget/WrFilesWidgetFactory.java +++ b/app/thirdparty/java/other/writeily/widget/WrFilesWidgetFactory.java @@ -54,6 +54,7 @@ public void onDataSetChanged() { private void updateFiles() { _widgetFilesList.clear(); final File dir = WrWidgetConfigure.getWidgetDirectory(_context, _appWidgetId); + final AppSettings as = ApplicationObject.settings(); if (dir.equals(GsFileBrowserListAdapter.VIRTUAL_STORAGE_RECENTS)) { _widgetFilesList.addAll(Arrays.asList(MarkorFileBrowserFactory.strlistToArray(ApplicationObject.settings().getRecentDocuments()))); @@ -62,11 +63,11 @@ private void updateFiles() { } else if (dir.equals(GsFileBrowserListAdapter.VIRTUAL_STORAGE_FAVOURITE)) { _widgetFilesList.addAll(ApplicationObject.settings().getFavouriteFiles()); } else if (dir.exists() && dir.canRead()) { - final File[] all = dir.listFiles(file -> true); + final boolean showDot = as.isFileBrowserFilterShowDotFiles(); + final File[] all = dir.listFiles(file -> !showDot || !file.getName().startsWith(".")); _widgetFilesList.addAll(all != null ? Arrays.asList(all) : Collections.emptyList()); + GsFileUtils.sortFiles(_widgetFilesList, as.getFileBrowserSortByType(), as.isFileBrowserSortFolderFirst(), as.isFileBrowserSortReverse()); // Sort only if actual folder } - AppSettings as = ApplicationObject.settings(); - GsFileUtils.sortFiles(_widgetFilesList, as.getFileBrowserSortByType(), as.isFileBrowserSortFolderFirst(), as.isFileBrowserSortReverse()); // Sort only if actual folder } @Override From fe0ff373ae5e379a5a96e9ad6cd29c0eba8ec9da Mon Sep 17 00:00:00 2001 From: Harshad Vedartham Date: Tue, 20 Sep 2022 22:09:32 -0700 Subject: [PATCH 07/40] Fixed --- .../java/other/writeily/widget/WrFilesWidgetFactory.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/thirdparty/java/other/writeily/widget/WrFilesWidgetFactory.java b/app/thirdparty/java/other/writeily/widget/WrFilesWidgetFactory.java index c9fe16e490..8c0331b507 100644 --- a/app/thirdparty/java/other/writeily/widget/WrFilesWidgetFactory.java +++ b/app/thirdparty/java/other/writeily/widget/WrFilesWidgetFactory.java @@ -64,10 +64,10 @@ private void updateFiles() { _widgetFilesList.addAll(ApplicationObject.settings().getFavouriteFiles()); } else if (dir.exists() && dir.canRead()) { final boolean showDot = as.isFileBrowserFilterShowDotFiles(); - final File[] all = dir.listFiles(file -> !showDot || !file.getName().startsWith(".")); + final File[] all = dir.listFiles(file -> showDot || !file.getName().startsWith(".")); _widgetFilesList.addAll(all != null ? Arrays.asList(all) : Collections.emptyList()); - GsFileUtils.sortFiles(_widgetFilesList, as.getFileBrowserSortByType(), as.isFileBrowserSortFolderFirst(), as.isFileBrowserSortReverse()); // Sort only if actual folder } + GsFileUtils.sortFiles(_widgetFilesList, as.getFileBrowserSortByType(), as.isFileBrowserSortFolderFirst(), as.isFileBrowserSortReverse()); } @Override From c3f8f4b4a78424682c7a5b7abe8f8fa052f29428 Mon Sep 17 00:00:00 2001 From: Harshad Vedartham Date: Sat, 24 Sep 2022 23:40:31 -0700 Subject: [PATCH 08/40] Improved jump prevention, bad load detection --- .../activity/DocumentEditAndViewFragment.java | 10 +++++++- .../markor/frontend/MarkorDialogFactory.java | 12 ++++++++++ .../frontend/textview/HighlightingEditor.java | 18 +++++++++++---- .../frontend/textview/TextViewUtils.java | 23 +++++++++++-------- .../net/gsantner/markor/model/Document.java | 10 ++++++++ .../frontend/GsSearchOrCustomTextDialog.java | 15 +++++++++--- .../main/res/menu/document__edit__menu.xml | 5 ++++ app/src/main/res/values/strings.xml | 1 + .../drawable/ic_baseline_error_outline_24.xml | 5 ++++ 9 files changed, 80 insertions(+), 19 deletions(-) create mode 100644 app/thirdparty/res/drawable/ic_baseline_error_outline_24.xml 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 141c97f691..5cfb0fe034 100644 --- a/app/src/main/java/net/gsantner/markor/activity/DocumentEditAndViewFragment.java +++ b/app/src/main/java/net/gsantner/markor/activity/DocumentEditAndViewFragment.java @@ -102,6 +102,7 @@ public static DocumentEditAndViewFragment newInstance(final @NonNull File path, private boolean _isPreviewVisible; private MarkorWebViewClient _webViewClient; private boolean _nextConvertToPrintMode = false; + private boolean _isExitWithoutSave = false; private MenuItem _saveMenuItem, _undoMenuItem, _redoMenuItem; // Wrap text setting and wrap text state are separated as the wrap text state may depend on @@ -266,7 +267,9 @@ public void onResume() { @Override public void onPause() { - saveDocument(false); + if (!_isExitWithoutSave) { + saveDocument(false); + } _webView.onPause(); _appSettings.addRecentDocument(_document.getFile()); _appSettings.setDocumentPreviewState(_document.getPath(), _isPreviewVisible); @@ -371,6 +374,11 @@ public boolean loadDocument() { if (_document.hasFileChangedSinceLastLoad()) { final String content = _document.loadContent(getContext()); + if (_document.fileBytes() - content.getBytes().length > 3) { + errorClipText(); + return false; + } + if (!_document.isContentSame(_hlEditor.getText())) { final int[] sel = TextViewUtils.getSelection(_hlEditor); 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 7b137a6051..9af62d97e8 100644 --- a/app/src/main/java/net/gsantner/markor/frontend/MarkorDialogFactory.java +++ b/app/src/main/java/net/gsantner/markor/frontend/MarkorDialogFactory.java @@ -32,6 +32,7 @@ import android.widget.EditText; import android.widget.Toast; +import androidx.annotation.StringRes; import androidx.core.content.ContextCompat; import com.vladsch.flexmark.util.collection.OrderedMap; @@ -697,6 +698,17 @@ public static void showInsertSnippetDialog(final Activity activity, final GsCall GsSearchOrCustomTextDialog.showMultiChoiceDialogWithSearchFilterUI(activity, dopt); } + public static void showConfirmationDialog(final Activity activity, @StringRes final int message, final GsCallback.a1 callback) { + final GsSearchOrCustomTextDialog.DialogOptions dopt = new GsSearchOrCustomTextDialog.DialogOptions(); + baseConf(activity, dopt); + + dopt.titleText = R.string.confirm; + dopt.messageText = activity.getString(message); + dopt.callback = (s) -> callback.callback(true); + dopt.negativeButtonCallback = () -> callback.callback(false); + GsSearchOrCustomTextDialog.showMultiChoiceDialogWithSearchFilterUI(activity, dopt); + } + public static void baseConf(Activity activity, GsSearchOrCustomTextDialog.DialogOptions dopt) { dopt.isDarkDialog = GsContextUtils.instance.isDarkModeEnabled(activity); dopt.clearInputIcon = R.drawable.ic_baseline_clear_24; 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 62bc0005a9..32e105798f 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 @@ -28,6 +28,7 @@ import androidx.annotation.NonNull; import androidx.annotation.RequiresApi; import androidx.appcompat.widget.AppCompatEditText; +import androidx.appcompat.widget.AppCompatMultiAutoCompleteTextView; import net.gsantner.markor.ApplicationObject; import net.gsantner.markor.activity.MainActivity; @@ -37,7 +38,7 @@ import net.gsantner.opoc.wrapper.GsTextWatcherAdapter; @SuppressWarnings("UnusedReturnValue") -public class HighlightingEditor extends AppCompatEditText { +public class HighlightingEditor extends AppCompatMultiAutoCompleteTextView { final static int HIGHLIGHT_SHIFT_LINES = 8; // Lines to scroll before hl updated final static float HIGHLIGHT_REGION_SIZE = 0.75f; // Minimum extra screens to highlight (should be > 0.5 to cover screen) @@ -139,26 +140,33 @@ private void updateHighlighting(final boolean recompute) { // Don't highlight unless shifted sufficiently or a recompute is required if (recompute || (visible && _hl.hasSpans() && isScrollSignificant())) { + final boolean heightSame = _hlRect.height() == _oldHlRect.height(); + // Addition of spans which require reflow can shift text on re-application of spans // we compute the resulting shift and scroll the view to compensate in order to make // the experience smooth for the user. int shiftTestLine = -1, oldOffset = -1; - if (_scrollView != null && _hlRect.height() == _oldHlRect.height()) { + if (_scrollView != null && heightSame) { shiftTestLine = layout.getLineForVertical(_hlRect.centerY()); oldOffset = layout.getLineBaseline(shiftTestLine); } + if (heightSame) { + // Hack to block bring point into view + // We don't call this when height is changing + blockBringPointIntoView(); + } + final int[] newHlRegion = hlRegion(_hlRect); // Compute this _before_ clear try { beginBatchEdit(); - blockBringPointIntoView(); // Hack to block bring point into view _hl.clear(); if (recompute) { _hl.recompute(); } _hl.apply(newHlRegion); } finally { - blockBringPointIntoView(); // Hack to block bring point into view + blockBringPointIntoView(); endBatchEdit(); } @@ -191,7 +199,7 @@ public void setHighlighter(final SyntaxHighlighterBase newHighlighter) { if (_hl != null) { initHighlighter(); - _hlDebounced = TextViewUtils.makeDebounced(_hl.getHighlightingDelay(), () -> updateHighlighting(true)); + _hlDebounced = TextViewUtils.makeDebounced(getHandler(), _hl.getHighlightingDelay(), () -> updateHighlighting(true)); _hlDebounced.run(); } else { _hlDebounced = null; 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 f192857ae9..4e80a81a6b 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 @@ -19,7 +19,6 @@ import android.text.TextUtils; import android.view.View; import android.view.ViewTreeObserver; -import android.view.animation.Animation; import android.widget.EditText; import android.widget.TextView; @@ -33,7 +32,6 @@ import java.util.Collections; import java.util.List; import java.util.Locale; -import java.util.Observer; import java.util.TreeSet; @SuppressWarnings({"CharsetObjectCanBeUsed", "WeakerAccess", "unused"}) @@ -281,10 +279,20 @@ public static void selectLines(final EditText edit, final List position } } - public static @NonNull Rect getRegionRect(final TextView text, final int start, final int end) { + public static @NonNull Rect getSelRect(final TextView text, final int ... sel) { final Rect region = new Rect(); + if (sel == null || sel.length == 0) { + return region; + } + + final int _start = Math.min(sel[0], sel.length > 1 ? sel[1] : sel[0]); + final int _end = Math.max(sel[0], sel.length > 1 ? sel[1] : sel[0]); + if (!inRange(0, text.length(), _start, _end)) { + return region; + } + // Get view info // ------------------------------------------------------------ final Layout layout = text.getLayout(); @@ -292,11 +300,6 @@ public static void selectLines(final EditText edit, final List position return region; } - final int _start = Math.min(start, end); - final int _end = Math.max(start, end); - if (start < 0 || end > text.length()) { - return region; - } final int lineStart = TextViewUtils.getLineStart(text.getText(), _start); final Rect viewSize = new Rect(); @@ -352,7 +355,7 @@ public SelectionShower(@NonNull TextView text, int start, int end, int iterCount } public void run() { - final Rect region = getRegionRect(_text, _start, _end); + final Rect region = getSelRect(_text, _start, _end); if (!region.isEmpty()) { if (_observer != null && _iterCount > 0) { _observer.addOnScrollChangedListener(this); @@ -364,7 +367,7 @@ public void run() { @Override public void onScrollChanged() { final Rect region; - if (--_iterCount <= 0 || (region = getRegionRect(_text, _start, _end)).isEmpty()) { + if (--_iterCount <= 0 || (region = getSelRect(_text, _start, _end)).isEmpty()) { _observer.removeOnScrollChangedListener(this); return; } diff --git a/app/src/main/java/net/gsantner/markor/model/Document.java b/app/src/main/java/net/gsantner/markor/model/Document.java index ad0f51b60f..9fb1293aec 100644 --- a/app/src/main/java/net/gsantner/markor/model/Document.java +++ b/app/src/main/java/net/gsantner/markor/model/Document.java @@ -147,6 +147,16 @@ public long fileModTime() { return _file.lastModified(); } + public long fileBytes() { + try { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + return Files.readAttributes(_file.toPath(), BasicFileAttributes.class).size(); + } + } catch (IOException ignored) { + } + return _file.length(); + } + public String getTitle() { return _title; } 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 623f78637f..7fe3d5ba8e 100644 --- a/app/src/main/java/net/gsantner/opoc/frontend/GsSearchOrCustomTextDialog.java +++ b/app/src/main/java/net/gsantner/opoc/frontend/GsSearchOrCustomTextDialog.java @@ -13,6 +13,7 @@ import android.app.Activity; import android.app.Dialog; import android.content.Context; +import android.content.DialogInterface; import android.content.res.ColorStateList; import android.graphics.Color; import android.graphics.Typeface; @@ -69,6 +70,10 @@ public static class DialogOptions { @Nullable public GsCallback.a1 callback = null; + // Callback for search text or text of single item + @Nullable + public GsCallback.a0 negativeButtonCallback = null; + // Callback for indices of selected items. // List will contain single item if isMultiSelectEnabled == false; @Nullable @@ -258,9 +263,13 @@ public static void showMultiChoiceDialogWithSearchFilterUI(final Activity activi listLayout.weight = 1; mainLayout.addView(listView, listLayout); - dialogBuilder.setView(mainLayout) - .setOnCancelListener(null) - .setNegativeButton(dopt.cancelButtonText, (dialogInterface, i) -> dialogInterface.dismiss()); + dialogBuilder.setView(mainLayout).setOnCancelListener(null) + .setNegativeButton(dopt.cancelButtonText, (dialogInterface, i) -> { + if (dopt.negativeButtonCallback != null) { + dopt.negativeButtonCallback.callback(); + } + dialogInterface.dismiss(); + }); // ========================================================================================= diff --git a/app/src/main/res/menu/document__edit__menu.xml b/app/src/main/res/menu/document__edit__menu.xml index 56fca7abd3..f18fc2a0c9 100644 --- a/app/src/main/res/menu/document__edit__menu.xml +++ b/app/src/main/res/menu/document__edit__menu.xml @@ -251,4 +251,9 @@ android:icon="@drawable/ic_redo_black_24dp" android:title="@string/reload" app:showAsAction="never" /> + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 26c22bdee5..7a5cdcd9e8 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -16,6 +16,7 @@ work. If not, see . Appearance Rename Confirm Delete + Confirm? Do you really want to delete this %s? Confirm Overwrite File already exists, overwrite? diff --git a/app/thirdparty/res/drawable/ic_baseline_error_outline_24.xml b/app/thirdparty/res/drawable/ic_baseline_error_outline_24.xml new file mode 100644 index 0000000000..7816afd1a0 --- /dev/null +++ b/app/thirdparty/res/drawable/ic_baseline_error_outline_24.xml @@ -0,0 +1,5 @@ + + + From dbcad79396013eb9856edde5434648619f48958a Mon Sep 17 00:00:00 2001 From: Harshad Vedartham Date: Sat, 24 Sep 2022 23:43:18 -0700 Subject: [PATCH 09/40] Removed unnecessary --- .../markor/activity/DocumentEditAndViewFragment.java | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) 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 5cfb0fe034..cb4241b5f8 100644 --- a/app/src/main/java/net/gsantner/markor/activity/DocumentEditAndViewFragment.java +++ b/app/src/main/java/net/gsantner/markor/activity/DocumentEditAndViewFragment.java @@ -102,7 +102,6 @@ public static DocumentEditAndViewFragment newInstance(final @NonNull File path, private boolean _isPreviewVisible; private MarkorWebViewClient _webViewClient; private boolean _nextConvertToPrintMode = false; - private boolean _isExitWithoutSave = false; private MenuItem _saveMenuItem, _undoMenuItem, _redoMenuItem; // Wrap text setting and wrap text state are separated as the wrap text state may depend on @@ -267,9 +266,7 @@ public void onResume() { @Override public void onPause() { - if (!_isExitWithoutSave) { - saveDocument(false); - } + saveDocument(false); _webView.onPause(); _appSettings.addRecentDocument(_document.getFile()); _appSettings.setDocumentPreviewState(_document.getPath(), _isPreviewVisible); From cb22b60ae6b936ec93fdc3ec1fa7a66b5b6e1e46 Mon Sep 17 00:00:00 2001 From: Harshad Vedartham Date: Sat, 24 Sep 2022 23:47:12 -0700 Subject: [PATCH 10/40] Cleanups --- .../markor/frontend/MarkorDialogFactory.java | 11 ----------- .../opoc/frontend/GsSearchOrCustomTextDialog.java | 15 +++------------ app/src/main/res/menu/document__edit__menu.xml | 5 ----- app/src/main/res/values/strings.xml | 1 - .../res/drawable/ic_baseline_error_outline_24.xml | 5 ----- 5 files changed, 3 insertions(+), 34 deletions(-) delete mode 100644 app/thirdparty/res/drawable/ic_baseline_error_outline_24.xml 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 9af62d97e8..722834a062 100644 --- a/app/src/main/java/net/gsantner/markor/frontend/MarkorDialogFactory.java +++ b/app/src/main/java/net/gsantner/markor/frontend/MarkorDialogFactory.java @@ -698,17 +698,6 @@ public static void showInsertSnippetDialog(final Activity activity, final GsCall GsSearchOrCustomTextDialog.showMultiChoiceDialogWithSearchFilterUI(activity, dopt); } - public static void showConfirmationDialog(final Activity activity, @StringRes final int message, final GsCallback.a1 callback) { - final GsSearchOrCustomTextDialog.DialogOptions dopt = new GsSearchOrCustomTextDialog.DialogOptions(); - baseConf(activity, dopt); - - dopt.titleText = R.string.confirm; - dopt.messageText = activity.getString(message); - dopt.callback = (s) -> callback.callback(true); - dopt.negativeButtonCallback = () -> callback.callback(false); - GsSearchOrCustomTextDialog.showMultiChoiceDialogWithSearchFilterUI(activity, dopt); - } - public static void baseConf(Activity activity, GsSearchOrCustomTextDialog.DialogOptions dopt) { dopt.isDarkDialog = GsContextUtils.instance.isDarkModeEnabled(activity); dopt.clearInputIcon = R.drawable.ic_baseline_clear_24; 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 7fe3d5ba8e..623f78637f 100644 --- a/app/src/main/java/net/gsantner/opoc/frontend/GsSearchOrCustomTextDialog.java +++ b/app/src/main/java/net/gsantner/opoc/frontend/GsSearchOrCustomTextDialog.java @@ -13,7 +13,6 @@ import android.app.Activity; import android.app.Dialog; import android.content.Context; -import android.content.DialogInterface; import android.content.res.ColorStateList; import android.graphics.Color; import android.graphics.Typeface; @@ -70,10 +69,6 @@ public static class DialogOptions { @Nullable public GsCallback.a1 callback = null; - // Callback for search text or text of single item - @Nullable - public GsCallback.a0 negativeButtonCallback = null; - // Callback for indices of selected items. // List will contain single item if isMultiSelectEnabled == false; @Nullable @@ -263,13 +258,9 @@ public static void showMultiChoiceDialogWithSearchFilterUI(final Activity activi listLayout.weight = 1; mainLayout.addView(listView, listLayout); - dialogBuilder.setView(mainLayout).setOnCancelListener(null) - .setNegativeButton(dopt.cancelButtonText, (dialogInterface, i) -> { - if (dopt.negativeButtonCallback != null) { - dopt.negativeButtonCallback.callback(); - } - dialogInterface.dismiss(); - }); + dialogBuilder.setView(mainLayout) + .setOnCancelListener(null) + .setNegativeButton(dopt.cancelButtonText, (dialogInterface, i) -> dialogInterface.dismiss()); // ========================================================================================= diff --git a/app/src/main/res/menu/document__edit__menu.xml b/app/src/main/res/menu/document__edit__menu.xml index f18fc2a0c9..56fca7abd3 100644 --- a/app/src/main/res/menu/document__edit__menu.xml +++ b/app/src/main/res/menu/document__edit__menu.xml @@ -251,9 +251,4 @@ android:icon="@drawable/ic_redo_black_24dp" android:title="@string/reload" app:showAsAction="never" /> - diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 7a5cdcd9e8..26c22bdee5 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -16,7 +16,6 @@ work. If not, see . Appearance Rename Confirm Delete - Confirm? Do you really want to delete this %s? Confirm Overwrite File already exists, overwrite? diff --git a/app/thirdparty/res/drawable/ic_baseline_error_outline_24.xml b/app/thirdparty/res/drawable/ic_baseline_error_outline_24.xml deleted file mode 100644 index 7816afd1a0..0000000000 --- a/app/thirdparty/res/drawable/ic_baseline_error_outline_24.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - From 2844cf0abfa97afd1740773e6202e757a8b31c62 Mon Sep 17 00:00:00 2001 From: Harshad Vedartham Date: Sun, 25 Sep 2022 09:25:32 -0700 Subject: [PATCH 11/40] Cleaned up handling --- .../activity/DocumentEditAndViewFragment.java | 17 +++++++----- .../activity/DocumentShareIntoFragment.java | 13 ++++++--- .../openeditor/OpenEditorActivity.java | 4 ++- .../markor/frontend/MarkorDialogFactory.java | 1 + .../net/gsantner/markor/model/Document.java | 27 +++++++++++++------ .../net/gsantner/opoc/util/GsFileUtils.java | 12 +++------ app/src/main/res/values-ar/strings.xml | 2 +- app/src/main/res/values-ca/strings.xml | 2 +- app/src/main/res/values-cs/strings.xml | 2 +- app/src/main/res/values-da/strings.xml | 2 +- app/src/main/res/values-de/strings.xml | 2 +- app/src/main/res/values-el/strings.xml | 2 +- app/src/main/res/values-es/strings.xml | 2 +- app/src/main/res/values-fi/strings.xml | 2 +- app/src/main/res/values-fr/strings.xml | 2 +- app/src/main/res/values-it/strings.xml | 2 +- app/src/main/res/values-ja/strings.xml | 2 +- app/src/main/res/values-nb-rNO/strings.xml | 2 +- app/src/main/res/values-nl/strings.xml | 2 +- app/src/main/res/values-no/strings.xml | 2 +- app/src/main/res/values-pl/strings.xml | 2 +- app/src/main/res/values-pt-rBR/strings.xml | 2 +- app/src/main/res/values-pt/strings.xml | 2 +- app/src/main/res/values-ro/strings.xml | 2 +- app/src/main/res/values-ru/strings.xml | 2 +- app/src/main/res/values-sc/strings.xml | 2 +- app/src/main/res/values-sv/strings.xml | 2 +- app/src/main/res/values-uk/strings.xml | 2 +- app/src/main/res/values-zh-rCN/strings.xml | 2 +- app/src/main/res/values/strings.xml | 3 +-- 30 files changed, 71 insertions(+), 52 deletions(-) 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 cb4241b5f8..f1b5b9639f 100644 --- a/app/src/main/java/net/gsantner/markor/activity/DocumentEditAndViewFragment.java +++ b/app/src/main/java/net/gsantner/markor/activity/DocumentEditAndViewFragment.java @@ -150,7 +150,7 @@ public void onViewCreated(@NonNull View view, Bundle savedInstanceState) { // It may cause reads or writes to _silently fail_ // Instead we try to create it, and exit if that isn't possible if (isStateBad()) { - Toast.makeText(activity, R.string.document_error_exit, Toast.LENGTH_LONG).show(); + Toast.makeText(activity, R.string.document_error, Toast.LENGTH_LONG).show(); activity.finish(); return; } @@ -362,16 +362,20 @@ private void updateUndoRedoIconStates() { } public boolean loadDocument() { + return loadDocument(false); + } + + public boolean loadDocument(final boolean forceReload) { if (isSdStatusBad() || isStateBad()) { errorClipText(); return false; } - // Only trigger the load process if constructing or file updated - if (_document.hasFileChangedSinceLastLoad()) { + // Only trigger the load process if constructing or file updated or force reload + if (forceReload || _document.hasFileChangedSinceLastLoad()) { final String content = _document.loadContent(getContext()); - if (_document.fileBytes() - content.getBytes().length > 3) { + if (content == null) { errorClipText(); return false; } @@ -436,7 +440,7 @@ public boolean onOptionsItemSelected(@NonNull final MenuItem item) { return true; } case R.id.action_reload: { - if (loadDocument()) { + if (loadDocument(true)) { Toast.makeText(activity, "✔", Toast.LENGTH_SHORT).show(); } return true; @@ -696,8 +700,9 @@ public void errorClipText() { Context context = getContext(); context = context == null ? ApplicationObject.get().getApplicationContext() : context; new MarkorContextUtils(context).setClipboard(getContext(), text); - Toast.makeText(getContext(), R.string.document_error_clip, Toast.LENGTH_LONG).show(); } + // Always show error message + Toast.makeText(getContext(), R.string.document_error, Toast.LENGTH_LONG).show(); } public boolean isSdStatusBad() { 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 965b0571bc..a9f3f620f2 100644 --- a/app/src/main/java/net/gsantner/markor/activity/DocumentShareIntoFragment.java +++ b/app/src/main/java/net/gsantner/markor/activity/DocumentShareIntoFragment.java @@ -197,10 +197,15 @@ private void appendToExistingDocument(final File file, final String separator, f final String shareIntoFormat = _cu.formatDateTime(context, _appSettings.getShareIntoPrefix(), System.currentTimeMillis()); final boolean isTodoTxt = FormatRegistry.CONVERTER_TODOTXT.isFileOutOfThisFormat(file.getAbsolutePath()); - final String newContent = document.loadContent(context).replaceAll("(^[\\r\\n]+|[\\r\\n]+$)", "") - + separator - + (isTodoTxt ? _sharedText : formatOrPrefixSharedText(shareIntoFormat, _sharedText)); - document.saveContent(context, newContent); + final String oldContent = document.loadContent(context); + if (oldContent != null) { + final String newContent = oldContent.replaceAll("(^[\\r\\n]+|[\\r\\n]+$)", "") + + separator + + (isTodoTxt ? _sharedText : formatOrPrefixSharedText(shareIntoFormat, _sharedText)); + document.saveContent(context, newContent); + } else { + Toast.makeText(context, R.string.document_error, Toast.LENGTH_LONG).show(); + } if (showEditor) { showInDocumentActivity(document); diff --git a/app/src/main/java/net/gsantner/markor/activity/openeditor/OpenEditorActivity.java b/app/src/main/java/net/gsantner/markor/activity/openeditor/OpenEditorActivity.java index 359302f4ac..fd66028b65 100644 --- a/app/src/main/java/net/gsantner/markor/activity/openeditor/OpenEditorActivity.java +++ b/app/src/main/java/net/gsantner/markor/activity/openeditor/OpenEditorActivity.java @@ -44,7 +44,9 @@ protected void openActivityAndClose(final Intent openIntent, File file) { file.getParentFile().mkdirs(); } if (!file.exists() && !file.isDirectory()) { - GsFileUtils.writeFile(file, "", new GsFileUtils.FileInfo().withBom(_appSettings.getNewFileDialogLastUsedUtf8Bom())); + final GsFileUtils.FileInfo info = new GsFileUtils.FileInfo(); + info.hasBom = _appSettings.getNewFileDialogLastUsedUtf8Bom(); + GsFileUtils.writeFile(file, "", info); } openIntent.putExtra(Document.EXTRA_PATH, openIntent.hasExtra(Document.EXTRA_PATH) ? openIntent.getSerializableExtra(Document.EXTRA_PATH) : file); _cu.animateToActivity(this, openIntent, true, 1); 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 722834a062..56f6efea26 100644 --- a/app/src/main/java/net/gsantner/markor/frontend/MarkorDialogFactory.java +++ b/app/src/main/java/net/gsantner/markor/frontend/MarkorDialogFactory.java @@ -54,6 +54,7 @@ import net.gsantner.opoc.wrapper.GsCallback; import java.io.File; +import java.io.IOException; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; diff --git a/app/src/main/java/net/gsantner/markor/model/Document.java b/app/src/main/java/net/gsantner/markor/model/Document.java index 9fb1293aec..fd431a9717 100644 --- a/app/src/main/java/net/gsantner/markor/model/Document.java +++ b/app/src/main/java/net/gsantner/markor/model/Document.java @@ -19,6 +19,7 @@ import android.widget.Toast; import androidx.annotation.NonNull; +import androidx.annotation.Nullable; import androidx.annotation.RequiresApi; import androidx.annotation.StringRes; @@ -206,15 +207,15 @@ public boolean isEncrypted() { } private void setContentHash(final CharSequence s) { - _lastLength = s.length(); - _lastHash = GsFileUtils.crc32(s); + _lastLength = s != null ? s.length() : 0; + _lastHash = s != null ? GsFileUtils.crc32(s) : 0; } public boolean isContentSame(final CharSequence s) { return s != null && s.length() == _lastLength && _lastHash == GsFileUtils.crc32(s); } - public synchronized String loadContent(final Context context) { + public synchronized @Nullable String loadContent(final Context context) { String content; final char[] pw; @@ -237,7 +238,11 @@ public synchronized String loadContent(final Context context) { content = ""; } } else { - final Pair result = GsFileUtils.readTextFileFast(_file); + // We try to load 2x. If both times fail, we return null + Pair result = GsFileUtils.readTextFileFast(_file); + if (result.second.ioError) { + result = GsFileUtils.readTextFileFast(_file); + } content = result.first; _fileInfo = result.second; } @@ -248,16 +253,22 @@ public synchronized String loadContent(final Context context) { + getName().replaceAll(".*\\.", "-") + ", chars: " + content.length() + " bytes:" + content.getBytes().length + "(" + GsFileUtils.getReadableFileSize(content.getBytes().length, true) + - "). Language >" + Locale.getDefault().toString() + "). Language >" + Locale.getDefault() + "<, Language override >" + ApplicationObject.settings().getLanguage() + "<"); } // Also set hash and time on load - should prevent unnecessary saves setContentHash(content); - _modTime = fileModTime(); - setGlobalTouchTime(); - return content; + if (_fileInfo.ioError) { + // Force next load on failure + resetChangeTracking(); + return null; + } else { + _modTime = fileModTime(); + setGlobalTouchTime(); + return content; + } } @RequiresApi(api = Build.VERSION_CODES.M) diff --git a/app/src/main/java/net/gsantner/opoc/util/GsFileUtils.java b/app/src/main/java/net/gsantner/opoc/util/GsFileUtils.java index 4d8bbfa0ee..b22517a49a 100644 --- a/app/src/main/java/net/gsantner/opoc/util/GsFileUtils.java +++ b/app/src/main/java/net/gsantner/opoc/util/GsFileUtils.java @@ -53,11 +53,7 @@ public class GsFileUtils { */ public static class FileInfo implements Serializable { public boolean hasBom = false; - - public FileInfo withBom(boolean bom) { - hasBom = bom; - return this; - } + public boolean ioError = false; } public static Pair readTextFileFast(final File file) { @@ -68,11 +64,10 @@ public static Pair readTextFileFast(final File file) { final byte[] bomBuffer = new byte[3]; final int bomReadLength = inputStream.read(bomBuffer); - info.withBom(bomReadLength == 3 && + info.hasBom = bomReadLength == 3 && bomBuffer[0] == (byte) 0xEF && bomBuffer[1] == (byte) 0xBB && - bomBuffer[2] == (byte) 0xBF - ); + bomBuffer[2] == (byte) 0xBF; if (!info.hasBom && bomReadLength > 0) { result.write(bomBuffer, 0, bomReadLength); @@ -90,6 +85,7 @@ public static Pair readTextFileFast(final File file) { System.err.println("readTextFileFast: File " + file + " not found."); } catch (IOException e) { e.printStackTrace(); + info.ioError = true; } return new Pair<>("", info); diff --git a/app/src/main/res/values-ar/strings.xml b/app/src/main/res/values-ar/strings.xml index 53eed9f611..a05add238d 100644 --- a/app/src/main/res/values-ar/strings.xml +++ b/app/src/main/res/values-ar/strings.xml @@ -438,6 +438,6 @@ work. If not, see . المشاركة في - التنسيق تنسيق تلقائي Insert snippet - خطأ: تعذر الفتح. + خطأ: تعذر الفتح. حدث خطأ: تم نسخ النص إلى الحافظة. diff --git a/app/src/main/res/values-ca/strings.xml b/app/src/main/res/values-ca/strings.xml index 28dd9a00cf..9e3b77c4d5 100644 --- a/app/src/main/res/values-ca/strings.xml +++ b/app/src/main/res/values-ca/strings.xml @@ -438,6 +438,6 @@ work. If not, see . Comparteix en - Format Format automàtic Insereix fragment - Error: no s\'ha pogut obrir. + Error: no s\'ha pogut obrir. S\'ha trobat un error: el text s\'ha copiat al porta-retalls. diff --git a/app/src/main/res/values-cs/strings.xml b/app/src/main/res/values-cs/strings.xml index 09dc71539f..892e07e06f 100644 --- a/app/src/main/res/values-cs/strings.xml +++ b/app/src/main/res/values-cs/strings.xml @@ -438,6 +438,6 @@ work. If not, see . Sdílet do - formát Automatický formát Insert snippet - Chyba: Nelze otevřít. + Chyba: Nelze otevřít. Došlo k chybě: Text zkopírován do schránky. diff --git a/app/src/main/res/values-da/strings.xml b/app/src/main/res/values-da/strings.xml index 36628e0a8a..8f6abcdd4e 100644 --- a/app/src/main/res/values-da/strings.xml +++ b/app/src/main/res/values-da/strings.xml @@ -438,6 +438,6 @@ work. If not, see . Del på - Format Auto-format Insert snippet - Fejl: Kunne ikke åbne. + Fejl: Kunne ikke åbne. Der opstod en fejl: Tekst kopieret til udklipsholder. diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index 5c602c0920..b9e0c23cbb 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -438,6 +438,6 @@ work. If not, see . Teilen in - Format Auto-Format Schnipsel einfügen - Fehler: Konnte nicht öffnen. + Fehler: Konnte nicht öffnen. Fehler aufgetreten: Text in die Zwischenablage kopiert. diff --git a/app/src/main/res/values-el/strings.xml b/app/src/main/res/values-el/strings.xml index 2c826485fa..17205ae5f7 100644 --- a/app/src/main/res/values-el/strings.xml +++ b/app/src/main/res/values-el/strings.xml @@ -439,6 +439,6 @@ work. If not, see . Κοινή χρήση σε - Μορφή Αυτόματη διαμόρφωση Εισαγωγή αποσπάσματος κώδικα - Σφάλμα: Αδύνατο το άνοιγμα. + Σφάλμα: Αδύνατο το άνοιγμα. Αντιμετώπιση σφάλματος: Το κείμενο αντιγράφηκε στο πρόχειρο. diff --git a/app/src/main/res/values-es/strings.xml b/app/src/main/res/values-es/strings.xml index 1764eff1f2..ffa15ae0ff 100644 --- a/app/src/main/res/values-es/strings.xml +++ b/app/src/main/res/values-es/strings.xml @@ -438,6 +438,6 @@ work. If not, see . Compartir en - Formato Auto-formato Insertar fragmento - Error: No se pudo abrir. + Error: No se pudo abrir. Error encontrado: Texto copiado al portapapeles. diff --git a/app/src/main/res/values-fi/strings.xml b/app/src/main/res/values-fi/strings.xml index bebd447ed5..787a557c9c 100644 --- a/app/src/main/res/values-fi/strings.xml +++ b/app/src/main/res/values-fi/strings.xml @@ -438,6 +438,6 @@ work. If not, see . Jaa - Muoto Automaattimuotoilu Insert snippet - Virhe: Ei voitu avata. + Virhe: Ei voitu avata. Tapahtui virhe: Teksti kopioitu leikepöydälle. diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml index eade7a3e84..7eaa4e3f3d 100644 --- a/app/src/main/res/values-fr/strings.xml +++ b/app/src/main/res/values-fr/strings.xml @@ -438,6 +438,6 @@ work. If not, see . Partager dans - Format Format automatique Insert snippet - Erreur: Impossible d\'ouvrir. + Erreur: Impossible d\'ouvrir. Erreur rencontrée: Texte copié dans le presse-papiers. diff --git a/app/src/main/res/values-it/strings.xml b/app/src/main/res/values-it/strings.xml index e853e98506..b03aeec6f8 100644 --- a/app/src/main/res/values-it/strings.xml +++ b/app/src/main/res/values-it/strings.xml @@ -438,6 +438,6 @@ work. If not, see . Condividi in - Formato Autoformattazione Inserisci estratto - Errore: impossibile aprire. + Errore: impossibile aprire. Trovato errore: Testo copiato negli appunti. diff --git a/app/src/main/res/values-ja/strings.xml b/app/src/main/res/values-ja/strings.xml index 7df942bc49..9352ab726c 100644 --- a/app/src/main/res/values-ja/strings.xml +++ b/app/src/main/res/values-ja/strings.xml @@ -438,6 +438,6 @@ work. If not, see . 共有 → 書式 自動フォーマット Insert snippet - エラー:開けませんでした。 + エラー:開けませんでした。 エラーが発生しました: テキストをクリップボードにコピーしました。 diff --git a/app/src/main/res/values-nb-rNO/strings.xml b/app/src/main/res/values-nb-rNO/strings.xml index 956611b46c..5982c9cce6 100644 --- a/app/src/main/res/values-nb-rNO/strings.xml +++ b/app/src/main/res/values-nb-rNO/strings.xml @@ -438,6 +438,6 @@ work. If not, see . Del inn - format Autokoformater Insert snippet - Feil: kunne ikke åpne. + Feil: kunne ikke åpne. Oppnådd tekst: kopiert til utklippstavlen. diff --git a/app/src/main/res/values-nl/strings.xml b/app/src/main/res/values-nl/strings.xml index 7dcb1345b7..652c2a1c59 100644 --- a/app/src/main/res/values-nl/strings.xml +++ b/app/src/main/res/values-nl/strings.xml @@ -438,6 +438,6 @@ work. If not, see . Deel mee - Formaat Auto-formaat Insert snippet - Fout: Kan niet openen. + Fout: Kan niet openen. Fout opgetreden: tekst gekopieerd naar klembord. diff --git a/app/src/main/res/values-no/strings.xml b/app/src/main/res/values-no/strings.xml index 33b2cd06a1..7d3a83acc7 100644 --- a/app/src/main/res/values-no/strings.xml +++ b/app/src/main/res/values-no/strings.xml @@ -438,6 +438,6 @@ work. If not, see . Del inn - format Autokoformater Insert snippet - Feil: kunne ikke åpne. + Feil: kunne ikke åpne. Oppnådd tekst: kopiert til utklippstavlen. diff --git a/app/src/main/res/values-pl/strings.xml b/app/src/main/res/values-pl/strings.xml index 07fca3895e..54e8298494 100644 --- a/app/src/main/res/values-pl/strings.xml +++ b/app/src/main/res/values-pl/strings.xml @@ -438,6 +438,6 @@ work. If not, see . Udostępnij - Format Automatyczny format Insert snippet - Błąd: Nie można otworzyć. + Błąd: Nie można otworzyć. Wystąpił błąd: Tekst skopiowany do schowka. diff --git a/app/src/main/res/values-pt-rBR/strings.xml b/app/src/main/res/values-pt-rBR/strings.xml index f415d0fb09..4a0f957ac8 100644 --- a/app/src/main/res/values-pt-rBR/strings.xml +++ b/app/src/main/res/values-pt-rBR/strings.xml @@ -438,6 +438,6 @@ work. If not, see . Compartilhar em - Formato Auto-formatar Insert snippet - Erro: Não foi possível abrir. + Erro: Não foi possível abrir. Erro encontrado: Texto copiado para área de transferência. diff --git a/app/src/main/res/values-pt/strings.xml b/app/src/main/res/values-pt/strings.xml index 9e5eb2fb5b..d807c08c72 100644 --- a/app/src/main/res/values-pt/strings.xml +++ b/app/src/main/res/values-pt/strings.xml @@ -438,6 +438,6 @@ work. If not, see . Compartilhar em - Formato Auto-formatar Insert snippet - Erro: Não foi possível abrir. + Erro: Não foi possível abrir. Erro encontrado: Texto copiado para área de transferência. diff --git a/app/src/main/res/values-ro/strings.xml b/app/src/main/res/values-ro/strings.xml index 837695c34d..cdcd379926 100644 --- a/app/src/main/res/values-ro/strings.xml +++ b/app/src/main/res/values-ro/strings.xml @@ -438,6 +438,6 @@ work. If not, see . Partajare în - Format Format automat Insert snippet - Eroare: Nu s-a putut deschide. + Eroare: Nu s-a putut deschide. Eroare întâlnită: textul a fost copiat în clipboard. diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index 8994a2da89..aaef5642cc 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -438,6 +438,6 @@ work. If not, see . Поделиться - Формат Автоформат Insert snippet - Ошибка: не удалось открыть. + Ошибка: не удалось открыть. Произошла ошибка: текст скопирован в буфер обмена. diff --git a/app/src/main/res/values-sc/strings.xml b/app/src/main/res/values-sc/strings.xml index d1894ad23b..df531eb857 100644 --- a/app/src/main/res/values-sc/strings.xml +++ b/app/src/main/res/values-sc/strings.xml @@ -438,6 +438,6 @@ work. If not, see . Cumpartzi in - Formadu Formatatzione automàtica Inserta frammentu - Errore: No at fatu a abèrrere. + Errore: No at fatu a abèrrere. Errore agatadu: testu copiadu in punta de billete. diff --git a/app/src/main/res/values-sv/strings.xml b/app/src/main/res/values-sv/strings.xml index 4ea0d05008..41f9eb158f 100644 --- a/app/src/main/res/values-sv/strings.xml +++ b/app/src/main/res/values-sv/strings.xml @@ -438,6 +438,6 @@ work. If not, see . Dela till - Format Auto-format Insert snippet - Fel: Kunde inte öppna. + Fel: Kunde inte öppna. Ett fel uppstod: Text kopierad till urklipp. diff --git a/app/src/main/res/values-uk/strings.xml b/app/src/main/res/values-uk/strings.xml index ce19b0c2ac..326e5c5fcd 100644 --- a/app/src/main/res/values-uk/strings.xml +++ b/app/src/main/res/values-uk/strings.xml @@ -438,6 +438,6 @@ work. If not, see . Поділитися у форматі - Формат Автоматичний формат Insert snippet - Помилка: Не вдалося відкрити. + Помилка: Не вдалося відкрити. Помилка виявлення: текст скопійовано до буфера обміну. diff --git a/app/src/main/res/values-zh-rCN/strings.xml b/app/src/main/res/values-zh-rCN/strings.xml index b238c3fd82..181be19c64 100644 --- a/app/src/main/res/values-zh-rCN/strings.xml +++ b/app/src/main/res/values-zh-rCN/strings.xml @@ -438,6 +438,6 @@ work. If not, see . 分享到 - 格式 自动格式 插入片段 - 错误:无法打开。 + 错误:无法打开。 出现错误:文本已复制到剪贴板。 diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 26c22bdee5..57d2d2c104 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -456,6 +456,5 @@ work. If not, see . Share into - Format Auto-format Insert snippet - Error: Could not open. - Error encountered: Text copied to clipboard. + Error: Could not open. From 83347cf41941468c6e1fb1f5797c2afb8aac3cc9 Mon Sep 17 00:00:00 2001 From: Harshad Vedartham Date: Sun, 25 Sep 2022 20:44:12 -0700 Subject: [PATCH 12/40] Fixed null state --- app/src/main/java/net/gsantner/markor/model/Document.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/app/src/main/java/net/gsantner/markor/model/Document.java b/app/src/main/java/net/gsantner/markor/model/Document.java index fd431a9717..c74dbcf267 100644 --- a/app/src/main/java/net/gsantner/markor/model/Document.java +++ b/app/src/main/java/net/gsantner/markor/model/Document.java @@ -257,14 +257,14 @@ public boolean isContentSame(final CharSequence s) { + "<, Language override >" + ApplicationObject.settings().getLanguage() + "<"); } - // Also set hash and time on load - should prevent unnecessary saves - setContentHash(content); - - if (_fileInfo.ioError) { + if (_fileInfo != null && _fileInfo.ioError) { // Force next load on failure + setContentHash(null); resetChangeTracking(); return null; } else { + // Also set hash and time on load - should prevent unnecessary saves + setContentHash(content); _modTime = fileModTime(); setGlobalTouchTime(); return content; From 9445d4a8183241a854635645073f41cb59bc9049 Mon Sep 17 00:00:00 2001 From: Harshad Vedartham Date: Mon, 26 Sep 2022 09:17:36 -0700 Subject: [PATCH 13/40] Removed redundant functions --- .../java/net/gsantner/opoc/format/GsTextUtils.java | 13 +------------ .../java/net/gsantner/opoc/util/GsContextUtils.java | 5 +++-- .../java/net/gsantner/opoc/util/GsFileUtils.java | 7 ++++--- app/src/main/res/values/strings.xml | 1 + 4 files changed, 9 insertions(+), 17 deletions(-) diff --git a/app/src/main/java/net/gsantner/opoc/format/GsTextUtils.java b/app/src/main/java/net/gsantner/opoc/format/GsTextUtils.java index 72d3bfb20c..80cd86b609 100644 --- a/app/src/main/java/net/gsantner/opoc/format/GsTextUtils.java +++ b/app/src/main/java/net/gsantner/opoc/format/GsTextUtils.java @@ -17,6 +17,7 @@ import java.text.SimpleDateFormat; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collections; import java.util.Date; import java.util.Random; @@ -163,17 +164,6 @@ public static int tryParseInt(final String value, int defaultValue) { } } - public static ArrayList toArrayList(T... array) { - ArrayList list = new ArrayList<>(); - Collections.addAll(list, array); - return list; - } - - // Not null, not empty, not spaces only - public static boolean ne(final String str) { - return str != null && !str.trim().isEmpty(); - } - /** * Convert an int color to a hex string. Optionally including alpha value. * @@ -185,7 +175,6 @@ public static String colorToHexString(final int intColor, final boolean... withA return String.format(a ? "#%08X" : "#%06X", (a ? 0xFFFFFFFF : 0xFFFFFF) & intColor); } - /** * Convert escape sequences in string to escaped special characters. For example, convert * A\tB -> A B 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 caa36b9192..61106c92c7 100644 --- a/app/src/main/java/net/gsantner/opoc/util/GsContextUtils.java +++ b/app/src/main/java/net/gsantner/opoc/util/GsContextUtils.java @@ -126,6 +126,7 @@ import com.google.android.material.snackbar.Snackbar; +import net.gsantner.markor.frontend.textview.TextViewUtils; import net.gsantner.opoc.format.GsSimpleMarkdownParser; import net.gsantner.opoc.format.GsTextUtils; import net.gsantner.opoc.wrapper.GsCallback; @@ -978,11 +979,11 @@ public String getMimeType(final Context context, String uri) { } // Try to guess if the recommended methods fail - if (GsTextUtils.ne(mimeType) && new File(uri).exists()) { + if (!TextViewUtils.isNullOrWhitespace(mimeType) && new File(uri).exists()) { mimeType = GsFileUtils.getMimeType(new File(uri)); } - if (GsTextUtils.ne((mimeType))) { + if (!TextViewUtils.isNullOrWhitespace((mimeType))) { mimeType = "*/*"; } return mimeType.toLowerCase(Locale.ROOT); diff --git a/app/src/main/java/net/gsantner/opoc/util/GsFileUtils.java b/app/src/main/java/net/gsantner/opoc/util/GsFileUtils.java index 268934dd2a..8e3c6fdf9c 100644 --- a/app/src/main/java/net/gsantner/opoc/util/GsFileUtils.java +++ b/app/src/main/java/net/gsantner/opoc/util/GsFileUtils.java @@ -13,6 +13,7 @@ import android.text.TextUtils; import android.util.Pair; +import net.gsantner.markor.frontend.textview.TextViewUtils; import net.gsantner.opoc.format.GsTextUtils; import java.io.BufferedInputStream; @@ -442,7 +443,7 @@ public static String getMimeType(File file) { String t; try { - if (GsTextUtils.ne(t = Files.probeContentType(file.toPath()))) { + if (!TextViewUtils.isNullOrWhitespace(t = Files.probeContentType(file.toPath()))) { return t; } } catch (Exception ignored) { @@ -450,7 +451,7 @@ public static String getMimeType(File file) { if (file.exists() && file.isFile()) { try (InputStream is = new BufferedInputStream(new FileInputStream(file))) { - if (GsTextUtils.ne(t = Files.probeContentType(file.toPath()))) { + if (!TextViewUtils.isNullOrWhitespace(t = Files.probeContentType(file.toPath()))) { return t; } } catch (Exception ignored) { @@ -458,7 +459,7 @@ public static String getMimeType(File file) { } t = URLConnection.guessContentTypeFromName(file.getName().replace(".jenc", "")); - return GsTextUtils.ne(t) ? "*/*" : t; + return !TextViewUtils.isNullOrWhitespace(t) ? "*/*" : t; } public static boolean isTextFile(File file) { diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 57d2d2c104..acce087122 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -457,4 +457,5 @@ work. If not, see . Auto-format Insert snippet Error: Could not open. + Error encountered: Text copied to clipboard. From 2ca54c67463ff4f105a3ab5d9ca4d505c5e89abf Mon Sep 17 00:00:00 2001 From: Harshad Vedartham Date: Tue, 27 Sep 2022 00:18:36 -0700 Subject: [PATCH 14/40] Attempt to solve onNewIntent crash in MainActivity --- .../markor/activity/MainActivity.java | 20 +++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) 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 2bb54493d8..20990532b6 100644 --- a/app/src/main/java/net/gsantner/markor/activity/MainActivity.java +++ b/app/src/main/java/net/gsantner/markor/activity/MainActivity.java @@ -64,6 +64,7 @@ public class MainActivity extends MarkorBaseActivity implements GsFileBrowserFra private SectionsPagerAdapter _viewPagerAdapter; private FloatingActionButton _fab; + private boolean _isNewIntent = false; private boolean _doubleBackToExitPressedOnce; private MarkorContextUtils _cu; @@ -113,12 +114,8 @@ public void onPageSelected(int position) { @Override protected void onNewIntent(final Intent intent) { super.onNewIntent(intent); - final File dir = getIntentDir(intent, null); - final GsFileBrowserFragment frag = getNotebook(); - if (frag != null && dir != null) { - frag.getAdapter().setCurrentFolder(dir, false); - _bottomNav.postDelayed(() -> _bottomNav.setSelectedItemId(R.id.nav_notebook), 10); - } + setIntent(intent); + _isNewIntent = true; } private static File getIntentDir(final Intent intent, final File fallback) { @@ -189,6 +186,7 @@ public boolean onCreateOptionsMenu(final Menu menu) { protected void onResume() { //new AndroidSupportMeWrapper(this).mainOnResume(); super.onResume(); + if (_appSettings.isRecreateMainRequired()) { // recreate(); // does not remake fragments final Intent intent = getIntent(); @@ -217,6 +215,16 @@ protected void onResume() { } catch (IOException e) { e.printStackTrace(); } + + if (_isNewIntent) { + final File dir = getIntentDir(getIntent(), null); + final GsFileBrowserFragment frag = getNotebook(); + if (frag != null && dir != null) { + frag.getAdapter().setCurrentFolder(dir, false); + _bottomNav.postDelayed(() -> _bottomNav.setSelectedItemId(R.id.nav_notebook), 10); + } + } + _isNewIntent = false; } private void restartMainActivity() { From 10322aaaf3e5f2e26e3942fdd7e453c9eb84b168 Mon Sep 17 00:00:00 2001 From: Harshad Vedartham Date: Tue, 27 Sep 2022 09:31:12 -0700 Subject: [PATCH 15/40] size based defence on write andmore loggin --- .../markor/activity/DocumentEditAndViewFragment.java | 2 ++ .../main/java/net/gsantner/markor/model/Document.java | 10 +++++++++- 2 files changed, 11 insertions(+), 1 deletion(-) 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 f1b5b9639f..e737fb44fa 100644 --- a/app/src/main/java/net/gsantner/markor/activity/DocumentEditAndViewFragment.java +++ b/app/src/main/java/net/gsantner/markor/activity/DocumentEditAndViewFragment.java @@ -20,6 +20,7 @@ import android.os.Build; import android.os.Bundle; import android.text.TextUtils; +import android.util.Log; import android.util.TypedValue; import android.view.Gravity; import android.view.Menu; @@ -703,6 +704,7 @@ public void errorClipText() { } // Always show error message Toast.makeText(getContext(), R.string.document_error, Toast.LENGTH_LONG).show(); + Log.i(DocumentEditAndViewFragment.class.getName(), "Triggering error text clipping"); } public boolean isSdStatusBad() { diff --git a/app/src/main/java/net/gsantner/markor/model/Document.java b/app/src/main/java/net/gsantner/markor/model/Document.java index c74dbcf267..527ecbfe1a 100644 --- a/app/src/main/java/net/gsantner/markor/model/Document.java +++ b/app/src/main/java/net/gsantner/markor/model/Document.java @@ -241,6 +241,7 @@ public boolean isContentSame(final CharSequence s) { // We try to load 2x. If both times fail, we return null Pair result = GsFileUtils.readTextFileFast(_file); if (result.second.ioError) { + Log.i(Document.class.getName(), "Read error, trying again"); result = GsFileUtils.readTextFileFast(_file); } content = result.first; @@ -261,6 +262,7 @@ public boolean isContentSame(final CharSequence s) { // Force next load on failure setContentHash(null); resetChangeTracking(); + Log.i(Document.class.getName(), "Read error, could not load file"); return null; } else { // Also set hash and time on load - should prevent unnecessary saves @@ -345,13 +347,17 @@ public synchronized boolean saveContent(final Activity context, final CharSequen if (isContentResolverProxyFile) { GsFileUtils.writeFile(_file, contentAsBytes, _fileInfo); } - } catch (Exception ignored) { + } catch (Exception e) { + Log.i(Document.class.toString(), e.getMessage()); } }); success = true; } else { success = GsFileUtils.writeFile(_file, contentAsBytes, _fileInfo); } + + success &= fileBytes() >= contentAsBytes.length; + } catch (JavaPasswordbasedCryption.EncryptionFailedException e) { Log.e(Document.class.getName(), "writeContent: encrypt failed for File " + getPath() + ". " + e.getMessage(), e); Toast.makeText(context, R.string.could_not_encrypt_file_content_the_file_was_not_saved, Toast.LENGTH_LONG).show(); @@ -362,6 +368,8 @@ public synchronized boolean saveContent(final Activity context, final CharSequen setContentHash(content); _modTime = fileModTime(); setGlobalTouchTime(); + } else { + Log.i(Document.class.getName(), "File write failed, size = " + fileBytes()); } return success; From a04b319362dbaee0ad6ec42c5b02ced2bf56d22c Mon Sep 17 00:00:00 2001 From: Harshad Vedartham Date: Tue, 27 Sep 2022 13:34:01 -0700 Subject: [PATCH 16/40] post in fragments --- .../markor/activity/MainActivity.java | 19 ++++++------------- .../opoc/frontend/base/GsFragmentBase.java | 18 ++++++++++++++++++ .../filebrowser/GsFileBrowserFragment.java | 2 +- 3 files changed, 25 insertions(+), 14 deletions(-) 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 20990532b6..f1021811cf 100644 --- a/app/src/main/java/net/gsantner/markor/activity/MainActivity.java +++ b/app/src/main/java/net/gsantner/markor/activity/MainActivity.java @@ -64,7 +64,6 @@ public class MainActivity extends MarkorBaseActivity implements GsFileBrowserFra private SectionsPagerAdapter _viewPagerAdapter; private FloatingActionButton _fab; - private boolean _isNewIntent = false; private boolean _doubleBackToExitPressedOnce; private MarkorContextUtils _cu; @@ -114,8 +113,12 @@ public void onPageSelected(int position) { @Override protected void onNewIntent(final Intent intent) { super.onNewIntent(intent); - setIntent(intent); - _isNewIntent = true; + final File dir = getIntentDir(intent, null); + final GsFileBrowserFragment frag = getNotebook(); + if (frag != null && dir != null) { + frag.post(() -> frag.getAdapter().setCurrentFolder(dir, false)); + _bottomNav.postDelayed(() -> _bottomNav.setSelectedItemId(R.id.nav_notebook), 10); + } } private static File getIntentDir(final Intent intent, final File fallback) { @@ -215,16 +218,6 @@ protected void onResume() { } catch (IOException e) { e.printStackTrace(); } - - if (_isNewIntent) { - final File dir = getIntentDir(getIntent(), null); - final GsFileBrowserFragment frag = getNotebook(); - if (frag != null && dir != null) { - frag.getAdapter().setCurrentFolder(dir, false); - _bottomNav.postDelayed(() -> _bottomNav.setSelectedItemId(R.id.nav_notebook), 10); - } - } - _isNewIntent = false; } private void restartMainActivity() { diff --git a/app/src/main/java/net/gsantner/opoc/frontend/base/GsFragmentBase.java b/app/src/main/java/net/gsantner/opoc/frontend/base/GsFragmentBase.java index 1c540cd6b4..173475a996 100644 --- a/app/src/main/java/net/gsantner/opoc/frontend/base/GsFragmentBase.java +++ b/app/src/main/java/net/gsantner/opoc/frontend/base/GsFragmentBase.java @@ -29,6 +29,10 @@ import net.gsantner.opoc.util.GsContextUtils; import net.gsantner.opoc.wrapper.GsMenuItemDummy; +import java.util.ArrayList; +import java.util.LinkedList; +import java.util.Queue; + /** * A common base fragment to extend from */ @@ -40,6 +44,7 @@ public abstract class GsFragmentBase _postTasks = new LinkedList<>(); @Override public void onCreate(Bundle savedInstanceState) { @@ -154,13 +159,26 @@ protected void attachToolbarClickListenersToFragment() { } } + public void post(final Runnable action) { + final View view = getView(); + if (isResumed() && view != null) { + view.post(action); + } else { + _postTasks.add(action); + } + } + @Override public void onResume() { super.onResume(); final View view = getView(); if (view != null) { view.postDelayed(this::checkRunFirstTimeVisible, 200); + while (!_postTasks.isEmpty()) { + view.post(_postTasks.remove()); + } } + _postTasks.clear(); } @Override diff --git a/app/src/main/java/net/gsantner/opoc/frontend/filebrowser/GsFileBrowserFragment.java b/app/src/main/java/net/gsantner/opoc/frontend/filebrowser/GsFileBrowserFragment.java index 3d936cc22b..72aacc64f8 100644 --- a/app/src/main/java/net/gsantner/opoc/frontend/filebrowser/GsFileBrowserFragment.java +++ b/app/src/main/java/net/gsantner/opoc/frontend/filebrowser/GsFileBrowserFragment.java @@ -102,7 +102,7 @@ public interface FilesystemFragmentOptionsListener { } @Override - public void onViewCreated(View root, @Nullable Bundle savedInstanceState) { + public void onViewCreated(@NonNull View root, @Nullable Bundle savedInstanceState) { super.onViewCreated(root, savedInstanceState); Context context = getContext(); _recyclerList = root.findViewById(R.id.ui__filesystem_dialog__list); From f0a51955516f04482125a46545437da27e364a01 Mon Sep 17 00:00:00 2001 From: Harshad Vedartham Date: Wed, 28 Sep 2022 08:58:06 -0700 Subject: [PATCH 17/40] Fixed mime type detection --- .../java/net/gsantner/opoc/frontend/base/GsFragmentBase.java | 1 + app/src/main/java/net/gsantner/opoc/util/GsContextUtils.java | 4 ++-- app/src/main/java/net/gsantner/opoc/util/GsFileUtils.java | 2 +- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/net/gsantner/opoc/frontend/base/GsFragmentBase.java b/app/src/main/java/net/gsantner/opoc/frontend/base/GsFragmentBase.java index 173475a996..bd61b14090 100644 --- a/app/src/main/java/net/gsantner/opoc/frontend/base/GsFragmentBase.java +++ b/app/src/main/java/net/gsantner/opoc/frontend/base/GsFragmentBase.java @@ -174,6 +174,7 @@ public void onResume() { final View view = getView(); if (view != null) { view.postDelayed(this::checkRunFirstTimeVisible, 200); + // Add any remaining tasks while (!_postTasks.isEmpty()) { view.post(_postTasks.remove()); } 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 61106c92c7..9c894d0494 100644 --- a/app/src/main/java/net/gsantner/opoc/util/GsContextUtils.java +++ b/app/src/main/java/net/gsantner/opoc/util/GsContextUtils.java @@ -979,11 +979,11 @@ public String getMimeType(final Context context, String uri) { } // Try to guess if the recommended methods fail - if (!TextViewUtils.isNullOrWhitespace(mimeType) && new File(uri).exists()) { + if (TextViewUtils.isNullOrWhitespace(mimeType) && new File(uri).exists()) { mimeType = GsFileUtils.getMimeType(new File(uri)); } - if (!TextViewUtils.isNullOrWhitespace((mimeType))) { + if (TextViewUtils.isNullOrWhitespace(mimeType)) { mimeType = "*/*"; } return mimeType.toLowerCase(Locale.ROOT); diff --git a/app/src/main/java/net/gsantner/opoc/util/GsFileUtils.java b/app/src/main/java/net/gsantner/opoc/util/GsFileUtils.java index 8e3c6fdf9c..10de6c7932 100644 --- a/app/src/main/java/net/gsantner/opoc/util/GsFileUtils.java +++ b/app/src/main/java/net/gsantner/opoc/util/GsFileUtils.java @@ -459,7 +459,7 @@ public static String getMimeType(File file) { } t = URLConnection.guessContentTypeFromName(file.getName().replace(".jenc", "")); - return !TextViewUtils.isNullOrWhitespace(t) ? "*/*" : t; + return TextViewUtils.isNullOrWhitespace(t) ? "*/*" : t; } public static boolean isTextFile(File file) { From a626a06461b3bc6c07e6a68ba91ecd09bf925831 Mon Sep 17 00:00:00 2001 From: Harshad Vedartham Date: Thu, 29 Sep 2022 00:05:03 -0700 Subject: [PATCH 18/40] Cleanups, ordered snippets --- .../markor/frontend/MarkorDialogFactory.java | 4 ++-- .../markor/frontend/textview/TextViewUtils.java | 15 --------------- .../java/net/gsantner/opoc/util/GsFileUtils.java | 2 +- 3 files changed, 3 insertions(+), 18 deletions(-) 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 7b137a6051..76a196aec2 100644 --- a/app/src/main/java/net/gsantner/markor/frontend/MarkorDialogFactory.java +++ b/app/src/main/java/net/gsantner/markor/frontend/MarkorDialogFactory.java @@ -60,6 +60,7 @@ import java.util.List; import java.util.Map; import java.util.Set; +import java.util.TreeMap; import java.util.TreeSet; public class MarkorDialogFactory { @@ -661,7 +662,7 @@ public static void showSetPasswordDialog(final Activity activity) { // Read all files in snippets folder with appropriate extension // Create a map of sippet title -> text public static Map getSnippets(final AppSettings as) { - final Map texts = new OrderedMap<>(); + final Map texts = new TreeMap<>(); final File folder = new File(as.getNotebookDirectory(), ".app/snippets"); if ((!folder.exists() || !folder.isDirectory() || !folder.canRead())) { if (!folder.mkdirs()) { @@ -688,7 +689,6 @@ public static void showInsertSnippetDialog(final Activity activity, final GsCall final Map texts = getSnippets(as()); final List data = new ArrayList<>(texts.keySet()); - Collections.sort(data); dopt.data = data; dopt.isSearchEnabled = true; dopt.titleText = R.string.insert_snippet; 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 dd66f7a22b..2d07cd9ef2 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 @@ -111,21 +111,6 @@ public static int getNextNonWhitespace(final CharSequence s, final int start) { return -1; } - public static boolean isNullOrWhitespace(final String str) { - if (str == null || str.isEmpty()) { - return true; - } - - for (int i = 0; i < str.length(); i++) { - char ch = str.charAt(i); - if (!Character.isWhitespace(ch)) { - return false; - } - } - - return true; - } - public static int[] getSelection(final TextView text) { return getSelection(text.getText()); } diff --git a/app/src/main/java/net/gsantner/opoc/util/GsFileUtils.java b/app/src/main/java/net/gsantner/opoc/util/GsFileUtils.java index 6fc5e710cc..ddf363373a 100644 --- a/app/src/main/java/net/gsantner/opoc/util/GsFileUtils.java +++ b/app/src/main/java/net/gsantner/opoc/util/GsFileUtils.java @@ -422,7 +422,7 @@ public static String relativePath(File src, File dest) { return "*/*"; } - String ext = getFilenameExtension(file).replace(".", ""); + final String ext = getFilenameExtension(file).replace(".", ""); if (file.isDirectory()) { return "inode/directory"; } else if (ext.matches("ya?ml")) { From 113ae3e2193c877c5c7d73a8e5f90c24c9b3653a Mon Sep 17 00:00:00 2001 From: Harshad Vedartham Date: Thu, 29 Sep 2022 10:15:12 -0700 Subject: [PATCH 19/40] Tweaks to minimize issues --- .../frontend/textview/HighlightingEditor.java | 2 +- .../java/net/gsantner/markor/model/Document.java | 10 +++++++++- .../main/res/layout/document__fragment__edit.xml | 15 +-------------- 3 files changed, 11 insertions(+), 16 deletions(-) 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 2f6085cbef..18ac8cc559 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 @@ -294,7 +294,7 @@ public void endBatchEdit() { @Override protected void onVisibilityChanged(@NonNull View changedView, int visibility) { super.onVisibilityChanged(changedView, visibility); - if (visibility == View.VISIBLE) { + if (changedView == this && visibility == View.VISIBLE) { updateHighlighting(true); } } diff --git a/app/src/main/java/net/gsantner/markor/model/Document.java b/app/src/main/java/net/gsantner/markor/model/Document.java index 0efc6b7b16..414a8e2e42 100644 --- a/app/src/main/java/net/gsantner/markor/model/Document.java +++ b/app/src/main/java/net/gsantner/markor/model/Document.java @@ -354,10 +354,18 @@ public synchronized boolean saveContent(final Activity context, final CharSequen }); success = true; } else { + // Try write 2x success = GsFileUtils.writeFile(_file, contentAsBytes, _fileInfo); + if (!success || fileBytes() < contentAsBytes.length) { + success = GsFileUtils.writeFile(_file, contentAsBytes, _fileInfo); + } } - success &= fileBytes() >= contentAsBytes.length; + final long size = fileBytes(); + if (fileBytes() < contentAsBytes.length) { + success = false; + Log.i(Document.class.getName(), "File write failed; size = " + size + "; length = " + contentAsBytes.length); + } } catch (JavaPasswordbasedCryption.EncryptionFailedException e) { Log.e(Document.class.getName(), "writeContent: encrypt failed for File " + getPath() + ". " + e.getMessage(), e); diff --git a/app/src/main/res/layout/document__fragment__edit.xml b/app/src/main/res/layout/document__fragment__edit.xml index 54e3561a5e..b644b86d75 100644 --- a/app/src/main/res/layout/document__fragment__edit.xml +++ b/app/src/main/res/layout/document__fragment__edit.xml @@ -38,7 +38,7 @@ android:paddingLeft="@dimen/activity_horizontal_margin" android:paddingTop="@dimen/activity_vertical_margin" android:paddingRight="@dimen/activity_horizontal_margin" - android:paddingBottom="120dp" + android:paddingBottom="@dimen/activity_vertical_margin" android:scrollbars="none" android:textCursorDrawable="@drawable/cursor_accent" /> @@ -60,19 +60,6 @@ android:orientation="horizontal" /> - - Date: Thu, 29 Sep 2022 10:15:12 -0700 Subject: [PATCH 20/40] Tweaks to minimize issues --- .../frontend/textview/HighlightingEditor.java | 2 +- .../java/net/gsantner/markor/model/Document.java | 10 +++++++++- .../main/res/layout/document__fragment__edit.xml | 15 +-------------- 3 files changed, 11 insertions(+), 16 deletions(-) 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 2f6085cbef..18ac8cc559 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 @@ -294,7 +294,7 @@ public void endBatchEdit() { @Override protected void onVisibilityChanged(@NonNull View changedView, int visibility) { super.onVisibilityChanged(changedView, visibility); - if (visibility == View.VISIBLE) { + if (changedView == this && visibility == View.VISIBLE) { updateHighlighting(true); } } diff --git a/app/src/main/java/net/gsantner/markor/model/Document.java b/app/src/main/java/net/gsantner/markor/model/Document.java index 0efc6b7b16..414a8e2e42 100644 --- a/app/src/main/java/net/gsantner/markor/model/Document.java +++ b/app/src/main/java/net/gsantner/markor/model/Document.java @@ -354,10 +354,18 @@ public synchronized boolean saveContent(final Activity context, final CharSequen }); success = true; } else { + // Try write 2x success = GsFileUtils.writeFile(_file, contentAsBytes, _fileInfo); + if (!success || fileBytes() < contentAsBytes.length) { + success = GsFileUtils.writeFile(_file, contentAsBytes, _fileInfo); + } } - success &= fileBytes() >= contentAsBytes.length; + final long size = fileBytes(); + if (fileBytes() < contentAsBytes.length) { + success = false; + Log.i(Document.class.getName(), "File write failed; size = " + size + "; length = " + contentAsBytes.length); + } } catch (JavaPasswordbasedCryption.EncryptionFailedException e) { Log.e(Document.class.getName(), "writeContent: encrypt failed for File " + getPath() + ". " + e.getMessage(), e); diff --git a/app/src/main/res/layout/document__fragment__edit.xml b/app/src/main/res/layout/document__fragment__edit.xml index 54e3561a5e..b644b86d75 100644 --- a/app/src/main/res/layout/document__fragment__edit.xml +++ b/app/src/main/res/layout/document__fragment__edit.xml @@ -38,7 +38,7 @@ android:paddingLeft="@dimen/activity_horizontal_margin" android:paddingTop="@dimen/activity_vertical_margin" android:paddingRight="@dimen/activity_horizontal_margin" - android:paddingBottom="120dp" + android:paddingBottom="@dimen/activity_vertical_margin" android:scrollbars="none" android:textCursorDrawable="@drawable/cursor_accent" /> @@ -60,19 +60,6 @@ android:orientation="horizontal" /> - - Date: Thu, 29 Sep 2022 20:39:45 -0700 Subject: [PATCH 21/40] Interpolate datetime in template as snippet --- .../net/gsantner/markor/frontend/NewFileDialog.java | 11 ++++++----- app/src/main/res/layout/document__fragment__edit.xml | 3 ++- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/app/src/main/java/net/gsantner/markor/frontend/NewFileDialog.java b/app/src/main/java/net/gsantner/markor/frontend/NewFileDialog.java index 0dde7aa615..cc52a422fe 100644 --- a/app/src/main/java/net/gsantner/markor/frontend/NewFileDialog.java +++ b/app/src/main/java/net/gsantner/markor/frontend/NewFileDialog.java @@ -10,6 +10,7 @@ package net.gsantner.markor.frontend; import android.annotation.SuppressLint; +import android.app.AlertDialog; import android.app.Dialog; import android.os.Build; import android.os.Bundle; @@ -26,7 +27,6 @@ import android.widget.Spinner; import androidx.annotation.NonNull; -import androidx.appcompat.app.AlertDialog; import androidx.documentfile.provider.DocumentFile; import androidx.fragment.app.DialogFragment; @@ -34,6 +34,7 @@ import net.gsantner.markor.R; import net.gsantner.markor.format.todotxt.TodoTxtTask; import net.gsantner.markor.format.wikitext.WikitextActionButtons; +import net.gsantner.markor.frontend.textview.TextViewUtils; import net.gsantner.markor.model.AppSettings; import net.gsantner.markor.model.Document; import net.gsantner.markor.util.MarkorContextUtils; @@ -292,7 +293,7 @@ private byte[] getTemplateContent(final Spinner templateSpinner, final File base break; } case 8: { - t = "---\ntags: []\ncreated: '{{ template.timestamp_date_yyyy_mm_dd }}'\ntitle: ''\n---\n\n"; + t = TextViewUtils.interpolateEscapedDateTime("---\ntags: []\ncreated: '`yyyy-MM-dd`'\ntitle: ''\n---\n\n"); if (basedir != null && new File(basedir.getParentFile(), ".notabledir").exists()) { t = t.replace("created:", "modified:"); } @@ -303,9 +304,9 @@ private byte[] getTemplateContent(final Spinner templateSpinner, final File base break; } default: { - Map snippets = MarkorDialogFactory.getSnippets(ApplicationObject.settings()); + final Map snippets = MarkorDialogFactory.getSnippets(ApplicationObject.settings()); if (templateSpinner.getSelectedItem() instanceof String && snippets.containsKey((String) templateSpinner.getSelectedItem())) { - t = GsFileUtils.readTextFileFast(snippets.get((String) templateSpinner.getSelectedItem())).first; + t = TextViewUtils.interpolateEscapedDateTime(GsFileUtils.readTextFileFast(snippets.get((String) templateSpinner.getSelectedItem())).first); break; } @@ -313,7 +314,6 @@ private byte[] getTemplateContent(final Spinner templateSpinner, final File base return null; } } - t = t.replace("{{ template.timestamp_date_yyyy_mm_dd }}", TodoTxtTask.DATEF_YYYY_MM_DD.format(new Date())); if (encrypt && Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { final char[] pass = ApplicationObject.settings().getDefaultPassword(); @@ -321,6 +321,7 @@ private byte[] getTemplateContent(final Spinner templateSpinner, final File base } else { bytes = t.getBytes(); } + return bytes; } } diff --git a/app/src/main/res/layout/document__fragment__edit.xml b/app/src/main/res/layout/document__fragment__edit.xml index b644b86d75..28a4faf846 100644 --- a/app/src/main/res/layout/document__fragment__edit.xml +++ b/app/src/main/res/layout/document__fragment__edit.xml @@ -31,6 +31,7 @@ android:id="@+id/document__fragment__edit__highlighting_editor" android:layout_width="match_parent" android:layout_height="match_parent" + android:minHeight="@dimen/appbar_padding_top" android:background="@color/background" android:gravity="top" android:imeOptions="flagNoExtractUi" @@ -38,7 +39,7 @@ android:paddingLeft="@dimen/activity_horizontal_margin" android:paddingTop="@dimen/activity_vertical_margin" android:paddingRight="@dimen/activity_horizontal_margin" - android:paddingBottom="@dimen/activity_vertical_margin" + android:paddingBottom="120dp" android:scrollbars="none" android:textCursorDrawable="@drawable/cursor_accent" /> From a08388d39a3c3c5649f6afb8acaef287ceb36821 Mon Sep 17 00:00:00 2001 From: Harshad Vedartham Date: Thu, 29 Sep 2022 21:17:49 -0700 Subject: [PATCH 22/40] Start position in templates --- .../markor/frontend/NewFileDialog.java | 28 ++++++++++++------- 1 file changed, 18 insertions(+), 10 deletions(-) diff --git a/app/src/main/java/net/gsantner/markor/frontend/NewFileDialog.java b/app/src/main/java/net/gsantner/markor/frontend/NewFileDialog.java index cc52a422fe..7d77e6a750 100644 --- a/app/src/main/java/net/gsantner/markor/frontend/NewFileDialog.java +++ b/app/src/main/java/net/gsantner/markor/frontend/NewFileDialog.java @@ -10,13 +10,13 @@ package net.gsantner.markor.frontend; import android.annotation.SuppressLint; -import android.app.AlertDialog; import android.app.Dialog; import android.os.Build; import android.os.Bundle; import android.os.Handler; import android.text.InputFilter; import android.text.TextUtils; +import android.util.Pair; import android.view.LayoutInflater; import android.view.View; import android.view.Window; @@ -27,6 +27,7 @@ import android.widget.Spinner; import androidx.annotation.NonNull; +import androidx.appcompat.app.AlertDialog; import androidx.documentfile.provider.DocumentFile; import androidx.fragment.app.DialogFragment; @@ -34,6 +35,7 @@ import net.gsantner.markor.R; import net.gsantner.markor.format.todotxt.TodoTxtTask; import net.gsantner.markor.format.wikitext.WikitextActionButtons; +import net.gsantner.markor.frontend.textview.HighlightingEditor; import net.gsantner.markor.frontend.textview.TextViewUtils; import net.gsantner.markor.model.AppSettings; import net.gsantner.markor.model.Document; @@ -177,14 +179,14 @@ private AlertDialog.Builder makeDialog(final File basedir, final boolean allowCr dialogBuilder .setNegativeButton(R.string.cancel, (dialogInterface, i) -> dialogInterface.dismiss()) .setPositiveButton(getString(android.R.string.ok), (dialogInterface, i) -> { - if (ez(fileNameEdit)) { - return; - } + if (ez(fileNameEdit)) { + return; + } appSettings.setNewFileDialogLastUsedExtension(fileExtEdit.getText().toString().trim()); final String usedFilename = getFileNameWithoutExtension(fileNameEdit.getText().toString(), templateSpinner.getSelectedItemPosition()); final File f = new File(basedir, Document.normalizeFilename(usedFilename.trim()) + fileExtEdit.getText().toString().trim()); - final byte[] templateContents = getTemplateContent(templateSpinner, basedir, f.getName(), encryptCheckbox.isChecked()); + final Pair templateContents = getTemplateContent(templateSpinner, basedir, f.getName(), encryptCheckbox.isChecked()); cu.writeFile(getActivity(), f, false, (arg_ok, arg_fos) -> { try { if (appSettings.getNewFileDialogLastUsedUtf8Bom()) { @@ -193,10 +195,13 @@ private AlertDialog.Builder makeDialog(final File basedir, final boolean allowCr arg_fos.write(0xBF); } if (templateContents != null && (!f.exists() || f.length() < GsContextUtils.TEXTFILE_OVERWRITE_MIN_TEXT_LENGTH)) { - arg_fos.write(templateContents); + arg_fos.write(templateContents.first); } } catch (Exception ignored) { } + if (templateContents.second >= 0) { + appSettings.setLastEditPosition(f.getAbsolutePath(), templateContents.second); + } callback(arg_ok || f.exists(), f); dialogInterface.dismiss(); }); @@ -260,9 +265,8 @@ private void callback(boolean ok, File file) { // 2) t = ""; | ctrl+shift+v "paste without formatting" // @SuppressLint("TrulyRandom") - private byte[] getTemplateContent(final Spinner templateSpinner, final File basedir, final String filename, final boolean encrypt) { - String t = null; - byte[] bytes = null; + private Pair getTemplateContent(final Spinner templateSpinner, final File basedir, final String filename, final boolean encrypt) { + String t; switch (templateSpinner.getSelectedItemPosition()) { case 1: { t = "# Markdown Reference\nAutomatically generate _table of contents_ by checking the option here: `Settings > Format > Markdown`.\n\n## H2 Header\n### H3 header\n#### H4 Header\n##### H5 Header\n###### H6 Header\n\n\n\n## Format Text\n\n*Italic emphasis* , _Alternative italic emphasis_\n\n**Bold emphasis** , __Alternative bold emphasis__\n\n~~Strikethrough~~\n\nBreak line (two spaces at end of line) \n\n> Block quote\n\n`Inline code`\n\n```\nCode blocks\nare\nawesome\n```\n\n\n \n## Lists\n### Ordered & unordered\n\n* Unordered list\n* ...with asterisk/star\n* Test\n\n- Another unordered list\n- ...with hyphen/minus\n- Test\n\n1. Ordered list\n2. Test\n3. Test\n4. Test\n\n- Nested lists\n * Unordered nested list\n * Test\n * Test\n * Test\n- Ordered nested list\n 1. Test\n 2. Test\n 3. Test\n 4. Test\n- Double-nested unordered list\n - Test\n - Unordered\n - Test a\n - Test b\n - Ordered\n 1. Test 1\n 2. Test 2\n\n### Checklist\n* [ ] Salad\n* [x] Potatoes\n\n1. [x] Clean\n2. [ ] Cook\n\n\n\n## Links\n[Link](https://duckduckgo.com/)\n\n[File in same folder as the document.](markor-markdown-reference.md) Use %20 for spaces!\n\n\n\n## Tables\n\n| Left aligned | Middle aligned | Right aligned |\n| :--------------- | :------------------: | -----------------: |\n| Test | Test | Test |\n| Test | Test | Test |\n\n÷÷÷÷\n\nShorter | Table | Syntax\n:---: | ---: | :---\nTest | Test | Test\nTest | Test | Test\n\n\n\n\n\n## Math (KaTeX)\nSee [reference](https://katex.org/docs/supported.html) & [examples](https://github.com/waylonflinn/markdown-it-katex/blob/master/README.md). Enable by checking Math at `Settings > Markdown`.\n\n### Math inline\n\n$ I = \\frac V R $\n\n### Math block\n\n$$\\begin{array}{c} \nabla \\times \\vec{\\mathbf{B}} -\\, \\frac1c\\, \\frac{\\partial\\vec{\\mathbf{E}}}{\\partial t} & = \\frac{4\\pi}{c}\\vec{\\mathbf{j}} \nabla \\cdot \\vec{\\mathbf{E}} & = 4 \\pi \\rho \\\\ \nabla \\times \\vec{\\mathbf{E}}\\, +\\, \\frac1c\\, \\frac{\\partial\\vec{\\mathbf{B}}}{\\partial t} & = \\vec{\\mathbf{0}} \\\\ \nabla \\cdot \\vec{\\mathbf{B}} & = 0 \\end{array}$$\n\n\n$$\\frac{k_t}{k_e} = \\sqrt{2}$$\n\n\n\n## Format Text (continued)\n\n### Text color\n\nText with background color / highlight\n\nText foreground color\n\nText with colored outline / Text with colored outline\n\n\n### Text sub & superscript\n\nUnderline\n\nThe Subway sandwich was super\n\nSuper special characters: ⁰ ¹ ² ³ ⁴ ⁵ ⁶ ⁷ ⁸ ⁹ ⁺ ⁻ ⁼ ⁽ ⁾ ⁿ ™ ® ℠\n\n### Text positioning\n
\n\ntext on the **right**\n\n
\n\n
\n\ntext in the **center** \n(one empy line above and below \nrequired for Markdown support OR markdown='1')\n\n
\n\n### Block Text\n\n
\nlorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. \n
\n\n### Dropdown\n\n
Click to Expand/Collapse\n\nExpanded content. Shows up and keeps visible when clicking expand. Hide again by clicking the dropdown button again.\n\n
\n\n\n### Break page\nTo break the page (/start a new page), put the div below into a own line.\nRelevant for creating printable pages from the document (Print / PDF).\n\n
\n\n\n\n\n## Multimedia\n\n### Images\n![Image](https://gsantner.net/assets/blog/img/markor/markor-v1-7-showcase-3.jpg)\n\n### Videos\n**Youtube** [Welcome to Upper Austria](https://www.youtube.com/watch?v=RJREFH7Lmm8)\n\n\n**Peertube** [Road in the wood](https://open.tube/videos/watch/8116312a-dbbd-43a3-9260-9ea6367c72fc)\n
\n\n\n\n### Audio & Music\n**Web audio** [Guifrog - Xia Yu](https://www.freemusicarchive.org/music/Guifrog/Xia_Yu)\n\n\n**Local audio** Yellowcard - Lights up in the sky\n\n\n## Charts / Graphs / Diagrams (mermaidjs)\nPie, flow, sequence, class, state, ER \nSee also: mermaidjs [live editor](https://mermaid-js.github.io/mermaid-live-editor/).\n\n```mermaid\ngraph LR\n A[Square Rect] -- Link text --> B((Circle))\n A --> C(Round Rect)\n B --> D{Rhombus}\n C --> D\n```\n\n\n\n## Admonition Extension\nCreate block-styled side content. \nUse one of these qualifiers to select the icon and the block color: abstract, summary, tldr, bug, danger, error, example, snippet, failure, fail, missing, question, help, faq, info, todo, note, seealso, quote, cite, success, check, done, tip, hint, important, warning, caution, attention.\n\n!!! warning 'Optional Title'\n Block-Styled Side Content with **Markdown support**\n\n!!! info ''\n No-Heading Content\n\n??? bug 'Collapsed by default'\n Collapsible Block-Styled Side Content\n\n???+ example 'Open by default'\n Collapsible Block-Styled Side Content\n\n------------------\n\nThis Markdown reference file was created for the [Markor](https://gsantner.net/project/markor?source=markdownref) project by [Gregor Santner](https://gsantner.net) and is licensed [Creative Commons Zero 1.0](https://creativecommons.org/publicdomain/zero/1.0/legalcode) (public domain). File revision 3.\n\n------------------\n\n\n"; @@ -315,6 +319,10 @@ private byte[] getTemplateContent(final Spinner templateSpinner, final File base } } + final int startingIndex = t.indexOf(HighlightingEditor.PLACE_CURSOR_HERE_TOKEN); + t = t.replace(HighlightingEditor.PLACE_CURSOR_HERE_TOKEN, ""); + + final byte[] bytes; if (encrypt && Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { final char[] pass = ApplicationObject.settings().getDefaultPassword(); bytes = new JavaPasswordbasedCryption(Build.VERSION.SDK_INT, new SecureRandom()).encrypt(t, pass); @@ -322,6 +330,6 @@ private byte[] getTemplateContent(final Spinner templateSpinner, final File base bytes = t.getBytes(); } - return bytes; + return Pair.create(bytes, startingIndex); } } From 3a706296ee6526bde144e818e698db5411b75c04 Mon Sep 17 00:00:00 2001 From: Harshad Vedartham Date: Sun, 2 Oct 2022 08:48:11 -0700 Subject: [PATCH 23/40] Always reload folder, Interpolate when inserting snippet --- .../markor/activity/MainActivity.java | 5 +- .../markor/frontend/NewFileDialog.java | 93 ++++++++++--------- .../filebrowser/GsFileBrowserFragment.java | 4 +- .../filebrowser/GsFileBrowserListAdapter.java | 6 +- 4 files changed, 53 insertions(+), 55 deletions(-) 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 f1021811cf..6ee2154055 100644 --- a/app/src/main/java/net/gsantner/markor/activity/MainActivity.java +++ b/app/src/main/java/net/gsantner/markor/activity/MainActivity.java @@ -116,7 +116,7 @@ protected void onNewIntent(final Intent intent) { final File dir = getIntentDir(intent, null); final GsFileBrowserFragment frag = getNotebook(); if (frag != null && dir != null) { - frag.post(() -> frag.getAdapter().setCurrentFolder(dir, false)); + frag.post(() -> frag.getAdapter().setCurrentFolder(dir)); _bottomNav.postDelayed(() -> _bottomNav.setSelectedItemId(R.id.nav_notebook), 10); } } @@ -258,8 +258,7 @@ public boolean onLongClickFab(View view) { GsFileBrowserFragment fsFrag = getNotebook(); if (fsFrag != null && permc.mkdirIfStoragePermissionGranted()) { fsFrag.getAdapter().setCurrentFolder(fsFrag.getCurrentFolder().equals(GsFileBrowserListAdapter.VIRTUAL_STORAGE_RECENTS) - ? GsFileBrowserListAdapter.VIRTUAL_STORAGE_FAVOURITE : GsFileBrowserListAdapter.VIRTUAL_STORAGE_RECENTS - , true); + ? GsFileBrowserListAdapter.VIRTUAL_STORAGE_FAVOURITE : GsFileBrowserListAdapter.VIRTUAL_STORAGE_RECENTS); } return true; } diff --git a/app/src/main/java/net/gsantner/markor/frontend/NewFileDialog.java b/app/src/main/java/net/gsantner/markor/frontend/NewFileDialog.java index 7d77e6a750..ef6b66c893 100644 --- a/app/src/main/java/net/gsantner/markor/frontend/NewFileDialog.java +++ b/app/src/main/java/net/gsantner/markor/frontend/NewFileDialog.java @@ -176,50 +176,50 @@ private AlertDialog.Builder makeDialog(final File basedir, final boolean allowCr fileNameEdit.requestFocus(); final MarkorContextUtils cu = new MarkorContextUtils(getContext()); - dialogBuilder - .setNegativeButton(R.string.cancel, (dialogInterface, i) -> dialogInterface.dismiss()) - .setPositiveButton(getString(android.R.string.ok), (dialogInterface, i) -> { - if (ez(fileNameEdit)) { - return; - } - - appSettings.setNewFileDialogLastUsedExtension(fileExtEdit.getText().toString().trim()); - final String usedFilename = getFileNameWithoutExtension(fileNameEdit.getText().toString(), templateSpinner.getSelectedItemPosition()); - final File f = new File(basedir, Document.normalizeFilename(usedFilename.trim()) + fileExtEdit.getText().toString().trim()); - final Pair templateContents = getTemplateContent(templateSpinner, basedir, f.getName(), encryptCheckbox.isChecked()); - cu.writeFile(getActivity(), f, false, (arg_ok, arg_fos) -> { - try { - if (appSettings.getNewFileDialogLastUsedUtf8Bom()) { - arg_fos.write(0xEF); - arg_fos.write(0xBB); - arg_fos.write(0xBF); - } - if (templateContents != null && (!f.exists() || f.length() < GsContextUtils.TEXTFILE_OVERWRITE_MIN_TEXT_LENGTH)) { - arg_fos.write(templateContents.first); - } - } catch (Exception ignored) { - } - if (templateContents.second >= 0) { - appSettings.setLastEditPosition(f.getAbsolutePath(), templateContents.second); - } - callback(arg_ok || f.exists(), f); - dialogInterface.dismiss(); - }); - }) - .setNeutralButton(R.string.folder, (dialogInterface, i) -> { - if (ez(fileNameEdit)) { - return; + dialogBuilder.setNegativeButton(R.string.cancel, (dialogInterface, i) -> dialogInterface.dismiss()); + dialogBuilder.setPositiveButton(getString(android.R.string.ok), (dialogInterface, i) -> { + if (ez(fileNameEdit)) { + return; + } + + appSettings.setNewFileDialogLastUsedExtension(fileExtEdit.getText().toString().trim()); + final String usedFilename = getFileNameWithoutExtension(fileNameEdit.getText().toString(), templateSpinner.getSelectedItemPosition()); + final File f = new File(basedir, Document.normalizeFilename(usedFilename.trim()) + fileExtEdit.getText().toString().trim()); + final Pair templateContents = getTemplateContent(templateSpinner, basedir, f.getName(), encryptCheckbox.isChecked()); + cu.writeFile(getActivity(), f, false, (arg_ok, arg_fos) -> { + try { + if (appSettings.getNewFileDialogLastUsedUtf8Bom()) { + arg_fos.write(0xEF); + arg_fos.write(0xBB); + arg_fos.write(0xBF); } - final String usedFoldername = getFileNameWithoutExtension(fileNameEdit.getText().toString().trim(), templateSpinner.getSelectedItemPosition()); - File f = new File(basedir, usedFoldername); - if (cu.isUnderStorageAccessFolder(getContext(), f, true)) { - DocumentFile dof = cu.getDocumentFile(getContext(), f, true); - callback(dof != null && dof.exists(), f); - } else { - callback(f.mkdirs() || f.exists(), f); + if (templateContents.first != null && (!f.exists() || f.length() < GsContextUtils.TEXTFILE_OVERWRITE_MIN_TEXT_LENGTH)) { + arg_fos.write(templateContents.first); } - dialogInterface.dismiss(); - }); + } catch (Exception ignored) { + } + if (templateContents.second >= 0) { + appSettings.setLastEditPosition(f.getAbsolutePath(), templateContents.second); + } + callback(arg_ok || f.exists(), f); + dialogInterface.dismiss(); + }); + }); + + dialogBuilder.setNeutralButton(R.string.folder, (dialogInterface, i) -> { + if (ez(fileNameEdit)) { + return; + } + final String usedFoldername = getFileNameWithoutExtension(fileNameEdit.getText().toString().trim(), templateSpinner.getSelectedItemPosition()); + File f = new File(basedir, usedFoldername); + if (cu.isUnderStorageAccessFolder(getContext(), f, true)) { + DocumentFile dof = cu.getDocumentFile(getContext(), f, true); + callback(dof != null && dof.exists(), f); + } else { + callback(f.mkdirs() || f.exists(), f); + } + dialogInterface.dismiss(); + }); if (!allowCreateDir) { dialogBuilder.setNeutralButton("", null); @@ -266,7 +266,7 @@ private void callback(boolean ok, File file) { // @SuppressLint("TrulyRandom") private Pair getTemplateContent(final Spinner templateSpinner, final File basedir, final String filename, final boolean encrypt) { - String t; + String t = null; switch (templateSpinner.getSelectedItemPosition()) { case 1: { t = "# Markdown Reference\nAutomatically generate _table of contents_ by checking the option here: `Settings > Format > Markdown`.\n\n## H2 Header\n### H3 header\n#### H4 Header\n##### H5 Header\n###### H6 Header\n\n\n\n## Format Text\n\n*Italic emphasis* , _Alternative italic emphasis_\n\n**Bold emphasis** , __Alternative bold emphasis__\n\n~~Strikethrough~~\n\nBreak line (two spaces at end of line) \n\n> Block quote\n\n`Inline code`\n\n```\nCode blocks\nare\nawesome\n```\n\n\n \n## Lists\n### Ordered & unordered\n\n* Unordered list\n* ...with asterisk/star\n* Test\n\n- Another unordered list\n- ...with hyphen/minus\n- Test\n\n1. Ordered list\n2. Test\n3. Test\n4. Test\n\n- Nested lists\n * Unordered nested list\n * Test\n * Test\n * Test\n- Ordered nested list\n 1. Test\n 2. Test\n 3. Test\n 4. Test\n- Double-nested unordered list\n - Test\n - Unordered\n - Test a\n - Test b\n - Ordered\n 1. Test 1\n 2. Test 2\n\n### Checklist\n* [ ] Salad\n* [x] Potatoes\n\n1. [x] Clean\n2. [ ] Cook\n\n\n\n## Links\n[Link](https://duckduckgo.com/)\n\n[File in same folder as the document.](markor-markdown-reference.md) Use %20 for spaces!\n\n\n\n## Tables\n\n| Left aligned | Middle aligned | Right aligned |\n| :--------------- | :------------------: | -----------------: |\n| Test | Test | Test |\n| Test | Test | Test |\n\n÷÷÷÷\n\nShorter | Table | Syntax\n:---: | ---: | :---\nTest | Test | Test\nTest | Test | Test\n\n\n\n\n\n## Math (KaTeX)\nSee [reference](https://katex.org/docs/supported.html) & [examples](https://github.com/waylonflinn/markdown-it-katex/blob/master/README.md). Enable by checking Math at `Settings > Markdown`.\n\n### Math inline\n\n$ I = \\frac V R $\n\n### Math block\n\n$$\\begin{array}{c} \nabla \\times \\vec{\\mathbf{B}} -\\, \\frac1c\\, \\frac{\\partial\\vec{\\mathbf{E}}}{\\partial t} & = \\frac{4\\pi}{c}\\vec{\\mathbf{j}} \nabla \\cdot \\vec{\\mathbf{E}} & = 4 \\pi \\rho \\\\ \nabla \\times \\vec{\\mathbf{E}}\\, +\\, \\frac1c\\, \\frac{\\partial\\vec{\\mathbf{B}}}{\\partial t} & = \\vec{\\mathbf{0}} \\\\ \nabla \\cdot \\vec{\\mathbf{B}} & = 0 \\end{array}$$\n\n\n$$\\frac{k_t}{k_e} = \\sqrt{2}$$\n\n\n\n## Format Text (continued)\n\n### Text color\n\nText with background color / highlight\n\nText foreground color\n\nText with colored outline / Text with colored outline\n\n\n### Text sub & superscript\n\nUnderline\n\nThe Subway sandwich was super\n\nSuper special characters: ⁰ ¹ ² ³ ⁴ ⁵ ⁶ ⁷ ⁸ ⁹ ⁺ ⁻ ⁼ ⁽ ⁾ ⁿ ™ ® ℠\n\n### Text positioning\n
\n\ntext on the **right**\n\n
\n\n
\n\ntext in the **center** \n(one empy line above and below \nrequired for Markdown support OR markdown='1')\n\n
\n\n### Block Text\n\n
\nlorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. \n
\n\n### Dropdown\n\n
Click to Expand/Collapse\n\nExpanded content. Shows up and keeps visible when clicking expand. Hide again by clicking the dropdown button again.\n\n
\n\n\n### Break page\nTo break the page (/start a new page), put the div below into a own line.\nRelevant for creating printable pages from the document (Print / PDF).\n\n
\n\n\n\n\n## Multimedia\n\n### Images\n![Image](https://gsantner.net/assets/blog/img/markor/markor-v1-7-showcase-3.jpg)\n\n### Videos\n**Youtube** [Welcome to Upper Austria](https://www.youtube.com/watch?v=RJREFH7Lmm8)\n\n\n**Peertube** [Road in the wood](https://open.tube/videos/watch/8116312a-dbbd-43a3-9260-9ea6367c72fc)\n
\n\n\n\n### Audio & Music\n**Web audio** [Guifrog - Xia Yu](https://www.freemusicarchive.org/music/Guifrog/Xia_Yu)\n\n\n**Local audio** Yellowcard - Lights up in the sky\n\n\n## Charts / Graphs / Diagrams (mermaidjs)\nPie, flow, sequence, class, state, ER \nSee also: mermaidjs [live editor](https://mermaid-js.github.io/mermaid-live-editor/).\n\n```mermaid\ngraph LR\n A[Square Rect] -- Link text --> B((Circle))\n A --> C(Round Rect)\n B --> D{Rhombus}\n C --> D\n```\n\n\n\n## Admonition Extension\nCreate block-styled side content. \nUse one of these qualifiers to select the icon and the block color: abstract, summary, tldr, bug, danger, error, example, snippet, failure, fail, missing, question, help, faq, info, todo, note, seealso, quote, cite, success, check, done, tip, hint, important, warning, caution, attention.\n\n!!! warning 'Optional Title'\n Block-Styled Side Content with **Markdown support**\n\n!!! info ''\n No-Heading Content\n\n??? bug 'Collapsed by default'\n Collapsible Block-Styled Side Content\n\n???+ example 'Open by default'\n Collapsible Block-Styled Side Content\n\n------------------\n\nThis Markdown reference file was created for the [Markor](https://gsantner.net/project/markor?source=markdownref) project by [Gregor Santner](https://gsantner.net) and is licensed [Creative Commons Zero 1.0](https://creativecommons.org/publicdomain/zero/1.0/legalcode) (public domain). File revision 3.\n\n------------------\n\n\n"; @@ -313,12 +313,13 @@ private Pair getTemplateContent(final Spinner templateSpinner, t = TextViewUtils.interpolateEscapedDateTime(GsFileUtils.readTextFileFast(snippets.get((String) templateSpinner.getSelectedItem())).first); break; } - - // Empty file template (that doesn't overwrite anything) - return null; } } + if (t == null) { + return new Pair<>(null, -1); + } + final int startingIndex = t.indexOf(HighlightingEditor.PLACE_CURSOR_HERE_TOKEN); t = t.replace(HighlightingEditor.PLACE_CURSOR_HERE_TOKEN, ""); diff --git a/app/src/main/java/net/gsantner/opoc/frontend/filebrowser/GsFileBrowserFragment.java b/app/src/main/java/net/gsantner/opoc/frontend/filebrowser/GsFileBrowserFragment.java index 9faf305a6a..1c9db2cce9 100644 --- a/app/src/main/java/net/gsantner/opoc/frontend/filebrowser/GsFileBrowserFragment.java +++ b/app/src/main/java/net/gsantner/opoc/frontend/filebrowser/GsFileBrowserFragment.java @@ -301,7 +301,7 @@ public void onResume() { super.onResume(); if (!_appSettings.getNotebookDirectoryAsStr().equals(_previousNotebookDirectory)) { _dopt.rootFolder = _appSettings.getNotebookDirectory(); - _filesystemViewerAdapter.setCurrentFolder(_dopt.rootFolder, false); + _filesystemViewerAdapter.setCurrentFolder(_dopt.rootFolder); } if (!firstResume) { @@ -432,7 +432,7 @@ public boolean onOptionsItemSelected(MenuItem item) { case R.id.action_go_to_appdata_sdcard_2: case R.id.action_go_to_appdata_public: { final File folder = _appSettings.getFolderToLoadByMenuId(_id); - _filesystemViewerAdapter.setCurrentFolder(folder, true); + _filesystemViewerAdapter.setCurrentFolder(folder); Toast.makeText(getContext(), folder.getAbsolutePath(), Toast.LENGTH_SHORT).show(); return true; } diff --git a/app/src/main/java/net/gsantner/opoc/frontend/filebrowser/GsFileBrowserListAdapter.java b/app/src/main/java/net/gsantner/opoc/frontend/filebrowser/GsFileBrowserListAdapter.java index bac06b2b7c..880304848e 100644 --- a/app/src/main/java/net/gsantner/opoc/frontend/filebrowser/GsFileBrowserListAdapter.java +++ b/app/src/main/java/net/gsantner/opoc/frontend/filebrowser/GsFileBrowserListAdapter.java @@ -257,11 +257,9 @@ public void reloadCurrentFolder() { restoreSavedInstanceState(saveInstanceState(null)); } - public void setCurrentFolder(File folder, boolean reload) { + public void setCurrentFolder(final File folder) { _currentFolder = folder; - if (reload) { - reloadCurrentFolder(); - } + reloadCurrentFolder(); } public void reconfigure() { From 136209a606d2e860e6b190c53738737b870ac51c Mon Sep 17 00:00:00 2001 From: Harshad Vedartham Date: Sun, 2 Oct 2022 09:00:26 -0700 Subject: [PATCH 24/40] Also detect open in markor files as plain text --- .../java/net/gsantner/opoc/util/GsContextUtils.java | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) 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 3f01b012c0..2977bdb200 100644 --- a/app/src/main/java/net/gsantner/opoc/util/GsContextUtils.java +++ b/app/src/main/java/net/gsantner/opoc/util/GsContextUtils.java @@ -126,6 +126,7 @@ import com.google.android.material.snackbar.Snackbar; +import net.gsantner.markor.model.AppSettings; import net.gsantner.opoc.format.GsSimpleMarkdownParser; import net.gsantner.opoc.format.GsTextUtils; import net.gsantner.opoc.wrapper.GsCallback; @@ -969,11 +970,11 @@ public String getMimeType(final Context context, final File file) { public String getMimeType(final Context context, String uri) { String mimeType; uri = uri.replaceFirst("\\.jenc$", ""); + final String ext = MimeTypeMap.getFileExtensionFromUrl(uri); if (uri.startsWith(ContentResolver.SCHEME_CONTENT + "://")) { ContentResolver cr = context.getContentResolver(); mimeType = cr.getType(Uri.parse(uri)); } else { - String ext = MimeTypeMap.getFileExtensionFromUrl(uri); mimeType = MimeTypeMap.getSingleton().getMimeTypeFromExtension(ext.toLowerCase()); } @@ -982,9 +983,16 @@ public String getMimeType(final Context context, String uri) { mimeType = GsFileUtils.getMimeType(new File(uri)); } + // Detect extensions which open in this app at plain text + // Done only if all other tests fail + if (GsTextUtils.isNullOrEmpty(mimeType) && new AppSettings().init(context).isExtOpenWithThisApp(ext)) { + mimeType = "text/plain"; + } + if (GsTextUtils.isNullOrEmpty((mimeType))) { mimeType = "*/*"; } + return mimeType.toLowerCase(Locale.ROOT); } From 5013ea4e02feaba295ec3af3fed089fec988597c Mon Sep 17 00:00:00 2001 From: Harshad Vedartham Date: Sun, 2 Oct 2022 09:50:15 -0700 Subject: [PATCH 25/40] heightSame tweaks --- .../markor/frontend/textview/HighlightingEditor.java | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) 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 18ac8cc559..9f30ac7926 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 @@ -139,18 +139,16 @@ private void updateHighlighting(final boolean recompute) { // Don't highlight unless shifted sufficiently or a recompute is required if (recompute || (visible && _hl.hasSpans() && isScrollSignificant())) { - final boolean heightSame = _hlRect.height() == _oldHlRect.height(); + final boolean heightSame = _oldHlRect.isEmpty() || Math.abs(_hlRect.height() - _oldHlRect.height()) <= 2; // Addition of spans which require reflow can shift text on re-application of spans // we compute the resulting shift and scroll the view to compensate in order to make // the experience smooth for the user. int shiftTestLine = -1, oldOffset = -1; - if (_scrollView != null && heightSame) { + if (heightSame) { shiftTestLine = layout.getLineForVertical(_hlRect.centerY()); oldOffset = layout.getLineBaseline(shiftTestLine); - } - if (heightSame) { // Hack to block bring point into view // We don't call this when height is changing blockBringPointIntoView(); @@ -169,7 +167,7 @@ private void updateHighlighting(final boolean recompute) { endBatchEdit(); } - if (shiftTestLine >= 0) { + if (_scrollView != null && shiftTestLine >= 0) { final int shift = layout.getLineBaseline(shiftTestLine) - oldOffset; _scrollView.slowScrollShift(shift); } From babab87ee24145a0ff5eade88867683dc6d3fe2c Mon Sep 17 00:00:00 2001 From: Harshad Vedartham Date: Sun, 2 Oct 2022 15:48:12 -0700 Subject: [PATCH 26/40] Step 0 toward horking out bringPointIntoView --- .../activity/DocumentEditAndViewFragment.java | 1 + .../DraggableScrollbarScrollView.java | 2 + .../frontend/textview/HighlightingEditor.java | 117 ++++++------------ 3 files changed, 40 insertions(+), 80 deletions(-) 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 b7f2f6b0bf..24ad8cd1c3 100644 --- a/app/src/main/java/net/gsantner/markor/activity/DocumentEditAndViewFragment.java +++ b/app/src/main/java/net/gsantner/markor/activity/DocumentEditAndViewFragment.java @@ -235,6 +235,7 @@ public void onViewCreated(@NonNull View view, Bundle savedInstanceState) { updateUndoRedoIconStates(); }); _hlEditor.addTextChangedListener(GsTextWatcherAdapter.after(s -> debounced.run())); + } @Override diff --git a/app/src/main/java/net/gsantner/markor/frontend/DraggableScrollbarScrollView.java b/app/src/main/java/net/gsantner/markor/frontend/DraggableScrollbarScrollView.java index 839b642ca2..807e05487a 100644 --- a/app/src/main/java/net/gsantner/markor/frontend/DraggableScrollbarScrollView.java +++ b/app/src/main/java/net/gsantner/markor/frontend/DraggableScrollbarScrollView.java @@ -9,6 +9,8 @@ import android.view.View; import android.widget.ScrollView; +import net.gsantner.opoc.wrapper.GsCallback; + @SuppressLint("ClickableViewAccessibility") public class DraggableScrollbarScrollView extends ScrollView { 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 9f30ac7926..f6f55b4135 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 @@ -10,7 +10,6 @@ package net.gsantner.markor.frontend.textview; import android.content.Context; -import android.graphics.Canvas; import android.graphics.Paint; import android.graphics.Rect; import android.os.Build; @@ -27,7 +26,7 @@ import androidx.annotation.NonNull; import androidx.annotation.RequiresApi; -import androidx.appcompat.widget.AppCompatMultiAutoCompleteTextView; +import androidx.appcompat.widget.AppCompatEditText; import net.gsantner.markor.ApplicationObject; import net.gsantner.markor.activity.MainActivity; @@ -37,28 +36,24 @@ import net.gsantner.opoc.wrapper.GsTextWatcherAdapter; @SuppressWarnings("UnusedReturnValue") -public class HighlightingEditor extends AppCompatMultiAutoCompleteTextView { +public class HighlightingEditor extends AppCompatEditText { final static int HIGHLIGHT_SHIFT_LINES = 8; // Lines to scroll before hl updated final static float HIGHLIGHT_REGION_SIZE = 0.75f; // Minimum extra screens to highlight (should be > 0.5 to cover screen) - final static long BRING_CURSOR_INTO_VIEW_DELAY_MS = 200; // Block auto-scrolling for time after highlighing (hack) public final static String PLACE_CURSOR_HERE_TOKEN = "%%PLACE_CURSOR_HERE%%"; - private long _minPointIntoViewTime = 0; - private int _blockBringPointIntoViewCount = 0; private boolean _accessibilityEnabled = true; private final boolean _isSpellingRedUnderline; private SyntaxHighlighterBase _hl; private DraggableScrollbarScrollView _scrollView; - private boolean _isUpdatingDynamicHighlighting = false; private boolean _isDynamicHighlightingEnabled = true; private Runnable _hlDebounced; // Debounced runnable which recomputes highlighting private boolean _hlEnabled; // Whether highlighting is enabled private final Rect _oldHlRect; // Rect highlighting was previously applied to - private final Rect _hlRect; // Current rect + private final Rect _oldVisRect; // Rect highlighting was previously applied to + private final Rect _visRect; // Current rect private int _hlShiftThreshold = -1; // How much to scroll before re-apply highlight - private volatile boolean _hlPostQueued = false; private InputFilter _autoFormatFilter; private TextWatcher _autoFormatModifier; private boolean _autoFormatEnabled; @@ -78,7 +73,8 @@ public HighlightingEditor(Context context, AttributeSet attrs) { _hlEnabled = false; _oldHlRect = new Rect(); - _hlRect = new Rect(); + _oldVisRect = new Rect(); + _visRect = new Rect(); addTextChangedListener(new GsTextWatcherAdapter() { @Override @@ -98,84 +94,64 @@ public void afterTextChanged(final Editable s) { // Listen to and update highlighting final ViewTreeObserver observer = getViewTreeObserver(); - observer.addOnScrollChangedListener(this::updateDynamicHighlighting); - observer.addOnGlobalLayoutListener(this::postUpdateDynamicHighlighting); + observer.addOnScrollChangedListener(this::onRegionChangeListener); + observer.addOnGlobalLayoutListener(this::onRegionChangeListener); // Fix for android 12 perf issues - https://github.com/gsantner/markor/discussions/1794 setEmojiCompatEnabled(false); } - // Highlighting - // --------------------------------------------------------------------------------------------- - - private boolean isScrollSignificant() { - return (_oldHlRect.top - _hlRect.top) > _hlShiftThreshold || - (_hlRect.bottom - _oldHlRect.bottom) > _hlShiftThreshold; - } - - private void updateDynamicHighlighting() { - if (_isDynamicHighlightingEnabled) { + private void onRegionChangeListener() { + if (getLocalVisibleRect(_visRect) && !_visRect.equals(_oldVisRect)) { updateHighlighting(false); + + // Significant change not due to overscroll + if (Math.abs(_oldVisRect.height() - _visRect.height()) >= 2 + && _visRect.top > 0 + && _visRect.bottom < getHeight()) { + post(() -> super.bringPointIntoView(getSelectionStart())); + } } + + _oldVisRect.set(_visRect); } - private synchronized void postUpdateDynamicHighlighting() { - if (!_hlPostQueued) { - _hlPostQueued = true; - post(() -> { - updateDynamicHighlighting(); - _hlPostQueued = false; - }); - } + // Highlighting + // --------------------------------------------------------------------------------------------- + + private boolean isScrollSignificant() { + return ( _oldHlRect.top - _visRect.top) > _hlShiftThreshold || + (_visRect.bottom - _oldHlRect.bottom) > _hlShiftThreshold; } private void updateHighlighting(final boolean recompute) { final Layout layout; - if (!_isUpdatingDynamicHighlighting && _hlEnabled && _hl != null && (layout = getLayout()) != null) { - _isUpdatingDynamicHighlighting = true; - - final boolean visible = getLocalVisibleRect(_hlRect); + if (_hlEnabled && _hl != null && (layout = getLayout()) != null) { // Don't highlight unless shifted sufficiently or a recompute is required - if (recompute || (visible && _hl.hasSpans() && isScrollSignificant())) { - - final boolean heightSame = _oldHlRect.isEmpty() || Math.abs(_hlRect.height() - _oldHlRect.height()) <= 2; + if (recompute || (_hl.hasSpans() && isScrollSignificant())) { // Addition of spans which require reflow can shift text on re-application of spans // we compute the resulting shift and scroll the view to compensate in order to make // the experience smooth for the user. - int shiftTestLine = -1, oldOffset = -1; - if (heightSame) { - shiftTestLine = layout.getLineForVertical(_hlRect.centerY()); - oldOffset = layout.getLineBaseline(shiftTestLine); - - // Hack to block bring point into view - // We don't call this when height is changing - blockBringPointIntoView(); - } + final boolean heightSame = Math.abs(_visRect.height() - _oldHlRect.height()) <= 2; + final int shiftTestLine = heightSame ? layout.getLineForVertical(_visRect.centerY()) : -1; + final int oldOffset = heightSame ? layout.getLineBaseline(shiftTestLine) : 0; - final int[] newHlRegion = hlRegion(_hlRect); // Compute this _before_ clear - try { - beginBatchEdit(); - _hl.clear(); - if (recompute) { - _hl.recompute(); - } - _hl.apply(newHlRegion); - } finally { - blockBringPointIntoView(); - endBatchEdit(); + final int[] newHlRegion = hlRegion(_visRect); // Compute this _before_ clear + _hl.clear(); + if (recompute) { + _hl.recompute(); } + _hl.apply(newHlRegion); if (_scrollView != null && shiftTestLine >= 0) { final int shift = layout.getLineBaseline(shiftTestLine) - oldOffset; _scrollView.slowScrollShift(shift); } - _oldHlRect.set(_hlRect); + _oldHlRect.set(_visRect); } - - _isUpdatingDynamicHighlighting = false; } } @@ -297,29 +273,10 @@ protected void onVisibilityChanged(@NonNull View changedView, int visibility) { } } - @Override - public void onDraw(Canvas canvas) { - super.onDraw(canvas); - } - - // We block the next call OR until time has passed - private void blockBringPointIntoView() { - _minPointIntoViewTime = System.currentTimeMillis() + BRING_CURSOR_INTO_VIEW_DELAY_MS; - _blockBringPointIntoViewCount++; - } - // Hack to prevent auto-scroll @Override public boolean bringPointIntoView(int cursor) { - if (_blockBringPointIntoViewCount <= 0 || System.currentTimeMillis() > _minPointIntoViewTime) { - _blockBringPointIntoViewCount = 0; - return super.bringPointIntoView(cursor); - } else { - if (_blockBringPointIntoViewCount > 0) { - _blockBringPointIntoViewCount -= 1; - } - return false; - } + return false; } @Override From f4088501d9344d4d26ca2a488455aa3d11ab333f Mon Sep 17 00:00:00 2001 From: Harshad Vedartham Date: Wed, 5 Oct 2022 23:03:52 -0700 Subject: [PATCH 27/40] Completely controlling bringing cursor into view --- .../DraggableScrollbarScrollView.java | 1 + .../frontend/textview/HighlightingEditor.java | 103 ++++++++++++++---- .../frontend/textview/TextViewUtils.java | 27 ++--- 3 files changed, 91 insertions(+), 40 deletions(-) diff --git a/app/src/main/java/net/gsantner/markor/frontend/DraggableScrollbarScrollView.java b/app/src/main/java/net/gsantner/markor/frontend/DraggableScrollbarScrollView.java index 807e05487a..99ed740ad0 100644 --- a/app/src/main/java/net/gsantner/markor/frontend/DraggableScrollbarScrollView.java +++ b/app/src/main/java/net/gsantner/markor/frontend/DraggableScrollbarScrollView.java @@ -55,6 +55,7 @@ public boolean onInterceptTouchEvent(MotionEvent ev) { } return true; } + return super.onInterceptTouchEvent(ev); } 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 f6f55b4135..4d93fb7c25 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 @@ -43,17 +43,19 @@ public class HighlightingEditor extends AppCompatEditText { public final static String PLACE_CURSOR_HERE_TOKEN = "%%PLACE_CURSOR_HERE%%"; + private int _prevSelStart = -1, _prevSelEnd = -1; + boolean _inSetSelection = false; private boolean _accessibilityEnabled = true; private final boolean _isSpellingRedUnderline; private SyntaxHighlighterBase _hl; private DraggableScrollbarScrollView _scrollView; private boolean _isDynamicHighlightingEnabled = true; - private Runnable _hlDebounced; // Debounced runnable which recomputes highlighting - private boolean _hlEnabled; // Whether highlighting is enabled - private final Rect _oldHlRect; // Rect highlighting was previously applied to - private final Rect _oldVisRect; // Rect highlighting was previously applied to - private final Rect _visRect; // Current rect - private int _hlShiftThreshold = -1; // How much to scroll before re-apply highlight + private Runnable _hlDebounced; // Debounced runnable which recomputes highlighting + private boolean _hlEnabled; // Whether highlighting is enabled + private final Rect _oldHlRect; // Rect highlighting was previously applied to + private final Rect _oldVisRect; // Rect highlighting was previously applied to + private final Rect _visRect; // Current rect + private int _hlShiftThreshold = -1; // How much to scroll before re-apply highlight private InputFilter _autoFormatFilter; private TextWatcher _autoFormatModifier; private boolean _autoFormatEnabled; @@ -82,6 +84,8 @@ public void onTextChanged(CharSequence s, int start, int before, int count) { if (_hlEnabled && _hl != null) { _hl.fixup(start, before, count); } + // Direct call to super as we block bringPointIntoView otherwise + HighlightingEditor.super.bringPointIntoView(getSelectionStart()); } @Override @@ -101,16 +105,46 @@ public void afterTextChanged(final Editable s) { setEmojiCompatEnabled(false); } + // + // --------------------------------------------------------------------------------------------- + + private boolean wasCursorPreviouslyVisible() { + final int sel = getSelectionStart(); + final Layout layout = getLayout(); + if (layout == null || !indexesValid(sel)) { + return false; + } + final int line = layout.getLineForOffset(sel); + final int Y = Math.round(0.5f * (layout.getLineTop(line) + layout.getLineBottom(line))); + final int X = Math.round(layout.getPrimaryHorizontal(sel)); + return _oldVisRect.contains(X, Y); + } + + private boolean isHeightChangeSignificant() { + final int ha = _oldVisRect.height(), hb = _visRect.height(); + final float min = Math.min(ha, hb), max = Math.max(ha, hb); + return max != 0 && ((max - min) / max) > 0.10; + } + + private void showCursorIfNecessary() { + if (isHeightChangeSignificant()) { + if (wasCursorPreviouslyVisible()) { + final int sel = getSelectionStart(); + post(() -> { + // Direct call to super as we block bringPointIntoView otherwise + super.bringPointIntoView(sel); + // Double call to handle case where changing hl moves sel + post(() -> super.bringPointIntoView(sel)); + }); + } + } + } + private void onRegionChangeListener() { if (getLocalVisibleRect(_visRect) && !_visRect.equals(_oldVisRect)) { updateHighlighting(false); - // Significant change not due to overscroll - if (Math.abs(_oldVisRect.height() - _visRect.height()) >= 2 - && _visRect.top > 0 - && _visRect.bottom < getHeight()) { - post(() -> super.bringPointIntoView(getSelectionStart())); - } + showCursorIfNecessary(); } _oldVisRect.set(_visRect); @@ -139,11 +173,16 @@ private void updateHighlighting(final boolean recompute) { final int oldOffset = heightSame ? layout.getLineBaseline(shiftTestLine) : 0; final int[] newHlRegion = hlRegion(_visRect); // Compute this _before_ clear - _hl.clear(); - if (recompute) { - _hl.recompute(); + try { + beginBatchEdit(); + _hl.clear(); + if (recompute) { + _hl.recompute(); + } + _hl.apply(newHlRegion); + } finally { + endBatchEdit(); } - _hl.apply(newHlRegion); if (_scrollView != null && shiftTestLine >= 0) { final int shift = layout.getLineBaseline(shiftTestLine) - oldOffset; @@ -274,6 +313,7 @@ protected void onVisibilityChanged(@NonNull View changedView, int visibility) { } // Hack to prevent auto-scroll + // Calls to super.bringPointIntoView are performed whenever necessary @Override public boolean bringPointIntoView(int cursor) { return false; @@ -341,21 +381,40 @@ public void setSelection(int index) { @Override public void setSelection(int start, int stop) { - if (indexesValid(start, stop)) { - super.setSelection(start, stop); - } else if (indexesValid(start, stop - 1)) { - super.setSelection(start, stop - 1); - } else if (indexesValid(start + 1, stop)) { - super.setSelection(start + 1, stop); + try { + _inSetSelection = true; + if (indexesValid(start, stop)) { + super.setSelection(start, stop); + } else if (indexesValid(start, stop - 1)) { + super.setSelection(start, stop - 1); + } else if (indexesValid(start + 1, stop)) { + super.setSelection(start + 1, stop); + } + } finally { + _inSetSelection = false; } } @Override protected void onSelectionChanged(int selStart, int selEnd) { super.onSelectionChanged(selStart, selEnd); + if (MainActivity.IS_DEBUG_ENABLED) { AppSettings.appendDebugLog("Selection changed: " + selStart + "->" + selEnd); } + + if (!_inSetSelection) { + // Bring appropriate piece into view + if (indexesValid(_prevSelStart, selStart) && _prevSelStart != selStart) { + // Start dragged + super.bringPointIntoView(selStart); + } else if (indexesValid(_prevSelEnd, selEnd) && _prevSelEnd != selEnd) { + // End dragged + super.bringPointIntoView(selEnd); + } + } + _prevSelStart = selStart; + _prevSelEnd = selEnd; } // Auto-format 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 2d07cd9ef2..eeaf94efdc 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 @@ -264,8 +264,7 @@ public static void selectLines(final EditText edit, final List position } } - public static @NonNull - Rect getSelRect(final TextView text, final int... sel) { + public static @NonNull Rect getSelRect(final TextView text, final int... sel) { final Rect region = new Rect(); @@ -403,8 +402,10 @@ public static void setSelectionAndShow(final EditText edit, final int... sel) { edit.requestFocus(); } - edit.setSelection(start, end); - edit.post(() -> showSelection(edit, start, end)); + edit.post(() -> { + showSelection(edit, start, end); + edit.setSelection(start, end); + }); }); } } @@ -678,20 +679,10 @@ public static Runnable makeDebounced(final Handler handler, final long delayMs, return () -> { synchronized (sync) { _handler.removeCallbacks(callback); - _handler.postDelayed(callback, delayMs); - } - }; - } - - public static Runnable blockReentry(final Runnable callback) { - final boolean[] block = new boolean[]{false}; - return () -> { - if (!block[0]) { - try { - block[0] = true; - callback.run(); - } finally { - block[0] = false; + if (delayMs > 0) { + _handler.postDelayed(callback, delayMs); + } else { + _handler.post(callback); } } }; From b38441430f46d72db5b712eab6ad7107578706e0 Mon Sep 17 00:00:00 2001 From: Harshad Vedartham Date: Wed, 5 Oct 2022 23:51:06 -0700 Subject: [PATCH 28/40] Some cleanups --- .../frontend/textview/HighlightingEditor.java | 35 +++++++------------ 1 file changed, 13 insertions(+), 22 deletions(-) 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 4d93fb7c25..1b09dfc536 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 @@ -84,8 +84,7 @@ public void onTextChanged(CharSequence s, int start, int before, int count) { if (_hlEnabled && _hl != null) { _hl.fixup(start, before, count); } - // Direct call to super as we block bringPointIntoView otherwise - HighlightingEditor.super.bringPointIntoView(getSelectionStart()); + _bringPointIntoView(getSelectionStart()); } @Override @@ -111,7 +110,7 @@ public void afterTextChanged(final Editable s) { private boolean wasCursorPreviouslyVisible() { final int sel = getSelectionStart(); final Layout layout = getLayout(); - if (layout == null || !indexesValid(sel)) { + if (_oldVisRect.isEmpty() || layout == null || !indexesValid(sel)) { return false; } final int line = layout.getLineForOffset(sel); @@ -126,25 +125,14 @@ private boolean isHeightChangeSignificant() { return max != 0 && ((max - min) / max) > 0.10; } - private void showCursorIfNecessary() { - if (isHeightChangeSignificant()) { - if (wasCursorPreviouslyVisible()) { - final int sel = getSelectionStart(); - post(() -> { - // Direct call to super as we block bringPointIntoView otherwise - super.bringPointIntoView(sel); - // Double call to handle case where changing hl moves sel - post(() -> super.bringPointIntoView(sel)); - }); - } - } - } - private void onRegionChangeListener() { if (getLocalVisibleRect(_visRect) && !_visRect.equals(_oldVisRect)) { updateHighlighting(false); - showCursorIfNecessary(); + // Heuristic for sip shown etc + if (isHeightChangeSignificant() && wasCursorPreviouslyVisible()) { + _bringPointIntoView(getSelectionStart()); + } } _oldVisRect.set(_visRect); @@ -312,13 +300,16 @@ protected void onVisibilityChanged(@NonNull View changedView, int visibility) { } } - // Hack to prevent auto-scroll - // Calls to super.bringPointIntoView are performed whenever necessary + // We block bringPointIntoView as it is called by the system when not needed @Override public boolean bringPointIntoView(int cursor) { return false; } + private boolean _bringPointIntoView(int cursor) { + return super.bringPointIntoView(cursor); + } + @Override public void setTextSize(float size) { super.setTextSize(size); @@ -407,10 +398,10 @@ protected void onSelectionChanged(int selStart, int selEnd) { // Bring appropriate piece into view if (indexesValid(_prevSelStart, selStart) && _prevSelStart != selStart) { // Start dragged - super.bringPointIntoView(selStart); + _bringPointIntoView(selStart); } else if (indexesValid(_prevSelEnd, selEnd) && _prevSelEnd != selEnd) { // End dragged - super.bringPointIntoView(selEnd); + _bringPointIntoView(selEnd); } } _prevSelStart = selStart; From 2a471dc3b8b8e1bc719af0e8840b733abc147fa1 Mon Sep 17 00:00:00 2001 From: Harshad Vedartham Date: Thu, 6 Oct 2022 15:24:37 -0700 Subject: [PATCH 29/40] Removed drag handling --- .../frontend/textview/HighlightingEditor.java | 41 +++---------------- .../res/layout/document__fragment__edit.xml | 3 +- 2 files changed, 7 insertions(+), 37 deletions(-) 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 1b09dfc536..3960cd1892 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 @@ -43,8 +43,6 @@ public class HighlightingEditor extends AppCompatEditText { public final static String PLACE_CURSOR_HERE_TOKEN = "%%PLACE_CURSOR_HERE%%"; - private int _prevSelStart = -1, _prevSelEnd = -1; - boolean _inSetSelection = false; private boolean _accessibilityEnabled = true; private final boolean _isSpellingRedUnderline; private SyntaxHighlighterBase _hl; @@ -372,40 +370,13 @@ public void setSelection(int index) { @Override public void setSelection(int start, int stop) { - try { - _inSetSelection = true; - if (indexesValid(start, stop)) { - super.setSelection(start, stop); - } else if (indexesValid(start, stop - 1)) { - super.setSelection(start, stop - 1); - } else if (indexesValid(start + 1, stop)) { - super.setSelection(start + 1, stop); - } - } finally { - _inSetSelection = false; - } - } - - @Override - protected void onSelectionChanged(int selStart, int selEnd) { - super.onSelectionChanged(selStart, selEnd); - - if (MainActivity.IS_DEBUG_ENABLED) { - AppSettings.appendDebugLog("Selection changed: " + selStart + "->" + selEnd); - } - - if (!_inSetSelection) { - // Bring appropriate piece into view - if (indexesValid(_prevSelStart, selStart) && _prevSelStart != selStart) { - // Start dragged - _bringPointIntoView(selStart); - } else if (indexesValid(_prevSelEnd, selEnd) && _prevSelEnd != selEnd) { - // End dragged - _bringPointIntoView(selEnd); - } + if (indexesValid(start, stop)) { + super.setSelection(start, stop); + } else if (indexesValid(start, stop - 1)) { + super.setSelection(start, stop - 1); + } else if (indexesValid(start + 1, stop)) { + super.setSelection(start + 1, stop); } - _prevSelStart = selStart; - _prevSelEnd = selEnd; } // Auto-format diff --git a/app/src/main/res/layout/document__fragment__edit.xml b/app/src/main/res/layout/document__fragment__edit.xml index 28a4faf846..b644b86d75 100644 --- a/app/src/main/res/layout/document__fragment__edit.xml +++ b/app/src/main/res/layout/document__fragment__edit.xml @@ -31,7 +31,6 @@ android:id="@+id/document__fragment__edit__highlighting_editor" android:layout_width="match_parent" android:layout_height="match_parent" - android:minHeight="@dimen/appbar_padding_top" android:background="@color/background" android:gravity="top" android:imeOptions="flagNoExtractUi" @@ -39,7 +38,7 @@ android:paddingLeft="@dimen/activity_horizontal_margin" android:paddingTop="@dimen/activity_vertical_margin" android:paddingRight="@dimen/activity_horizontal_margin" - android:paddingBottom="120dp" + android:paddingBottom="@dimen/activity_vertical_margin" android:scrollbars="none" android:textCursorDrawable="@drawable/cursor_accent" /> From ae1c6e516827ac8f630ef09195c9adb0686feec5 Mon Sep 17 00:00:00 2001 From: Harshad Vedartham Date: Thu, 6 Oct 2022 22:13:45 -0700 Subject: [PATCH 30/40] Separate dynamic / static highlighting --- .../activity/DocumentEditAndViewFragment.java | 1 - .../markor/frontend/MarkorDialogFactory.java | 4 +- .../frontend/textview/HighlightingEditor.java | 133 ++++-------------- .../textview/SyntaxHighlighterBase.java | 82 +++++++++-- .../frontend/textview/TextViewUtils.java | 109 +++----------- 5 files changed, 117 insertions(+), 212 deletions(-) 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 24ad8cd1c3..5f618d348f 100644 --- a/app/src/main/java/net/gsantner/markor/activity/DocumentEditAndViewFragment.java +++ b/app/src/main/java/net/gsantner/markor/activity/DocumentEditAndViewFragment.java @@ -195,7 +195,6 @@ public void onViewCreated(@NonNull View view, Bundle savedInstanceState) { ((DocumentActivity) activity).setDocumentTitle(_document.getTitle()); } - _hlEditor.setScrollView(_primaryScrollView); _hlEditor.setLineSpacing(0, _appSettings.getEditorLineSpacing()); _hlEditor.setTextSize(TypedValue.COMPLEX_UNIT_SP, _appSettings.getDocumentFontSize(_document.getPath())); _hlEditor.setTypeface(GsFontPreferenceCompat.typeface(getContext(), _appSettings.getFontFamily(), Typeface.NORMAL)); 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 5f5b30e131..3f3e6cefb8 100644 --- a/app/src/main/java/net/gsantner/markor/frontend/MarkorDialogFactory.java +++ b/app/src/main/java/net/gsantner/markor/frontend/MarkorDialogFactory.java @@ -34,8 +34,6 @@ import androidx.core.content.ContextCompat; -import com.vladsch.flexmark.util.collection.OrderedMap; - import net.gsantner.markor.ApplicationObject; import net.gsantner.markor.R; import net.gsantner.markor.format.todotxt.TodoTxtBasicSyntaxHighlighter; @@ -514,7 +512,7 @@ public static void showSttProjectDialog(Activity activity, List availabl private static GsCallback.a1 getSttHighlighter() { final SyntaxHighlighterBase h = new TodoTxtBasicSyntaxHighlighter(as()).configure(); - return s -> h.setSpannable(s).recompute().apply(); + return s -> h.setSpannable(s).recompute().applyAll(); } public static void showSearchDialog(final Activity activity, final EditText text) { 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 3960cd1892..316ffc8ba6 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 @@ -30,7 +30,6 @@ import net.gsantner.markor.ApplicationObject; import net.gsantner.markor.activity.MainActivity; -import net.gsantner.markor.frontend.DraggableScrollbarScrollView; import net.gsantner.markor.model.AppSettings; import net.gsantner.opoc.wrapper.GsCallback; import net.gsantner.opoc.wrapper.GsTextWatcherAdapter; @@ -46,14 +45,12 @@ public class HighlightingEditor extends AppCompatEditText { private boolean _accessibilityEnabled = true; private final boolean _isSpellingRedUnderline; private SyntaxHighlighterBase _hl; - private DraggableScrollbarScrollView _scrollView; private boolean _isDynamicHighlightingEnabled = true; - private Runnable _hlDebounced; // Debounced runnable which recomputes highlighting - private boolean _hlEnabled; // Whether highlighting is enabled - private final Rect _oldHlRect; // Rect highlighting was previously applied to - private final Rect _oldVisRect; // Rect highlighting was previously applied to - private final Rect _visRect; // Current rect - private int _hlShiftThreshold = -1; // How much to scroll before re-apply highlight + private Runnable _hlDebounced; // Debounced runnable which recomputes highlighting + private boolean _hlEnabled; // Whether highlighting is enabled + private final Rect _oldHlRect; // Rect highlighting was previously applied to + private final Rect _hlRect; // Current rect + private int _hlShiftThreshold = -1; // How much to scroll before re-apply highlight private InputFilter _autoFormatFilter; private TextWatcher _autoFormatModifier; private boolean _autoFormatEnabled; @@ -73,8 +70,7 @@ public HighlightingEditor(Context context, AttributeSet attrs) { _hlEnabled = false; _oldHlRect = new Rect(); - _oldVisRect = new Rect(); - _visRect = new Rect(); + _hlRect = new Rect(); addTextChangedListener(new GsTextWatcherAdapter() { @Override @@ -82,7 +78,6 @@ public void onTextChanged(CharSequence s, int start, int before, int count) { if (_hlEnabled && _hl != null) { _hl.fixup(start, before, count); } - _bringPointIntoView(getSelectionStart()); } @Override @@ -95,87 +90,35 @@ public void afterTextChanged(final Editable s) { // Listen to and update highlighting final ViewTreeObserver observer = getViewTreeObserver(); - observer.addOnScrollChangedListener(this::onRegionChangeListener); - observer.addOnGlobalLayoutListener(this::onRegionChangeListener); + observer.addOnScrollChangedListener(() -> updateHighlighting(false)); + observer.addOnGlobalLayoutListener(() -> updateHighlighting(false)); // Fix for android 12 perf issues - https://github.com/gsantner/markor/discussions/1794 setEmojiCompatEnabled(false); } - // - // --------------------------------------------------------------------------------------------- - - private boolean wasCursorPreviouslyVisible() { - final int sel = getSelectionStart(); - final Layout layout = getLayout(); - if (_oldVisRect.isEmpty() || layout == null || !indexesValid(sel)) { - return false; - } - final int line = layout.getLineForOffset(sel); - final int Y = Math.round(0.5f * (layout.getLineTop(line) + layout.getLineBottom(line))); - final int X = Math.round(layout.getPrimaryHorizontal(sel)); - return _oldVisRect.contains(X, Y); - } - - private boolean isHeightChangeSignificant() { - final int ha = _oldVisRect.height(), hb = _visRect.height(); - final float min = Math.min(ha, hb), max = Math.max(ha, hb); - return max != 0 && ((max - min) / max) > 0.10; - } - - private void onRegionChangeListener() { - if (getLocalVisibleRect(_visRect) && !_visRect.equals(_oldVisRect)) { - updateHighlighting(false); - - // Heuristic for sip shown etc - if (isHeightChangeSignificant() && wasCursorPreviouslyVisible()) { - _bringPointIntoView(getSelectionStart()); - } - } - - _oldVisRect.set(_visRect); - } - // Highlighting // --------------------------------------------------------------------------------------------- private boolean isScrollSignificant() { - return ( _oldHlRect.top - _visRect.top) > _hlShiftThreshold || - (_visRect.bottom - _oldHlRect.bottom) > _hlShiftThreshold; + return ( _oldHlRect.top - _hlRect.top) > _hlShiftThreshold || + (_hlRect.bottom - _oldHlRect.bottom) > _hlShiftThreshold; } private void updateHighlighting(final boolean recompute) { - final Layout layout; - if (_hlEnabled && _hl != null && (layout = getLayout()) != null) { + if (_hlEnabled && _hl != null && getLayout() != null) { + + final boolean visible = getLocalVisibleRect(_hlRect); // Don't highlight unless shifted sufficiently or a recompute is required - if (recompute || (_hl.hasSpans() && isScrollSignificant())) { - - // Addition of spans which require reflow can shift text on re-application of spans - // we compute the resulting shift and scroll the view to compensate in order to make - // the experience smooth for the user. - final boolean heightSame = Math.abs(_visRect.height() - _oldHlRect.height()) <= 2; - final int shiftTestLine = heightSame ? layout.getLineForVertical(_visRect.centerY()) : -1; - final int oldOffset = heightSame ? layout.getLineBaseline(shiftTestLine) : 0; - - final int[] newHlRegion = hlRegion(_visRect); // Compute this _before_ clear - try { - beginBatchEdit(); - _hl.clear(); - if (recompute) { - _hl.recompute(); - } - _hl.apply(newHlRegion); - } finally { - endBatchEdit(); - } + if (recompute || (visible && _hl.hasSpans() && isScrollSignificant())) { - if (_scrollView != null && shiftTestLine >= 0) { - final int shift = layout.getLineBaseline(shiftTestLine) - oldOffset; - _scrollView.slowScrollShift(shift); + final int[] newHlRegion = hlRegion(_hlRect); // Compute this _before_ clear + _hl.clearDynamic(); + if (recompute) { + _hl.clearStatic().recompute().applyStatic(); } - - _oldHlRect.set(_visRect); + _hl.applyDynamic(newHlRegion); } } } @@ -190,7 +133,7 @@ public boolean isDynamicHighlightingEnabled() { public void setHighlighter(final SyntaxHighlighterBase newHighlighter) { if (_hl != null) { - _hl.clear(); + _hl.clearDynamic(); } _hl = newHighlighter; @@ -231,7 +174,7 @@ public boolean setHighlightingEnabled(final boolean enable) { } else if (!enable && _hlEnabled) { _hlEnabled = false; if (_hl != null) { - _hl.clear(); + _hl.clearDynamic(); } } return prev; @@ -261,10 +204,6 @@ private int rowEnd(final int y) { return layout.getLineEnd(line); } - public void setScrollView(final DraggableScrollbarScrollView view) { - _scrollView = view; - } - // Various overrides // --------------------------------------------------------------------------------------------- public void setSaveInstanceState(final boolean save) { @@ -278,18 +217,6 @@ public Parcelable onSaveInstanceState() { return _saveInstanceState ? state : null; } - @Override - public void beginBatchEdit() { - _accessibilityEnabled = false; - super.beginBatchEdit(); - } - - @Override - public void endBatchEdit() { - super.endBatchEdit(); - _accessibilityEnabled = true; - } - @Override protected void onVisibilityChanged(@NonNull View changedView, int visibility) { super.onVisibilityChanged(changedView, visibility); @@ -298,16 +225,6 @@ protected void onVisibilityChanged(@NonNull View changedView, int visibility) { } } - // We block bringPointIntoView as it is called by the system when not needed - @Override - public boolean bringPointIntoView(int cursor) { - return false; - } - - private boolean _bringPointIntoView(int cursor) { - return super.bringPointIntoView(cursor); - } - @Override public void setTextSize(float size) { super.setTextSize(size); @@ -379,6 +296,14 @@ public void setSelection(int start, int stop) { } } + @Override + protected void onSelectionChanged(int selStart, int selEnd) { + super.onSelectionChanged(selStart, selEnd); + if (MainActivity.IS_DEBUG_ENABLED) { + AppSettings.appendDebugLog("Selection changed: " + selStart + "->" + selEnd); + } + } + // Auto-format // --------------------------------------------------------------------------------------------- diff --git a/app/src/main/java/net/gsantner/markor/frontend/textview/SyntaxHighlighterBase.java b/app/src/main/java/net/gsantner/markor/frontend/textview/SyntaxHighlighterBase.java index f6ae5f3870..8b6c2d17b2 100644 --- a/app/src/main/java/net/gsantner/markor/frontend/textview/SyntaxHighlighterBase.java +++ b/app/src/main/java/net/gsantner/markor/frontend/textview/SyntaxHighlighterBase.java @@ -107,11 +107,13 @@ public SyntaxHighlighterBase configure(@Nullable final Paint paint) { public static class SpanGroup implements Comparable { int start, end; final Object span; + final boolean isStatic; SpanGroup(Object o, int s, int e) { span = o; start = s; end = e; + isStatic = o instanceof UpdateLayout; } @Override @@ -127,7 +129,8 @@ private static class ForceUpdateLayout implements UpdateLayout { private final ForceUpdateLayout _layoutUpdater; private final List _groups; - private final NavigableSet _applied; + private final NavigableSet _appliedDynamic; + private boolean _staticApplied = false; protected Spannable _spannable; protected final AppSettings _appSettings; @@ -135,28 +138,54 @@ private static class ForceUpdateLayout implements UpdateLayout { public SyntaxHighlighterBase(final AppSettings as) { _appSettings = as; _groups = new ArrayList<>(); - _applied = new TreeSet<>(); + _appliedDynamic = new TreeSet<>(); _layoutUpdater = new ForceUpdateLayout(); } // --------------------------------------------------------------------------------------------- + public SyntaxHighlighterBase clearAll() { + return clearDynamic().clearStatic(); + } + /** - * Removes all spans applied by this highlighter to the currently set spannable + * Removes all dynamic spans applied by this highlighter to the currently set spannable * * @return this */ - public synchronized SyntaxHighlighterBase clear() { + public synchronized SyntaxHighlighterBase clearDynamic() { if (_spannable == null) { return this; } - final Iterator it = _applied.descendingIterator(); + final Iterator it = _appliedDynamic.descendingIterator(); while (it.hasNext()) { _spannable.removeSpan(_groups.get(it.next()).span); } - _applied.clear(); + _appliedDynamic.clear(); + + return this; + } + + /** + * Removes all static spans applied by this highlighter to the currently set spannable + * + * @return this + */ + public synchronized SyntaxHighlighterBase clearStatic() { + if (_spannable == null) { + return this; + } + + for (int i = _groups.size() - 1; i >= 0; i--) { + final SpanGroup group = _groups.get(i); + if (group.isStatic) { + _spannable.removeSpan(group.span); + } + } + + _staticApplied = false; return this; } @@ -171,7 +200,7 @@ public synchronized SyntaxHighlighterBase clear() { public synchronized SyntaxHighlighterBase setSpannable(@Nullable final Spannable spannable) { if (spannable != _spannable) { _groups.clear(); - _applied.clear(); + _appliedDynamic.clear(); _spannable = spannable; } @@ -187,7 +216,6 @@ public boolean hasSpans() { return _spannable != null && _groups.size() > 0; } - /** * Helper to change spans in 'onTextChanged' */ @@ -219,8 +247,12 @@ public synchronized SyntaxHighlighterBase fixup(final int after, final int delta return this; } - public SyntaxHighlighterBase apply() { - return apply(new int[]{0, _spannable.length()}); + public SyntaxHighlighterBase applyAll() { + return applyDynamic().applyStatic(); + } + + public SyntaxHighlighterBase applyDynamic() { + return applyDynamic(new int[]{0, _spannable.length()}); } /** @@ -228,7 +260,7 @@ public SyntaxHighlighterBase apply() { * * @return this */ - public synchronized SyntaxHighlighterBase apply(final int[] range) { + public synchronized SyntaxHighlighterBase applyDynamic(final int[] range) { if (_spannable == null) { return this; } @@ -241,18 +273,39 @@ public synchronized SyntaxHighlighterBase apply(final int[] range) { for (int i = 0; i < _groups.size(); i++) { final SpanGroup group = _groups.get(i); + if (group.isStatic) { + continue; + } + if (group.start >= range[1]) { // As we are sorted on start, we can break out after the first group.start > end break; } final boolean valid = group.start >= 0 && group.end > range[0] && group.end <= length; - if (valid && !_applied.contains(i)) { + if (valid && !_appliedDynamic.contains(i)) { + _spannable.setSpan(group.span, group.start, group.end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + _appliedDynamic.add(i); + } + } + + return this; + } + + public synchronized SyntaxHighlighterBase applyStatic() { + if (_spannable == null || _staticApplied) { + return this; + } + + for (int i = 0; i < _groups.size(); i++) { + final SpanGroup group = _groups.get(i); + if (group.isStatic) { _spannable.setSpan(group.span, group.start, group.end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); - _applied.add(i); } } + _staticApplied = true; + return this; } @@ -277,7 +330,8 @@ public final synchronized SyntaxHighlighterBase reflow(final int[] range) { */ public synchronized final SyntaxHighlighterBase recompute() { _groups.clear(); - _applied.clear(); + _appliedDynamic.clear(); + _staticApplied = false; if (TextUtils.isEmpty(_spannable)) { return this; 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 eeaf94efdc..6ec25412c3 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 @@ -17,8 +17,6 @@ import android.text.Layout; import android.text.Selection; import android.text.TextUtils; -import android.view.View; -import android.view.ViewTreeObserver; import android.widget.EditText; import android.widget.TextView; @@ -264,32 +262,29 @@ public static void selectLines(final EditText edit, final List position } } - public static @NonNull Rect getSelRect(final TextView text, final int... sel) { - - final Rect region = new Rect(); - - if (sel == null || sel.length == 0) { - return region; - } + public static void showSelection(final TextView text) { + showSelection(text, text.getSelectionStart(), text.getSelectionEnd()); + } - final int _start = Math.min(sel[0], sel.length > 1 ? sel[1] : sel[0]); - final int _end = Math.max(sel[0], sel.length > 1 ? sel[1] : sel[0]); - if (!inRange(0, text.length(), _start, _end)) { - return region; - } + public static void showSelection(final TextView text, final int start, final int end) { // Get view info // ------------------------------------------------------------ final Layout layout = text.getLayout(); if (layout == null) { - return region; + return; } + final int _start = Math.min(start, end); + final int _end = Math.max(start, end); + if (start < 0 || end > text.length()) { + return; + } final int lineStart = TextViewUtils.getLineStart(text.getText(), _start); final Rect viewSize = new Rect(); if (!text.getLocalVisibleRect(viewSize)) { - return region; + return; } // Region in Y @@ -299,6 +294,8 @@ public static void selectLines(final EditText edit, final List position final int selStartLineTop = layout.getLineTop(selStartLine); final int lineStartLineTop = layout.getLineTop(lineStartLine); + final Rect region = new Rect(); + if ((selStartLine - lineStartLine) <= 3) { // good to see the start of the line if close enough region.top = lineStartLineTop; @@ -315,77 +312,11 @@ public static void selectLines(final EditText edit, final List position final int startLeft = (int) layout.getPrimaryHorizontal(_start); final int halfWidth = viewSize.width() / 2; // Push the start to the middle of the screen - region.left = Math.max(startLeft - halfWidth, 0); - region.right = Math.min(startLeft + halfWidth, viewSize.width()); - - return region; - } - - // Due to dynamic highlighting we can't just requestRectangleOnScreen as - // when we scroll to the rect, the rect may no longer represent the ROI (layout changes). - // So we adopt an iterative scheme - scroll to the rect, check if rect is good and then - // repeat until we are satisfied - private static class SelectionShower implements ViewTreeObserver.OnScrollChangedListener { - final @NonNull - TextView _text; - final ViewTreeObserver _observer; - final int _start, _end; - int _iterCount; - - public SelectionShower(@NonNull TextView text, int start, int end, int iterCount) { - _text = text; - _observer = text.getViewTreeObserver(); - _start = start; - _end = end; - _iterCount = iterCount; - } - - public void run() { - final Rect region = getSelRect(_text, _start, _end); - if (!region.isEmpty()) { - if (_observer != null && _iterCount > 0) { - _observer.addOnScrollChangedListener(this); - } - _text.post(() -> _text.requestRectangleOnScreen(region)); - } - } + region.left = startLeft - halfWidth; + region.right = startLeft + halfWidth; - @Override - public void onScrollChanged() { - final Rect region; - if (--_iterCount <= 0 || (region = getSelRect(_text, _start, _end)).isEmpty()) { - _observer.removeOnScrollChangedListener(this); - return; - } - - if (!regionVisible(_text, region)) { - _text.post(() -> _text.requestRectangleOnScreen(region)); - } - } - } - - public static void showSelection(final TextView text, final int... sel) { - if (sel != null && sel.length > 0) { - new SelectionShower(text, sel[0], sel.length > 1 ? sel[1] : sel[0], 5).run(); - } - } - - // Check if rect is visible - public static boolean regionVisible(final View view, final Rect region) { - if (region == null || region.isEmpty()) { - return true; - } - - final Rect visible = new Rect(); - if (!view.getLocalVisibleRect(visible) || visible.isEmpty()) { - return true; - } - - if (region.height() >= visible.height()) { - return visible.contains(region.left, region.top); - } else { - return visible.contains(region); - } + // Call in post to try to make sure we run after any pending actions + text.post(() -> text.requestRectangleOnScreen(region)); } public static void setSelectionAndShow(final EditText edit, final int... sel) { @@ -402,10 +333,8 @@ public static void setSelectionAndShow(final EditText edit, final int... sel) { edit.requestFocus(); } - edit.post(() -> { - showSelection(edit, start, end); - edit.setSelection(start, end); - }); + showSelection(edit, start, end); + edit.setSelection(start, end); }); } } From abdfab7b1c091776905344281620ae1f3f2cb2b8 Mon Sep 17 00:00:00 2001 From: Harshad Vedartham Date: Thu, 6 Oct 2022 22:28:52 -0700 Subject: [PATCH 31/40] Calling correct clear --- .../gsantner/markor/frontend/textview/HighlightingEditor.java | 4 ++-- .../net/gsantner/markor/frontend/textview/TextViewUtils.java | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) 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 316ffc8ba6..a034bf8cdf 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 @@ -133,7 +133,7 @@ public boolean isDynamicHighlightingEnabled() { public void setHighlighter(final SyntaxHighlighterBase newHighlighter) { if (_hl != null) { - _hl.clearDynamic(); + _hl.clearAll(); } _hl = newHighlighter; @@ -174,7 +174,7 @@ public boolean setHighlightingEnabled(final boolean enable) { } else if (!enable && _hlEnabled) { _hlEnabled = false; if (_hl != null) { - _hl.clearDynamic(); + _hl.clearAll(); } } return prev; 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 6ec25412c3..3e957732c0 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 @@ -333,8 +333,8 @@ public static void setSelectionAndShow(final EditText edit, final int... sel) { edit.requestFocus(); } - showSelection(edit, start, end); edit.setSelection(start, end); + edit.post(() -> showSelection(edit, start, end)); }); } } From 32ef3a6001a929b957489b4755399a280af914e6 Mon Sep 17 00:00:00 2001 From: Gregor Santner Date: Sun, 9 Oct 2022 20:40:55 +0200 Subject: [PATCH 32/40] optimize code --- .../main/java/net/gsantner/markor/activity/MainActivity.java | 2 +- .../markor/frontend/DraggableScrollbarScrollView.java | 2 -- .../gsantner/markor/frontend/textview/HighlightingEditor.java | 4 ++-- 3 files changed, 3 insertions(+), 5 deletions(-) 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 46d2f76f0d..cd4a72ae85 100644 --- a/app/src/main/java/net/gsantner/markor/activity/MainActivity.java +++ b/app/src/main/java/net/gsantner/markor/activity/MainActivity.java @@ -257,7 +257,7 @@ public boolean onLongClickFab(View view) { GsFileBrowserFragment fsFrag = getNotebook(); if (fsFrag != null && permc.mkdirIfStoragePermissionGranted()) { fsFrag.getAdapter().setCurrentFolder(fsFrag.getCurrentFolder().equals(GsFileBrowserListAdapter.VIRTUAL_STORAGE_RECENTS) - ? GsFileBrowserListAdapter.VIRTUAL_STORAGE_FAVOURITE : GsFileBrowserListAdapter.VIRTUAL_STORAGE_RECENTS); + ? GsFileBrowserListAdapter.VIRTUAL_STORAGE_FAVOURITE : GsFileBrowserListAdapter.VIRTUAL_STORAGE_RECENTS); } return true; } diff --git a/app/src/main/java/net/gsantner/markor/frontend/DraggableScrollbarScrollView.java b/app/src/main/java/net/gsantner/markor/frontend/DraggableScrollbarScrollView.java index 99ed740ad0..03add26563 100644 --- a/app/src/main/java/net/gsantner/markor/frontend/DraggableScrollbarScrollView.java +++ b/app/src/main/java/net/gsantner/markor/frontend/DraggableScrollbarScrollView.java @@ -9,8 +9,6 @@ import android.view.View; import android.widget.ScrollView; -import net.gsantner.opoc.wrapper.GsCallback; - @SuppressLint("ClickableViewAccessibility") public class DraggableScrollbarScrollView extends ScrollView { 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 a034bf8cdf..51039c71f0 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 @@ -101,8 +101,8 @@ public void afterTextChanged(final Editable s) { // --------------------------------------------------------------------------------------------- private boolean isScrollSignificant() { - return ( _oldHlRect.top - _hlRect.top) > _hlShiftThreshold || - (_hlRect.bottom - _oldHlRect.bottom) > _hlShiftThreshold; + return (_oldHlRect.top - _hlRect.top) > _hlShiftThreshold || + (_hlRect.bottom - _oldHlRect.bottom) > _hlShiftThreshold; } private void updateHighlighting(final boolean recompute) { From 5c09c44a1edff87b43d2a850a9dac4af32fb6af2 Mon Sep 17 00:00:00 2001 From: Gregor Santner Date: Sun, 9 Oct 2022 20:43:57 +0200 Subject: [PATCH 33/40] rename error string id --- .../gsantner/markor/activity/DocumentEditAndViewFragment.java | 4 ++-- .../gsantner/markor/activity/DocumentShareIntoFragment.java | 2 +- app/src/main/res/values-ar/strings.xml | 2 +- app/src/main/res/values-ca/strings.xml | 2 +- app/src/main/res/values-cs/strings.xml | 2 +- app/src/main/res/values-da/strings.xml | 2 +- app/src/main/res/values-de/strings.xml | 2 +- app/src/main/res/values-el/strings.xml | 2 +- app/src/main/res/values-es/strings.xml | 2 +- app/src/main/res/values-fi/strings.xml | 2 +- app/src/main/res/values-fr/strings.xml | 2 +- app/src/main/res/values-it/strings.xml | 2 +- app/src/main/res/values-ja/strings.xml | 2 +- app/src/main/res/values-nb-rNO/strings.xml | 2 +- app/src/main/res/values-nl/strings.xml | 2 +- app/src/main/res/values-no/strings.xml | 2 +- app/src/main/res/values-pl/strings.xml | 2 +- app/src/main/res/values-pt-rBR/strings.xml | 2 +- app/src/main/res/values-pt/strings.xml | 2 +- app/src/main/res/values-ro/strings.xml | 2 +- app/src/main/res/values-ru/strings.xml | 2 +- app/src/main/res/values-sc/strings.xml | 2 +- app/src/main/res/values-sv/strings.xml | 2 +- app/src/main/res/values-uk/strings.xml | 2 +- app/src/main/res/values-zh-rCN/strings.xml | 2 +- app/src/main/res/values/strings.xml | 2 +- 26 files changed, 27 insertions(+), 27 deletions(-) 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 5f618d348f..31d85f67e1 100644 --- a/app/src/main/java/net/gsantner/markor/activity/DocumentEditAndViewFragment.java +++ b/app/src/main/java/net/gsantner/markor/activity/DocumentEditAndViewFragment.java @@ -151,7 +151,7 @@ public void onViewCreated(@NonNull View view, Bundle savedInstanceState) { // It may cause reads or writes to _silently fail_ // Instead we try to create it, and exit if that isn't possible if (isStateBad()) { - Toast.makeText(activity, R.string.document_error, Toast.LENGTH_LONG).show(); + Toast.makeText(activity, R.string.error_could_not_open_file, Toast.LENGTH_LONG).show(); activity.finish(); return; } @@ -699,7 +699,7 @@ public void errorClipText() { new MarkorContextUtils(context).setClipboard(getContext(), text); } // Always show error message - Toast.makeText(getContext(), R.string.document_error, Toast.LENGTH_LONG).show(); + Toast.makeText(getContext(), R.string.error_could_not_open_file, Toast.LENGTH_LONG).show(); Log.i(DocumentEditAndViewFragment.class.getName(), "Triggering error text clipping"); } 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 a9f3f620f2..c3586b5d43 100644 --- a/app/src/main/java/net/gsantner/markor/activity/DocumentShareIntoFragment.java +++ b/app/src/main/java/net/gsantner/markor/activity/DocumentShareIntoFragment.java @@ -204,7 +204,7 @@ private void appendToExistingDocument(final File file, final String separator, f + (isTodoTxt ? _sharedText : formatOrPrefixSharedText(shareIntoFormat, _sharedText)); document.saveContent(context, newContent); } else { - Toast.makeText(context, R.string.document_error, Toast.LENGTH_LONG).show(); + Toast.makeText(context, R.string.error_could_not_open_file, Toast.LENGTH_LONG).show(); } if (showEditor) { diff --git a/app/src/main/res/values-ar/strings.xml b/app/src/main/res/values-ar/strings.xml index a05add238d..247c25747a 100644 --- a/app/src/main/res/values-ar/strings.xml +++ b/app/src/main/res/values-ar/strings.xml @@ -438,6 +438,6 @@ work. If not, see . المشاركة في - التنسيق تنسيق تلقائي Insert snippet - خطأ: تعذر الفتح. + خطأ: تعذر الفتح. حدث خطأ: تم نسخ النص إلى الحافظة. diff --git a/app/src/main/res/values-ca/strings.xml b/app/src/main/res/values-ca/strings.xml index 9e3b77c4d5..7ea2757b63 100644 --- a/app/src/main/res/values-ca/strings.xml +++ b/app/src/main/res/values-ca/strings.xml @@ -438,6 +438,6 @@ work. If not, see . Comparteix en - Format Format automàtic Insereix fragment - Error: no s\'ha pogut obrir. + Error: no s\'ha pogut obrir. S\'ha trobat un error: el text s\'ha copiat al porta-retalls. diff --git a/app/src/main/res/values-cs/strings.xml b/app/src/main/res/values-cs/strings.xml index 892e07e06f..a3a3015f67 100644 --- a/app/src/main/res/values-cs/strings.xml +++ b/app/src/main/res/values-cs/strings.xml @@ -438,6 +438,6 @@ work. If not, see . Sdílet do - formát Automatický formát Insert snippet - Chyba: Nelze otevřít. + Chyba: Nelze otevřít. Došlo k chybě: Text zkopírován do schránky. diff --git a/app/src/main/res/values-da/strings.xml b/app/src/main/res/values-da/strings.xml index 8f6abcdd4e..bc0f111a1f 100644 --- a/app/src/main/res/values-da/strings.xml +++ b/app/src/main/res/values-da/strings.xml @@ -438,6 +438,6 @@ work. If not, see . Del på - Format Auto-format Insert snippet - Fejl: Kunne ikke åbne. + Fejl: Kunne ikke åbne. Der opstod en fejl: Tekst kopieret til udklipsholder. diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index b9e0c23cbb..e9fdcda313 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -438,6 +438,6 @@ work. If not, see . Teilen in - Format Auto-Format Schnipsel einfügen - Fehler: Konnte nicht öffnen. + Fehler: Konnte nicht öffnen. Fehler aufgetreten: Text in die Zwischenablage kopiert. diff --git a/app/src/main/res/values-el/strings.xml b/app/src/main/res/values-el/strings.xml index 17205ae5f7..0dbc4eb595 100644 --- a/app/src/main/res/values-el/strings.xml +++ b/app/src/main/res/values-el/strings.xml @@ -439,6 +439,6 @@ work. If not, see . Κοινή χρήση σε - Μορφή Αυτόματη διαμόρφωση Εισαγωγή αποσπάσματος κώδικα - Σφάλμα: Αδύνατο το άνοιγμα. + Σφάλμα: Αδύνατο το άνοιγμα. Αντιμετώπιση σφάλματος: Το κείμενο αντιγράφηκε στο πρόχειρο. diff --git a/app/src/main/res/values-es/strings.xml b/app/src/main/res/values-es/strings.xml index ffa15ae0ff..f6b043ee85 100644 --- a/app/src/main/res/values-es/strings.xml +++ b/app/src/main/res/values-es/strings.xml @@ -438,6 +438,6 @@ work. If not, see . Compartir en - Formato Auto-formato Insertar fragmento - Error: No se pudo abrir. + Error: No se pudo abrir. Error encontrado: Texto copiado al portapapeles. diff --git a/app/src/main/res/values-fi/strings.xml b/app/src/main/res/values-fi/strings.xml index 787a557c9c..11cf2904ac 100644 --- a/app/src/main/res/values-fi/strings.xml +++ b/app/src/main/res/values-fi/strings.xml @@ -438,6 +438,6 @@ work. If not, see . Jaa - Muoto Automaattimuotoilu Insert snippet - Virhe: Ei voitu avata. + Virhe: Ei voitu avata. Tapahtui virhe: Teksti kopioitu leikepöydälle. diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml index 7eaa4e3f3d..c20ca87785 100644 --- a/app/src/main/res/values-fr/strings.xml +++ b/app/src/main/res/values-fr/strings.xml @@ -438,6 +438,6 @@ work. If not, see . Partager dans - Format Format automatique Insert snippet - Erreur: Impossible d\'ouvrir. + Erreur: Impossible d\'ouvrir. Erreur rencontrée: Texte copié dans le presse-papiers. diff --git a/app/src/main/res/values-it/strings.xml b/app/src/main/res/values-it/strings.xml index b03aeec6f8..1c9e1c59b6 100644 --- a/app/src/main/res/values-it/strings.xml +++ b/app/src/main/res/values-it/strings.xml @@ -438,6 +438,6 @@ work. If not, see . Condividi in - Formato Autoformattazione Inserisci estratto - Errore: impossibile aprire. + Errore: impossibile aprire. Trovato errore: Testo copiato negli appunti. diff --git a/app/src/main/res/values-ja/strings.xml b/app/src/main/res/values-ja/strings.xml index 9352ab726c..018b48ce92 100644 --- a/app/src/main/res/values-ja/strings.xml +++ b/app/src/main/res/values-ja/strings.xml @@ -438,6 +438,6 @@ work. If not, see . 共有 → 書式 自動フォーマット Insert snippet - エラー:開けませんでした。 + エラー:開けませんでした。 エラーが発生しました: テキストをクリップボードにコピーしました。 diff --git a/app/src/main/res/values-nb-rNO/strings.xml b/app/src/main/res/values-nb-rNO/strings.xml index 5982c9cce6..3e23c04d68 100644 --- a/app/src/main/res/values-nb-rNO/strings.xml +++ b/app/src/main/res/values-nb-rNO/strings.xml @@ -438,6 +438,6 @@ work. If not, see . Del inn - format Autokoformater Insert snippet - Feil: kunne ikke åpne. + Feil: kunne ikke åpne. Oppnådd tekst: kopiert til utklippstavlen. diff --git a/app/src/main/res/values-nl/strings.xml b/app/src/main/res/values-nl/strings.xml index 652c2a1c59..a539640b6c 100644 --- a/app/src/main/res/values-nl/strings.xml +++ b/app/src/main/res/values-nl/strings.xml @@ -438,6 +438,6 @@ work. If not, see . Deel mee - Formaat Auto-formaat Insert snippet - Fout: Kan niet openen. + Fout: Kan niet openen. Fout opgetreden: tekst gekopieerd naar klembord. diff --git a/app/src/main/res/values-no/strings.xml b/app/src/main/res/values-no/strings.xml index 7d3a83acc7..8d121b6548 100644 --- a/app/src/main/res/values-no/strings.xml +++ b/app/src/main/res/values-no/strings.xml @@ -438,6 +438,6 @@ work. If not, see . Del inn - format Autokoformater Insert snippet - Feil: kunne ikke åpne. + Feil: kunne ikke åpne. Oppnådd tekst: kopiert til utklippstavlen. diff --git a/app/src/main/res/values-pl/strings.xml b/app/src/main/res/values-pl/strings.xml index 54e8298494..4975537a4f 100644 --- a/app/src/main/res/values-pl/strings.xml +++ b/app/src/main/res/values-pl/strings.xml @@ -438,6 +438,6 @@ work. If not, see . Udostępnij - Format Automatyczny format Insert snippet - Błąd: Nie można otworzyć. + Błąd: Nie można otworzyć. Wystąpił błąd: Tekst skopiowany do schowka. diff --git a/app/src/main/res/values-pt-rBR/strings.xml b/app/src/main/res/values-pt-rBR/strings.xml index 4a0f957ac8..a116a2ef51 100644 --- a/app/src/main/res/values-pt-rBR/strings.xml +++ b/app/src/main/res/values-pt-rBR/strings.xml @@ -438,6 +438,6 @@ work. If not, see . Compartilhar em - Formato Auto-formatar Insert snippet - Erro: Não foi possível abrir. + Erro: Não foi possível abrir. Erro encontrado: Texto copiado para área de transferência. diff --git a/app/src/main/res/values-pt/strings.xml b/app/src/main/res/values-pt/strings.xml index d807c08c72..b35b3d41eb 100644 --- a/app/src/main/res/values-pt/strings.xml +++ b/app/src/main/res/values-pt/strings.xml @@ -438,6 +438,6 @@ work. If not, see . Compartilhar em - Formato Auto-formatar Insert snippet - Erro: Não foi possível abrir. + Erro: Não foi possível abrir. Erro encontrado: Texto copiado para área de transferência. diff --git a/app/src/main/res/values-ro/strings.xml b/app/src/main/res/values-ro/strings.xml index cdcd379926..5cdea3bb6d 100644 --- a/app/src/main/res/values-ro/strings.xml +++ b/app/src/main/res/values-ro/strings.xml @@ -438,6 +438,6 @@ work. If not, see . Partajare în - Format Format automat Insert snippet - Eroare: Nu s-a putut deschide. + Eroare: Nu s-a putut deschide. Eroare întâlnită: textul a fost copiat în clipboard. diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index aaef5642cc..55a4ba5f7f 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -438,6 +438,6 @@ work. If not, see . Поделиться - Формат Автоформат Insert snippet - Ошибка: не удалось открыть. + Ошибка: не удалось открыть. Произошла ошибка: текст скопирован в буфер обмена. diff --git a/app/src/main/res/values-sc/strings.xml b/app/src/main/res/values-sc/strings.xml index df531eb857..533939c17c 100644 --- a/app/src/main/res/values-sc/strings.xml +++ b/app/src/main/res/values-sc/strings.xml @@ -438,6 +438,6 @@ work. If not, see . Cumpartzi in - Formadu Formatatzione automàtica Inserta frammentu - Errore: No at fatu a abèrrere. + Errore: No at fatu a abèrrere. Errore agatadu: testu copiadu in punta de billete. diff --git a/app/src/main/res/values-sv/strings.xml b/app/src/main/res/values-sv/strings.xml index 41f9eb158f..27a152dd69 100644 --- a/app/src/main/res/values-sv/strings.xml +++ b/app/src/main/res/values-sv/strings.xml @@ -438,6 +438,6 @@ work. If not, see . Dela till - Format Auto-format Insert snippet - Fel: Kunde inte öppna. + Fel: Kunde inte öppna. Ett fel uppstod: Text kopierad till urklipp. diff --git a/app/src/main/res/values-uk/strings.xml b/app/src/main/res/values-uk/strings.xml index 326e5c5fcd..6e57c2d1a4 100644 --- a/app/src/main/res/values-uk/strings.xml +++ b/app/src/main/res/values-uk/strings.xml @@ -438,6 +438,6 @@ work. If not, see . Поділитися у форматі - Формат Автоматичний формат Insert snippet - Помилка: Не вдалося відкрити. + Помилка: Не вдалося відкрити. Помилка виявлення: текст скопійовано до буфера обміну. diff --git a/app/src/main/res/values-zh-rCN/strings.xml b/app/src/main/res/values-zh-rCN/strings.xml index 181be19c64..9a208aad0c 100644 --- a/app/src/main/res/values-zh-rCN/strings.xml +++ b/app/src/main/res/values-zh-rCN/strings.xml @@ -438,6 +438,6 @@ work. If not, see . 分享到 - 格式 自动格式 插入片段 - 错误:无法打开。 + 错误:无法打开。 出现错误:文本已复制到剪贴板。 diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index acce087122..bda9c7e865 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -456,6 +456,6 @@ work. If not, see . Share into - Format Auto-format Insert snippet - Error: Could not open. + Error: Could not open file. Error encountered: Text copied to clipboard. From 356876ab509b0706b1f515d81bfe7b4b2abfbe44 Mon Sep 17 00:00:00 2001 From: Gregor Santner Date: Sun, 9 Oct 2022 21:01:29 +0200 Subject: [PATCH 34/40] Revert unneeded changes --- .../gsantner/markor/frontend/DraggableScrollbarScrollView.java | 1 - app/src/main/java/net/gsantner/opoc/format/GsTextUtils.java | 1 + 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/net/gsantner/markor/frontend/DraggableScrollbarScrollView.java b/app/src/main/java/net/gsantner/markor/frontend/DraggableScrollbarScrollView.java index 03add26563..839b642ca2 100644 --- a/app/src/main/java/net/gsantner/markor/frontend/DraggableScrollbarScrollView.java +++ b/app/src/main/java/net/gsantner/markor/frontend/DraggableScrollbarScrollView.java @@ -53,7 +53,6 @@ public boolean onInterceptTouchEvent(MotionEvent ev) { } return true; } - return super.onInterceptTouchEvent(ev); } diff --git a/app/src/main/java/net/gsantner/opoc/format/GsTextUtils.java b/app/src/main/java/net/gsantner/opoc/format/GsTextUtils.java index e331c0a2e4..bb90172cb1 100644 --- a/app/src/main/java/net/gsantner/opoc/format/GsTextUtils.java +++ b/app/src/main/java/net/gsantner/opoc/format/GsTextUtils.java @@ -185,6 +185,7 @@ public static String colorToHexString(final int intColor, final boolean... withA return String.format(a ? "#%08X" : "#%06X", (a ? 0xFFFFFFFF : 0xFFFFFF) & intColor); } + /** * Convert escape sequences in string to escaped special characters. For example, convert * A\tB -> A B From 12fa28bef4d4480c8f1cc7e8b75a6107c604f8dd Mon Sep 17 00:00:00 2001 From: Harshad Vedartham Date: Wed, 12 Oct 2022 10:52:32 -0700 Subject: [PATCH 35/40] Better bottom spacing, mime type fixes --- .../gsantner/markor/frontend/MarkorDialogFactory.java | 3 ++- .../java/net/gsantner/opoc/util/GsContextUtils.java | 10 +--------- .../main/java/net/gsantner/opoc/util/GsFileUtils.java | 2 +- app/src/main/res/layout/document__fragment__edit.xml | 2 +- app/src/main/res/values/dimens.xml | 1 + 5 files changed, 6 insertions(+), 12 deletions(-) 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 3f3e6cefb8..114359882c 100644 --- a/app/src/main/java/net/gsantner/markor/frontend/MarkorDialogFactory.java +++ b/app/src/main/java/net/gsantner/markor/frontend/MarkorDialogFactory.java @@ -36,6 +36,7 @@ import net.gsantner.markor.ApplicationObject; import net.gsantner.markor.R; +import net.gsantner.markor.format.FormatRegistry; import net.gsantner.markor.format.todotxt.TodoTxtBasicSyntaxHighlighter; import net.gsantner.markor.format.todotxt.TodoTxtFilter; import net.gsantner.markor.format.todotxt.TodoTxtTask; @@ -671,7 +672,7 @@ public static Map getSnippets(final AppSettings as) { // Read all files in snippets folder with appropriate extension // Create a map of snippet title -> text for (final File f : GsFileUtils.replaceFilesWithCachedVariants(folder.listFiles())) { - if (f.exists() && f.canRead() && GsFileUtils.isTextFile(f)) { + if (f.exists() && f.canRead() && FormatRegistry.isFileSupported(f, true)) { texts.put(f.getName(), f); } } 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 2977bdb200..3f01b012c0 100644 --- a/app/src/main/java/net/gsantner/opoc/util/GsContextUtils.java +++ b/app/src/main/java/net/gsantner/opoc/util/GsContextUtils.java @@ -126,7 +126,6 @@ import com.google.android.material.snackbar.Snackbar; -import net.gsantner.markor.model.AppSettings; import net.gsantner.opoc.format.GsSimpleMarkdownParser; import net.gsantner.opoc.format.GsTextUtils; import net.gsantner.opoc.wrapper.GsCallback; @@ -970,11 +969,11 @@ public String getMimeType(final Context context, final File file) { public String getMimeType(final Context context, String uri) { String mimeType; uri = uri.replaceFirst("\\.jenc$", ""); - final String ext = MimeTypeMap.getFileExtensionFromUrl(uri); if (uri.startsWith(ContentResolver.SCHEME_CONTENT + "://")) { ContentResolver cr = context.getContentResolver(); mimeType = cr.getType(Uri.parse(uri)); } else { + String ext = MimeTypeMap.getFileExtensionFromUrl(uri); mimeType = MimeTypeMap.getSingleton().getMimeTypeFromExtension(ext.toLowerCase()); } @@ -983,16 +982,9 @@ public String getMimeType(final Context context, String uri) { mimeType = GsFileUtils.getMimeType(new File(uri)); } - // Detect extensions which open in this app at plain text - // Done only if all other tests fail - if (GsTextUtils.isNullOrEmpty(mimeType) && new AppSettings().init(context).isExtOpenWithThisApp(ext)) { - mimeType = "text/plain"; - } - if (GsTextUtils.isNullOrEmpty((mimeType))) { mimeType = "*/*"; } - return mimeType.toLowerCase(Locale.ROOT); } diff --git a/app/src/main/java/net/gsantner/opoc/util/GsFileUtils.java b/app/src/main/java/net/gsantner/opoc/util/GsFileUtils.java index 58dbf3b9f8..0e2e882f6c 100644 --- a/app/src/main/java/net/gsantner/opoc/util/GsFileUtils.java +++ b/app/src/main/java/net/gsantner/opoc/util/GsFileUtils.java @@ -500,7 +500,7 @@ public static String getMimeType(File file) { } public static boolean isTextFile(File file) { - String mime = getMimeType(file); + final String mime = getMimeType(file); return mime != null && mime.startsWith("text/"); } diff --git a/app/src/main/res/layout/document__fragment__edit.xml b/app/src/main/res/layout/document__fragment__edit.xml index b644b86d75..e296bc725f 100644 --- a/app/src/main/res/layout/document__fragment__edit.xml +++ b/app/src/main/res/layout/document__fragment__edit.xml @@ -38,7 +38,7 @@ android:paddingLeft="@dimen/activity_horizontal_margin" android:paddingTop="@dimen/activity_vertical_margin" android:paddingRight="@dimen/activity_horizontal_margin" - android:paddingBottom="@dimen/activity_vertical_margin" + android:paddingBottom="@dimen/editor_bottom_margin" android:scrollbars="none" android:textCursorDrawable="@drawable/cursor_accent" /> diff --git a/app/src/main/res/values/dimens.xml b/app/src/main/res/values/dimens.xml index cbcd7ab824..befc79dab8 100644 --- a/app/src/main/res/values/dimens.xml +++ b/app/src/main/res/values/dimens.xml @@ -2,6 +2,7 @@ 16dp 16dp + 32dp 8dp 46dp From 3bd0651565ecb0d00d43f1510127f5f49e7f941b Mon Sep 17 00:00:00 2001 From: Harshad Vedartham Date: Thu, 13 Oct 2022 08:18:51 -0700 Subject: [PATCH 36/40] Don't default preview state --- .../markor/activity/DocumentEditAndViewFragment.java | 10 ++++------ .../net/gsantner/markor/activity/MainActivity.java | 4 ++-- 2 files changed, 6 insertions(+), 8 deletions(-) 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 31d85f67e1..1b9a886494 100644 --- a/app/src/main/java/net/gsantner/markor/activity/DocumentEditAndViewFragment.java +++ b/app/src/main/java/net/gsantner/markor/activity/DocumentEditAndViewFragment.java @@ -73,22 +73,20 @@ public class DocumentEditAndViewFragment extends MarkorBaseFragment implements F public static final String SAVESTATE_DOCUMENT = "DOCUMENT"; public static final String START_PREVIEW = "START_PREVIEW"; - public static DocumentEditAndViewFragment newInstance(final @NonNull Document document, final Integer lineNumber, final boolean preview) { + public static DocumentEditAndViewFragment newInstance(final @NonNull Document document, final Integer lineNumber, final Boolean preview) { DocumentEditAndViewFragment f = new DocumentEditAndViewFragment(); Bundle args = new Bundle(); args.putSerializable(Document.EXTRA_DOCUMENT, document); if (lineNumber != null) { args.putInt(Document.EXTRA_FILE_LINE_NUMBER, lineNumber); } - args.putBoolean(START_PREVIEW, preview); + if (preview != null) { + args.putBoolean(START_PREVIEW, preview); + } f.setArguments(args); return f; } - public static DocumentEditAndViewFragment newInstance(final @NonNull File path, final Integer lineNumber) { - return newInstance(new Document(path), lineNumber, false); - } - private HighlightingEditor _hlEditor; private ViewGroup _textActionsBar; private WebView _webView; 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 cd4a72ae85..4bb8084bf1 100644 --- a/app/src/main/java/net/gsantner/markor/activity/MainActivity.java +++ b/app/src/main/java/net/gsantner/markor/activity/MainActivity.java @@ -424,9 +424,9 @@ public GsFragmentBase get(int pos) { final GsFragmentBase frag; final int id = _bottomNav.getMenu().getItem(pos).getItemId(); if (id == R.id.nav_quicknote) { - frag = DocumentEditAndViewFragment.newInstance(_appSettings.getQuickNoteFile(), Document.EXTRA_FILE_LINE_NUMBER_LAST); + frag = DocumentEditAndViewFragment.newInstance(new Document(_appSettings.getQuickNoteFile()), Document.EXTRA_FILE_LINE_NUMBER_LAST, null); } else if (id == R.id.nav_todo) { - frag = DocumentEditAndViewFragment.newInstance(_appSettings.getTodoFile(), Document.EXTRA_FILE_LINE_NUMBER_LAST); + frag = DocumentEditAndViewFragment.newInstance(new Document(_appSettings.getQuickNoteFile()), Document.EXTRA_FILE_LINE_NUMBER_LAST, null); } else if (id == R.id.nav_more) { frag = MoreFragment.newInstance(); } else { From 2c7cc2ca2246a7fa78cb1ef5207837dd39cd9ca3 Mon Sep 17 00:00:00 2001 From: Harshad Vedartham Date: Thu, 13 Oct 2022 09:46:20 -0700 Subject: [PATCH 37/40] More changes to start in preview correctly --- .../gsantner/markor/activity/DocumentActivity.java | 12 +++++++----- .../markor/activity/DocumentShareIntoFragment.java | 4 ++-- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/app/src/main/java/net/gsantner/markor/activity/DocumentActivity.java b/app/src/main/java/net/gsantner/markor/activity/DocumentActivity.java index b1e6663b32..e3f5b4fe08 100644 --- a/app/src/main/java/net/gsantner/markor/activity/DocumentActivity.java +++ b/app/src/main/java/net/gsantner/markor/activity/DocumentActivity.java @@ -203,12 +203,14 @@ private void handleLaunchingIntent(final Intent intent) { startLine = TextViewUtils.tryParseInt(intentData.getQueryParameter("line"), -1); } - final boolean startInPreview = (startLine == null) && (false - || intent.getBooleanExtra(EXTRA_DO_PREVIEW, false) - || _appSettings.getDocumentPreviewState(doc.getPath()) - || file.getName().startsWith("index.") - ); + Boolean startInPreview = null; + if (startLine == null && (intent.getBooleanExtra(EXTRA_DO_PREVIEW, false) || file.getName().startsWith("index."))) { + // Start in edit mode if required. Otherwise let the fragment decide + startInPreview = true; + } + showTextEditor(doc, startLine, startInPreview); + } else if (!showedShareInto) { showNotSupportedMessage(); } 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 c3586b5d43..a34547d93e 100644 --- a/app/src/main/java/net/gsantner/markor/activity/DocumentShareIntoFragment.java +++ b/app/src/main/java/net/gsantner/markor/activity/DocumentShareIntoFragment.java @@ -267,10 +267,10 @@ public void onFsViewerSelected(String request, File dir, final Integer lineNumbe }, getFragmentManager(), getActivity()); } - private void showInDocumentActivity(Document document) { + private void showInDocumentActivity(final Document document) { if (getActivity() instanceof DocumentActivity) { DocumentActivity a = (DocumentActivity) getActivity(); - a.showTextEditor(document, null, false); + a.showTextEditor(document, null, null); } } From 9998ddbd2d1ed0571544b9ab213c7790809cdf52 Mon Sep 17 00:00:00 2001 From: Harshad Vedartham Date: Thu, 13 Oct 2022 10:03:28 -0700 Subject: [PATCH 38/40] Further tweaks --- .../net/gsantner/markor/activity/DocumentActivity.java | 8 +++++--- .../java/net/gsantner/markor/activity/MainActivity.java | 4 ++-- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/app/src/main/java/net/gsantner/markor/activity/DocumentActivity.java b/app/src/main/java/net/gsantner/markor/activity/DocumentActivity.java index e3f5b4fe08..41af7fa54c 100644 --- a/app/src/main/java/net/gsantner/markor/activity/DocumentActivity.java +++ b/app/src/main/java/net/gsantner/markor/activity/DocumentActivity.java @@ -166,7 +166,6 @@ protected void onNewIntent(Intent intent) { handleLaunchingIntent(intent); } - @SuppressWarnings("PointlessBooleanExpression") private void handleLaunchingIntent(final Intent intent) { if (intent == null) return; @@ -203,9 +202,12 @@ private void handleLaunchingIntent(final Intent intent) { startLine = TextViewUtils.tryParseInt(intentData.getQueryParameter("line"), -1); } + // Start in a specific mode if required. Otherwise let the fragment decide Boolean startInPreview = null; - if (startLine == null && (intent.getBooleanExtra(EXTRA_DO_PREVIEW, false) || file.getName().startsWith("index."))) { - // Start in edit mode if required. Otherwise let the fragment decide + if (startLine != null) { + // If a line is requested, open in edit mode so the line is shown + startInPreview = false; + } else if (intent.getBooleanExtra(EXTRA_DO_PREVIEW, false) || file.getName().startsWith("index.")) { startInPreview = true; } 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 4bb8084bf1..0d5337008c 100644 --- a/app/src/main/java/net/gsantner/markor/activity/MainActivity.java +++ b/app/src/main/java/net/gsantner/markor/activity/MainActivity.java @@ -424,9 +424,9 @@ public GsFragmentBase get(int pos) { final GsFragmentBase frag; final int id = _bottomNav.getMenu().getItem(pos).getItemId(); if (id == R.id.nav_quicknote) { - frag = DocumentEditAndViewFragment.newInstance(new Document(_appSettings.getQuickNoteFile()), Document.EXTRA_FILE_LINE_NUMBER_LAST, null); + frag = DocumentEditAndViewFragment.newInstance(new Document(_appSettings.getQuickNoteFile()), Document.EXTRA_FILE_LINE_NUMBER_LAST, false); } else if (id == R.id.nav_todo) { - frag = DocumentEditAndViewFragment.newInstance(new Document(_appSettings.getQuickNoteFile()), Document.EXTRA_FILE_LINE_NUMBER_LAST, null); + frag = DocumentEditAndViewFragment.newInstance(new Document(_appSettings.getQuickNoteFile()), Document.EXTRA_FILE_LINE_NUMBER_LAST, false); } else if (id == R.id.nav_more) { frag = MoreFragment.newInstance(); } else { From f4637e1da655cf1ba0e17263d0acc211a7ee1275 Mon Sep 17 00:00:00 2001 From: Harshad Vedartham Date: Thu, 13 Oct 2022 23:23:07 -0700 Subject: [PATCH 39/40] Open correct file --- .../main/java/net/gsantner/markor/activity/MainActivity.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 0d5337008c..6bcad8770e 100644 --- a/app/src/main/java/net/gsantner/markor/activity/MainActivity.java +++ b/app/src/main/java/net/gsantner/markor/activity/MainActivity.java @@ -426,7 +426,7 @@ public GsFragmentBase get(int pos) { if (id == R.id.nav_quicknote) { frag = DocumentEditAndViewFragment.newInstance(new Document(_appSettings.getQuickNoteFile()), Document.EXTRA_FILE_LINE_NUMBER_LAST, false); } else if (id == R.id.nav_todo) { - frag = DocumentEditAndViewFragment.newInstance(new Document(_appSettings.getQuickNoteFile()), Document.EXTRA_FILE_LINE_NUMBER_LAST, false); + frag = DocumentEditAndViewFragment.newInstance(new Document(_appSettings.getTodoFile()), Document.EXTRA_FILE_LINE_NUMBER_LAST, false); } else if (id == R.id.nav_more) { frag = MoreFragment.newInstance(); } else { From 642c2c67e637920a646eff72bcab6381d1350276 Mon Sep 17 00:00:00 2001 From: Gregor Santner Date: Sun, 16 Oct 2022 11:01:56 +0200 Subject: [PATCH 40/40] add filename to error --- .../main/java/net/gsantner/markor/model/Document.java | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/app/src/main/java/net/gsantner/markor/model/Document.java b/app/src/main/java/net/gsantner/markor/model/Document.java index 414a8e2e42..8f3ab7872e 100644 --- a/app/src/main/java/net/gsantner/markor/model/Document.java +++ b/app/src/main/java/net/gsantner/markor/model/Document.java @@ -153,7 +153,7 @@ public long fileBytes() { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { return Files.readAttributes(_file.toPath(), BasicFileAttributes.class).size(); } - } catch (IOException ignored) { + } catch (Exception ignored) { } return _file.length(); } @@ -242,7 +242,7 @@ String loadContent(final Context context) { // We try to load 2x. If both times fail, we return null Pair result = GsFileUtils.readTextFileFast(_file); if (result.second.ioError) { - Log.i(Document.class.getName(), "Read error, trying again"); + Log.i(Document.class.getName(), "loadDocument: File " + _file + " read error, trying again."); result = GsFileUtils.readTextFileFast(_file); } content = result.first; @@ -263,7 +263,7 @@ String loadContent(final Context context) { // Force next load on failure setContentHash(null); resetChangeTracking(); - Log.i(Document.class.getName(), "Read error, could not load file"); + Log.i(Document.class.getName(), "loadDocument: File " + _file + " read error, could not load file."); return null; } else { // Also set hash and time on load - should prevent unnecessary saves @@ -364,7 +364,7 @@ public synchronized boolean saveContent(final Activity context, final CharSequen final long size = fileBytes(); if (fileBytes() < contentAsBytes.length) { success = false; - Log.i(Document.class.getName(), "File write failed; size = " + size + "; length = " + contentAsBytes.length); + Log.i(Document.class.getName(), "File write failed; size = " + size + "; length = " + contentAsBytes.length + "; file=" + _file); } } catch (JavaPasswordbasedCryption.EncryptionFailedException e) { @@ -378,7 +378,7 @@ public synchronized boolean saveContent(final Activity context, final CharSequen _modTime = fileModTime(); setGlobalTouchTime(); } else { - Log.i(Document.class.getName(), "File write failed, size = " + fileBytes()); + Log.i(Document.class.getName(), "File write failed, size = " + fileBytes() + "; file=" + _file); } return success;