| 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); |
| } |
| } |