package org.wordpress.android.ui.notifications.adapters; import android.content.Context; import android.os.AsyncTask; import android.support.v7.widget.RecyclerView; import android.text.TextUtils; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.TextView; import org.wordpress.android.R; 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.NotificationsListFragment; import org.wordpress.android.util.GravatarUtils; import org.wordpress.android.widgets.NoticonTextView; import org.wordpress.android.widgets.WPNetworkImageView; import java.util.ArrayList; import java.util.Collections; import java.util.List; public class NotesAdapter extends RecyclerView.Adapter<NotesAdapter.NoteViewHolder> { private final int mAvatarSz; private final int mColorRead; private final int mColorUnread; private final int mTextIndentSize; private final List<String> mHiddenNoteIds = new ArrayList<>(); private final List<String> mModeratingNoteIds = new ArrayList<>(); private final DataLoadedListener mDataLoadedListener; private final OnLoadMoreListener mOnLoadMoreListener; private final ArrayList<Note> mNotes = new ArrayList<>(); private final ArrayList<Note> mFilteredNotes = new ArrayList<>(); public enum FILTERS { FILTER_ALL, FILTER_LIKE, FILTER_COMMENT, FILTER_UNREAD, FILTER_FOLLOW } private FILTERS mCurrentFilter = FILTERS.FILTER_ALL; public interface DataLoadedListener { void onDataLoaded(int itemsCount); } public interface OnLoadMoreListener { void onLoadMore(long timestamp); } private NotificationsListFragment.OnNoteClickListener mOnNoteClickListener; public NotesAdapter(Context context, DataLoadedListener dataLoadedListener, OnLoadMoreListener onLoadMoreListener) { super(); mDataLoadedListener = dataLoadedListener; mOnLoadMoreListener = onLoadMoreListener; setHasStableIds(true); mAvatarSz = (int) context.getResources().getDimension(R.dimen.notifications_avatar_sz); mColorRead = context.getResources().getColor(R.color.white); mColorUnread = context.getResources().getColor(R.color.grey_light); mTextIndentSize = context.getResources().getDimensionPixelSize(R.dimen.notifications_text_indent_sz); } public void setFilter(FILTERS newFilter) { mCurrentFilter = newFilter; myNotifyDatasetChanged(); } public FILTERS getCurrentFilter() { return mCurrentFilter; } public void addHiddenNoteId(String noteId) { mHiddenNoteIds.add(noteId); myNotifyDatasetChanged(); } public void removeHiddenNoteId(String noteId) { mHiddenNoteIds.remove(noteId); myNotifyDatasetChanged(); } public void addModeratingNoteId(String noteId) { mModeratingNoteIds.add(noteId); myNotifyDatasetChanged(); } public void removeModeratingNoteId(String noteId) { mModeratingNoteIds.remove(noteId); // Reload the notifications from DB since the state of at least one of them is changed. // DB already has the fresh value in it. reloadNotesFromDBAsync(); } public void addAll(List<Note> 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 filterd notes list dinamically, create it once and re-use it. // Otherwise it's re-created so many times during layout. public static void buildFilteredNotesList(ArrayList<Note> filteredNotes, ArrayList<Note> 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; } public void replaceNote(Note newNote) { if (newNote != null) { int position = getPositionForNoteUnfiltered(newNote.getId()); if (position != RecyclerView.NO_POSITION && position < mNotes.size()) { mNotes.set(position, newNote); } } } private boolean isValidPosition(int position) { return (position >= 0 && position < mFilteredNotes.size()); } @Override public int getItemCount() { return mFilteredNotes.size(); } @Override public long getItemId(int position) { Note note = getNoteAtPosition(position); if (note == null) { return 0; } return Long.valueOf(note.getId()); } @Override public void onBindViewHolder(NoteViewHolder noteViewHolder, int position) { final Note note = getNoteAtPosition(position); if (note == null) { return; } noteViewHolder.itemView.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.headerView.setVisibility(View.GONE); } else { if (timeGroup == Note.NoteTimeGroup.GROUP_TODAY) { noteViewHolder.headerText.setText(R.string.stats_timeframe_today); } else if (timeGroup == Note.NoteTimeGroup.GROUP_YESTERDAY) { noteViewHolder.headerText.setText(R.string.stats_timeframe_yesterday); } else if (timeGroup == Note.NoteTimeGroup.GROUP_OLDER_TWO_DAYS) { noteViewHolder.headerText.setText(R.string.older_two_days); } else if (timeGroup == Note.NoteTimeGroup.GROUP_OLDER_WEEK) { noteViewHolder.headerText.setText(R.string.older_last_week); } else { noteViewHolder.headerText.setText(R.string.older_month); } noteViewHolder.headerView.setVisibility(View.VISIBLE); } if (mHiddenNoteIds.size() > 0 && mHiddenNoteIds.contains(note.getId())) { noteViewHolder.contentView.setVisibility(View.GONE); noteViewHolder.headerView.setVisibility(View.GONE); } else { noteViewHolder.contentView.setVisibility(View.VISIBLE); } CommentStatus commentStatus = CommentStatus.ALL; if (note.getCommentStatus() == CommentStatus.UNAPPROVED) { commentStatus = CommentStatus.UNAPPROVED; } if (!TextUtils.isEmpty(note.getLocalStatus())) { commentStatus = CommentStatus.fromString(note.getLocalStatus()); } if (mModeratingNoteIds.size() > 0 && mModeratingNoteIds.contains(note.getId())) { noteViewHolder.progressBar.setVisibility(View.VISIBLE); } else { noteViewHolder.progressBar.setVisibility(View.GONE); } // Subject is stored in db as html to preserve text formatting CharSequence noteSubjectSpanned = note.getFormattedSubject(); // Trim the '\n\n' added by Html.fromHtml() noteSubjectSpanned = noteSubjectSpanned.subSequence(0, TextUtils.getTrimmedLength(noteSubjectSpanned)); noteViewHolder.txtSubject.setText(noteSubjectSpanned); String noteSubjectNoticon = note.getCommentSubjectNoticon(); if (!TextUtils.isEmpty(noteSubjectNoticon)) { CommentUtils.indentTextViewFirstLine(noteViewHolder.txtSubject, mTextIndentSize); noteViewHolder.txtSubjectNoticon.setText(noteSubjectNoticon); noteViewHolder.txtSubjectNoticon.setVisibility(View.VISIBLE); } else { noteViewHolder.txtSubjectNoticon.setVisibility(View.GONE); } String noteSnippet = note.getCommentSubject(); if (!TextUtils.isEmpty(noteSnippet)) { noteViewHolder.txtSubject.setMaxLines(2); noteViewHolder.txtDetail.setText(noteSnippet); noteViewHolder.txtDetail.setVisibility(View.VISIBLE); } else { noteViewHolder.txtSubject.setMaxLines(3); noteViewHolder.txtDetail.setVisibility(View.GONE); } String avatarUrl = GravatarUtils.fixGravatarUrl(note.getIconURL(), mAvatarSz); noteViewHolder.imgAvatar.setImageUrl(avatarUrl, WPNetworkImageView.ImageType.AVATAR); boolean isUnread = note.isUnread(); String noticonCharacter = note.getNoticonCharacter(); noteViewHolder.noteIcon.setText(noticonCharacter); if (commentStatus == CommentStatus.UNAPPROVED) { noteViewHolder.noteIcon.setBackgroundResource(R.drawable.shape_oval_orange); } else if (isUnread) { noteViewHolder.noteIcon.setBackgroundResource(R.drawable.shape_oval_blue_white_stroke); } else { noteViewHolder.noteIcon.setBackgroundResource(R.drawable.shape_oval_grey); } if (isUnread) { noteViewHolder.itemView.setBackgroundColor(mColorUnread); } else { noteViewHolder.itemView.setBackgroundColor(mColorRead); } // request to load more comments when we near the end if (mOnLoadMoreListener != null && position >= getItemCount() - 1) { mOnLoadMoreListener.onLoadMore(note.getTimestamp()); } } public int getPositionForNote(String noteId) { return getPositionForNoteInArray(noteId, mFilteredNotes); } private int getPositionForNoteUnfiltered(String noteId) { return getPositionForNoteInArray(noteId, mNotes); } private int getPositionForNoteInArray(String noteId, ArrayList<Note> 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(NotificationsListFragment.OnNoteClickListener mNoteClickListener) { mOnNoteClickListener = mNoteClickListener; } public void reloadNotesFromDBAsync() { new ReloadNotesFromDBTask().executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); } private class ReloadNotesFromDBTask extends AsyncTask<Void, Void, ArrayList<Note>> { @Override protected ArrayList<Note> doInBackground(Void... voids) { return NotificationsTable.getLatestNotes(); } @Override protected void onPostExecute(ArrayList<Note> notes) { mNotes.clear(); mNotes.addAll(notes); myNotifyDatasetChanged(); } } class NoteViewHolder extends RecyclerView.ViewHolder { private final View headerView; private final View contentView; private final TextView headerText; private final TextView txtSubject; private final TextView txtSubjectNoticon; private final TextView txtDetail; private final WPNetworkImageView imgAvatar; private final NoticonTextView noteIcon; private final View progressBar; NoteViewHolder(View view) { super(view); headerView = view.findViewById(R.id.time_header); contentView = view.findViewById(R.id.note_content_container); headerText = (TextView)view.findViewById(R.id.header_date_text); txtSubject = (TextView) view.findViewById(R.id.note_subject); txtSubjectNoticon = (TextView) view.findViewById(R.id.note_subject_noticon); txtDetail = (TextView) view.findViewById(R.id.note_detail); imgAvatar = (WPNetworkImageView) view.findViewById(R.id.note_avatar); noteIcon = (NoticonTextView) view.findViewById(R.id.note_icon); progressBar = view.findViewById(R.id.moderate_progress); itemView.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()); } } }; }