package org.wordpress.android.ui.notifications; import static org.wordpress.android.WordPress.restClient; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import android.content.Intent; import android.os.Bundle; import android.support.v4.app.Fragment; import android.support.v4.app.FragmentManager; import android.support.v4.app.FragmentTransaction; import android.support.v4.content.IntentCompat; import android.util.Log; import android.view.View; import android.widget.ListAdapter; import android.widget.ListView; import android.widget.Toast; import com.actionbarsherlock.app.ActionBar; import com.actionbarsherlock.view.Menu; import com.actionbarsherlock.view.MenuInflater; import com.actionbarsherlock.view.MenuItem; import com.android.volley.VolleyError; import com.justsystems.hpb.pad.R; import com.wordpress.rest.RestRequest; import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; import org.wordpress.android.GCMIntentService; import org.wordpress.android.WordPress; import org.wordpress.android.models.Note; import org.wordpress.android.ui.WPActionBarActivity; public class NotificationsActivity extends WPActionBarActivity { public static final String TAG = "WPNotifications"; public static final String NOTE_ID_EXTRA = "noteId"; public static final String FROM_NOTIFICATION_EXTRA = "fromNotification"; public static final String NOTE_REPLY_EXTRA = "replyContent"; public static final String NOTE_INSTANT_REPLY_EXTRA = "instantReply"; public static final int FLAG_FROM_NOTE = Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_SINGLE_TOP | Intent.FLAG_ACTIVITY_NEW_TASK | IntentCompat.FLAG_ACTIVITY_CLEAR_TASK; Set<FragmentDetector> fragmentDetectors = new HashSet<FragmentDetector>(); private NotificationsListFragment mNotesList; private MenuItem mRefreshMenuItem; private boolean mLoadingMore = false; private boolean mFirstLoadComplete = false; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); createMenuDrawer(R.layout.notifications); ActionBar actionBar = getSupportActionBar(); actionBar.setDisplayShowTitleEnabled(true); setTitle(getResources().getString(R.string.notifications)); FragmentManager fm = getSupportFragmentManager(); fm.addOnBackStackChangedListener(mOnBackStackChangedListener); mNotesList = (NotificationsListFragment) fm .findFragmentById(R.id.notes_list); mNotesList.setNoteProvider(new NoteProvider()); mNotesList.setOnNoteClickListener(new NoteClickListener()); try { if (WordPress.latestNotes != null) { mNotesList.getNotesAdapter().addAll( parseNotes(WordPress.latestNotes)); } } catch (JSONException e) { Log.e(TAG, "No cached notes"); } fragmentDetectors.add(new FragmentDetector() { @Override public Fragment getFragment(Note note) { if (note.isCommentType()) { Fragment fragment = new NoteCommentFragment(); return fragment; } return null; } }); fragmentDetectors.add(new FragmentDetector() { @Override public Fragment getFragment(Note note) { if (note.isSingleLineListTemplate()) { Fragment fragment = new SingleLineListFragment(); return fragment; } return null; } }); fragmentDetectors.add(new FragmentDetector() { @Override public Fragment getFragment(Note note) { Log.d(TAG, String.format("Is it a big badge template? %b", note.isBigBadgeTemplate())); if (note.isBigBadgeTemplate()) { Fragment fragment = new BigBadgeFragment(); return fragment; } return null; } }); GCMIntentService.activeNotificationsMap.clear(); if (savedInstanceState == null) launchWithNoteId(); refreshNotes(); if (savedInstanceState != null) popNoteDetail(); } @Override protected void onNewIntent(Intent intent) { super.onNewIntent(intent); GCMIntentService.activeNotificationsMap.clear(); launchWithNoteId(); } private FragmentManager.OnBackStackChangedListener mOnBackStackChangedListener = new FragmentManager.OnBackStackChangedListener() { public void onBackStackChanged() { if (getSupportFragmentManager().getBackStackEntryCount() == 0) mMenuDrawer.setDrawerIndicatorEnabled(true); } }; /** * Detect if Intent has a noteId extra and display that specific note detail * fragment */ private void launchWithNoteId() { final Intent intent = getIntent(); if (intent.hasExtra(NOTE_ID_EXTRA)) { // find it/load it etc Map<String, String> params = new HashMap<String, String>(); params.put("ids", intent.getStringExtra(NOTE_ID_EXTRA)); NotesResponseHandler handler = new NotesResponseHandler() { @Override public void onNotes(List<Note> notes) { // there should only be one note! if (!notes.isEmpty()) { Note note = notes.get(0); openNote(note); } } }; restClient.getNotifications(params, handler, handler); } } @Override public boolean onOptionsItemSelected(final MenuItem item) { if (item.equals(mRefreshMenuItem)) { refreshNotes(); return true; } else if (item.getItemId() == android.R.id.home) { FragmentManager fm = getSupportFragmentManager(); if (fm.getBackStackEntryCount() > 0) { popNoteDetail(); return true; } } return super.onOptionsItemSelected(item); } @Override public boolean onCreateOptionsMenu(Menu menu) { super.onCreateOptionsMenu(menu); MenuInflater inflater = getSupportMenuInflater(); inflater.inflate(R.menu.notifications, menu); mRefreshMenuItem = menu.findItem(R.id.menu_refresh); if (shouldAnimateRefreshButton) { shouldAnimateRefreshButton = false; startAnimatingRefreshButton(mRefreshMenuItem); } return true; } public void popNoteDetail() { FragmentManager fm = getSupportFragmentManager(); Fragment f = fm.findFragmentById(R.id.commentDetail); if (f == null) { fm.popBackStack(); } } /** * Tries to pick the correct fragment detail type for a given note using the * fragment detectors */ private Fragment fragmentForNote(Note note) { Iterator<FragmentDetector> templates = fragmentDetectors.iterator(); while (templates.hasNext()) { FragmentDetector detector = templates.next(); Fragment fragment = detector.getFragment(note); if (fragment != null) { return fragment; } } return null; } /** * Open a note fragment based on the type of note */ public void openNote(final Note note) { if (note == null || isFinishing()) return; // if note is "unread" set note to "read" if (note.isUnread()) { // send a request to mark note as read restClient.markNoteAsRead(note, new RestRequest.Listener() { @Override public void onResponse(JSONObject response) { note.setUnreadCount("0"); mNotesList.getNotesAdapter().notifyDataSetChanged(); } }, new RestRequest.ErrorListener() { @Override public void onErrorResponse(VolleyError error) { Log.d(TAG, String.format("Failed to mark as read %s", error)); } }); } FragmentManager fm = getSupportFragmentManager(); // remove the note detail if it's already on there if (fm.getBackStackEntryCount() > 0) { fm.popBackStack(); } Fragment fragment = fragmentForNote(note); if (fragment == null) { Log.d(TAG, String.format("No fragment found for %s", note.toJSONObject())); return; } // swap the fragment NotificationFragment noteFragment = (NotificationFragment) fragment; Intent intent = getIntent(); if (intent.hasExtra(NOTE_ID_EXTRA) && intent.getStringExtra(NOTE_ID_EXTRA).equals(note.getId())) { if (intent.hasExtra(NOTE_REPLY_EXTRA) || intent.hasExtra(NOTE_INSTANT_REPLY_EXTRA)) { fragment.setArguments(intent.getExtras()); } } noteFragment.setNote(note); FragmentTransaction transaction = fm.beginTransaction(); View container = findViewById(R.id.note_fragment_container); transaction.replace(R.id.note_fragment_container, fragment); transaction.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE); // only add to backstack if we're removing the list view from the fragment container if (container.findViewById(R.id.notes_list) != null) { mMenuDrawer.setDrawerIndicatorEnabled(false); transaction.addToBackStack(null); } transaction.commitAllowingStateLoss(); } public void moderateComment(String siteId, String commentId, String status, final Note originalNote) { RestRequest.Listener success = new RestRequest.Listener() { @Override public void onResponse(JSONObject response) { Map<String, String> params = new HashMap<String, String>(); params.put("ids", originalNote.getId()); NotesResponseHandler handler = new NotesResponseHandler() { @Override public void onNotes(List<Note> notes) { // there should only be one note! if (!notes.isEmpty()) { Note updatedNote = notes.get(0); updateNote(originalNote, updatedNote); } } }; WordPress.restClient.getNotifications(params, handler, handler); } }; RestRequest.ErrorListener failure = new RestRequest.ErrorListener() { @Override public void onErrorResponse(VolleyError error) { Log.d(TAG, String.format("Error moderating comment: %s", error)); if (isFinishing()) return; Toast.makeText(NotificationsActivity.this, getString(R.string.error_moderate_comment), Toast.LENGTH_LONG).show(); FragmentManager fm = getSupportFragmentManager(); NoteCommentFragment f = (NoteCommentFragment) fm .findFragmentById(R.id.note_fragment_container); if (f != null) { f.animateModeration(false); } } }; WordPress.restClient.moderateComment(siteId, commentId, status, success, failure); } public void updateNote(Note originalNote, Note updatedNote) { if (isFinishing()) return; int position = mNotesList.getNotesAdapter().getPosition(originalNote); if (position >= 0) { mNotesList.getNotesAdapter().remove(originalNote); mNotesList.getNotesAdapter().insert(updatedNote, position); mNotesList.getNotesAdapter().notifyDataSetChanged(); // Update comment detail fragment if we're still viewing the same note if (position == mNotesList.getListView().getCheckedItemPosition()) { FragmentManager fm = getSupportFragmentManager(); NoteCommentFragment f = (NoteCommentFragment) fm .findFragmentById(R.id.note_fragment_container); if (f != null) { f.setNote(updatedNote); f.onStart(); f.animateModeration(false); } } } } public void refreshNotes() { mFirstLoadComplete = false; shouldAnimateRefreshButton = true; startAnimatingRefreshButton(mRefreshMenuItem); NotesResponseHandler handler = new NotesResponseHandler() { @Override public void onNotes(List<Note> notes) { mFirstLoadComplete = true; final NotificationsListFragment.NotesAdapter adapter = mNotesList .getNotesAdapter(); adapter.clear(); adapter.addAll(notes); adapter.notifyDataSetChanged(); // mark last seen timestampe if (!notes.isEmpty()) { updateLastSeen(notes.get(0).getTimestamp()); } stopAnimatingRefreshButton(mRefreshMenuItem); } @Override public void onErrorResponse(VolleyError error) { //We need to show an error message? and remove the loading indicator from the list? mFirstLoadComplete = true; final NotificationsListFragment.NotesAdapter adapter = mNotesList .getNotesAdapter(); adapter.clear(); adapter.addAll(new ArrayList<Note>()); adapter.notifyDataSetChanged(); Toast.makeText( NotificationsActivity.this, String.format( getResources() .getString(R.string.error_refresh), getResources().getText(R.string.notifications) .toString().toLowerCase()), Toast.LENGTH_LONG).show(); stopAnimatingRefreshButton(mRefreshMenuItem); shouldAnimateRefreshButton = false; } }; WordPress.refreshNotifications(this, handler, handler); } protected void updateLastSeen(String timestamp) { restClient.markNotificationsSeen(timestamp, new RestRequest.Listener() { @Override public void onResponse(JSONObject response) { Log.d(TAG, String.format("Set last seen time %s", response)); } }, new RestRequest.ErrorListener() { @Override public void onErrorResponse(VolleyError error) { Log.d(TAG, String.format("Could not set last seen time %s", error)); } }); } public void requestNotesBefore(Note note) { Map<String, String> params = new HashMap<String, String>(); Log.d(TAG, String.format("Requesting more notes before %s", note.queryJSON("timestamp", ""))); params.put("before", note.queryJSON("timestamp", "")); NotesResponseHandler handler = new NotesResponseHandler() { @Override public void onNotes(List<Note> notes) { // API returns 'on or before' timestamp, so remove first item if (notes.size() >= 1) notes.remove(0); NotificationsListFragment.NotesAdapter adapter = mNotesList .getNotesAdapter(); adapter.addAll(notes); adapter.notifyDataSetChanged(); } }; restClient.getNotifications(params, handler, handler); } private class NoteProvider implements NotificationsListFragment.NoteProvider { @Override public void onRequestMoreNotifications(ListView notesList, ListAdapter notesAdapter) { if (mFirstLoadComplete && !mLoadingMore) { NotificationsListFragment.NotesAdapter adapter = mNotesList .getNotesAdapter(); if (adapter.getCount() > 0) { Note lastNote = adapter.getItem(adapter.getCount() - 1); requestNotesBefore(lastNote); } } } } private class NoteClickListener implements NotificationsListFragment.OnNoteClickListener { @Override public void onClickNote(Note note) { openNote(note); } } abstract class NotesResponseHandler implements RestRequest.Listener, RestRequest.ErrorListener { NotesResponseHandler() { mLoadingMore = true; } abstract void onNotes(List<Note> notes); @Override public void onResponse(JSONObject response) { mLoadingMore = false; List<Note> notes = null; if (response == null) { //Not sure this could ever happen, but make sure we're catching all response types Log.w(TAG, "Success, but did not receive any notes"); notes = new ArrayList<Note>(0); onNotes(notes); return; } try { notes = parseNotes(response); onNotes(notes); } catch (JSONException e) { Log.e(TAG, "Success, but can't parse the response", e); showError(getString(R.string.error_parsing_response)); return; } } @Override public void onErrorResponse(VolleyError error) { mLoadingMore = false; showError(); Log.d(TAG, String.format("Error retrieving notes: %s", error)); } public void showError(String errorMessage) { Toast.makeText(NotificationsActivity.this, errorMessage, Toast.LENGTH_LONG).show(); } public void showError() { Toast.makeText(NotificationsActivity.this, getString(R.string.error_generic), Toast.LENGTH_LONG) .show(); } } private abstract class FragmentDetector { abstract public Fragment getFragment(Note note); } public static List<Note> parseNotes(JSONObject response) throws JSONException { List<Note> notes = null; JSONArray notesJSON = response.getJSONArray("notes"); notes = new ArrayList<Note>(notesJSON.length()); for (int i = 0; i < notesJSON.length(); i++) { Note n = new Note(notesJSON.getJSONObject(i)); notes.add(n); } return notes; } @Override public void onSaveInstanceState(Bundle outState) { if (outState.isEmpty()) { outState.putBoolean("bug_19917_fix", true); } outState.remove(NOTE_ID_EXTRA); super.onSaveInstanceState(outState); } }