Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 9e71a820 authored by Stefan Niedermann's avatar Stefan Niedermann
Browse files

Fix #722 IndexOutOfBoundsException in note containing external images

parent 07071fbb
Loading
Loading
Loading
Loading
+3 −2
Original line number Diff line number Diff line
@@ -22,6 +22,7 @@ import it.niedermann.owncloud.notes.util.MarkDownUtil;
import it.niedermann.owncloud.notes.util.Notes;

import static it.niedermann.owncloud.notes.android.appwidget.SingleNoteWidget.DARK_THEME_KEY;
import static it.niedermann.owncloud.notes.util.MarkDownUtil.parseCompat;

public class SingleNoteWidgetFactory implements RemoteViewsService.RemoteViewsFactory {

@@ -107,12 +108,12 @@ public class SingleNoteWidgetFactory implements RemoteViewsService.RemoteViewsFa
        if (darkModeActive) {
            note_content = new RemoteViews(context.getPackageName(), R.layout.widget_single_note_content_dark);
            note_content.setOnClickFillInIntent(R.id.single_note_content_tv_dark, fillInIntent);
            note_content.setTextViewText(R.id.single_note_content_tv_dark, markdownProcessor.parse(note.getContent()));
            note_content.setTextViewText(R.id.single_note_content_tv_dark, parseCompat(markdownProcessor, note.getContent()));

        } else {
            note_content = new RemoteViews(context.getPackageName(), R.layout.widget_single_note_content);
            note_content.setOnClickFillInIntent(R.id.single_note_content_tv, fillInIntent);
            note_content.setTextViewText(R.id.single_note_content_tv, markdownProcessor.parse(note.getContent()));
            note_content.setTextViewText(R.id.single_note_content_tv, parseCompat(markdownProcessor, note.getContent()));
        }

        return note_content;
+11 −11
Original line number Diff line number Diff line
@@ -37,15 +37,18 @@ import it.niedermann.owncloud.notes.android.activity.EditNoteActivity;
import it.niedermann.owncloud.notes.databinding.FragmentNotePreviewBinding;
import it.niedermann.owncloud.notes.model.LoginStatus;
import it.niedermann.owncloud.notes.persistence.NotesDatabase;
import it.niedermann.owncloud.notes.util.DisplayUtils;
import it.niedermann.owncloud.notes.util.MarkDownUtil;
import it.niedermann.owncloud.notes.util.NoteLinksUtils;
import it.niedermann.owncloud.notes.util.SSOUtil;

import static it.niedermann.owncloud.notes.util.DisplayUtils.searchAndColor;
import static it.niedermann.owncloud.notes.util.MarkDownUtil.CHECKBOX_CHECKED_MINUS;
import static it.niedermann.owncloud.notes.util.MarkDownUtil.CHECKBOX_CHECKED_STAR;
import static it.niedermann.owncloud.notes.util.MarkDownUtil.CHECKBOX_UNCHECKED_MINUS;
import static it.niedermann.owncloud.notes.util.MarkDownUtil.CHECKBOX_UNCHECKED_STAR;
import static it.niedermann.owncloud.notes.util.MarkDownUtil.parseCompat;
import static it.niedermann.owncloud.notes.util.NoteLinksUtils.extractNoteRemoteId;
import static it.niedermann.owncloud.notes.util.NoteLinksUtils.replaceNoteLinksWithDummyUrls;

public class NotePreviewFragment extends SearchableBaseNoteFragment implements OnRefreshListener {

@@ -138,7 +141,7 @@ public class NotePreviewFragment extends SearchableBaseNoteFragment implements O
                                        }

                                        changedText = TextUtils.join("\n", lines);
                                        binding.singleNoteContent.setText(markdownProcessor.parse(changedText));
                                        binding.singleNoteContent.setText(parseCompat(markdownProcessor, changedText));
                                        saveNote(null);
                                    } catch (IndexOutOfBoundsException e) {
                                        Toast.makeText(getActivity(), R.string.checkbox_could_not_be_toggled, Toast.LENGTH_SHORT).show();
@@ -149,10 +152,8 @@ public class NotePreviewFragment extends SearchableBaseNoteFragment implements O
                        )
                        .setOnLinkClickCallback((view, link) -> {
                            if (NoteLinksUtils.isNoteLink(link)) {
                                long noteRemoteId = NoteLinksUtils.extractNoteRemoteId(link);
                                long noteLocalId = db.getLocalIdByRemoteId(this.note.getAccountId(), noteRemoteId);
                                Intent intent = new Intent(requireActivity().getApplicationContext(), EditNoteActivity.class);
                                intent.putExtra(EditNoteActivity.PARAM_NOTE_ID, noteLocalId);
                                final Intent intent = new Intent(requireActivity().getApplicationContext(), EditNoteActivity.class)
                                        .putExtra(EditNoteActivity.PARAM_NOTE_ID, db.getLocalIdByRemoteId(this.note.getAccountId(), extractNoteRemoteId(link)));
                                startActivity(intent);
                            } else {
                                Intent browserIntent = new Intent(Intent.ACTION_VIEW, Uri.parse(link));
@@ -161,11 +162,10 @@ public class NotePreviewFragment extends SearchableBaseNoteFragment implements O
                        })
                        .build());
        try {
            CharSequence parsedMarkdown = markdownProcessor.parse(NoteLinksUtils.replaceNoteLinksWithDummyUrls(note.getContent(), db.getRemoteIds(note.getAccountId())));
            binding.singleNoteContent.setText(parsedMarkdown);
            binding.singleNoteContent.setText(parseCompat(markdownProcessor, replaceNoteLinksWithDummyUrls(note.getContent(), db.getRemoteIds(note.getAccountId()))));
        } catch (StringIndexOutOfBoundsException e) {
            // Workaround for RxMarkdown: https://github.com/stefan-niedermann/nextcloud-notes/issues/668
            binding.singleNoteContent.setText(NoteLinksUtils.replaceNoteLinksWithDummyUrls(note.getContent(), db.getRemoteIds(note.getAccountId())));
            binding.singleNoteContent.setText(replaceNoteLinksWithDummyUrls(note.getContent(), db.getRemoteIds(note.getAccountId())));
            Toast.makeText(binding.singleNoteContent.getContext(), R.string.could_not_load_preview_two_digit_numbered_list, Toast.LENGTH_LONG).show();
            e.printStackTrace();
        }
@@ -185,7 +185,7 @@ public class NotePreviewFragment extends SearchableBaseNoteFragment implements O
    @Override
    protected void colorWithText(String newText) {
        if (binding != null && ViewCompat.isAttachedToWindow(binding.singleNoteContent)) {
            binding.singleNoteContent.setText(markdownProcessor.parse(DisplayUtils.searchAndColor(getContent(), new SpannableString
            binding.singleNoteContent.setText(parseCompat(markdownProcessor, searchAndColor(getContent(), new SpannableString
                            (getContent()), newText, getResources().getColor(R.color.primary))),
                    TextView.BufferType.SPANNABLE);
        }
@@ -204,7 +204,7 @@ public class NotePreviewFragment extends SearchableBaseNoteFragment implements O
                SingleSignOnAccount ssoAccount = SingleAccountHelper.getCurrentSingleSignOnAccount(requireContext());
                db.getNoteServerSyncHelper().addCallbackPull(ssoAccount, () -> {
                    note = db.getNote(note.getAccountId(), note.getId());
                    binding.singleNoteContent.setText(markdownProcessor.parse(NoteLinksUtils.replaceNoteLinksWithDummyUrls(note.getContent(), db.getRemoteIds(note.getAccountId()))));
                    binding.singleNoteContent.setText(parseCompat(markdownProcessor, replaceNoteLinksWithDummyUrls(note.getContent(), db.getRemoteIds(note.getAccountId()))));
                    binding.swiperefreshlayout.setRefreshing(false);
                });
                db.getNoteServerSyncHelper().scheduleSync(ssoAccount, false);
+6 −3
Original line number Diff line number Diff line
@@ -35,6 +35,8 @@ import it.niedermann.owncloud.notes.util.DisplayUtils;
import it.niedermann.owncloud.notes.util.MarkDownUtil;
import it.niedermann.owncloud.notes.util.NoteLinksUtils;

import static it.niedermann.owncloud.notes.util.MarkDownUtil.parseCompat;

public class NoteReadonlyFragment extends SearchableBaseNoteFragment {

    private MarkdownProcessor markdownProcessor;
@@ -114,10 +116,11 @@ public class NoteReadonlyFragment extends SearchableBaseNoteFragment {
                        })
                        .build());
        try {
            binding.singleNoteContent.setText(markdownProcessor.parse(note.getContent()));
            binding.singleNoteContent.setText(parseCompat(markdownProcessor, note.getContent()));
            onResume();
        } catch (StringIndexOutOfBoundsException e) {
            // Workaround for RxMarkdown: https://github.com/stefan-niedermann/nextcloud-notes/issues/668
            binding.singleNoteContent.setText(note.getContent());
            Toast.makeText(binding.singleNoteContent.getContext(), R.string.could_not_load_preview_two_digit_numbered_list, Toast.LENGTH_LONG).show();
            e.printStackTrace();
        }
@@ -145,8 +148,8 @@ public class NoteReadonlyFragment extends SearchableBaseNoteFragment {

    @Override
    protected void colorWithText(String newText) {
        if (binding != null && ViewCompat.isAttachedToWindow(binding.singleNoteContent)) {
            binding.singleNoteContent.setText(markdownProcessor.parse(DisplayUtils.searchAndColor(getContent(), new SpannableString
        if ((binding != null) && ViewCompat.isAttachedToWindow(binding.singleNoteContent)) {
            binding.singleNoteContent.setText(parseCompat(markdownProcessor, DisplayUtils.searchAndColor(getContent(), new SpannableString
                            (getContent()), newText, getResources().getColor(R.color.primary))),
                    TextView.BufferType.SPANNABLE);
        }
+36 −0
Original line number Diff line number Diff line
@@ -3,12 +3,15 @@ package it.niedermann.owncloud.notes.util;
import android.content.Context;
import android.graphics.Color;
import android.text.Spanned;
import android.text.TextUtils;
import android.util.Log;

import androidx.annotation.NonNull;
import androidx.core.content.res.ResourcesCompat;

import com.yydcdut.markdown.MarkdownConfiguration;
import com.yydcdut.markdown.MarkdownConfiguration.Builder;
import com.yydcdut.markdown.MarkdownProcessor;
import com.yydcdut.markdown.span.MDImageSpan;
import com.yydcdut.markdown.theme.ThemeDefault;
import com.yydcdut.markdown.theme.ThemeSonsOfObsidian;
@@ -22,6 +25,8 @@ import it.niedermann.owncloud.notes.R;
@SuppressWarnings("WeakerAccess")
public class MarkDownUtil {

    private static final String TAG = MarkDownUtil.class.getCanonicalName();

    public static final String CHECKBOX_UNCHECKED_MINUS = "- [ ]";
    public static final String CHECKBOX_UNCHECKED_MINUS_TRAILING_SPACE = CHECKBOX_UNCHECKED_MINUS + " ";
    public static final String CHECKBOX_UNCHECKED_STAR = "* [ ]";
@@ -29,6 +34,11 @@ public class MarkDownUtil {
    public static final String CHECKBOX_CHECKED_MINUS = "- [x]";
    public static final String CHECKBOX_CHECKED_STAR = "* [x]";

    private static final String MD_IMAGE_WITH_EMPTY_DESCRIPTION = "![](";
    private static final String MD_IMAGE_WITH_SPACE_DESCRIPTION = "![ ](";
    private static final String[] MD_IMAGE_WITH_EMPTY_DESCRIPTION_ARRAY = new String[]{MD_IMAGE_WITH_EMPTY_DESCRIPTION};
    private static final String[] MD_IMAGE_WITH_SPACE_DESCRIPTION_ARRAY = new String[]{MD_IMAGE_WITH_SPACE_DESCRIPTION};

    /**
     * Ensures every instance of RxMD uses the same configuration
     *
@@ -60,6 +70,32 @@ public class MarkDownUtil {
                .setDefaultImageSize(400, 300);
    }

    /**
     * This is a compatibility-method that provides workarounds for several bugs in RxMarkdown
     *
     * https://github.com/stefan-niedermann/nextcloud-notes/issues/772
     *
     * @param markdownProcessor RxMarkdown MarkdownProcessor instance
     * @param text CharSequence that should be parsed
     * @return the processed text but with several workarounds for Bugs in RxMarkdown
     */
    @NonNull
    public static CharSequence parseCompat(@NonNull final MarkdownProcessor markdownProcessor, CharSequence text) {
        if (TextUtils.isEmpty(text)) {
            return "";
        }

        Log.v(TAG, "parseCompat - Original: \"" + text + "\"");

        while (TextUtils.indexOf(text, MD_IMAGE_WITH_EMPTY_DESCRIPTION) >= 0) {
            text = TextUtils.replace(text, MD_IMAGE_WITH_EMPTY_DESCRIPTION_ARRAY, MD_IMAGE_WITH_SPACE_DESCRIPTION_ARRAY);
        }

        Log.v(TAG, "parseCompat - Replaced empty image descriptions: \"" + text + "\"");

        return markdownProcessor.parse(text);
    }

    public static boolean containsImageSpan(@NonNull CharSequence text) {
        return ((Spanned) text).getSpans(0, text.length(), MDImageSpan.class).length > 0;
    }