From e0d8cf8b0be895f81d4522f9f999770ddf874769 Mon Sep 17 00:00:00 2001 From: Matthew Kevins Date: Tue, 6 Feb 2024 11:52:45 +1000 Subject: [PATCH 01/15] Remove unused methods This appears to be dead code. --- .../ui/notifications/adapters/NotesAdapter.java | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/WordPress/src/main/java/org/wordpress/android/ui/notifications/adapters/NotesAdapter.java b/WordPress/src/main/java/org/wordpress/android/ui/notifications/adapters/NotesAdapter.java index 3ab7e7f1e6a5..8f5658b06a05 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/notifications/adapters/NotesAdapter.java +++ b/WordPress/src/main/java/org/wordpress/android/ui/notifications/adapters/NotesAdapter.java @@ -320,22 +320,6 @@ private void handleMaxLines(final TextView subject, final TextView detail) { }); } - private int getPositionForNoteUnfiltered(String noteId) { - return getPositionForNoteInArray(noteId, mNotes); - } - - private int getPositionForNoteInArray(String noteId, ArrayList notes) { - if (notes != null && noteId != null) { - for (int i = 0; i < notes.size(); i++) { - String noteKey = notes.get(i).getId(); - if (noteKey != null && noteKey.equals(noteId)) { - return i; - } - } - } - return RecyclerView.NO_POSITION; - } - public void setOnNoteClickListener(OnNoteClickListener mNoteClickListener) { mOnNoteClickListener = mNoteClickListener; } From 42f641484e9a9b5e6574107bf6c7ca25e6a6f5d2 Mon Sep 17 00:00:00 2001 From: Matthew Kevins Date: Tue, 6 Feb 2024 14:47:53 +1000 Subject: [PATCH 02/15] Add nullability annotations This is a preparation for converting to Kotlin. --- .../notifications/adapters/NotesAdapter.java | 76 ++++++++++--------- 1 file changed, 41 insertions(+), 35 deletions(-) diff --git a/WordPress/src/main/java/org/wordpress/android/ui/notifications/adapters/NotesAdapter.java b/WordPress/src/main/java/org/wordpress/android/ui/notifications/adapters/NotesAdapter.java index 8f5658b06a05..75550fa6e68b 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/notifications/adapters/NotesAdapter.java +++ b/WordPress/src/main/java/org/wordpress/android/ui/notifications/adapters/NotesAdapter.java @@ -15,6 +15,8 @@ import android.widget.ImageView; import android.widget.TextView; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; import androidx.core.text.BidiFormatter; import androidx.core.view.ViewCompat; import androidx.recyclerview.widget.RecyclerView; @@ -36,6 +38,7 @@ import java.util.ArrayList; import java.util.Collections; import java.util.List; +import java.util.Objects; import javax.inject.Inject; @@ -43,8 +46,8 @@ public class NotesAdapter extends RecyclerView.Adapter mNotes = new ArrayList<>(); private final ArrayList mFilteredNotes = new ArrayList<>(); @Inject protected ImageManager mImageManager; @@ -75,8 +78,8 @@ public String toString() { } } - private FILTERS mCurrentFilter = FILTERS.FILTER_ALL; - private ReloadNotesFromDBTask mReloadNotesFromDBTask; + @NonNull private FILTERS mCurrentFilter = FILTERS.FILTER_ALL; + @Nullable private ReloadNotesFromDBTask mReloadNotesFromDBTask; public interface DataLoadedListener { void onDataLoaded(int itemsCount); @@ -86,9 +89,10 @@ public interface OnLoadMoreListener { void onLoadMore(long timestamp); } - private OnNoteClickListener mOnNoteClickListener; + @Nullable private OnNoteClickListener mOnNoteClickListener; - public NotesAdapter(Context context, DataLoadedListener dataLoadedListener, OnLoadMoreListener onLoadMoreListener) { + public NotesAdapter(@NonNull Context context, @NonNull DataLoadedListener dataLoadedListener, + @Nullable OnLoadMoreListener onLoadMoreListener) { super(); ((WordPress) context.getApplicationContext()).component().inject(this); mDataLoadedListener = dataLoadedListener; @@ -104,15 +108,16 @@ public NotesAdapter(Context context, DataLoadedListener dataLoadedListener, OnLo mTextIndentSize = context.getResources().getDimensionPixelSize(R.dimen.notifications_text_indent_sz); } - public void setFilter(FILTERS newFilter) { + public void setFilter(@NonNull FILTERS newFilter) { mCurrentFilter = newFilter; } + @NonNull public FILTERS getCurrentFilter() { return mCurrentFilter; } - public void addAll(List notes, boolean clearBeforeAdding) { + public void addAll(@NonNull List notes, boolean clearBeforeAdding) { Collections.sort(notes, new Note.TimeStampComparator()); try { if (clearBeforeAdding) { @@ -124,16 +129,16 @@ public void addAll(List notes, boolean clearBeforeAdding) { } } + @SuppressLint("NotifyDataSetChanged") private void myNotifyDatasetChanged() { buildFilteredNotesList(mFilteredNotes, mNotes, mCurrentFilter); notifyDataSetChanged(); - if (mDataLoadedListener != null) { - mDataLoadedListener.onDataLoaded(getItemCount()); - } + mDataLoadedListener.onDataLoaded(getItemCount()); } + @NonNull @Override - public NoteViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { + public NoteViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.notifications_list_item, parent, false); return new NoteViewHolder(view); @@ -173,6 +178,7 @@ public static void buildFilteredNotesList(ArrayList filteredNotes, ArrayLi } } + @Nullable private Note getNoteAtPosition(int position) { if (isValidPosition(position)) { return mFilteredNotes.get(position); @@ -191,7 +197,7 @@ public int getItemCount() { } @Override - public void onBindViewHolder(NoteViewHolder noteViewHolder, int position) { + public void onBindViewHolder(@NonNull NoteViewHolder noteViewHolder, int position) { final Note note = getNoteAtPosition(position); if (note == null) { return; @@ -306,7 +312,7 @@ public void onBindViewHolder(NoteViewHolder noteViewHolder, int position) { noteViewHolder.mHeaderText.setLayoutParams(layoutParams); } - private void handleMaxLines(final TextView subject, final TextView detail) { + private void handleMaxLines(@NonNull final TextView subject, @NonNull final TextView detail) { subject.getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() { @Override public boolean onPreDraw() { subject.getViewTreeObserver().removeOnPreDrawListener(this); @@ -320,7 +326,7 @@ private void handleMaxLines(final TextView subject, final TextView detail) { }); } - public void setOnNoteClickListener(OnNoteClickListener mNoteClickListener) { + public void setOnNoteClickListener(@Nullable OnNoteClickListener mNoteClickListener) { mOnNoteClickListener = mNoteClickListener; } @@ -331,7 +337,6 @@ public void cancelReloadNotesTask() { } } - @SuppressWarnings("deprecation") public void reloadNotesFromDBAsync() { cancelReloadNotesTask(); mReloadNotesFromDBTask = new ReloadNotesFromDBTask(); @@ -341,13 +346,14 @@ public void reloadNotesFromDBAsync() { @SuppressWarnings("deprecation") @SuppressLint("StaticFieldLeak") private class ReloadNotesFromDBTask extends AsyncTask> { + @NonNull @Override - protected ArrayList doInBackground(Void... voids) { + protected ArrayList doInBackground(@Nullable Void... voids) { return NotificationsTable.getLatestNotes(); } @Override - protected void onPostExecute(ArrayList notes) { + protected void onPostExecute(@NonNull ArrayList notes) { mNotes.clear(); mNotes.addAll(notes); myNotifyDatasetChanged(); @@ -355,23 +361,23 @@ protected void onPostExecute(ArrayList notes) { } class NoteViewHolder extends RecyclerView.ViewHolder { - private final View mContentView; - private final TextView mHeaderText; - private final TextView mTxtSubject; - private final TextView mTxtSubjectNoticon; - private final TextView mTxtDetail; - private final ImageView mImgAvatar; - private final View mUnreadNotificationView; - - NoteViewHolder(View view) { + @NonNull private final View mContentView; + @NonNull private final TextView mHeaderText; + @NonNull private final TextView mTxtSubject; + @NonNull private final TextView mTxtSubjectNoticon; + @NonNull private final TextView mTxtDetail; + @NonNull private final ImageView mImgAvatar; + @NonNull private final View mUnreadNotificationView; + + NoteViewHolder(@NonNull View view) { super(view); - mContentView = view.findViewById(R.id.note_content_container); - mHeaderText = view.findViewById(R.id.header_text); - mTxtSubject = view.findViewById(R.id.note_subject); - mTxtSubjectNoticon = view.findViewById(R.id.note_subject_noticon); - mTxtDetail = view.findViewById(R.id.note_detail); - mImgAvatar = view.findViewById(R.id.note_avatar); - mUnreadNotificationView = view.findViewById(R.id.notification_unread); + mContentView = Objects.requireNonNull(view.findViewById(R.id.note_content_container)); + mHeaderText = Objects.requireNonNull(view.findViewById(R.id.header_text)); + mTxtSubject = Objects.requireNonNull(view.findViewById(R.id.note_subject)); + mTxtSubjectNoticon = Objects.requireNonNull(view.findViewById(R.id.note_subject_noticon)); + mTxtDetail = Objects.requireNonNull(view.findViewById(R.id.note_detail)); + mImgAvatar = Objects.requireNonNull(view.findViewById(R.id.note_avatar)); + mUnreadNotificationView = Objects.requireNonNull(view.findViewById(R.id.notification_unread)); mContentView.setOnClickListener(mOnClickListener); } @@ -379,7 +385,7 @@ class NoteViewHolder extends RecyclerView.ViewHolder { private final View.OnClickListener mOnClickListener = new View.OnClickListener() { @Override - public void onClick(View v) { + public void onClick(@NonNull View v) { if (mOnNoteClickListener != null && v.getTag() instanceof String) { mOnNoteClickListener.onClickNote((String) v.getTag()); } From d2ec7a6f29ea02e4fad1548beedbb1f056179cce Mon Sep 17 00:00:00 2001 From: Matthew Kevins Date: Tue, 6 Feb 2024 14:49:00 +1000 Subject: [PATCH 03/15] Use List sort in addAll --- .../android/ui/notifications/adapters/NotesAdapter.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/WordPress/src/main/java/org/wordpress/android/ui/notifications/adapters/NotesAdapter.java b/WordPress/src/main/java/org/wordpress/android/ui/notifications/adapters/NotesAdapter.java index 75550fa6e68b..4b7f68fb177b 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/notifications/adapters/NotesAdapter.java +++ b/WordPress/src/main/java/org/wordpress/android/ui/notifications/adapters/NotesAdapter.java @@ -36,7 +36,6 @@ import org.wordpress.android.util.image.ImageType; import java.util.ArrayList; -import java.util.Collections; import java.util.List; import java.util.Objects; @@ -118,7 +117,7 @@ public FILTERS getCurrentFilter() { } public void addAll(@NonNull List notes, boolean clearBeforeAdding) { - Collections.sort(notes, new Note.TimeStampComparator()); + notes.sort(new Note.TimeStampComparator()); try { if (clearBeforeAdding) { mNotes.clear(); From 8be4ca2e7745cde19cf0b8b591316e7f2f1f0118 Mon Sep 17 00:00:00 2001 From: Matthew Kevins Date: Tue, 6 Feb 2024 14:50:04 +1000 Subject: [PATCH 04/15] Remove more dead code from notes adapter --- .../ui/notifications/adapters/NotesAdapter.java | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/WordPress/src/main/java/org/wordpress/android/ui/notifications/adapters/NotesAdapter.java b/WordPress/src/main/java/org/wordpress/android/ui/notifications/adapters/NotesAdapter.java index 4b7f68fb177b..85e6d86cb05d 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/notifications/adapters/NotesAdapter.java +++ b/WordPress/src/main/java/org/wordpress/android/ui/notifications/adapters/NotesAdapter.java @@ -24,7 +24,6 @@ import org.wordpress.android.R; import org.wordpress.android.WordPress; import org.wordpress.android.datasets.NotificationsTable; -import org.wordpress.android.fluxc.model.CommentStatus; import org.wordpress.android.models.Note; import org.wordpress.android.ui.comments.CommentUtils; import org.wordpress.android.ui.notifications.NotificationsListFragmentPage.OnNoteClickListener; @@ -230,15 +229,6 @@ public void onBindViewHolder(@NonNull NoteViewHolder noteViewHolder, int positio } } - CommentStatus commentStatus = CommentStatus.ALL; - if (note.getCommentStatus() == CommentStatus.UNAPPROVED) { - commentStatus = CommentStatus.UNAPPROVED; - } - - if (!TextUtils.isEmpty(note.getLocalStatus())) { - commentStatus = CommentStatus.fromString(note.getLocalStatus()); - } - // Subject is stored in db as html to preserve text formatting Spanned noteSubjectSpanned = note.getFormattedSubject(mNotificationsUtilsWrapper); // Trim the '\n\n' added by HtmlCompat.fromHtml(...) From 8319f2faab2d8e3d0b8b8d513ad183da9b818e50 Mon Sep 17 00:00:00 2001 From: Matthew Kevins Date: Tue, 6 Feb 2024 14:55:41 +1000 Subject: [PATCH 05/15] Apply automatic Kotlin conversion to NotesAdapter Manual changes are intentionally ommited from this commit in order to better track the rest of the conversion / refactor in later steps. --- .../notifications/adapters/NotesAdapter.java | 383 ------------------ .../ui/notifications/adapters/NotesAdapter.kt | 357 ++++++++++++++++ 2 files changed, 357 insertions(+), 383 deletions(-) delete mode 100644 WordPress/src/main/java/org/wordpress/android/ui/notifications/adapters/NotesAdapter.java create mode 100644 WordPress/src/main/java/org/wordpress/android/ui/notifications/adapters/NotesAdapter.kt diff --git a/WordPress/src/main/java/org/wordpress/android/ui/notifications/adapters/NotesAdapter.java b/WordPress/src/main/java/org/wordpress/android/ui/notifications/adapters/NotesAdapter.java deleted file mode 100644 index 85e6d86cb05d..000000000000 --- a/WordPress/src/main/java/org/wordpress/android/ui/notifications/adapters/NotesAdapter.java +++ /dev/null @@ -1,383 +0,0 @@ -package org.wordpress.android.ui.notifications.adapters; - -import android.annotation.SuppressLint; -import android.content.Context; -import android.os.AsyncTask; -import android.os.AsyncTask.Status; -import android.text.Spanned; -import android.text.TextUtils; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.view.ViewGroup.MarginLayoutParams; -import android.view.ViewParent; -import android.view.ViewTreeObserver; -import android.widget.ImageView; -import android.widget.TextView; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.core.text.BidiFormatter; -import androidx.core.view.ViewCompat; -import androidx.recyclerview.widget.RecyclerView; - -import org.wordpress.android.R; -import org.wordpress.android.WordPress; -import org.wordpress.android.datasets.NotificationsTable; -import org.wordpress.android.models.Note; -import org.wordpress.android.ui.comments.CommentUtils; -import org.wordpress.android.ui.notifications.NotificationsListFragmentPage.OnNoteClickListener; -import org.wordpress.android.ui.notifications.blocks.NoteBlockClickableSpan; -import org.wordpress.android.ui.notifications.utils.NotificationsUtilsWrapper; -import org.wordpress.android.util.GravatarUtils; -import org.wordpress.android.util.RtlUtils; -import org.wordpress.android.util.image.ImageManager; -import org.wordpress.android.util.image.ImageType; - -import java.util.ArrayList; -import java.util.List; -import java.util.Objects; - -import javax.inject.Inject; - -public class NotesAdapter extends RecyclerView.Adapter { - private final int mAvatarSz; - private final int mTextIndentSize; - - @NonNull private final DataLoadedListener mDataLoadedListener; - @Nullable private final OnLoadMoreListener mOnLoadMoreListener; - private final ArrayList mNotes = new ArrayList<>(); - private final ArrayList mFilteredNotes = new ArrayList<>(); - @Inject protected ImageManager mImageManager; - @Inject protected NotificationsUtilsWrapper mNotificationsUtilsWrapper; - - public enum FILTERS { - FILTER_ALL, - FILTER_COMMENT, - FILTER_FOLLOW, - FILTER_LIKE, - FILTER_UNREAD; - - public String toString() { - switch (this) { - case FILTER_ALL: - return "all"; - case FILTER_COMMENT: - return "comment"; - case FILTER_FOLLOW: - return "follow"; - case FILTER_LIKE: - return "like"; - case FILTER_UNREAD: - return "unread"; - default: - return "all"; - } - } - } - - @NonNull private FILTERS mCurrentFilter = FILTERS.FILTER_ALL; - @Nullable private ReloadNotesFromDBTask mReloadNotesFromDBTask; - - public interface DataLoadedListener { - void onDataLoaded(int itemsCount); - } - - public interface OnLoadMoreListener { - void onLoadMore(long timestamp); - } - - @Nullable private OnNoteClickListener mOnNoteClickListener; - - public NotesAdapter(@NonNull Context context, @NonNull DataLoadedListener dataLoadedListener, - @Nullable OnLoadMoreListener onLoadMoreListener) { - super(); - ((WordPress) context.getApplicationContext()).component().inject(this); - mDataLoadedListener = dataLoadedListener; - mOnLoadMoreListener = onLoadMoreListener; - - // this is on purpose - we don't show more than a hundred or so notifications at a time so no need to set - // stable IDs. This helps prevent crashes in case a note comes with no ID (we've code checking for that - // elsewhere, but telling the RecyclerView.Adapter the notes have stable Ids and then failing to provide them - // will make things go south as in https://github.com/wordpress-mobile/WordPress-Android/issues/8741 - setHasStableIds(false); - - mAvatarSz = (int) context.getResources().getDimension(R.dimen.notifications_avatar_sz); - mTextIndentSize = context.getResources().getDimensionPixelSize(R.dimen.notifications_text_indent_sz); - } - - public void setFilter(@NonNull FILTERS newFilter) { - mCurrentFilter = newFilter; - } - - @NonNull - public FILTERS getCurrentFilter() { - return mCurrentFilter; - } - - public void addAll(@NonNull List notes, boolean clearBeforeAdding) { - notes.sort(new Note.TimeStampComparator()); - try { - if (clearBeforeAdding) { - mNotes.clear(); - } - mNotes.addAll(notes); - } finally { - myNotifyDatasetChanged(); - } - } - - @SuppressLint("NotifyDataSetChanged") - private void myNotifyDatasetChanged() { - buildFilteredNotesList(mFilteredNotes, mNotes, mCurrentFilter); - notifyDataSetChanged(); - mDataLoadedListener.onDataLoaded(getItemCount()); - } - - @NonNull - @Override - public NoteViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { - View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.notifications_list_item, parent, false); - - return new NoteViewHolder(view); - } - - // Instead of building the filtered notes list dynamically, create it once and re-use it. - // Otherwise it's re-created so many times during layout. - public static void buildFilteredNotesList(ArrayList filteredNotes, ArrayList notes, FILTERS filter) { - filteredNotes.clear(); - if (notes.isEmpty() || filter == FILTERS.FILTER_ALL) { - filteredNotes.addAll(notes); - return; - } - for (Note currentNote : notes) { - switch (filter) { - case FILTER_COMMENT: - if (currentNote.isCommentType()) { - filteredNotes.add(currentNote); - } - break; - case FILTER_FOLLOW: - if (currentNote.isFollowType()) { - filteredNotes.add(currentNote); - } - break; - case FILTER_UNREAD: - if (currentNote.isUnread()) { - filteredNotes.add(currentNote); - } - break; - case FILTER_LIKE: - if (currentNote.isLikeType()) { - filteredNotes.add(currentNote); - } - break; - } - } - } - - @Nullable - private Note getNoteAtPosition(int position) { - if (isValidPosition(position)) { - return mFilteredNotes.get(position); - } - - return null; - } - - private boolean isValidPosition(int position) { - return (position >= 0 && position < mFilteredNotes.size()); - } - - @Override - public int getItemCount() { - return mFilteredNotes.size(); - } - - @Override - public void onBindViewHolder(@NonNull NoteViewHolder noteViewHolder, int position) { - final Note note = getNoteAtPosition(position); - if (note == null) { - return; - } - noteViewHolder.mContentView.setTag(note.getId()); - - // Display group header - Note.NoteTimeGroup timeGroup = Note.getTimeGroupForTimestamp(note.getTimestamp()); - - Note.NoteTimeGroup previousTimeGroup = null; - if (position > 0) { - Note previousNote = getNoteAtPosition(position - 1); - previousTimeGroup = Note.getTimeGroupForTimestamp(previousNote.getTimestamp()); - } - - if (previousTimeGroup != null && previousTimeGroup == timeGroup) { - noteViewHolder.mHeaderText.setVisibility(View.GONE); - } else { - noteViewHolder.mHeaderText.setVisibility(View.VISIBLE); - - if (timeGroup == Note.NoteTimeGroup.GROUP_TODAY) { - noteViewHolder.mHeaderText.setText(R.string.stats_timeframe_today); - } else if (timeGroup == Note.NoteTimeGroup.GROUP_YESTERDAY) { - noteViewHolder.mHeaderText.setText(R.string.stats_timeframe_yesterday); - } else if (timeGroup == Note.NoteTimeGroup.GROUP_OLDER_TWO_DAYS) { - noteViewHolder.mHeaderText.setText(R.string.older_two_days); - } else if (timeGroup == Note.NoteTimeGroup.GROUP_OLDER_WEEK) { - noteViewHolder.mHeaderText.setText(R.string.older_last_week); - } else { - noteViewHolder.mHeaderText.setText(R.string.older_month); - } - } - - // Subject is stored in db as html to preserve text formatting - Spanned noteSubjectSpanned = note.getFormattedSubject(mNotificationsUtilsWrapper); - // Trim the '\n\n' added by HtmlCompat.fromHtml(...) - noteSubjectSpanned = - (Spanned) noteSubjectSpanned.subSequence(0, TextUtils.getTrimmedLength(noteSubjectSpanned)); - - NoteBlockClickableSpan[] spans = - noteSubjectSpanned.getSpans(0, noteSubjectSpanned.length(), NoteBlockClickableSpan.class); - for (NoteBlockClickableSpan span : spans) { - span.enableColors(noteViewHolder.mContentView.getContext()); - } - - noteViewHolder.mTxtSubject.setText(noteSubjectSpanned); - - String noteSubjectNoticon = note.getCommentSubjectNoticon(); - if (!TextUtils.isEmpty(noteSubjectNoticon)) { - ViewParent parent = noteViewHolder.mTxtSubject.getParent(); - // Fix position of the subject noticon in the RtL mode - if (parent instanceof ViewGroup) { - int textDirection = BidiFormatter.getInstance().isRtl(noteViewHolder.mTxtSubject.getText()) - ? ViewCompat.LAYOUT_DIRECTION_RTL : ViewCompat.LAYOUT_DIRECTION_LTR; - ViewCompat.setLayoutDirection((ViewGroup) parent, textDirection); - } - // mirror noticon in the rtl mode - if (RtlUtils.isRtl(noteViewHolder.itemView.getContext())) { - noteViewHolder.mTxtSubjectNoticon.setScaleX(-1); - } - CommentUtils.indentTextViewFirstLine(noteViewHolder.mTxtSubject, mTextIndentSize); - noteViewHolder.mTxtSubjectNoticon.setText(noteSubjectNoticon); - noteViewHolder.mTxtSubjectNoticon.setVisibility(View.VISIBLE); - } else { - noteViewHolder.mTxtSubjectNoticon.setVisibility(View.GONE); - } - - String noteSnippet = note.getCommentSubject(); - - if (!TextUtils.isEmpty(noteSnippet)) { - handleMaxLines(noteViewHolder.mTxtSubject, noteViewHolder.mTxtDetail); - noteViewHolder.mTxtDetail.setText(noteSnippet); - noteViewHolder.mTxtDetail.setVisibility(View.VISIBLE); - } else { - noteViewHolder.mTxtDetail.setVisibility(View.GONE); - } - - String avatarUrl = GravatarUtils.fixGravatarUrl(note.getIconURL(), mAvatarSz); - mImageManager.loadIntoCircle(noteViewHolder.mImgAvatar, ImageType.AVATAR_WITH_BACKGROUND, avatarUrl); - - if (note.isUnread()) { - noteViewHolder.mUnreadNotificationView.setVisibility(View.VISIBLE); - } else { - noteViewHolder.mUnreadNotificationView.setVisibility(View.GONE); - } - - // request to load more comments when we near the end - if (mOnLoadMoreListener != null && position >= getItemCount() - 1) { - mOnLoadMoreListener.onLoadMore(note.getTimestamp()); - } - - final int headerMarginTop; - final Context context = noteViewHolder.itemView.getContext(); - if (position == 0) { - headerMarginTop = context.getResources() - .getDimensionPixelSize(R.dimen.notifications_header_margin_top_position_0); - } else { - headerMarginTop = context.getResources() - .getDimensionPixelSize(R.dimen.notifications_header_margin_top_position_n); - } - MarginLayoutParams layoutParams = (MarginLayoutParams) noteViewHolder.mHeaderText.getLayoutParams(); - layoutParams.topMargin = headerMarginTop; - noteViewHolder.mHeaderText.setLayoutParams(layoutParams); - } - - private void handleMaxLines(@NonNull final TextView subject, @NonNull final TextView detail) { - subject.getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() { - @Override public boolean onPreDraw() { - subject.getViewTreeObserver().removeOnPreDrawListener(this); - if (subject.getLineCount() == 2) { - detail.setMaxLines(1); - } else { - detail.setMaxLines(2); - } - return false; - } - }); - } - - public void setOnNoteClickListener(@Nullable OnNoteClickListener mNoteClickListener) { - mOnNoteClickListener = mNoteClickListener; - } - - public void cancelReloadNotesTask() { - if (mReloadNotesFromDBTask != null && mReloadNotesFromDBTask.getStatus() != Status.FINISHED) { - mReloadNotesFromDBTask.cancel(true); - mReloadNotesFromDBTask = null; - } - } - - public void reloadNotesFromDBAsync() { - cancelReloadNotesTask(); - mReloadNotesFromDBTask = new ReloadNotesFromDBTask(); - mReloadNotesFromDBTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); - } - - @SuppressWarnings("deprecation") - @SuppressLint("StaticFieldLeak") - private class ReloadNotesFromDBTask extends AsyncTask> { - @NonNull - @Override - protected ArrayList doInBackground(@Nullable Void... voids) { - return NotificationsTable.getLatestNotes(); - } - - @Override - protected void onPostExecute(@NonNull ArrayList notes) { - mNotes.clear(); - mNotes.addAll(notes); - myNotifyDatasetChanged(); - } - } - - class NoteViewHolder extends RecyclerView.ViewHolder { - @NonNull private final View mContentView; - @NonNull private final TextView mHeaderText; - @NonNull private final TextView mTxtSubject; - @NonNull private final TextView mTxtSubjectNoticon; - @NonNull private final TextView mTxtDetail; - @NonNull private final ImageView mImgAvatar; - @NonNull private final View mUnreadNotificationView; - - NoteViewHolder(@NonNull View view) { - super(view); - mContentView = Objects.requireNonNull(view.findViewById(R.id.note_content_container)); - mHeaderText = Objects.requireNonNull(view.findViewById(R.id.header_text)); - mTxtSubject = Objects.requireNonNull(view.findViewById(R.id.note_subject)); - mTxtSubjectNoticon = Objects.requireNonNull(view.findViewById(R.id.note_subject_noticon)); - mTxtDetail = Objects.requireNonNull(view.findViewById(R.id.note_detail)); - mImgAvatar = Objects.requireNonNull(view.findViewById(R.id.note_avatar)); - mUnreadNotificationView = Objects.requireNonNull(view.findViewById(R.id.notification_unread)); - - mContentView.setOnClickListener(mOnClickListener); - } - } - - private final View.OnClickListener mOnClickListener = new View.OnClickListener() { - @Override - public void onClick(@NonNull View v) { - if (mOnNoteClickListener != null && v.getTag() instanceof String) { - mOnNoteClickListener.onClickNote((String) v.getTag()); - } - } - }; -} diff --git a/WordPress/src/main/java/org/wordpress/android/ui/notifications/adapters/NotesAdapter.kt b/WordPress/src/main/java/org/wordpress/android/ui/notifications/adapters/NotesAdapter.kt new file mode 100644 index 000000000000..34907a4d3e2e --- /dev/null +++ b/WordPress/src/main/java/org/wordpress/android/ui/notifications/adapters/NotesAdapter.kt @@ -0,0 +1,357 @@ +package org.wordpress.android.ui.notifications.adapters + +import android.annotation.SuppressLint +import android.content.Context +import android.os.AsyncTask +import android.text.Spanned +import android.text.TextUtils +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.view.ViewGroup.MarginLayoutParams +import android.view.ViewTreeObserver +import android.widget.ImageView +import android.widget.TextView +import androidx.core.text.BidiFormatter +import androidx.core.view.ViewCompat +import androidx.recyclerview.widget.RecyclerView +import org.wordpress.android.R +import org.wordpress.android.WordPress +import org.wordpress.android.datasets.NotificationsTable +import org.wordpress.android.models.Note +import org.wordpress.android.models.Note.NoteTimeGroup +import org.wordpress.android.models.Note.TimeStampComparator +import org.wordpress.android.ui.comments.CommentUtils +import org.wordpress.android.ui.notifications.NotificationsListFragmentPage.OnNoteClickListener +import org.wordpress.android.ui.notifications.adapters.NotesAdapter.NoteViewHolder +import org.wordpress.android.ui.notifications.blocks.NoteBlockClickableSpan +import org.wordpress.android.ui.notifications.utils.NotificationsUtilsWrapper +import org.wordpress.android.util.GravatarUtils +import org.wordpress.android.util.RtlUtils +import org.wordpress.android.util.image.ImageManager +import org.wordpress.android.util.image.ImageType +import java.util.Objects +import javax.inject.Inject + +class NotesAdapter( + context: Context, dataLoadedListener: DataLoadedListener, + onLoadMoreListener: OnLoadMoreListener? +) : RecyclerView.Adapter() { + private val mAvatarSz: Int + private val mTextIndentSize: Int + private val mDataLoadedListener: DataLoadedListener + private val mOnLoadMoreListener: OnLoadMoreListener? + private val mNotes = ArrayList() + private val mFilteredNotes = ArrayList() + + @JvmField + @Inject + var mImageManager: ImageManager? = null + + @JvmField + @Inject + var mNotificationsUtilsWrapper: NotificationsUtilsWrapper? = null + + enum class FILTERS { + FILTER_ALL, + FILTER_COMMENT, + FILTER_FOLLOW, + FILTER_LIKE, + FILTER_UNREAD; + + override fun toString(): String { + return when (this) { + FILTER_ALL -> "all" + FILTER_COMMENT -> "comment" + FILTER_FOLLOW -> "follow" + FILTER_LIKE -> "like" + FILTER_UNREAD -> "unread" + else -> "all" + } + } + } + + var currentFilter = FILTERS.FILTER_ALL + private set + private var mReloadNotesFromDBTask: ReloadNotesFromDBTask? = null + + interface DataLoadedListener { + fun onDataLoaded(itemsCount: Int) + } + + interface OnLoadMoreListener { + fun onLoadMore(timestamp: Long) + } + + private var mOnNoteClickListener: OnNoteClickListener? = null + fun setFilter(newFilter: FILTERS) { + currentFilter = newFilter + } + + fun addAll(notes: List, clearBeforeAdding: Boolean) { + notes.sort(TimeStampComparator()) + try { + if (clearBeforeAdding) { + mNotes.clear() + } + mNotes.addAll(notes) + } finally { + myNotifyDatasetChanged() + } + } + + @SuppressLint("NotifyDataSetChanged") + private fun myNotifyDatasetChanged() { + buildFilteredNotesList(mFilteredNotes, mNotes, currentFilter) + notifyDataSetChanged() + mDataLoadedListener.onDataLoaded(itemCount) + } + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): NoteViewHolder { + val view = LayoutInflater.from(parent.context) + .inflate(R.layout.notifications_list_item, parent, false) + return NoteViewHolder(view) + } + + private fun getNoteAtPosition(position: Int): Note? { + return if (isValidPosition(position)) { + mFilteredNotes[position] + } else null + } + + private fun isValidPosition(position: Int): Boolean { + return position >= 0 && position < mFilteredNotes.size + } + + override fun getItemCount(): Int { + return mFilteredNotes.size + } + + override fun onBindViewHolder(noteViewHolder: NoteViewHolder, position: Int) { + val note = getNoteAtPosition(position) ?: return + noteViewHolder.mContentView.tag = note.id + + // Display group header + val timeGroup = Note.getTimeGroupForTimestamp(note.timestamp) + var previousTimeGroup: NoteTimeGroup? = null + if (position > 0) { + val previousNote = getNoteAtPosition(position - 1) + previousTimeGroup = Note.getTimeGroupForTimestamp( + previousNote!!.timestamp + ) + } + if (previousTimeGroup != null && previousTimeGroup == timeGroup) { + noteViewHolder.mHeaderText.visibility = View.GONE + } else { + noteViewHolder.mHeaderText.visibility = View.VISIBLE + if (timeGroup == NoteTimeGroup.GROUP_TODAY) { + noteViewHolder.mHeaderText.setText(R.string.stats_timeframe_today) + } else if (timeGroup == NoteTimeGroup.GROUP_YESTERDAY) { + noteViewHolder.mHeaderText.setText(R.string.stats_timeframe_yesterday) + } else if (timeGroup == NoteTimeGroup.GROUP_OLDER_TWO_DAYS) { + noteViewHolder.mHeaderText.setText(R.string.older_two_days) + } else if (timeGroup == NoteTimeGroup.GROUP_OLDER_WEEK) { + noteViewHolder.mHeaderText.setText(R.string.older_last_week) + } else { + noteViewHolder.mHeaderText.setText(R.string.older_month) + } + } + + // Subject is stored in db as html to preserve text formatting + var noteSubjectSpanned: Spanned = note.getFormattedSubject(mNotificationsUtilsWrapper) + // Trim the '\n\n' added by HtmlCompat.fromHtml(...) + noteSubjectSpanned = noteSubjectSpanned.subSequence( + 0, + TextUtils.getTrimmedLength(noteSubjectSpanned) + ) as Spanned + val spans = noteSubjectSpanned.getSpans( + 0, + noteSubjectSpanned.length, + NoteBlockClickableSpan::class.java + ) + for (span in spans) { + span.enableColors(noteViewHolder.mContentView.context) + } + noteViewHolder.mTxtSubject.text = noteSubjectSpanned + val noteSubjectNoticon = note.commentSubjectNoticon + if (!TextUtils.isEmpty(noteSubjectNoticon)) { + val parent = noteViewHolder.mTxtSubject.parent + // Fix position of the subject noticon in the RtL mode + if (parent is ViewGroup) { + val textDirection = if (BidiFormatter.getInstance() + .isRtl(noteViewHolder.mTxtSubject.text) + ) ViewCompat.LAYOUT_DIRECTION_RTL else ViewCompat.LAYOUT_DIRECTION_LTR + ViewCompat.setLayoutDirection(parent, textDirection) + } + // mirror noticon in the rtl mode + if (RtlUtils.isRtl(noteViewHolder.itemView.context)) { + noteViewHolder.mTxtSubjectNoticon.scaleX = -1f + } + CommentUtils.indentTextViewFirstLine(noteViewHolder.mTxtSubject, mTextIndentSize) + noteViewHolder.mTxtSubjectNoticon.text = noteSubjectNoticon + noteViewHolder.mTxtSubjectNoticon.visibility = View.VISIBLE + } else { + noteViewHolder.mTxtSubjectNoticon.visibility = View.GONE + } + val noteSnippet = note.commentSubject + if (!TextUtils.isEmpty(noteSnippet)) { + handleMaxLines(noteViewHolder.mTxtSubject, noteViewHolder.mTxtDetail) + noteViewHolder.mTxtDetail.text = noteSnippet + noteViewHolder.mTxtDetail.visibility = View.VISIBLE + } else { + noteViewHolder.mTxtDetail.visibility = View.GONE + } + val avatarUrl = GravatarUtils.fixGravatarUrl(note.iconURL, mAvatarSz) + mImageManager!!.loadIntoCircle( + noteViewHolder.mImgAvatar, + ImageType.AVATAR_WITH_BACKGROUND, + avatarUrl + ) + if (note.isUnread) { + noteViewHolder.mUnreadNotificationView.visibility = View.VISIBLE + } else { + noteViewHolder.mUnreadNotificationView.visibility = View.GONE + } + + // request to load more comments when we near the end + if (mOnLoadMoreListener != null && position >= itemCount - 1) { + mOnLoadMoreListener.onLoadMore(note.timestamp) + } + val headerMarginTop: Int + val context = noteViewHolder.itemView.context + headerMarginTop = if (position == 0) { + context.resources + .getDimensionPixelSize(R.dimen.notifications_header_margin_top_position_0) + } else { + context.resources + .getDimensionPixelSize(R.dimen.notifications_header_margin_top_position_n) + } + val layoutParams = noteViewHolder.mHeaderText.layoutParams as MarginLayoutParams + layoutParams.topMargin = headerMarginTop + noteViewHolder.mHeaderText.layoutParams = layoutParams + } + + private fun handleMaxLines(subject: TextView, detail: TextView) { + subject.viewTreeObserver.addOnPreDrawListener(object : ViewTreeObserver.OnPreDrawListener { + override fun onPreDraw(): Boolean { + subject.viewTreeObserver.removeOnPreDrawListener(this) + if (subject.lineCount == 2) { + detail.maxLines = 1 + } else { + detail.maxLines = 2 + } + return false + } + }) + } + + fun setOnNoteClickListener(mNoteClickListener: OnNoteClickListener?) { + mOnNoteClickListener = mNoteClickListener + } + + fun cancelReloadNotesTask() { + if (mReloadNotesFromDBTask != null && mReloadNotesFromDBTask!!.status != AsyncTask.Status.FINISHED) { + mReloadNotesFromDBTask!!.cancel(true) + mReloadNotesFromDBTask = null + } + } + + fun reloadNotesFromDBAsync() { + cancelReloadNotesTask() + mReloadNotesFromDBTask = ReloadNotesFromDBTask() + mReloadNotesFromDBTask!!.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR) + } + + @Suppress("deprecation") + @SuppressLint("StaticFieldLeak") + private inner class ReloadNotesFromDBTask : AsyncTask>() { + override fun doInBackground(vararg voids: Void?): ArrayList { + return NotificationsTable.getLatestNotes() + } + + override fun onPostExecute(notes: ArrayList) { + mNotes.clear() + mNotes.addAll(notes) + myNotifyDatasetChanged() + } + } + + inner class NoteViewHolder(view: View) : RecyclerView.ViewHolder(view) { + val mContentView: View + val mHeaderText: TextView + val mTxtSubject: TextView + val mTxtSubjectNoticon: TextView + val mTxtDetail: TextView + val mImgAvatar: ImageView + val mUnreadNotificationView: View + + init { + mContentView = Objects.requireNonNull(view.findViewById(R.id.note_content_container)) + mHeaderText = Objects.requireNonNull(view.findViewById(R.id.header_text)) + mTxtSubject = Objects.requireNonNull(view.findViewById(R.id.note_subject)) + mTxtSubjectNoticon = + Objects.requireNonNull(view.findViewById(R.id.note_subject_noticon)) + mTxtDetail = Objects.requireNonNull(view.findViewById(R.id.note_detail)) + mImgAvatar = Objects.requireNonNull(view.findViewById(R.id.note_avatar)) + mUnreadNotificationView = + Objects.requireNonNull(view.findViewById(R.id.notification_unread)) + mContentView.setOnClickListener(mOnClickListener) + } + } + + private val mOnClickListener = View.OnClickListener { v -> + if (mOnNoteClickListener != null && v.tag is String) { + mOnNoteClickListener!!.onClickNote(v.tag as String) + } + } + + init { + (context.applicationContext as WordPress).component().inject(this) + mDataLoadedListener = dataLoadedListener + mOnLoadMoreListener = onLoadMoreListener + + // this is on purpose - we don't show more than a hundred or so notifications at a time so no need to set + // stable IDs. This helps prevent crashes in case a note comes with no ID (we've code checking for that + // elsewhere, but telling the RecyclerView.Adapter the notes have stable Ids and then failing to provide them + // will make things go south as in https://github.com/wordpress-mobile/WordPress-Android/issues/8741 + setHasStableIds(false) + mAvatarSz = context.resources.getDimension(R.dimen.notifications_avatar_sz).toInt() + mTextIndentSize = + context.resources.getDimensionPixelSize(R.dimen.notifications_text_indent_sz) + } + + companion object { + // Instead of building the filtered notes list dynamically, create it once and re-use it. + // Otherwise it's re-created so many times during layout. + fun buildFilteredNotesList( + filteredNotes: ArrayList, + notes: ArrayList, + filter: FILTERS + ) { + filteredNotes.clear() + if (notes.isEmpty() || filter == FILTERS.FILTER_ALL) { + filteredNotes.addAll(notes) + return + } + for (currentNote in notes) { + when (filter) { + FILTERS.FILTER_COMMENT -> if (currentNote.isCommentType) { + filteredNotes.add(currentNote) + } + + FILTERS.FILTER_FOLLOW -> if (currentNote.isFollowType) { + filteredNotes.add(currentNote) + } + + FILTERS.FILTER_UNREAD -> if (currentNote.isUnread) { + filteredNotes.add(currentNote) + } + + FILTERS.FILTER_LIKE -> if (currentNote.isLikeType) { + filteredNotes.add(currentNote) + } + } + } + } + } +} From 2a37563e84fc9c66d5c444690f3d771be0b93b16 Mon Sep 17 00:00:00 2001 From: Matthew Kevins Date: Tue, 6 Feb 2024 15:03:55 +1000 Subject: [PATCH 06/15] Fix addAll sort after Kotlin conversion --- .../wordpress/android/ui/notifications/adapters/NotesAdapter.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/WordPress/src/main/java/org/wordpress/android/ui/notifications/adapters/NotesAdapter.kt b/WordPress/src/main/java/org/wordpress/android/ui/notifications/adapters/NotesAdapter.kt index 34907a4d3e2e..84cfcd8497ee 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/notifications/adapters/NotesAdapter.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/notifications/adapters/NotesAdapter.kt @@ -89,7 +89,7 @@ class NotesAdapter( } fun addAll(notes: List, clearBeforeAdding: Boolean) { - notes.sort(TimeStampComparator()) + notes.sortedWith(TimeStampComparator()) try { if (clearBeforeAdding) { mNotes.clear() From bb2d84a591026b04a11823046b72f549350f524f Mon Sep 17 00:00:00 2001 From: Matthew Kevins Date: Tue, 6 Feb 2024 15:04:11 +1000 Subject: [PATCH 07/15] Add missing else clause to when --- .../android/ui/notifications/adapters/NotesAdapter.kt | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/WordPress/src/main/java/org/wordpress/android/ui/notifications/adapters/NotesAdapter.kt b/WordPress/src/main/java/org/wordpress/android/ui/notifications/adapters/NotesAdapter.kt index 84cfcd8497ee..6858f36ed2f3 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/notifications/adapters/NotesAdapter.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/notifications/adapters/NotesAdapter.kt @@ -338,18 +338,16 @@ class NotesAdapter( FILTERS.FILTER_COMMENT -> if (currentNote.isCommentType) { filteredNotes.add(currentNote) } - FILTERS.FILTER_FOLLOW -> if (currentNote.isFollowType) { filteredNotes.add(currentNote) } - FILTERS.FILTER_UNREAD -> if (currentNote.isUnread) { filteredNotes.add(currentNote) } - FILTERS.FILTER_LIKE -> if (currentNote.isLikeType) { filteredNotes.add(currentNote) } + else -> Unit } } } From fe0d2db3150b0db4bac14374f5d48bc33d53bc5c Mon Sep 17 00:00:00 2001 From: Matthew Kevins Date: Tue, 6 Feb 2024 15:06:06 +1000 Subject: [PATCH 08/15] Simplify null checks in viewholder init --- .../ui/notifications/adapters/NotesAdapter.kt | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/WordPress/src/main/java/org/wordpress/android/ui/notifications/adapters/NotesAdapter.kt b/WordPress/src/main/java/org/wordpress/android/ui/notifications/adapters/NotesAdapter.kt index 6858f36ed2f3..335c9e631b33 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/notifications/adapters/NotesAdapter.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/notifications/adapters/NotesAdapter.kt @@ -30,7 +30,6 @@ import org.wordpress.android.util.GravatarUtils import org.wordpress.android.util.RtlUtils import org.wordpress.android.util.image.ImageManager import org.wordpress.android.util.image.ImageType -import java.util.Objects import javax.inject.Inject class NotesAdapter( @@ -286,15 +285,13 @@ class NotesAdapter( val mUnreadNotificationView: View init { - mContentView = Objects.requireNonNull(view.findViewById(R.id.note_content_container)) - mHeaderText = Objects.requireNonNull(view.findViewById(R.id.header_text)) - mTxtSubject = Objects.requireNonNull(view.findViewById(R.id.note_subject)) - mTxtSubjectNoticon = - Objects.requireNonNull(view.findViewById(R.id.note_subject_noticon)) - mTxtDetail = Objects.requireNonNull(view.findViewById(R.id.note_detail)) - mImgAvatar = Objects.requireNonNull(view.findViewById(R.id.note_avatar)) - mUnreadNotificationView = - Objects.requireNonNull(view.findViewById(R.id.notification_unread)) + mContentView = checkNotNull(view.findViewById(R.id.note_content_container)) + mHeaderText = checkNotNull(view.findViewById(R.id.header_text)) + mTxtSubject = checkNotNull(view.findViewById(R.id.note_subject)) + mTxtSubjectNoticon = checkNotNull(view.findViewById(R.id.note_subject_noticon)) + mTxtDetail = checkNotNull(view.findViewById(R.id.note_detail)) + mImgAvatar = checkNotNull(view.findViewById(R.id.note_avatar)) + mUnreadNotificationView = checkNotNull(view.findViewById(R.id.notification_unread)) mContentView.setOnClickListener(mOnClickListener) } } From 818a70bf107a19e603c5e1d241e6ed3522ade747 Mon Sep 17 00:00:00 2001 From: Matthew Kevins Date: Tue, 6 Feb 2024 15:31:25 +1000 Subject: [PATCH 09/15] Remove else from exhaustive when --- .../wordpress/android/ui/notifications/adapters/NotesAdapter.kt | 1 - 1 file changed, 1 deletion(-) diff --git a/WordPress/src/main/java/org/wordpress/android/ui/notifications/adapters/NotesAdapter.kt b/WordPress/src/main/java/org/wordpress/android/ui/notifications/adapters/NotesAdapter.kt index 335c9e631b33..5649ec63d438 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/notifications/adapters/NotesAdapter.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/notifications/adapters/NotesAdapter.kt @@ -65,7 +65,6 @@ class NotesAdapter( FILTER_FOLLOW -> "follow" FILTER_LIKE -> "like" FILTER_UNREAD -> "unread" - else -> "all" } } } From b365498e169b0abba49aea2f843961cd49e96f6d Mon Sep 17 00:00:00 2001 From: Matthew Kevins Date: Tue, 6 Feb 2024 15:31:44 +1000 Subject: [PATCH 10/15] Mark notes adapter as deprecated at the file level We need to mark this to avoid warnings as errors, but we will be refactoring the deprecated usages in later stages. --- .../android/ui/notifications/adapters/NotesAdapter.kt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/WordPress/src/main/java/org/wordpress/android/ui/notifications/adapters/NotesAdapter.kt b/WordPress/src/main/java/org/wordpress/android/ui/notifications/adapters/NotesAdapter.kt index 5649ec63d438..0857e0e8874d 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/notifications/adapters/NotesAdapter.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/notifications/adapters/NotesAdapter.kt @@ -1,3 +1,5 @@ +@file:Suppress("DEPRECATION") + package org.wordpress.android.ui.notifications.adapters import android.annotation.SuppressLint @@ -260,7 +262,6 @@ class NotesAdapter( mReloadNotesFromDBTask!!.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR) } - @Suppress("deprecation") @SuppressLint("StaticFieldLeak") private inner class ReloadNotesFromDBTask : AsyncTask>() { override fun doInBackground(vararg voids: Void?): ArrayList { From a48cc3bb36a84b927f0df9472667866928f1fe4b Mon Sep 17 00:00:00 2001 From: Matthew Kevins Date: Tue, 6 Feb 2024 15:32:37 +1000 Subject: [PATCH 11/15] Fix reference to buildFilteredNotesList This was a static helper method, and is now a companion function. --- .../android/ui/notifications/NotificationsDetailActivity.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/WordPress/src/main/java/org/wordpress/android/ui/notifications/NotificationsDetailActivity.java b/WordPress/src/main/java/org/wordpress/android/ui/notifications/NotificationsDetailActivity.java index 0dc9e1d6f0da..18cbb239c3df 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/notifications/NotificationsDetailActivity.java +++ b/WordPress/src/main/java/org/wordpress/android/ui/notifications/NotificationsDetailActivity.java @@ -382,7 +382,7 @@ private NotificationDetailFragmentAdapter buildNoteListAdapterAndSetPosition(Not ArrayList filteredNotes = new ArrayList<>(); // apply filter to the list so we show the same items that the list show vertically, but horizontally - NotesAdapter.buildFilteredNotesList(filteredNotes, notes, filter); + NotesAdapter.Companion.buildFilteredNotesList(filteredNotes, notes, filter); adapter = new NotificationDetailFragmentAdapter(getSupportFragmentManager(), filteredNotes); if (mBinding != null) { From f2400599e7146be54419e5da744f09b6ac5df97d Mon Sep 17 00:00:00 2001 From: Matthew Kevins Date: Tue, 6 Feb 2024 16:08:33 +1000 Subject: [PATCH 12/15] Suppress lint issues in notes adapter --- .../wordpress/android/ui/notifications/adapters/NotesAdapter.kt | 1 + 1 file changed, 1 insertion(+) diff --git a/WordPress/src/main/java/org/wordpress/android/ui/notifications/adapters/NotesAdapter.kt b/WordPress/src/main/java/org/wordpress/android/ui/notifications/adapters/NotesAdapter.kt index 0857e0e8874d..6b4103bd5719 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/notifications/adapters/NotesAdapter.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/notifications/adapters/NotesAdapter.kt @@ -127,6 +127,7 @@ class NotesAdapter( return mFilteredNotes.size } + @Suppress("CyclomaticComplexMethod", "LongMethod") override fun onBindViewHolder(noteViewHolder: NoteViewHolder, position: Int) { val note = getNoteAtPosition(position) ?: return noteViewHolder.mContentView.tag = note.id From 3c02ee26c7a37bce587b7e04f0a0a38022b5208b Mon Sep 17 00:00:00 2001 From: Matthew Kevins Date: Tue, 6 Feb 2024 17:46:16 +1000 Subject: [PATCH 13/15] Rename notes adapter members --- .../ui/notifications/adapters/NotesAdapter.kt | 156 +++++++++--------- 1 file changed, 78 insertions(+), 78 deletions(-) diff --git a/WordPress/src/main/java/org/wordpress/android/ui/notifications/adapters/NotesAdapter.kt b/WordPress/src/main/java/org/wordpress/android/ui/notifications/adapters/NotesAdapter.kt index 6b4103bd5719..9582d6ca4cdc 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/notifications/adapters/NotesAdapter.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/notifications/adapters/NotesAdapter.kt @@ -38,20 +38,20 @@ class NotesAdapter( context: Context, dataLoadedListener: DataLoadedListener, onLoadMoreListener: OnLoadMoreListener? ) : RecyclerView.Adapter() { - private val mAvatarSz: Int - private val mTextIndentSize: Int - private val mDataLoadedListener: DataLoadedListener - private val mOnLoadMoreListener: OnLoadMoreListener? - private val mNotes = ArrayList() - private val mFilteredNotes = ArrayList() + private val avatarSize: Int + private val textIndentSize: Int + private val dataLoadedListener: DataLoadedListener + private val onLoadMoreListener: OnLoadMoreListener? + private val notes = ArrayList() + private val filteredNotes = ArrayList() @JvmField @Inject - var mImageManager: ImageManager? = null + var imageManager: ImageManager? = null @JvmField @Inject - var mNotificationsUtilsWrapper: NotificationsUtilsWrapper? = null + var notificationsUtilsWrapper: NotificationsUtilsWrapper? = null enum class FILTERS { FILTER_ALL, @@ -73,7 +73,7 @@ class NotesAdapter( var currentFilter = FILTERS.FILTER_ALL private set - private var mReloadNotesFromDBTask: ReloadNotesFromDBTask? = null + private var reloadNotesFromDBTask: ReloadNotesFromDBTask? = null interface DataLoadedListener { fun onDataLoaded(itemsCount: Int) @@ -83,7 +83,7 @@ class NotesAdapter( fun onLoadMore(timestamp: Long) } - private var mOnNoteClickListener: OnNoteClickListener? = null + private var onNoteClickListener: OnNoteClickListener? = null fun setFilter(newFilter: FILTERS) { currentFilter = newFilter } @@ -92,9 +92,9 @@ class NotesAdapter( notes.sortedWith(TimeStampComparator()) try { if (clearBeforeAdding) { - mNotes.clear() + this.notes.clear() } - mNotes.addAll(notes) + this.notes.addAll(notes) } finally { myNotifyDatasetChanged() } @@ -102,9 +102,9 @@ class NotesAdapter( @SuppressLint("NotifyDataSetChanged") private fun myNotifyDatasetChanged() { - buildFilteredNotesList(mFilteredNotes, mNotes, currentFilter) + buildFilteredNotesList(filteredNotes, notes, currentFilter) notifyDataSetChanged() - mDataLoadedListener.onDataLoaded(itemCount) + dataLoadedListener.onDataLoaded(itemCount) } override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): NoteViewHolder { @@ -115,22 +115,22 @@ class NotesAdapter( private fun getNoteAtPosition(position: Int): Note? { return if (isValidPosition(position)) { - mFilteredNotes[position] + filteredNotes[position] } else null } private fun isValidPosition(position: Int): Boolean { - return position >= 0 && position < mFilteredNotes.size + return position >= 0 && position < filteredNotes.size } override fun getItemCount(): Int { - return mFilteredNotes.size + return filteredNotes.size } @Suppress("CyclomaticComplexMethod", "LongMethod") override fun onBindViewHolder(noteViewHolder: NoteViewHolder, position: Int) { val note = getNoteAtPosition(position) ?: return - noteViewHolder.mContentView.tag = note.id + noteViewHolder.contentView.tag = note.id // Display group header val timeGroup = Note.getTimeGroupForTimestamp(note.timestamp) @@ -142,24 +142,24 @@ class NotesAdapter( ) } if (previousTimeGroup != null && previousTimeGroup == timeGroup) { - noteViewHolder.mHeaderText.visibility = View.GONE + noteViewHolder.headerText.visibility = View.GONE } else { - noteViewHolder.mHeaderText.visibility = View.VISIBLE + noteViewHolder.headerText.visibility = View.VISIBLE if (timeGroup == NoteTimeGroup.GROUP_TODAY) { - noteViewHolder.mHeaderText.setText(R.string.stats_timeframe_today) + noteViewHolder.headerText.setText(R.string.stats_timeframe_today) } else if (timeGroup == NoteTimeGroup.GROUP_YESTERDAY) { - noteViewHolder.mHeaderText.setText(R.string.stats_timeframe_yesterday) + noteViewHolder.headerText.setText(R.string.stats_timeframe_yesterday) } else if (timeGroup == NoteTimeGroup.GROUP_OLDER_TWO_DAYS) { - noteViewHolder.mHeaderText.setText(R.string.older_two_days) + noteViewHolder.headerText.setText(R.string.older_two_days) } else if (timeGroup == NoteTimeGroup.GROUP_OLDER_WEEK) { - noteViewHolder.mHeaderText.setText(R.string.older_last_week) + noteViewHolder.headerText.setText(R.string.older_last_week) } else { - noteViewHolder.mHeaderText.setText(R.string.older_month) + noteViewHolder.headerText.setText(R.string.older_month) } } // Subject is stored in db as html to preserve text formatting - var noteSubjectSpanned: Spanned = note.getFormattedSubject(mNotificationsUtilsWrapper) + var noteSubjectSpanned: Spanned = note.getFormattedSubject(notificationsUtilsWrapper) // Trim the '\n\n' added by HtmlCompat.fromHtml(...) noteSubjectSpanned = noteSubjectSpanned.subSequence( 0, @@ -171,52 +171,52 @@ class NotesAdapter( NoteBlockClickableSpan::class.java ) for (span in spans) { - span.enableColors(noteViewHolder.mContentView.context) + span.enableColors(noteViewHolder.contentView.context) } - noteViewHolder.mTxtSubject.text = noteSubjectSpanned + noteViewHolder.textSubject.text = noteSubjectSpanned val noteSubjectNoticon = note.commentSubjectNoticon if (!TextUtils.isEmpty(noteSubjectNoticon)) { - val parent = noteViewHolder.mTxtSubject.parent + val parent = noteViewHolder.textSubject.parent // Fix position of the subject noticon in the RtL mode if (parent is ViewGroup) { val textDirection = if (BidiFormatter.getInstance() - .isRtl(noteViewHolder.mTxtSubject.text) + .isRtl(noteViewHolder.textSubject.text) ) ViewCompat.LAYOUT_DIRECTION_RTL else ViewCompat.LAYOUT_DIRECTION_LTR ViewCompat.setLayoutDirection(parent, textDirection) } // mirror noticon in the rtl mode if (RtlUtils.isRtl(noteViewHolder.itemView.context)) { - noteViewHolder.mTxtSubjectNoticon.scaleX = -1f + noteViewHolder.textSubjectNoticon.scaleX = -1f } - CommentUtils.indentTextViewFirstLine(noteViewHolder.mTxtSubject, mTextIndentSize) - noteViewHolder.mTxtSubjectNoticon.text = noteSubjectNoticon - noteViewHolder.mTxtSubjectNoticon.visibility = View.VISIBLE + CommentUtils.indentTextViewFirstLine(noteViewHolder.textSubject, textIndentSize) + noteViewHolder.textSubjectNoticon.text = noteSubjectNoticon + noteViewHolder.textSubjectNoticon.visibility = View.VISIBLE } else { - noteViewHolder.mTxtSubjectNoticon.visibility = View.GONE + noteViewHolder.textSubjectNoticon.visibility = View.GONE } val noteSnippet = note.commentSubject if (!TextUtils.isEmpty(noteSnippet)) { - handleMaxLines(noteViewHolder.mTxtSubject, noteViewHolder.mTxtDetail) - noteViewHolder.mTxtDetail.text = noteSnippet - noteViewHolder.mTxtDetail.visibility = View.VISIBLE + handleMaxLines(noteViewHolder.textSubject, noteViewHolder.textDetail) + noteViewHolder.textDetail.text = noteSnippet + noteViewHolder.textDetail.visibility = View.VISIBLE } else { - noteViewHolder.mTxtDetail.visibility = View.GONE + noteViewHolder.textDetail.visibility = View.GONE } - val avatarUrl = GravatarUtils.fixGravatarUrl(note.iconURL, mAvatarSz) - mImageManager!!.loadIntoCircle( - noteViewHolder.mImgAvatar, + val avatarUrl = GravatarUtils.fixGravatarUrl(note.iconURL, avatarSize) + imageManager!!.loadIntoCircle( + noteViewHolder.imageAvatar, ImageType.AVATAR_WITH_BACKGROUND, avatarUrl ) if (note.isUnread) { - noteViewHolder.mUnreadNotificationView.visibility = View.VISIBLE + noteViewHolder.unreadNotificationView.visibility = View.VISIBLE } else { - noteViewHolder.mUnreadNotificationView.visibility = View.GONE + noteViewHolder.unreadNotificationView.visibility = View.GONE } // request to load more comments when we near the end - if (mOnLoadMoreListener != null && position >= itemCount - 1) { - mOnLoadMoreListener.onLoadMore(note.timestamp) + if (onLoadMoreListener != null && position >= itemCount - 1) { + onLoadMoreListener.onLoadMore(note.timestamp) } val headerMarginTop: Int val context = noteViewHolder.itemView.context @@ -227,9 +227,9 @@ class NotesAdapter( context.resources .getDimensionPixelSize(R.dimen.notifications_header_margin_top_position_n) } - val layoutParams = noteViewHolder.mHeaderText.layoutParams as MarginLayoutParams + val layoutParams = noteViewHolder.headerText.layoutParams as MarginLayoutParams layoutParams.topMargin = headerMarginTop - noteViewHolder.mHeaderText.layoutParams = layoutParams + noteViewHolder.headerText.layoutParams = layoutParams } private fun handleMaxLines(subject: TextView, detail: TextView) { @@ -247,20 +247,20 @@ class NotesAdapter( } fun setOnNoteClickListener(mNoteClickListener: OnNoteClickListener?) { - mOnNoteClickListener = mNoteClickListener + onNoteClickListener = mNoteClickListener } fun cancelReloadNotesTask() { - if (mReloadNotesFromDBTask != null && mReloadNotesFromDBTask!!.status != AsyncTask.Status.FINISHED) { - mReloadNotesFromDBTask!!.cancel(true) - mReloadNotesFromDBTask = null + if (reloadNotesFromDBTask != null && reloadNotesFromDBTask!!.status != AsyncTask.Status.FINISHED) { + reloadNotesFromDBTask!!.cancel(true) + reloadNotesFromDBTask = null } } fun reloadNotesFromDBAsync() { cancelReloadNotesTask() - mReloadNotesFromDBTask = ReloadNotesFromDBTask() - mReloadNotesFromDBTask!!.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR) + reloadNotesFromDBTask = ReloadNotesFromDBTask() + reloadNotesFromDBTask!!.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR) } @SuppressLint("StaticFieldLeak") @@ -270,51 +270,51 @@ class NotesAdapter( } override fun onPostExecute(notes: ArrayList) { - mNotes.clear() - mNotes.addAll(notes) + this@NotesAdapter.notes.clear() + this@NotesAdapter.notes.addAll(notes) myNotifyDatasetChanged() } } inner class NoteViewHolder(view: View) : RecyclerView.ViewHolder(view) { - val mContentView: View - val mHeaderText: TextView - val mTxtSubject: TextView - val mTxtSubjectNoticon: TextView - val mTxtDetail: TextView - val mImgAvatar: ImageView - val mUnreadNotificationView: View + val contentView: View + val headerText: TextView + val textSubject: TextView + val textSubjectNoticon: TextView + val textDetail: TextView + val imageAvatar: ImageView + val unreadNotificationView: View init { - mContentView = checkNotNull(view.findViewById(R.id.note_content_container)) - mHeaderText = checkNotNull(view.findViewById(R.id.header_text)) - mTxtSubject = checkNotNull(view.findViewById(R.id.note_subject)) - mTxtSubjectNoticon = checkNotNull(view.findViewById(R.id.note_subject_noticon)) - mTxtDetail = checkNotNull(view.findViewById(R.id.note_detail)) - mImgAvatar = checkNotNull(view.findViewById(R.id.note_avatar)) - mUnreadNotificationView = checkNotNull(view.findViewById(R.id.notification_unread)) - mContentView.setOnClickListener(mOnClickListener) + contentView = checkNotNull(view.findViewById(R.id.note_content_container)) + headerText = checkNotNull(view.findViewById(R.id.header_text)) + textSubject = checkNotNull(view.findViewById(R.id.note_subject)) + textSubjectNoticon = checkNotNull(view.findViewById(R.id.note_subject_noticon)) + textDetail = checkNotNull(view.findViewById(R.id.note_detail)) + imageAvatar = checkNotNull(view.findViewById(R.id.note_avatar)) + unreadNotificationView = checkNotNull(view.findViewById(R.id.notification_unread)) + contentView.setOnClickListener(onClickListener) } } - private val mOnClickListener = View.OnClickListener { v -> - if (mOnNoteClickListener != null && v.tag is String) { - mOnNoteClickListener!!.onClickNote(v.tag as String) + private val onClickListener = View.OnClickListener { view -> + if (onNoteClickListener != null && view.tag is String) { + onNoteClickListener!!.onClickNote(view.tag as String) } } init { (context.applicationContext as WordPress).component().inject(this) - mDataLoadedListener = dataLoadedListener - mOnLoadMoreListener = onLoadMoreListener + this.dataLoadedListener = dataLoadedListener + this.onLoadMoreListener = onLoadMoreListener // this is on purpose - we don't show more than a hundred or so notifications at a time so no need to set // stable IDs. This helps prevent crashes in case a note comes with no ID (we've code checking for that // elsewhere, but telling the RecyclerView.Adapter the notes have stable Ids and then failing to provide them // will make things go south as in https://github.com/wordpress-mobile/WordPress-Android/issues/8741 setHasStableIds(false) - mAvatarSz = context.resources.getDimension(R.dimen.notifications_avatar_sz).toInt() - mTextIndentSize = + avatarSize = context.resources.getDimension(R.dimen.notifications_avatar_sz).toInt() + textIndentSize = context.resources.getDimensionPixelSize(R.dimen.notifications_text_indent_sz) } From 1a729ca46839c8ddba432221a50e2c9597f006c9 Mon Sep 17 00:00:00 2001 From: Matthew Kevins Date: Tue, 6 Feb 2024 18:29:07 +1000 Subject: [PATCH 14/15] Update Kotlin code to clean up else ifs --- .../ui/notifications/adapters/NotesAdapter.kt | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/WordPress/src/main/java/org/wordpress/android/ui/notifications/adapters/NotesAdapter.kt b/WordPress/src/main/java/org/wordpress/android/ui/notifications/adapters/NotesAdapter.kt index 9582d6ca4cdc..55b233fad93f 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/notifications/adapters/NotesAdapter.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/notifications/adapters/NotesAdapter.kt @@ -141,20 +141,20 @@ class NotesAdapter( previousNote!!.timestamp ) } - if (previousTimeGroup != null && previousTimeGroup == timeGroup) { + if (previousTimeGroup?.let { it == timeGroup } == true) { noteViewHolder.headerText.visibility = View.GONE } else { noteViewHolder.headerText.visibility = View.VISIBLE - if (timeGroup == NoteTimeGroup.GROUP_TODAY) { - noteViewHolder.headerText.setText(R.string.stats_timeframe_today) - } else if (timeGroup == NoteTimeGroup.GROUP_YESTERDAY) { - noteViewHolder.headerText.setText(R.string.stats_timeframe_yesterday) - } else if (timeGroup == NoteTimeGroup.GROUP_OLDER_TWO_DAYS) { - noteViewHolder.headerText.setText(R.string.older_two_days) - } else if (timeGroup == NoteTimeGroup.GROUP_OLDER_WEEK) { - noteViewHolder.headerText.setText(R.string.older_last_week) - } else { - noteViewHolder.headerText.setText(R.string.older_month) + timeGroup?.let { + noteViewHolder.headerText.setText( + when (it) { + NoteTimeGroup.GROUP_TODAY -> R.string.stats_timeframe_today + NoteTimeGroup.GROUP_YESTERDAY -> R.string.stats_timeframe_yesterday + NoteTimeGroup.GROUP_OLDER_TWO_DAYS -> R.string.older_two_days + NoteTimeGroup.GROUP_OLDER_WEEK -> R.string.older_last_week + NoteTimeGroup.GROUP_OLDER_MONTH -> R.string.older_month + } + ) } } From d2c57d351a1a99b513e77eb399d357e70d41f861 Mon Sep 17 00:00:00 2001 From: Matthew Kevins Date: Tue, 6 Feb 2024 18:30:43 +1000 Subject: [PATCH 15/15] Simplify isUnread logic for note visibility --- .../android/ui/notifications/adapters/NotesAdapter.kt | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/WordPress/src/main/java/org/wordpress/android/ui/notifications/adapters/NotesAdapter.kt b/WordPress/src/main/java/org/wordpress/android/ui/notifications/adapters/NotesAdapter.kt index 55b233fad93f..905e5275c57a 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/notifications/adapters/NotesAdapter.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/notifications/adapters/NotesAdapter.kt @@ -16,6 +16,7 @@ import android.widget.ImageView import android.widget.TextView import androidx.core.text.BidiFormatter import androidx.core.view.ViewCompat +import androidx.core.view.isVisible import androidx.recyclerview.widget.RecyclerView import org.wordpress.android.R import org.wordpress.android.WordPress @@ -208,11 +209,7 @@ class NotesAdapter( ImageType.AVATAR_WITH_BACKGROUND, avatarUrl ) - if (note.isUnread) { - noteViewHolder.unreadNotificationView.visibility = View.VISIBLE - } else { - noteViewHolder.unreadNotificationView.visibility = View.GONE - } + noteViewHolder.unreadNotificationView.isVisible = note.isUnread // request to load more comments when we near the end if (onLoadMoreListener != null && position >= itemCount - 1) {