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) { 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 3ab7e7f1e6a5..000000000000 --- a/WordPress/src/main/java/org/wordpress/android/ui/notifications/adapters/NotesAdapter.java +++ /dev/null @@ -1,404 +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.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.fluxc.model.CommentStatus; -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.Collections; -import java.util.List; - -import javax.inject.Inject; - -public class NotesAdapter extends RecyclerView.Adapter { - private final int mAvatarSz; - private final int mTextIndentSize; - - private final DataLoadedListener mDataLoadedListener; - 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"; - } - } - } - - private FILTERS mCurrentFilter = FILTERS.FILTER_ALL; - private ReloadNotesFromDBTask mReloadNotesFromDBTask; - - public interface DataLoadedListener { - void onDataLoaded(int itemsCount); - } - - public interface OnLoadMoreListener { - void onLoadMore(long timestamp); - } - - private OnNoteClickListener mOnNoteClickListener; - - public NotesAdapter(Context context, DataLoadedListener dataLoadedListener, 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(FILTERS newFilter) { - mCurrentFilter = newFilter; - } - - public FILTERS getCurrentFilter() { - return mCurrentFilter; - } - - public void addAll(List notes, boolean clearBeforeAdding) { - Collections.sort(notes, new Note.TimeStampComparator()); - try { - if (clearBeforeAdding) { - mNotes.clear(); - } - mNotes.addAll(notes); - } finally { - myNotifyDatasetChanged(); - } - } - - private void myNotifyDatasetChanged() { - buildFilteredNotesList(mFilteredNotes, mNotes, mCurrentFilter); - notifyDataSetChanged(); - if (mDataLoadedListener != null) { - mDataLoadedListener.onDataLoaded(getItemCount()); - } - } - - @Override - public NoteViewHolder onCreateViewHolder(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; - } - } - } - - 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(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); - } - } - - 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(...) - 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(final TextView subject, 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; - } - }); - } - - 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; - } - - public void cancelReloadNotesTask() { - if (mReloadNotesFromDBTask != null && mReloadNotesFromDBTask.getStatus() != Status.FINISHED) { - mReloadNotesFromDBTask.cancel(true); - mReloadNotesFromDBTask = null; - } - } - - @SuppressWarnings("deprecation") - public void reloadNotesFromDBAsync() { - cancelReloadNotesTask(); - mReloadNotesFromDBTask = new ReloadNotesFromDBTask(); - mReloadNotesFromDBTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); - } - - @SuppressWarnings("deprecation") - @SuppressLint("StaticFieldLeak") - private class ReloadNotesFromDBTask extends AsyncTask> { - @Override - protected ArrayList doInBackground(Void... voids) { - return NotificationsTable.getLatestNotes(); - } - - @Override - protected void onPostExecute(ArrayList notes) { - mNotes.clear(); - mNotes.addAll(notes); - myNotifyDatasetChanged(); - } - } - - 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) { - 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.setOnClickListener(mOnClickListener); - } - } - - private final View.OnClickListener mOnClickListener = new View.OnClickListener() { - @Override - public void onClick(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..905e5275c57a --- /dev/null +++ b/WordPress/src/main/java/org/wordpress/android/ui/notifications/adapters/NotesAdapter.kt @@ -0,0 +1,350 @@ +@file:Suppress("DEPRECATION") + +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.core.view.isVisible +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 javax.inject.Inject + +class NotesAdapter( + context: Context, dataLoadedListener: DataLoadedListener, + onLoadMoreListener: OnLoadMoreListener? +) : RecyclerView.Adapter() { + 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 imageManager: ImageManager? = null + + @JvmField + @Inject + var notificationsUtilsWrapper: 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" + } + } + } + + var currentFilter = FILTERS.FILTER_ALL + private set + private var reloadNotesFromDBTask: ReloadNotesFromDBTask? = null + + interface DataLoadedListener { + fun onDataLoaded(itemsCount: Int) + } + + interface OnLoadMoreListener { + fun onLoadMore(timestamp: Long) + } + + private var onNoteClickListener: OnNoteClickListener? = null + fun setFilter(newFilter: FILTERS) { + currentFilter = newFilter + } + + fun addAll(notes: List, clearBeforeAdding: Boolean) { + notes.sortedWith(TimeStampComparator()) + try { + if (clearBeforeAdding) { + this.notes.clear() + } + this.notes.addAll(notes) + } finally { + myNotifyDatasetChanged() + } + } + + @SuppressLint("NotifyDataSetChanged") + private fun myNotifyDatasetChanged() { + buildFilteredNotesList(filteredNotes, notes, currentFilter) + notifyDataSetChanged() + dataLoadedListener.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)) { + filteredNotes[position] + } else null + } + + private fun isValidPosition(position: Int): Boolean { + return position >= 0 && position < filteredNotes.size + } + + override fun getItemCount(): Int { + return filteredNotes.size + } + + @Suppress("CyclomaticComplexMethod", "LongMethod") + override fun onBindViewHolder(noteViewHolder: NoteViewHolder, position: Int) { + val note = getNoteAtPosition(position) ?: return + noteViewHolder.contentView.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?.let { it == timeGroup } == true) { + noteViewHolder.headerText.visibility = View.GONE + } else { + noteViewHolder.headerText.visibility = View.VISIBLE + 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 + } + ) + } + } + + // Subject is stored in db as html to preserve text formatting + var noteSubjectSpanned: Spanned = note.getFormattedSubject(notificationsUtilsWrapper) + // 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.contentView.context) + } + noteViewHolder.textSubject.text = noteSubjectSpanned + val noteSubjectNoticon = note.commentSubjectNoticon + if (!TextUtils.isEmpty(noteSubjectNoticon)) { + 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.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.textSubjectNoticon.scaleX = -1f + } + CommentUtils.indentTextViewFirstLine(noteViewHolder.textSubject, textIndentSize) + noteViewHolder.textSubjectNoticon.text = noteSubjectNoticon + noteViewHolder.textSubjectNoticon.visibility = View.VISIBLE + } else { + noteViewHolder.textSubjectNoticon.visibility = View.GONE + } + val noteSnippet = note.commentSubject + if (!TextUtils.isEmpty(noteSnippet)) { + handleMaxLines(noteViewHolder.textSubject, noteViewHolder.textDetail) + noteViewHolder.textDetail.text = noteSnippet + noteViewHolder.textDetail.visibility = View.VISIBLE + } else { + noteViewHolder.textDetail.visibility = View.GONE + } + val avatarUrl = GravatarUtils.fixGravatarUrl(note.iconURL, avatarSize) + imageManager!!.loadIntoCircle( + noteViewHolder.imageAvatar, + ImageType.AVATAR_WITH_BACKGROUND, + avatarUrl + ) + noteViewHolder.unreadNotificationView.isVisible = note.isUnread + + // request to load more comments when we near the end + if (onLoadMoreListener != null && position >= itemCount - 1) { + onLoadMoreListener.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.headerText.layoutParams as MarginLayoutParams + layoutParams.topMargin = headerMarginTop + noteViewHolder.headerText.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?) { + onNoteClickListener = mNoteClickListener + } + + fun cancelReloadNotesTask() { + if (reloadNotesFromDBTask != null && reloadNotesFromDBTask!!.status != AsyncTask.Status.FINISHED) { + reloadNotesFromDBTask!!.cancel(true) + reloadNotesFromDBTask = null + } + } + + fun reloadNotesFromDBAsync() { + cancelReloadNotesTask() + reloadNotesFromDBTask = ReloadNotesFromDBTask() + reloadNotesFromDBTask!!.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR) + } + + @SuppressLint("StaticFieldLeak") + private inner class ReloadNotesFromDBTask : AsyncTask>() { + override fun doInBackground(vararg voids: Void?): ArrayList { + return NotificationsTable.getLatestNotes() + } + + override fun onPostExecute(notes: ArrayList) { + this@NotesAdapter.notes.clear() + this@NotesAdapter.notes.addAll(notes) + myNotifyDatasetChanged() + } + } + + inner class NoteViewHolder(view: View) : RecyclerView.ViewHolder(view) { + val contentView: View + val headerText: TextView + val textSubject: TextView + val textSubjectNoticon: TextView + val textDetail: TextView + val imageAvatar: ImageView + val unreadNotificationView: View + + init { + 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 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) + 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) + avatarSize = context.resources.getDimension(R.dimen.notifications_avatar_sz).toInt() + textIndentSize = + 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) + } + else -> Unit + } + } + } + } +}