diff options
Diffstat (limited to 'WordPress/src/main/java/org/wordpress/android/ui/notifications/NotificationsListFragment.java')
-rw-r--r-- | WordPress/src/main/java/org/wordpress/android/ui/notifications/NotificationsListFragment.java | 542 |
1 files changed, 542 insertions, 0 deletions
diff --git a/WordPress/src/main/java/org/wordpress/android/ui/notifications/NotificationsListFragment.java b/WordPress/src/main/java/org/wordpress/android/ui/notifications/NotificationsListFragment.java new file mode 100644 index 000000000..8ad48256c --- /dev/null +++ b/WordPress/src/main/java/org/wordpress/android/ui/notifications/NotificationsListFragment.java @@ -0,0 +1,542 @@ +package org.wordpress.android.ui.notifications; + +import android.app.Activity; +import android.app.Fragment; +import android.content.Intent; +import android.os.Bundle; +import android.support.annotation.NonNull; +import android.support.annotation.StringRes; +import android.support.design.widget.AppBarLayout; +import android.support.v7.widget.LinearLayoutManager; +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.RadioGroup; +import android.widget.TextView; + +import com.simperium.client.Bucket; +import com.simperium.client.BucketObjectMissingException; + +import org.wordpress.android.GCMMessageService; +import org.wordpress.android.R; +import org.wordpress.android.WordPress; +import org.wordpress.android.models.AccountHelper; +import org.wordpress.android.models.Note; +import org.wordpress.android.ui.ActivityLauncher; +import org.wordpress.android.ui.RequestCodes; +import org.wordpress.android.ui.main.WPMainActivity; +import org.wordpress.android.ui.notifications.adapters.NotesAdapter; +import org.wordpress.android.ui.notifications.utils.SimperiumUtils; +import org.wordpress.android.util.AppLog; +import org.wordpress.android.util.ToastUtils; +import org.wordpress.android.util.ToastUtils.Duration; + +import de.greenrobot.event.EventBus; + +public class NotificationsListFragment extends Fragment + implements Bucket.Listener<Note>, + WPMainActivity.OnScrollToTopListener, RadioGroup.OnCheckedChangeListener { + public static final String NOTE_ID_EXTRA = "noteId"; + public static final String NOTE_INSTANT_REPLY_EXTRA = "instantReply"; + public static final String NOTE_INSTANT_LIKE_EXTRA = "instantLike"; + public static final String NOTE_INSTANT_APPROVE_EXTRA = "instantApprove"; + public static final String NOTE_MODERATE_ID_EXTRA = "moderateNoteId"; + public static final String NOTE_MODERATE_STATUS_EXTRA = "moderateNoteStatus"; + + private static final String KEY_LIST_SCROLL_POSITION = "scrollPosition"; + + private NotesAdapter mNotesAdapter; + private LinearLayoutManager mLinearLayoutManager; + private RecyclerView mRecyclerView; + private ViewGroup mEmptyView; + private View mFilterView; + private RadioGroup mFilterRadioGroup; + private View mFilterDivider; + + private int mRestoredScrollPosition; + + private Bucket<Note> mBucket; + + public static NotificationsListFragment newInstance() { + return new NotificationsListFragment(); + } + + /** + * For responding to tapping of notes + */ + public interface OnNoteClickListener { + void onClickNote(String noteId); + } + + @Override + public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { + View view = inflater.inflate(R.layout.notifications_fragment_notes_list, container, false); + + mRecyclerView = (RecyclerView) view.findViewById(R.id.recycler_view_notes); + + mFilterRadioGroup = (RadioGroup)view.findViewById(R.id.notifications_radio_group); + mFilterRadioGroup.setOnCheckedChangeListener(this); + mFilterDivider = view.findViewById(R.id.notifications_filter_divider); + mEmptyView = (ViewGroup) view.findViewById(R.id.empty_view); + mFilterView = view.findViewById(R.id.notifications_filter); + + mLinearLayoutManager = new LinearLayoutManager(getActivity()); + mRecyclerView.setLayoutManager(mLinearLayoutManager); + + return view; + } + + @Override + public void onActivityCreated(Bundle savedInstanceState) { + super.onActivityCreated(savedInstanceState); + + if (savedInstanceState != null) { + setRestoredListPosition(savedInstanceState.getInt(KEY_LIST_SCROLL_POSITION, RecyclerView.NO_POSITION)); + } + } + + @Override + public void onResume() { + super.onResume(); + + configureBucketAndAdapter(); + refreshNotes(); + + // start listening to bucket change events + if (mBucket != null) { + mBucket.addListener(this); + } + + // Removes app notifications from the system bar + new Thread(new Runnable() { + public void run() { + GCMMessageService.removeAllNotifications(getActivity()); + } + }).start(); + + if (SimperiumUtils.isUserAuthorized()) { + SimperiumUtils.startBuckets(); + AppLog.i(AppLog.T.NOTIFS, "Starting Simperium buckets"); + } + } + + @Override + public void onPause() { + // unregister the listener + if (mBucket != null) { + mBucket.removeListener(this); + } + super.onPause(); + } + + @Override + public void onDestroy() { + // Close Simperium cursor + if (mNotesAdapter != null) { + mNotesAdapter.closeCursor(); + } + + super.onDestroy(); + } + + // Sets up the notes bucket and list adapter + private void configureBucketAndAdapter() { + mBucket = SimperiumUtils.getNotesBucket(); + if (mBucket != null) { + if (mNotesAdapter == null) { + mNotesAdapter = new NotesAdapter(getActivity(), mBucket); + mNotesAdapter.setOnNoteClickListener(mOnNoteClickListener); + } + + if (mRecyclerView.getAdapter() == null) { + mRecyclerView.setAdapter(mNotesAdapter); + } + } else { + if (!AccountHelper.isSignedInWordPressDotCom()) { + // let user know that notifications require a wp.com account and enable sign-in + showEmptyView(R.string.notifications_account_required, 0, R.string.sign_in); + mFilterRadioGroup.setVisibility(View.GONE); + } else { + // failed for some other reason + showEmptyView(R.string.error_refresh_notifications); + } + } + } + + private final OnNoteClickListener mOnNoteClickListener = new OnNoteClickListener() { + @Override + public void onClickNote(String noteId) { + if (!isAdded()) { + return; + } + + if (TextUtils.isEmpty(noteId)) return; + + // open the latest version of this note just in case it has changed - this can + // happen if the note was tapped from the list fragment after it was updated + // by another fragment (such as NotificationCommentLikeFragment) + openNoteForReply(getActivity(), noteId, false); + } + }; + + private static Intent getOpenNoteIntent(Activity activity, + String noteId) { + Intent detailIntent = new Intent(activity, NotificationsDetailActivity.class); + detailIntent.putExtra(NOTE_ID_EXTRA, noteId); + return detailIntent; + } + + /** + * Open a note fragment based on the type of note + */ + public static void openNoteForReply(Activity activity, + String noteId, + boolean shouldShowKeyboard) { + if (noteId == null || activity == null) { + return; + } + + if (activity.isFinishing()) { + return; + } + + Intent detailIntent = getOpenNoteIntent(activity, noteId); + detailIntent.putExtra(NOTE_INSTANT_REPLY_EXTRA, shouldShowKeyboard); + activity.startActivityForResult(detailIntent, RequestCodes.NOTE_DETAIL); + } + + /** + * Open a note fragment based on the type of note, signaling to issue a like action immediately + */ + public static void openNoteForLike(Activity activity, + String noteId) { + if (noteId == null || activity == null) { + return; + } + + if (activity.isFinishing()) { + return; + } + + Intent detailIntent = getOpenNoteIntent(activity, noteId); + detailIntent.putExtra(NOTE_INSTANT_LIKE_EXTRA, true); + activity.startActivityForResult(detailIntent, RequestCodes.NOTE_DETAIL); + } + + /** + * Open a note fragment based on the type of note, signaling to issue a moderate:approve action immediately + */ + public static void openNoteForApprove(Activity activity, + String noteId) { + if (noteId == null || activity == null) { + return; + } + + if (activity.isFinishing()) { + return; + } + + Intent detailIntent = getOpenNoteIntent(activity, noteId); + detailIntent.putExtra(NOTE_INSTANT_APPROVE_EXTRA, true); + activity.startActivityForResult(detailIntent, RequestCodes.NOTE_DETAIL); + } + + private void setNoteIsHidden(String noteId, boolean isHidden) { + if (mNotesAdapter == null) return; + + if (isHidden) { + mNotesAdapter.addHiddenNoteId(noteId); + } else { + // Scroll the row into view if it isn't visible so the animation can be seen + int notePosition = mNotesAdapter.getPositionForNote(noteId); + if (notePosition != RecyclerView.NO_POSITION && + mLinearLayoutManager.findFirstCompletelyVisibleItemPosition() > notePosition) { + mLinearLayoutManager.scrollToPosition(notePosition); + } + + mNotesAdapter.removeHiddenNoteId(noteId); + } + } + + private void setNoteIsModerating(String noteId, boolean isModerating) { + if (mNotesAdapter == null) return; + + if (isModerating) { + mNotesAdapter.addModeratingNoteId(noteId); + } else { + mNotesAdapter.removeModeratingNoteId(noteId); + } + } + + private void showEmptyView(@StringRes int titleResId) { + showEmptyView(titleResId, 0, 0); + } + + private void showEmptyView(@StringRes int titleResId, @StringRes int descriptionResId, @StringRes int buttonResId) { + if (isAdded() && mEmptyView != null) { + mEmptyView.setVisibility(View.VISIBLE); + mFilterDivider.setVisibility(View.GONE); + setFilterViewScrollable(false); + ((TextView) mEmptyView.findViewById(R.id.text_empty)).setText(titleResId); + + TextView descriptionTextView = (TextView) mEmptyView.findViewById(R.id.text_empty_description); + if (descriptionResId > 0) { + descriptionTextView.setText(descriptionResId); + } else { + descriptionTextView.setVisibility(View.GONE); + } + + TextView btnAction = (TextView)mEmptyView.findViewById(R.id.button_empty_action); + if (buttonResId > 0) { + btnAction.setText(buttonResId); + btnAction.setVisibility(View.VISIBLE); + } else { + btnAction.setVisibility(View.GONE); + } + + btnAction.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + performActionForActiveFilter(); + } + }); + } + } + + private void setFilterViewScrollable(boolean isScrollable) { + if (mFilterView != null && mFilterView.getLayoutParams() instanceof AppBarLayout.LayoutParams) { + AppBarLayout.LayoutParams params = (AppBarLayout.LayoutParams) mFilterView.getLayoutParams(); + if (isScrollable) { + params.setScrollFlags( + AppBarLayout.LayoutParams.SCROLL_FLAG_SCROLL| + AppBarLayout.LayoutParams.SCROLL_FLAG_ENTER_ALWAYS + ); + } else { + params.setScrollFlags(0); + } + } + } + + private void hideEmptyView() { + if (isAdded() && mEmptyView != null) { + setFilterViewScrollable(true); + mEmptyView.setVisibility(View.GONE); + mFilterDivider.setVisibility(View.VISIBLE); + } + } + + private void refreshNotes() { + if (!isAdded() || mNotesAdapter == null) { + return; + } + + getActivity().runOnUiThread(new Runnable() { + @Override + public void run() { + // Filter the list according to the RadioGroup selection + int checkedId = mFilterRadioGroup.getCheckedRadioButtonId(); + if (checkedId == R.id.notifications_filter_all) { + mNotesAdapter.queryNotes(); + } else if (checkedId == R.id.notifications_filter_unread) { + mNotesAdapter.queryNotes(Note.Schema.UNREAD_INDEX, 1); + } else if (checkedId == R.id.notifications_filter_comments) { + mNotesAdapter.queryNotes(Note.Schema.TYPE_INDEX, Note.NOTE_COMMENT_TYPE); + } else if (checkedId == R.id.notifications_filter_follows) { + mNotesAdapter.queryNotes(Note.Schema.TYPE_INDEX, Note.NOTE_FOLLOW_TYPE); + } else if (checkedId == R.id.notifications_filter_likes) { + mNotesAdapter.queryNotes(Note.Schema.TYPE_INDEX, Note.NOTE_LIKE_TYPE); + } else { + mNotesAdapter.queryNotes(); + } + + restoreListScrollPosition(); + if (mNotesAdapter.getCount() > 0) { + hideEmptyView(); + } else { + showEmptyViewForCurrentFilter(); + } + } + }); + } + + // Show different empty list message and action button based on the active filter + private void showEmptyViewForCurrentFilter() { + if (!AccountHelper.isSignedInWordPressDotCom()) return; + + int i = mFilterRadioGroup.getCheckedRadioButtonId(); + if (i == R.id.notifications_filter_all) { + showEmptyView( + R.string.notifications_empty_all, + R.string.notifications_empty_action_all, + R.string.notifications_empty_view_reader + ); + } else if (i == R.id.notifications_filter_unread) {// User might not have a blog, if so just show the title + if (WordPress.getCurrentBlog() == null) { + showEmptyView(R.string.notifications_empty_unread); + } else { + showEmptyView( + R.string.notifications_empty_unread, + R.string.notifications_empty_action_unread, + R.string.new_post + ); + } + } else if (i == R.id.notifications_filter_comments) { + showEmptyView( + R.string.notifications_empty_comments, + R.string.notifications_empty_action_comments, + R.string.notifications_empty_view_reader + ); + } else if (i == R.id.notifications_filter_follows) { + showEmptyView( + R.string.notifications_empty_followers, + R.string.notifications_empty_action_followers_likes, + R.string.notifications_empty_view_reader + ); + } else if (i == R.id.notifications_filter_likes) { + showEmptyView( + R.string.notifications_empty_likes, + R.string.notifications_empty_action_followers_likes, + R.string.notifications_empty_view_reader + ); + } else { + showEmptyView(R.string.notifications_empty_list); + } + } + + private void performActionForActiveFilter() { + if (mFilterRadioGroup == null || !isAdded()) return; + + if (!AccountHelper.isSignedInWordPressDotCom()) { + ActivityLauncher.showSignInForResult(getActivity()); + return; + } + + int i = mFilterRadioGroup.getCheckedRadioButtonId(); + if (i == R.id.notifications_filter_unread) {// Create a new post + ActivityLauncher.addNewBlogPostOrPageForResult(getActivity(), WordPress.getCurrentBlog(), false); + } else {// Switch to Reader tab + if (getActivity() instanceof WPMainActivity) { + ((WPMainActivity) getActivity()).setReaderTabActive(); + } + } + } + + private void restoreListScrollPosition() { + if (isAdded() && mRecyclerView != null && mRestoredScrollPosition != RecyclerView.NO_POSITION + && mRestoredScrollPosition < mNotesAdapter.getCount()) { + // Restore scroll position in list + mLinearLayoutManager.scrollToPosition(mRestoredScrollPosition); + mRestoredScrollPosition = RecyclerView.NO_POSITION; + } + } + + @Override + public void onSaveInstanceState(@NonNull Bundle outState) { + if (outState.isEmpty()) { + outState.putBoolean("bug_19917_fix", true); + } + + // Save list view scroll position + outState.putInt(KEY_LIST_SCROLL_POSITION, getScrollPosition()); + + super.onSaveInstanceState(outState); + } + + private int getScrollPosition() { + if (!isAdded() || mRecyclerView == null) { + return RecyclerView.NO_POSITION; + } + + return mLinearLayoutManager.findFirstVisibleItemPosition(); + } + + private void setRestoredListPosition(int listPosition) { + mRestoredScrollPosition = listPosition; + } + + // Notification filter methods + @Override + public void onCheckedChanged(RadioGroup radioGroup, int checkedId) { + refreshNotes(); + } + + /** + * Simperium bucket listener methods + */ + @Override + public void onSaveObject(Bucket<Note> bucket, final Note object) { + refreshNotes(); + } + + @Override + public void onDeleteObject(Bucket<Note> bucket, final Note object) { + refreshNotes(); + } + + @Override + public void onNetworkChange(Bucket<Note> bucket, final Bucket.ChangeType type, final String key) { + // Reset the note's local status when a remote change is received + if (type == Bucket.ChangeType.MODIFY) { + try { + Note note = bucket.get(key); + if (note.isCommentType()) { + note.setLocalStatus(null); + note.save(); + } + } catch (BucketObjectMissingException e) { + AppLog.e(AppLog.T.NOTIFS, "Could not create note after receiving change."); + } + } + + refreshNotes(); + } + + @Override + public void onBeforeUpdateObject(Bucket<Note> noteBucket, Note note) { + //noop + } + + @Override + public void onScrollToTop() { + if (isAdded() && getScrollPosition() > 0) { + mLinearLayoutManager.smoothScrollToPosition(mRecyclerView, null, 0); + } + } + + @Override + public void onStop() { + EventBus.getDefault().unregister(this); + super.onStop(); + } + + @Override + public void onStart() { + super.onStart(); + EventBus.getDefault().registerSticky(this); + } + + @SuppressWarnings("unused") + public void onEventMainThread(NotificationEvents.NoteModerationStatusChanged event) { + setNoteIsModerating(event.mNoteId, event.mIsModerating); + + EventBus.getDefault().removeStickyEvent(NotificationEvents.NoteModerationStatusChanged.class); + } + + @SuppressWarnings("unused") + public void onEventMainThread(NotificationEvents.NoteVisibilityChanged event) { + setNoteIsHidden(event.mNoteId, event.mIsHidden); + + EventBus.getDefault().removeStickyEvent(NotificationEvents.NoteVisibilityChanged.class); + } + + @SuppressWarnings("unused") + public void onEventMainThread(NotificationEvents.NoteModerationFailed event) { + if (isAdded()) { + ToastUtils.showToast(getActivity(), R.string.error_moderate_comment, Duration.LONG); + } + + EventBus.getDefault().removeStickyEvent(NotificationEvents.NoteModerationFailed.class); + } +} |