From 6b0e47da8593afbf45eb9e240094647a5106bc84 Mon Sep 17 00:00:00 2001 From: Stefan Niedermann Date: Wed, 10 Jun 2020 13:00:23 +0200 Subject: Performance and rendering enhancements --- .../android/activity/NotesListViewActivity.java | 5 +- .../android/fragment/PreferencesFragment.java | 2 + .../owncloud/notes/model/ItemAdapter.java | 1 - .../owncloud/notes/model/NoteViewGridHolder.java | 8 +-- .../owncloud/notes/model/NoteViewHolder.java | 60 ++++++++++--------- .../notes/model/NoteViewHolderWithExcerpt.java | 7 ++- .../notes/model/NoteViewHolderWithoutExcerpt.java | 4 +- .../owncloud/notes/model/SectionViewHolder.java | 8 +-- .../owncloud/notes/persistence/NotesDatabase.java | 15 ++++- .../owncloud/notes/util/MarkDownUtil.java | 4 -- .../niedermann/owncloud/notes/util/NoteUtil.java | 22 +++++-- .../res/layout/item_notes_list_note_item_grid.xml | 67 ++++++++++++---------- 12 files changed, 118 insertions(+), 85 deletions(-) (limited to 'app/src/main') diff --git a/app/src/main/java/it/niedermann/owncloud/notes/android/activity/NotesListViewActivity.java b/app/src/main/java/it/niedermann/owncloud/notes/android/activity/NotesListViewActivity.java index a309dc5d..c6b14fef 100644 --- a/app/src/main/java/it/niedermann/owncloud/notes/android/activity/NotesListViewActivity.java +++ b/app/src/main/java/it/niedermann/owncloud/notes/android/activity/NotesListViewActivity.java @@ -325,7 +325,7 @@ public class NotesListViewActivity extends LockedActivity implements NoteClickLi } private void setupNotesList() { - initList(); + initRecyclerView(); ((RecyclerView) findViewById(R.id.recycler_view)).addOnScrollListener(new RecyclerView.OnScrollListener() { @Override @@ -596,7 +596,7 @@ public class NotesListViewActivity extends LockedActivity implements NoteClickLi binding.navigationMenu.setAdapter(adapterMenu); } - private void initList() { + private void initRecyclerView() { adapter = new ItemAdapter(this, gridView); listView.setAdapter(adapter); @@ -802,7 +802,6 @@ public class NotesListViewActivity extends LockedActivity implements NoteClickLi Intent intent = new Intent(getApplicationContext(), EditNoteActivity.class); intent.putExtra(EditNoteActivity.PARAM_NOTE_ID, note.getId()); startActivityForResult(intent, show_single_note_cmd); - } } diff --git a/app/src/main/java/it/niedermann/owncloud/notes/android/fragment/PreferencesFragment.java b/app/src/main/java/it/niedermann/owncloud/notes/android/fragment/PreferencesFragment.java index db2d8ee5..d8034ae6 100644 --- a/app/src/main/java/it/niedermann/owncloud/notes/android/fragment/PreferencesFragment.java +++ b/app/src/main/java/it/niedermann/owncloud/notes/android/fragment/PreferencesFragment.java @@ -16,6 +16,7 @@ import it.niedermann.owncloud.notes.android.DarkModeSetting; import it.niedermann.owncloud.notes.branding.Branded; import it.niedermann.owncloud.notes.branding.BrandedSwitchPreference; import it.niedermann.owncloud.notes.branding.BrandingUtil; +import it.niedermann.owncloud.notes.persistence.NotesDatabase; import it.niedermann.owncloud.notes.persistence.SyncWorker; import it.niedermann.owncloud.notes.util.DeviceCredentialUtil; import it.niedermann.owncloud.notes.util.Notes; @@ -61,6 +62,7 @@ public class PreferencesFragment extends PreferenceFragmentCompat implements Bra if (gridViewPref != null) { gridViewPref.setOnPreferenceChangeListener((Preference preference, Object newValue) -> { final Boolean gridView = (Boolean) newValue; + NotesDatabase.getInstance(requireContext()).regenerateExcerpts(!gridView); Log.v(TAG, "gridView: " + gridView); requireActivity().setResult(Activity.RESULT_OK); return true; diff --git a/app/src/main/java/it/niedermann/owncloud/notes/model/ItemAdapter.java b/app/src/main/java/it/niedermann/owncloud/notes/model/ItemAdapter.java index 0b2bb528..05a5bcfb 100644 --- a/app/src/main/java/it/niedermann/owncloud/notes/model/ItemAdapter.java +++ b/app/src/main/java/it/niedermann/owncloud/notes/model/ItemAdapter.java @@ -11,7 +11,6 @@ import androidx.annotation.ColorInt; import androidx.annotation.IntRange; import androidx.annotation.NonNull; import androidx.recyclerview.widget.RecyclerView; -import androidx.recyclerview.widget.StaggeredGridLayoutManager; import java.util.ArrayList; import java.util.List; diff --git a/app/src/main/java/it/niedermann/owncloud/notes/model/NoteViewGridHolder.java b/app/src/main/java/it/niedermann/owncloud/notes/model/NoteViewGridHolder.java index d9cd30f1..7091d87e 100644 --- a/app/src/main/java/it/niedermann/owncloud/notes/model/NoteViewGridHolder.java +++ b/app/src/main/java/it/niedermann/owncloud/notes/model/NoteViewGridHolder.java @@ -1,6 +1,7 @@ package it.niedermann.owncloud.notes.model; import android.content.Context; +import android.util.Log; import android.view.View; import androidx.annotation.NonNull; @@ -13,13 +14,12 @@ public class NoteViewGridHolder extends NoteViewHolder { private final ItemNotesListNoteItemGridBinding binding; public NoteViewGridHolder(@NonNull ItemNotesListNoteItemGridBinding binding, @NonNull NoteClickListener noteClickListener) { - super(binding.getRoot(), noteClickListener); + super(binding.getRoot(), noteClickListener, true); this.binding = binding; itemView.setOnClickListener(this); itemView.setOnLongClickListener(this); } - public void showSwipe(boolean left) { } @@ -29,8 +29,8 @@ public class NoteViewGridHolder extends NoteViewHolder { bindCategory(context, binding.noteCategory, showCategory, note.getCategory(), mainColor); binding.noteStatus.setVisibility(DBStatus.VOID.equals(note.getStatus()) ? View.INVISIBLE : View.VISIBLE); bindFavorite(binding.noteFavorite, note.isFavorite()); - bindTitle(context, binding.noteTitle, searchQuery, note, mainColor); - bindExcerpt(context, binding.noteContent, searchQuery, note, mainColor); + bindSearchableContent(context, binding.noteTitle, searchQuery, note.getTitle(), mainColor); + bindSearchableContent(context, binding.noteContent, searchQuery, note.getExcerpt(), mainColor); } public View getNoteSwipeable() { diff --git a/app/src/main/java/it/niedermann/owncloud/notes/model/NoteViewHolder.java b/app/src/main/java/it/niedermann/owncloud/notes/model/NoteViewHolder.java index 6272dcf4..db8f39f8 100644 --- a/app/src/main/java/it/niedermann/owncloud/notes/model/NoteViewHolder.java +++ b/app/src/main/java/it/niedermann/owncloud/notes/model/NoteViewHolder.java @@ -4,6 +4,8 @@ import android.content.Context; import android.graphics.Color; import android.graphics.drawable.GradientDrawable; import android.os.Build; +import android.os.Handler; +import android.os.Looper; import android.text.SpannableString; import android.text.TextUtils; import android.text.style.BackgroundColorSpan; @@ -18,23 +20,36 @@ import androidx.annotation.Nullable; import androidx.core.graphics.drawable.DrawableCompat; import androidx.recyclerview.widget.RecyclerView; +import com.yydcdut.markdown.MarkdownProcessor; +import com.yydcdut.markdown.syntax.text.TextFactory; + import java.util.regex.Matcher; import java.util.regex.Pattern; import it.niedermann.owncloud.notes.R; import it.niedermann.owncloud.notes.branding.BrandingUtil; +import it.niedermann.owncloud.notes.util.MarkDownUtil; import it.niedermann.owncloud.notes.util.Notes; import static androidx.recyclerview.widget.RecyclerView.NO_POSITION; import static it.niedermann.owncloud.notes.util.ColorUtil.contrastRatioIsSufficient; import static it.niedermann.owncloud.notes.util.ColorUtil.isColorDark; +import static it.niedermann.owncloud.notes.util.MarkDownUtil.parseCompat; public abstract class NoteViewHolder extends RecyclerView.ViewHolder implements View.OnLongClickListener, View.OnClickListener { private final NoteClickListener noteClickListener; + private final boolean renderMarkdown; + private MarkdownProcessor markdownProcessor; - public NoteViewHolder(@NonNull View v, @NonNull NoteClickListener noteClickListener) { + public NoteViewHolder(@NonNull View v, @NonNull NoteClickListener noteClickListener, boolean renderMarkdown) { super(v); this.noteClickListener = noteClickListener; + this.renderMarkdown = renderMarkdown; + if (renderMarkdown) { + markdownProcessor = new MarkdownProcessor(itemView.getContext()); + markdownProcessor.factory(TextFactory.create()); + markdownProcessor.config(MarkDownUtil.getMarkDownConfiguration(itemView.getContext()).build()); + } v.setOnClickListener(this); v.setOnLongClickListener(this); } @@ -99,10 +114,9 @@ public abstract class NoteViewHolder extends RecyclerView.ViewHolder implements noteFavorite.setOnClickListener(view -> noteClickListener.onNoteFavoriteClick(getAdapterPosition(), view)); } - protected void bindTitle(@NonNull Context context, @NonNull TextView noteTitle, @Nullable CharSequence searchQuery, @NonNull DBNote note, int mainColor) { - if (TextUtils.isEmpty(searchQuery)) { - noteTitle.setText(note.getTitle()); - } else { + protected void bindSearchableContent(@NonNull Context context, @NonNull TextView textView, @Nullable CharSequence searchQuery, @NonNull String content, int mainColor) { + CharSequence processedContent = content; + if (!TextUtils.isEmpty(searchQuery)) { @ColorInt final int searchBackground = context.getResources().getColor(R.color.bg_highlighted); @ColorInt final int searchForeground = BrandingUtil.getSecondaryForegroundColorDependingOnTheme(context, mainColor); @@ -110,36 +124,30 @@ public abstract class NoteViewHolder extends RecyclerView.ViewHolder implements // It implies that the string between \Q and \E is a literal string and thus the reserved keyword in such string will be ignored. // See https://stackoverflow.com/questions/15409296/what-is-the-use-of-pattern-quote-method final Pattern pattern = Pattern.compile("(" + Pattern.quote(searchQuery.toString()) + ")", Pattern.CASE_INSENSITIVE); - SpannableString spannableString = new SpannableString(note.getTitle()); + SpannableString spannableString = new SpannableString(content); Matcher matcher = pattern.matcher(spannableString); while (matcher.find()) { spannableString.setSpan(new ForegroundColorSpan(searchForeground), matcher.start(), matcher.end(), 0); spannableString.setSpan(new BackgroundColorSpan(searchBackground), matcher.start(), matcher.end(), 0); } - noteTitle.setText(spannableString); + + processedContent = spannableString; } + bindContent(textView, processedContent); } - protected void bindExcerpt(@NonNull Context context, @NonNull TextView noteExcerpt, @Nullable CharSequence searchQuery, @NonNull DBNote note, int mainColor) { - if (TextUtils.isEmpty(searchQuery)) { - noteExcerpt.setText(note.getExcerpt()); - } else { - @ColorInt final int searchBackground = context.getResources().getColor(R.color.bg_highlighted); - @ColorInt final int searchForeground = BrandingUtil.getSecondaryForegroundColorDependingOnTheme(context, mainColor); - - // The Pattern.quote method will add \Q to the very beginning of the string and \E to the end of the string - // It implies that the string between \Q and \E is a literal string and thus the reserved keyword in such string will be ignored. - // See https://stackoverflow.com/questions/15409296/what-is-the-use-of-pattern-quote-method - final Pattern pattern = Pattern.compile("(" + Pattern.quote(searchQuery.toString()) + ")", Pattern.CASE_INSENSITIVE); - SpannableString spannableString = new SpannableString(note.getExcerpt()); - Matcher matcher = pattern.matcher(spannableString); - - while (matcher.find()) { - spannableString.setSpan(new ForegroundColorSpan(searchForeground), matcher.start(), matcher.end(), 0); - spannableString.setSpan(new BackgroundColorSpan(searchBackground), matcher.start(), matcher.end(), 0); - } - noteExcerpt.setText(spannableString); + private void bindContent(@NonNull TextView textView, @NonNull CharSequence charSequence) { + textView.setText(charSequence); + if (renderMarkdown) { + new Thread(() -> { + try { + final CharSequence parsedCharSequence = parseCompat(markdownProcessor, charSequence); + new Handler(Looper.getMainLooper()).post(() -> textView.setText(parsedCharSequence)); + } catch (StringIndexOutOfBoundsException e) { + // Workaround for RxMarkdown: https://github.com/stefan-niedermann/nextcloud-notes/issues/668 + } + }).start(); } } diff --git a/app/src/main/java/it/niedermann/owncloud/notes/model/NoteViewHolderWithExcerpt.java b/app/src/main/java/it/niedermann/owncloud/notes/model/NoteViewHolderWithExcerpt.java index 4433b982..05a743ab 100644 --- a/app/src/main/java/it/niedermann/owncloud/notes/model/NoteViewHolderWithExcerpt.java +++ b/app/src/main/java/it/niedermann/owncloud/notes/model/NoteViewHolderWithExcerpt.java @@ -14,7 +14,7 @@ public class NoteViewHolderWithExcerpt extends NoteViewHolder { private final ItemNotesListNoteItemWithExcerptBinding binding; public NoteViewHolderWithExcerpt(@NonNull ItemNotesListNoteItemWithExcerptBinding binding, @NonNull NoteClickListener noteClickListener) { - super(binding.getRoot(), noteClickListener); + super(binding.getRoot(), noteClickListener, false); this.binding = binding; itemView.setOnClickListener(this); itemView.setOnLongClickListener(this); @@ -32,8 +32,9 @@ public class NoteViewHolderWithExcerpt extends NoteViewHolder { bindCategory(context, binding.noteCategory, showCategory, note.getCategory(), mainColor); binding.noteStatus.setVisibility(DBStatus.VOID.equals(note.getStatus()) ? View.INVISIBLE : View.VISIBLE); bindFavorite(binding.noteFavorite, note.isFavorite()); - bindTitle(context, binding.noteTitle, searchQuery, note, mainColor); - bindExcerpt(context, binding.noteExcerpt, searchQuery, note, mainColor); + + bindSearchableContent(context, binding.noteTitle, searchQuery, note.getTitle(), mainColor); + bindSearchableContent(context, binding.noteExcerpt, searchQuery, note.getExcerpt(), mainColor); } public View getNoteSwipeable() { diff --git a/app/src/main/java/it/niedermann/owncloud/notes/model/NoteViewHolderWithoutExcerpt.java b/app/src/main/java/it/niedermann/owncloud/notes/model/NoteViewHolderWithoutExcerpt.java index acacda32..59a42f5c 100644 --- a/app/src/main/java/it/niedermann/owncloud/notes/model/NoteViewHolderWithoutExcerpt.java +++ b/app/src/main/java/it/niedermann/owncloud/notes/model/NoteViewHolderWithoutExcerpt.java @@ -14,7 +14,7 @@ public class NoteViewHolderWithoutExcerpt extends NoteViewHolder { private final ItemNotesListNoteItemWithoutExcerptBinding binding; public NoteViewHolderWithoutExcerpt(@NonNull ItemNotesListNoteItemWithoutExcerptBinding binding, @NonNull NoteClickListener noteClickListener) { - super(binding.getRoot(), noteClickListener); + super(binding.getRoot(), noteClickListener, false); this.binding = binding; itemView.setOnClickListener(this); itemView.setOnLongClickListener(this); @@ -33,7 +33,7 @@ public class NoteViewHolderWithoutExcerpt extends NoteViewHolder { bindCategory(context, binding.noteCategory, showCategory, note.getCategory(), mainColor); binding.noteStatus.setVisibility(DBStatus.VOID.equals(note.getStatus()) ? View.INVISIBLE : View.VISIBLE); bindFavorite(binding.noteFavorite, note.isFavorite()); - bindTitle(context, binding.noteTitle, searchQuery, note, mainColor); + bindSearchableContent(context, binding.noteTitle, searchQuery, note.getTitle(), mainColor); } public View getNoteSwipeable() { diff --git a/app/src/main/java/it/niedermann/owncloud/notes/model/SectionViewHolder.java b/app/src/main/java/it/niedermann/owncloud/notes/model/SectionViewHolder.java index b39f77ad..45eeb140 100644 --- a/app/src/main/java/it/niedermann/owncloud/notes/model/SectionViewHolder.java +++ b/app/src/main/java/it/niedermann/owncloud/notes/model/SectionViewHolder.java @@ -11,13 +11,13 @@ public class SectionViewHolder extends RecyclerView.ViewHolder { public SectionViewHolder(ItemNotesListSectionItemBinding binding) { super(binding.getRoot()); this.binding = binding; - } - - public void bind(SectionItem item) { - binding.sectionTitle.setText(item.getTitle()); if (itemView.getLayoutParams() != null && itemView.getLayoutParams() instanceof StaggeredGridLayoutManager.LayoutParams) { ((StaggeredGridLayoutManager.LayoutParams) itemView.getLayoutParams()).setFullSpan(true); } } + + public void bind(SectionItem item) { + binding.sectionTitle.setText(item.getTitle()); + } } \ No newline at end of file diff --git a/app/src/main/java/it/niedermann/owncloud/notes/persistence/NotesDatabase.java b/app/src/main/java/it/niedermann/owncloud/notes/persistence/NotesDatabase.java index 1a31ae0a..286bfbd2 100644 --- a/app/src/main/java/it/niedermann/owncloud/notes/persistence/NotesDatabase.java +++ b/app/src/main/java/it/niedermann/owncloud/notes/persistence/NotesDatabase.java @@ -41,8 +41,6 @@ import java.util.Set; import it.niedermann.owncloud.notes.R; import it.niedermann.owncloud.notes.android.activity.EditNoteActivity; -import it.niedermann.owncloud.notes.android.appwidget.NoteListWidget; -import it.niedermann.owncloud.notes.android.appwidget.SingleNoteWidget; import it.niedermann.owncloud.notes.model.ApiVersion; import it.niedermann.owncloud.notes.model.Capabilities; import it.niedermann.owncloud.notes.model.CloudNote; @@ -137,6 +135,17 @@ public class NotesDatabase extends AbstractNotesDatabase { return db.insert(table_notes, null, values); } + public void regenerateExcerpts(boolean stripMarkdown) { + SQLiteDatabase db = this.getWritableDatabase(); + Cursor cursor = db.query(table_notes, new String[]{key_id, key_content}, key_status + " != ?", new String[]{DBStatus.LOCAL_DELETED.getTitle()}, null, null, null); + ContentValues values = new ContentValues(1); + while (cursor.moveToNext()) { + values.put(key_excerpt, NoteUtil.generateNoteExcerpt(cursor.getString(1), stripMarkdown)); + db.update(table_notes, values, key_id + " = ?", new String[]{String.valueOf(cursor.getInt(0))}); + } + cursor.close(); + } + public void moveNoteToAnotherAccount(SingleSignOnAccount ssoAccount, long oldAccountId, DBNote note, long newAccountId) { // Add new note addNoteAndSync(ssoAccount, newAccountId, new CloudNote(0, note.getModified(), note.getTitle(), note.getContent(), note.isFavorite(), note.getCategory(), null)); @@ -616,7 +625,7 @@ public class NotesDatabase extends AbstractNotesDatabase { if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { ShortcutManager shortcutManager = getContext().getSystemService(ShortcutManager.class); - if(shortcutManager != null) { + if (shortcutManager != null) { shortcutManager.getPinnedShortcuts().forEach((shortcut) -> { String shortcutId = id + ""; if (shortcut.getId().equals(shortcutId)) { diff --git a/app/src/main/java/it/niedermann/owncloud/notes/util/MarkDownUtil.java b/app/src/main/java/it/niedermann/owncloud/notes/util/MarkDownUtil.java index 66f89280..1a8c4b15 100644 --- a/app/src/main/java/it/niedermann/owncloud/notes/util/MarkDownUtil.java +++ b/app/src/main/java/it/niedermann/owncloud/notes/util/MarkDownUtil.java @@ -85,14 +85,10 @@ public class MarkDownUtil { 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); } diff --git a/app/src/main/java/it/niedermann/owncloud/notes/util/NoteUtil.java b/app/src/main/java/it/niedermann/owncloud/notes/util/NoteUtil.java index bd83ce5c..11802b2b 100644 --- a/app/src/main/java/it/niedermann/owncloud/notes/util/NoteUtil.java +++ b/app/src/main/java/it/niedermann/owncloud/notes/util/NoteUtil.java @@ -81,15 +81,29 @@ public class NoteUtil { /** * Generates an excerpt of a content String (reads second line which is not empty) * - * @param content String + * @param content {@link String} * @return excerpt String */ @NonNull public static String generateNoteExcerpt(@NonNull String content) { - if (content.contains("\n")) - return truncateString(removeMarkDown(content.replaceFirst("^.*\n", "")), 200).replace("\n", " "); - else + return generateNoteExcerpt(content, true); // TODO check gridview + } + + /** + * Generates an excerpt of a content String (reads second line which is not empty) + * + * @param content {@link String} + * @param stripMarkdown whether or not the markdown should be stripped from the excerpt + * @return excerpt String + */ + @NonNull + public static String generateNoteExcerpt(@NonNull String content, boolean stripMarkdown) { + if (!content.contains("\n")) { return ""; + } + return stripMarkdown + ? truncateString(removeMarkDown(content.replaceFirst("^.*\n", "")), 200).replace("\n", " ") + : truncateString(content.replaceFirst("^.*\n", ""), 200); } @NonNull diff --git a/app/src/main/res/layout/item_notes_list_note_item_grid.xml b/app/src/main/res/layout/item_notes_list_note_item_grid.xml index 694b5dd4..c3fe3676 100644 --- a/app/src/main/res/layout/item_notes_list_note_item_grid.xml +++ b/app/src/main/res/layout/item_notes_list_note_item_grid.xml @@ -2,40 +2,57 @@ + android:focusable="true"> + + - @@ -65,18 +82,6 @@ - -