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..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,12 +202,17 @@ 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.") - ); + // Start in a specific mode if required. Otherwise let the fragment decide + Boolean startInPreview = null; + 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; + } + showTextEditor(doc, startLine, startInPreview); + } else if (!showedShareInto) { showNotSupportedMessage(); } 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 ce07be6462..1b9a886494 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; @@ -72,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; @@ -150,7 +149,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.error_could_not_open_file, Toast.LENGTH_LONG).show(); activity.finish(); return; } @@ -194,7 +193,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)); @@ -234,6 +232,7 @@ public void onViewCreated(@NonNull View view, Bundle savedInstanceState) { updateUndoRedoIconStates(); }); _hlEditor.addTextChangedListener(GsTextWatcherAdapter.after(s -> debounced.run())); + } @Override @@ -358,15 +357,24 @@ 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 (content == null) { + errorClipText(); + return false; + } + if (!_document.isContentSame(_hlEditor.getText())) { final int[] sel = TextViewUtils.getSelection(_hlEditor); @@ -427,7 +435,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; @@ -651,6 +659,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(); @@ -666,8 +677,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)); } } @@ -682,8 +695,10 @@ 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.error_could_not_open_file, 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/activity/DocumentShareIntoFragment.java b/app/src/main/java/net/gsantner/markor/activity/DocumentShareIntoFragment.java index c7916aa8c9..a34547d93e 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; @@ -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.error_could_not_open_file, Toast.LENGTH_LONG).show(); + } if (showEditor) { showInDocumentActivity(document); @@ -262,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); } } @@ -311,7 +316,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/activity/MainActivity.java b/app/src/main/java/net/gsantner/markor/activity/MainActivity.java index ee3e80fb3e..6bcad8770e 100644 --- a/app/src/main/java/net/gsantner/markor/activity/MainActivity.java +++ b/app/src/main/java/net/gsantner/markor/activity/MainActivity.java @@ -115,7 +115,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); } } @@ -257,8 +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 - , true); + ? GsFileBrowserListAdapter.VIRTUAL_STORAGE_FAVOURITE : GsFileBrowserListAdapter.VIRTUAL_STORAGE_RECENTS); } return true; } @@ -425,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, false); } else if (id == R.id.nav_todo) { - frag = DocumentEditAndViewFragment.newInstance(_appSettings.getTodoFile(), Document.EXTRA_FILE_LINE_NUMBER_LAST); + 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 { 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/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 10cd4c1629..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 @@ -37,31 +37,36 @@ 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) { + 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); @@ -109,7 +114,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 +171,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 +185,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(j); + 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/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 493fa19f2f..114359882c 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; @@ -34,13 +34,12 @@ 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.FormatRegistry; 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; @@ -60,6 +59,7 @@ import java.util.List; import java.util.Map; import java.util.Set; +import java.util.TreeMap; import java.util.TreeSet; public class MarkorDialogFactory { @@ -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,9 +395,12 @@ 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])); + final GsSearchOrCustomTextDialog.DialogOptions doptSel = makeSttLineSelectionDialog( + activity, text, TodoTxtFilter.taskSelector(selKeys, getKeys, useAnd[0])); doptSel.messageText = activity.getString(title); // Callback to save view @@ -422,10 +425,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++) { @@ -510,7 +513,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) { @@ -589,7 +592,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)); } @@ -658,7 +661,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()) { @@ -669,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); } } @@ -683,7 +686,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/NewFileDialog.java b/app/src/main/java/net/gsantner/markor/frontend/NewFileDialog.java index a70b509992..ef6b66c893 100644 --- a/app/src/main/java/net/gsantner/markor/frontend/NewFileDialog.java +++ b/app/src/main/java/net/gsantner/markor/frontend/NewFileDialog.java @@ -16,6 +16,7 @@ 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; @@ -32,8 +33,10 @@ 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.frontend.textview.HighlightingEditor; +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; @@ -143,7 +146,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()) + "-"; } @@ -173,47 +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; - } + 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 byte[] 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); - } - } catch (Exception ignored) { - } - callback(arg_ok || f.exists(), f); - dialogInterface.dismiss(); - }); - }) - .setNeutralButton(R.string.folder, (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); @@ -259,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) { + private Pair getTemplateContent(final Spinner templateSpinner, final File basedir, final String filename, final boolean encrypt) { String t = null; - byte[] bytes = 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"; @@ -292,7 +297,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,24 +308,29 @@ 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; } - - // Empty file template (that doesn't overwrite anything) - return null; } } - t = t.replace("{{ template.timestamp_date_yyyy_mm_dd }}", TodoTxtParser.DATEF_YYYY_MM_DD.format(new Date())); + 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, ""); + + 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); } else { bytes = t.getBytes(); } - return bytes; + + return Pair.create(bytes, startingIndex); } } 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..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 @@ -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; @@ -31,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; @@ -41,24 +39,18 @@ 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 _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; private InputFilter _autoFormatFilter; private TextWatcher _autoFormatModifier; private boolean _autoFormatEnabled; @@ -77,7 +69,7 @@ public HighlightingEditor(Context context, AttributeSet attrs) { } _hlEnabled = false; - _olhHlRect = new Rect(); + _oldHlRect = new Rect(); _hlRect = new Rect(); addTextChangedListener(new GsTextWatcherAdapter() { @@ -98,8 +90,8 @@ 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(() -> updateHighlighting(false)); + observer.addOnGlobalLayoutListener(() -> updateHighlighting(false)); // Fix for android 12 perf issues - https://github.com/gsantner/markor/discussions/1794 setEmojiCompatEnabled(false); @@ -109,66 +101,25 @@ public void afterTextChanged(final Editable s) { // --------------------------------------------------------------------------------------------- private boolean isScrollSignificant() { - return Math.abs(_hlRect.top - _olhHlRect.top) > _hlShiftThreshold || - Math.abs(_hlRect.bottom - _olhHlRect.bottom) > _hlShiftThreshold; - } - - private void updateDynamicHighlighting() { - if (_isDynamicHighlightingEnabled) { - updateHighlighting(false); - } - } - - private synchronized void postUpdateDynamicHighlighting() { - if (!_hlPostQueued) { - _hlPostQueued = true; - post(() -> { - updateDynamicHighlighting(); - _hlPostQueued = false; - }); - } + return (_oldHlRect.top - _hlRect.top) > _hlShiftThreshold || + (_hlRect.bottom - _oldHlRect.bottom) > _hlShiftThreshold; } private void updateHighlighting(final boolean recompute) { - final Layout layout; - if (!_isUpdatingDynamicHighlighting && _hlEnabled && _hl != null && (layout = getLayout()) != null) { - _isUpdatingDynamicHighlighting = true; + if (_hlEnabled && _hl != null && getLayout() != null) { final boolean visible = getLocalVisibleRect(_hlRect); // Don't highlight unless shifted sufficiently or a recompute is required if (recompute || (visible && _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 int shiftTestLine = layout.getLineForVertical(_hlRect.centerY()); - final int oldOffset = layout.getLineBaseline(shiftTestLine); - 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 - 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 - _scrollView.slowScrollShift(shift); + _hl.clearDynamic(); + if (recompute) { + _hl.clearStatic().recompute().applyStatic(); } + _hl.applyDynamic(newHlRegion); } - - _isUpdatingDynamicHighlighting = false; } } @@ -182,14 +133,14 @@ public boolean isDynamicHighlightingEnabled() { public void setHighlighter(final SyntaxHighlighterBase newHighlighter) { if (_hl != null) { - _hl.clear(); + _hl.clearAll(); } _hl = 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; @@ -208,6 +159,10 @@ public SyntaxHighlighterBase getHighlighter() { return _hl; } + public boolean getHighlightingEnabled() { + return _hlEnabled; + } + public boolean setHighlightingEnabled(final boolean enable) { final boolean prev = _hlEnabled; if (enable && !_hlEnabled) { @@ -219,7 +174,7 @@ public boolean setHighlightingEnabled(final boolean enable) { } else if (!enable && _hlEnabled) { _hlEnabled = false; if (_hl != null) { - _hl.clear(); + _hl.clearAll(); } } return prev; @@ -249,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) { @@ -266,51 +217,14 @@ 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); - if (visibility == View.VISIBLE) { + if (changedView == this && visibility == View.VISIBLE) { updateHighlighting(true); } } - @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; - } - } - @Override public void setTextSize(float size) { super.setTextSize(size); @@ -458,7 +372,7 @@ 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); 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 4e9d6fd6fd..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 @@ -109,21 +109,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()); } @@ -334,16 +319,22 @@ public static void showSelection(final TextView text, final int start, final int text.post(() -> text.requestRectangleOnScreen(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)); }); } } @@ -617,20 +608,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); } } }; 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..8f3ab7872e 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; @@ -147,6 +148,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 (Exception ignored) { + } + return _file.length(); + } + public String getTitle() { return _title; } @@ -196,15 +207,16 @@ 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; @@ -227,7 +239,12 @@ 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) { + Log.i(Document.class.getName(), "loadDocument: File " + _file + " read error, trying again."); + result = GsFileUtils.readTextFileFast(_file); + } content = result.first; _fileInfo = result.second; } @@ -238,16 +255,23 @@ 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 != null && _fileInfo.ioError) { + // Force next load on failure + setContentHash(null); + resetChangeTracking(); + 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 + setContentHash(content); + _modTime = fileModTime(); + setGlobalTouchTime(); + return content; + } } @RequiresApi(api = Build.VERSION_CODES.M) @@ -324,13 +348,25 @@ 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 { + // Try write 2x success = GsFileUtils.writeFile(_file, contentAsBytes, _fileInfo); + if (!success || fileBytes() < contentAsBytes.length) { + success = GsFileUtils.writeFile(_file, contentAsBytes, _fileInfo); + } } + + final long size = fileBytes(); + if (fileBytes() < contentAsBytes.length) { + success = false; + Log.i(Document.class.getName(), "File write failed; size = " + size + "; length = " + contentAsBytes.length + "; file=" + _file); + } + } 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(); @@ -341,6 +377,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() + "; file=" + _file); } return success; 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 5f9bf23966..8ee3a72a7c 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 @@ -98,7 +98,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); @@ -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 6198f28677..7b3d09c533 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 @@ -256,11 +256,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() { 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 702ea8b635..0a3d7fd134 100644 --- a/app/src/main/java/net/gsantner/opoc/util/GsFileUtils.java +++ b/app/src/main/java/net/gsantner/opoc/util/GsFileUtils.java @@ -69,11 +69,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) { @@ -84,11 +80,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); @@ -106,6 +101,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); @@ -429,7 +425,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")) { @@ -505,7 +501,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 54e3561a5e..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="120dp" + android:paddingBottom="@dimen/editor_bottom_margin" android:scrollbars="none" android:textCursorDrawable="@drawable/cursor_accent" /> @@ -60,19 +60,6 @@ android:orientation="horizontal" /> - - . المشاركة في - التنسيق تنسيق تلقائي Insert snippet - خطأ: تعذر الفتح. + خطأ: تعذر الفتح. حدث خطأ: تم نسخ النص إلى الحافظة. diff --git a/app/src/main/res/values-ca/strings.xml b/app/src/main/res/values-ca/strings.xml index 28dd9a00cf..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 09dc71539f..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 36628e0a8a..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 5c602c0920..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 2c826485fa..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 1764eff1f2..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 bebd447ed5..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 eade7a3e84..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 e853e98506..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 7df942bc49..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 956611b46c..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 7dcb1345b7..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 33b2cd06a1..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 07fca3895e..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 f415d0fb09..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 9e5eb2fb5b..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 837695c34d..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 8994a2da89..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 d1894ad23b..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 4ea0d05008..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 ce19b0c2ac..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 b238c3fd82..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/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 diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 26c22bdee5..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. 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); diff --git a/app/thirdparty/java/other/writeily/widget/WrFilesWidgetFactory.java b/app/thirdparty/java/other/writeily/widget/WrFilesWidgetFactory.java index 77fe3c2ddb..8c0331b507 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()); } - AppSettings as = ApplicationObject.settings(); - GsFileUtils.sortFiles(_widgetFilesList, as.getFileBrowserSortByType(), as.isFileBrowserSortFolderFirst(), as.isFileBrowserSortReverse()); // Sort only if actual folder + GsFileUtils.sortFiles(_widgetFilesList, as.getFileBrowserSortByType(), as.isFileBrowserSortFolderFirst(), as.isFileBrowserSortReverse()); } @Override