aboutsummaryrefslogtreecommitdiff
path: root/WordPress/src/main/java/org/wordpress/android/ui/people
diff options
context:
space:
mode:
Diffstat (limited to 'WordPress/src/main/java/org/wordpress/android/ui/people')
-rw-r--r--WordPress/src/main/java/org/wordpress/android/ui/people/PeopleInviteFragment.java667
-rw-r--r--WordPress/src/main/java/org/wordpress/android/ui/people/PeopleListFragment.java432
-rw-r--r--WordPress/src/main/java/org/wordpress/android/ui/people/PeopleManagementActivity.java664
-rw-r--r--WordPress/src/main/java/org/wordpress/android/ui/people/PersonDetailFragment.java209
-rw-r--r--WordPress/src/main/java/org/wordpress/android/ui/people/RoleChangeDialogFragment.java148
-rw-r--r--WordPress/src/main/java/org/wordpress/android/ui/people/RoleSelectDialogFragment.java66
-rw-r--r--WordPress/src/main/java/org/wordpress/android/ui/people/utils/PeopleUtils.java527
7 files changed, 2713 insertions, 0 deletions
diff --git a/WordPress/src/main/java/org/wordpress/android/ui/people/PeopleInviteFragment.java b/WordPress/src/main/java/org/wordpress/android/ui/people/PeopleInviteFragment.java
new file mode 100644
index 000000000..32e8d2341
--- /dev/null
+++ b/WordPress/src/main/java/org/wordpress/android/ui/people/PeopleInviteFragment.java
@@ -0,0 +1,667 @@
+package org.wordpress.android.ui.people;
+
+
+import android.app.Fragment;
+import android.content.Intent;
+import android.net.Uri;
+import android.os.Bundle;
+import android.support.annotation.Nullable;
+import android.support.v4.content.ContextCompat;
+import android.text.Editable;
+import android.text.TextUtils;
+import android.text.TextWatcher;
+import android.view.KeyEvent;
+import android.view.LayoutInflater;
+import android.view.Menu;
+import android.view.MenuInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.inputmethod.EditorInfo;
+import android.widget.EditText;
+import android.widget.ImageButton;
+import android.widget.ImageView;
+import android.widget.TextView;
+
+import org.wordpress.android.R;
+import org.wordpress.android.WordPress;
+import org.wordpress.android.models.Blog;
+import org.wordpress.android.models.Role;
+import org.wordpress.android.ui.people.utils.PeopleUtils;
+import org.wordpress.android.ui.people.utils.PeopleUtils.ValidateUsernameCallback.ValidationResult;
+import org.wordpress.android.util.EditTextUtils;
+import org.wordpress.android.util.NetworkUtils;
+import org.wordpress.android.util.StringUtils;
+import org.wordpress.android.util.ToastUtils;
+import org.wordpress.android.widgets.MultiUsernameEditText;
+import org.wordpress.passcodelock.AppLockManager;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Hashtable;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+
+public class PeopleInviteFragment extends Fragment implements
+ RoleSelectDialogFragment.OnRoleSelectListener,
+ PeopleManagementActivity.InvitationSender {
+
+ private static final String FLAG_SUCCESS = "SUCCESS";
+
+ private static final String ARG_BLOGID = "ARG_BLOGID";
+
+ private static final int MAX_NUMBER_OF_INVITEES = 10;
+ private static final String[] USERNAME_DELIMITERS = {" ", ","};
+ private final Map<String, ViewGroup> mUsernameButtons = new LinkedHashMap<>();
+ private final HashMap<String, String> mUsernameResults = new HashMap<>();
+ private final Map<String, View> mUsernameErrorViews = new Hashtable<>();
+ private ViewGroup mUsernamesContainer;
+ private MultiUsernameEditText mUsernameEditText;
+ private TextView mRoleTextView;
+ private EditText mCustomMessageEditText;
+
+ private Role mRole;
+ private String mCustomMessage = "";
+ private boolean mInviteOperationInProgress = false;
+
+ public static PeopleInviteFragment newInstance(String dotComBlogId) {
+ PeopleInviteFragment peopleInviteFragment = new PeopleInviteFragment();
+
+ Bundle bundle = new Bundle();
+ bundle.putString(ARG_BLOGID, dotComBlogId);
+
+ peopleInviteFragment.setArguments(bundle);
+ return peopleInviteFragment;
+ }
+
+ @Override
+ public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
+ inflater.inflate(R.menu.people_invite, menu);
+ super.onCreateOptionsMenu(menu, inflater);
+ }
+
+ @Override
+ public void onPrepareOptionsMenu(Menu menu) {
+ menu.getItem(0).setEnabled(!mInviteOperationInProgress); // here pass the index of send menu item
+ super.onPrepareOptionsMenu(menu);
+ }
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ // retain this fragment across configuration changes
+ // WARNING: use setRetainInstance wisely. In this case we need this to be able to get the
+ // results of network connections in the same fragment if going through a configuration change
+ // (for example, device rotation occurs). Given the simplicity of this particular use case
+ // (the fragment state keeps only a couple of EditText components and the SAVE button, it is
+ // OK to use it here.
+ setRetainInstance(true);
+ }
+
+ @Override
+ public View onCreateView(final LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
+ setHasOptionsMenu(true);
+ return inflater.inflate(R.layout.people_invite_fragment, container, false);
+ }
+
+ @Override
+ public void onViewCreated(View view, Bundle savedInstanceState) {
+ super.onViewCreated(view, savedInstanceState);
+
+ mUsernamesContainer = (ViewGroup) view.findViewById(R.id.usernames);
+ mUsernamesContainer.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ EditTextUtils.showSoftInput(mUsernameEditText);
+ }
+ });
+
+ Role role = mRole;
+ if (role == null) {
+ role = getDefaultRole();
+ }
+
+ mUsernameEditText = (MultiUsernameEditText) view.findViewById(R.id.invite_usernames);
+
+ //handle key preses from hardware keyboard
+ mUsernameEditText.setOnKeyListener(new View.OnKeyListener() {
+ @Override
+ public boolean onKey(View view, int i, KeyEvent keyEvent) {
+ return keyEvent.getKeyCode() == KeyEvent.KEYCODE_DEL
+ && keyEvent.getAction() == KeyEvent.ACTION_DOWN
+ && removeLastEnteredUsername();
+ }
+ });
+
+ mUsernameEditText.setOnBackspacePressedListener(new MultiUsernameEditText.OnBackspacePressedListener() {
+ @Override
+ public boolean onBackspacePressed() {
+ return removeLastEnteredUsername();
+ }
+ });
+
+ mUsernameEditText.addTextChangedListener(new TextWatcher() {
+ private boolean shouldIgnoreChanges = false;
+
+ @Override
+ public void beforeTextChanged(CharSequence s, int start, int count, int after) {
+ }
+
+ @Override
+ public void onTextChanged(CharSequence s, int start, int before, int count) {
+ if (shouldIgnoreChanges) { //used to avoid double call after calling setText from this method
+ return;
+ }
+
+ shouldIgnoreChanges = true;
+ if (mUsernameButtons.size() >= MAX_NUMBER_OF_INVITEES && !TextUtils.isEmpty(s)) {
+ resetEditTextContent(mUsernameEditText);
+ } else if (endsWithDelimiter(mUsernameEditText.getText().toString())) {
+ addUsername(mUsernameEditText, null);
+ }
+ shouldIgnoreChanges = false;
+ }
+
+ @Override
+ public void afterTextChanged(Editable s) {
+ }
+ });
+
+ mUsernameEditText.setOnEditorActionListener(new TextView.OnEditorActionListener() {
+ @Override
+ public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
+ if (actionId == EditorInfo.IME_ACTION_DONE || (event != null && event.getKeyCode() == KeyEvent
+ .KEYCODE_ENTER)) {
+ addUsername(mUsernameEditText, null);
+ return true;
+ } else {
+ return false;
+ }
+ }
+ });
+
+ mUsernameEditText.setOnFocusChangeListener(new View.OnFocusChangeListener() {
+ @Override
+ public void onFocusChange(View v, boolean hasFocus) {
+ if (!hasFocus && mUsernameEditText.getText().toString().length() > 0) {
+ addUsername(mUsernameEditText, null);
+ }
+ }
+ });
+
+
+ if (mUsernameButtons.size() > 0) {
+ ArrayList<String> usernames = new ArrayList<>(mUsernameButtons.keySet());
+ populateUsernameButtons(usernames);
+ }
+
+
+ view.findViewById(R.id.role_container).setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ RoleSelectDialogFragment.show(PeopleInviteFragment.this, 0, isPrivateSite());
+ }
+ });
+
+ mRoleTextView = (TextView) view.findViewById(R.id.role);
+ setRole(role);
+ ImageView imgRoleInfo = (ImageView) view.findViewById(R.id.imgRoleInfo);
+ imgRoleInfo.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ Uri uri = Uri.parse(getString(R.string.role_info_url));
+ AppLockManager.getInstance().setExtendedTimeout();
+ startActivity(new Intent(Intent.ACTION_VIEW, uri));
+ }
+ });
+
+ final int MAX_CHARS = getResources().getInteger(R.integer.invite_message_char_limit);
+ final TextView remainingCharsTextView = (TextView) view.findViewById(R.id.message_remaining);
+
+ mCustomMessageEditText = (EditText) view.findViewById(R.id.message);
+ mCustomMessageEditText.addTextChangedListener(new TextWatcher() {
+ @Override
+ public void beforeTextChanged(CharSequence s, int start, int count, int after) {
+ }
+
+ @Override
+ public void onTextChanged(CharSequence s, int start, int before, int count) {
+ mCustomMessage = mCustomMessageEditText.getText().toString();
+ updateRemainingCharsView(remainingCharsTextView, mCustomMessage, MAX_CHARS);
+ }
+
+ @Override
+ public void afterTextChanged(Editable s) {
+ }
+ });
+ updateRemainingCharsView(remainingCharsTextView, mCustomMessage, MAX_CHARS);
+ }
+
+ private boolean endsWithDelimiter(String string) {
+ if (TextUtils.isEmpty(string)) {
+ return false;
+ }
+
+ for (String usernameDelimiter : USERNAME_DELIMITERS) {
+ if (string.endsWith(usernameDelimiter)) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ private String removeDelimiterFromUsername(String username) {
+ if (TextUtils.isEmpty(username)) {
+ return username;
+ }
+
+ String trimmedUsername = username.trim();
+
+ for (String usernameDelimiter : USERNAME_DELIMITERS) {
+ if (trimmedUsername.endsWith(usernameDelimiter)) {
+ return trimmedUsername.substring(0, trimmedUsername.length() - usernameDelimiter.length());
+ }
+ }
+
+ return trimmedUsername;
+ }
+
+ private void resetEditTextContent(EditText editText) {
+ if (editText != null) {
+ editText.setText("");
+ }
+ }
+
+ private Role getDefaultRole() {
+ Role[] inviteRoles = Role.inviteRoles(isPrivateSite());
+ return inviteRoles[0];
+ }
+
+ private void updateRemainingCharsView(TextView remainingCharsTextView, String currentString, int limit) {
+ remainingCharsTextView.setText(StringUtils.getQuantityString(getActivity(),
+ R.string.invite_message_remaining_zero,
+ R.string.invite_message_remaining_one,
+ R.string.invite_message_remaining_other, limit - (currentString == null ? 0 : currentString.length())));
+ }
+
+ private void populateUsernameButtons(Collection<String> usernames) {
+ if (usernames != null && usernames.size() > 0) {
+
+ for (String username : usernames) {
+ mUsernameButtons.put(username, buttonizeUsername(username));
+ }
+
+ validateAndStyleUsername(usernames, null);
+ }
+ }
+
+ private ViewGroup buttonizeUsername(final String username) {
+ if (!isAdded()) {
+ return null;
+ }
+
+ final ViewGroup usernameButton = (ViewGroup) LayoutInflater.from(getActivity()).inflate(R.layout
+ .invite_username_button, null);
+ final TextView usernameTextView = (TextView) usernameButton.findViewById(R.id.username);
+ usernameTextView.setText(username);
+
+ mUsernamesContainer.addView(usernameButton, mUsernamesContainer.getChildCount() - 1);
+
+ final ImageButton delete = (ImageButton) usernameButton.findViewById(R.id.username_delete);
+ delete.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ removeUsername(username);
+ }
+ });
+
+ return usernameButton;
+ }
+
+ private void addUsername(EditText editText, ValidationEndListener validationEndListener) {
+ String username = removeDelimiterFromUsername(editText.getText().toString());
+ resetEditTextContent(editText);
+
+ if (username.isEmpty() || mUsernameButtons.keySet().contains(username)) {
+ if (validationEndListener != null) {
+ validationEndListener.onValidationEnd();
+ }
+ return;
+ }
+
+ final ViewGroup usernameButton = buttonizeUsername(username);
+
+ mUsernameButtons.put(username, usernameButton);
+
+ validateAndStyleUsername(Collections.singletonList(username), validationEndListener);
+ }
+
+ private void removeUsername(String username) {
+ final ViewGroup usernamesView = (ViewGroup) getView().findViewById(R.id.usernames);
+
+ ViewGroup removedButton = mUsernameButtons.remove(username);
+ mUsernameResults.remove(username);
+ usernamesView.removeView(removedButton);
+
+ updateUsernameError(username, null);
+ }
+
+ private boolean isUserInInvitees(String username) {
+ return mUsernameButtons.get(username) != null;
+ }
+
+ /**
+ * Deletes the last entered username.
+ *
+ * @return true if the username was deleted
+ */
+ private boolean removeLastEnteredUsername() {
+ if (!TextUtils.isEmpty(mUsernameEditText.getText())) {
+ return false;
+ }
+
+ //try and remove the last entered username
+ List<String> list = new ArrayList<>(mUsernameButtons.keySet());
+ if (!list.isEmpty()) {
+ String username = list.get(list.size() - 1);
+ removeUsername(username);
+ return true;
+ }
+ return false;
+ }
+
+ @Override
+ public void onRoleSelected(Role newRole) {
+ setRole(newRole);
+
+ if (!mUsernameButtons.keySet().isEmpty()) {
+ // clear the username results list and let the 'validate' routine do the updates
+ mUsernameResults.clear();
+
+ validateAndStyleUsername(mUsernameButtons.keySet(), null);
+ }
+ }
+
+ private void setRole(Role newRole) {
+ mRole = newRole;
+ mRoleTextView.setText(newRole.toDisplayString());
+ }
+
+ private void validateAndStyleUsername(Collection<String> usernames, final ValidationEndListener validationEndListener) {
+ List<String> usernamesToCheck = new ArrayList<>();
+
+ for (String username : usernames) {
+ if (mUsernameResults.containsKey(username)) {
+ String resultMessage = mUsernameResults.get(username);
+ styleButton(username, resultMessage);
+ updateUsernameError(username, resultMessage);
+ } else {
+ styleButton(username, null);
+ updateUsernameError(username, null);
+
+ usernamesToCheck.add(username);
+ }
+ }
+
+ if (usernamesToCheck.size() > 0) {
+
+ String dotComBlogId = getArguments().getString(ARG_BLOGID);
+ PeopleUtils.validateUsernames(usernamesToCheck, mRole, dotComBlogId, new PeopleUtils.ValidateUsernameCallback() {
+ @Override
+ public void onUsernameValidation(String username, ValidationResult validationResult) {
+ if (!isAdded()) {
+ return;
+ }
+
+ if (!isUserInInvitees(username)) {
+ //user is removed from invitees before validation
+ return;
+ }
+
+ final String usernameResultString = getValidationErrorString(username, validationResult);
+ mUsernameResults.put(username, usernameResultString);
+
+ styleButton(username, usernameResultString);
+ updateUsernameError(username, usernameResultString);
+ }
+
+ @Override
+ public void onValidationFinished() {
+ if (validationEndListener != null) {
+ validationEndListener.onValidationEnd();
+ }
+ }
+
+ @Override
+ public void onError() {
+ // properly style the button
+ }
+ });
+ } else {
+ if (validationEndListener != null) {
+ validationEndListener.onValidationEnd();
+ }
+ }
+ }
+
+ private void styleButton(String username, @Nullable String validationResultMessage) {
+ if (!isAdded()) {
+ return;
+ }
+
+ TextView textView = (TextView) mUsernameButtons.get(username).findViewById(R.id.username);
+ textView.setTextColor(ContextCompat.getColor(getActivity(),
+ validationResultMessage == null ? R.color.grey_dark :
+ (validationResultMessage.equals(FLAG_SUCCESS) ? R.color.blue_wordpress : R.color.alert_red)));
+ }
+
+ private
+ @Nullable
+ String getValidationErrorString(String username, ValidationResult validationResult) {
+ switch (validationResult) {
+ case USER_NOT_FOUND:
+ return getString(R.string.invite_username_not_found, username);
+ case ALREADY_MEMBER:
+ return getString(R.string.invite_already_a_member, username);
+ case ALREADY_FOLLOWING:
+ return getString(R.string.invite_already_following, username);
+ case BLOCKED_INVITES:
+ return getString(R.string.invite_user_blocked_invites, username);
+ case INVALID_EMAIL:
+ return getString(R.string.invite_invalid_email, username);
+ case USER_FOUND:
+ return FLAG_SUCCESS;
+ }
+
+ return null;
+ }
+
+ private void updateUsernameError(String username, @Nullable String usernameResult) {
+ if (!isAdded()) {
+ return;
+ }
+
+ TextView usernameErrorTextView;
+ if (mUsernameErrorViews.containsKey(username)) {
+ usernameErrorTextView = (TextView) mUsernameErrorViews.get(username);
+
+ if (usernameResult == null || usernameResult.equals(FLAG_SUCCESS)) {
+ // no error so we need to remove the existing error view
+ ((ViewGroup) usernameErrorTextView.getParent()).removeView(usernameErrorTextView);
+ mUsernameErrorViews.remove(username);
+ return;
+ }
+ } else {
+ if (usernameResult == null || usernameResult.equals(FLAG_SUCCESS)) {
+ // no error so no need to create a new error view
+ return;
+ }
+
+ usernameErrorTextView = (TextView) LayoutInflater.from(getActivity())
+ .inflate(R.layout.people_invite_error_view, null);
+
+ final ViewGroup usernameErrorsContainer = (ViewGroup) getView()
+ .findViewById(R.id.username_errors_container);
+ usernameErrorsContainer.addView(usernameErrorTextView);
+
+ mUsernameErrorViews.put(username, usernameErrorTextView);
+ }
+ usernameErrorTextView.setText(usernameResult);
+ }
+
+ private void clearUsernames(Collection<String> usernames) {
+ for (String username : usernames) {
+ removeUsername(username);
+ }
+
+ if (mUsernameButtons.size() == 0) {
+ setRole(getDefaultRole());
+ resetEditTextContent(mCustomMessageEditText);
+ }
+ }
+
+ @Override
+ public void send() {
+ if (!isAdded()) {
+ return;
+ }
+
+ if (!NetworkUtils.checkConnection(getActivity())) {
+ enableSendButton(true);
+ return;
+ }
+
+ enableSendButton(false);
+
+ if (mUsernameEditText.getText().toString().length() > 0) {
+ addUsername(mUsernameEditText, new ValidationEndListener() {
+ @Override
+ public void onValidationEnd() {
+ if (!checkAndSend()) {
+ //re-enable SEND button if validation failed
+ enableSendButton(true);
+ }
+ }
+ });
+ } else {
+ if (!checkAndSend()) {
+ //re-enable SEND button if validation failed
+ enableSendButton(true);
+ }
+ }
+ }
+
+ /*
+ * returns true if send is attempted, false if validation failed
+ * */
+ private boolean checkAndSend() {
+ if (!isAdded()) {
+ return false;
+ }
+
+ if (!NetworkUtils.checkConnection(getActivity())) {
+ return false;
+ }
+
+ if (mUsernameButtons.size() == 0) {
+ ToastUtils.showToast(getActivity(), R.string.invite_error_no_usernames);
+ return false;
+ }
+
+ int invalidCount = 0;
+ for (String usernameResultString : mUsernameResults.values()) {
+ if (!usernameResultString.equals(FLAG_SUCCESS)) {
+ invalidCount++;
+ }
+ }
+
+ if (invalidCount > 0) {
+ ToastUtils.showToast(getActivity(), StringUtils.getQuantityString(getActivity(), 0,
+ R.string.invite_error_invalid_usernames_one,
+ R.string.invite_error_invalid_usernames_multiple, invalidCount));
+ return false;
+ }
+
+ //set the "SEND" option disabled
+ enableSendButton(false);
+
+ String dotComBlogId = getArguments().getString(ARG_BLOGID);
+ PeopleUtils.sendInvitations(new ArrayList<>(mUsernameButtons.keySet()), mRole, mCustomMessage, dotComBlogId,
+ new PeopleUtils.InvitationsSendCallback() {
+ @Override
+ public void onSent(List<String> succeededUsernames, Map<String, String> failedUsernameErrors) {
+ if (!isAdded()) {
+ return;
+ }
+
+ clearUsernames(succeededUsernames);
+
+ if (failedUsernameErrors.size() != 0) {
+ clearUsernames(failedUsernameErrors.keySet());
+
+ for (Map.Entry<String, String> error : failedUsernameErrors.entrySet()) {
+ final String username = error.getKey();
+ final String errorMessage = error.getValue();
+ mUsernameResults.put(username, getString(R.string.invite_error_for_username,
+ username, errorMessage));
+ }
+
+ populateUsernameButtons(failedUsernameErrors.keySet());
+
+ ToastUtils.showToast(getActivity(), succeededUsernames.isEmpty()
+ ? R.string.invite_error_sending : R.string.invite_error_some_failed);
+ } else {
+ ToastUtils.showToast(getActivity(), R.string.invite_sent, ToastUtils.Duration.LONG);
+ }
+
+ //set the "SEND" option enabled again
+ enableSendButton(true);
+ }
+
+ @Override
+ public void onError() {
+ if (!isAdded()) {
+ return;
+ }
+
+ ToastUtils.showToast(getActivity(), R.string.invite_error_sending);
+
+ //set the "SEND" option enabled again
+ enableSendButton(true);
+
+ }
+ });
+
+ return true;
+ }
+
+ private void enableSendButton(boolean enable) {
+ mInviteOperationInProgress = !enable;
+ if (getActivity() != null) {
+ getActivity().invalidateOptionsMenu();
+ }
+ }
+
+ @Override
+ public void onDestroyView() {
+ super.onDestroyView();
+ //we need to remove focus listener when view is destroyed (ex. orientation change) to prevent mUsernameEditText
+ //content from being converted to username
+ if (mUsernameEditText != null) {
+ mUsernameEditText.setOnFocusChangeListener(null);
+ }
+ }
+
+ private boolean isPrivateSite() {
+ String dotComBlogId = getArguments().getString(ARG_BLOGID);
+ Blog blog = WordPress.wpDB.getBlogForDotComBlogId(dotComBlogId);
+ return blog != null && blog.isPrivate();
+ }
+
+ public interface ValidationEndListener {
+ void onValidationEnd();
+ }
+}
diff --git a/WordPress/src/main/java/org/wordpress/android/ui/people/PeopleListFragment.java b/WordPress/src/main/java/org/wordpress/android/ui/people/PeopleListFragment.java
new file mode 100644
index 000000000..ca878b3cf
--- /dev/null
+++ b/WordPress/src/main/java/org/wordpress/android/ui/people/PeopleListFragment.java
@@ -0,0 +1,432 @@
+package org.wordpress.android.ui.people;
+
+import android.app.Fragment;
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.drawable.Drawable;
+import android.os.Bundle;
+import android.support.v4.content.ContextCompat;
+import android.support.v7.widget.RecyclerView;
+import android.view.LayoutInflater;
+import android.view.Menu;
+import android.view.MenuInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.TextView;
+
+import org.wordpress.android.R;
+import org.wordpress.android.WordPress;
+import org.wordpress.android.datasets.PeopleTable;
+import org.wordpress.android.models.Blog;
+import org.wordpress.android.models.FilterCriteria;
+import org.wordpress.android.models.PeopleListFilter;
+import org.wordpress.android.models.Person;
+import org.wordpress.android.ui.EmptyViewMessageType;
+import org.wordpress.android.ui.FilteredRecyclerView;
+import org.wordpress.android.ui.prefs.AppPrefs;
+import org.wordpress.android.util.AppLog;
+import org.wordpress.android.util.GravatarUtils;
+import org.wordpress.android.util.NetworkUtils;
+import org.wordpress.android.util.StringUtils;
+import org.wordpress.android.widgets.WPNetworkImageView;
+
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+public class PeopleListFragment extends Fragment {
+ private static final String ARG_LOCAL_TABLE_BLOG_ID = "local_table_blog_id";
+
+ private int mLocalTableBlogID;
+ private OnPersonSelectedListener mOnPersonSelectedListener;
+ private OnFetchPeopleListener mOnFetchPeopleListener;
+
+ private FilteredRecyclerView mFilteredRecyclerView;
+ private PeopleListFilter mPeopleListFilter;
+
+ public static PeopleListFragment newInstance(int localTableBlogID) {
+ PeopleListFragment peopleListFragment = new PeopleListFragment();
+ Bundle bundle = new Bundle();
+ bundle.putInt(ARG_LOCAL_TABLE_BLOG_ID, localTableBlogID);
+ peopleListFragment.setArguments(bundle);
+ return peopleListFragment;
+ }
+
+ public void setOnPersonSelectedListener(OnPersonSelectedListener listener) {
+ mOnPersonSelectedListener = listener;
+ }
+
+ public void setOnFetchPeopleListener(OnFetchPeopleListener listener) {
+ mOnFetchPeopleListener = listener;
+ }
+
+ @Override
+ public void onDetach() {
+ super.onDetach();
+ mOnPersonSelectedListener = null;
+ mOnFetchPeopleListener = null;
+ }
+
+ @Override
+ public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
+ inflater.inflate(R.menu.people_list, menu);
+ super.onCreateOptionsMenu(menu, inflater);
+ }
+
+ @Override
+ public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
+ setHasOptionsMenu(true);
+
+ final ViewGroup rootView = (ViewGroup) inflater.inflate(R.layout.people_list_fragment, container, false);
+
+ mLocalTableBlogID = getArguments().getInt(ARG_LOCAL_TABLE_BLOG_ID);
+ final Blog blog = WordPress.getBlog(mLocalTableBlogID);
+ final boolean isPrivate = blog != null && blog.isPrivate();
+
+ mFilteredRecyclerView = (FilteredRecyclerView) rootView.findViewById(R.id.filtered_recycler_view);
+ mFilteredRecyclerView.addItemDecoration(new PeopleItemDecoration(getActivity(), R.drawable.people_list_divider));
+ mFilteredRecyclerView.setLogT(AppLog.T.PEOPLE);
+ mFilteredRecyclerView.setSwipeToRefreshEnabled(false);
+
+ // the following will change the look and feel of the toolbar to match the current design
+ mFilteredRecyclerView.setToolbarBackgroundColor(ContextCompat.getColor(getActivity(), R.color.blue_medium));
+ mFilteredRecyclerView.setToolbarSpinnerTextColor(ContextCompat.getColor(getActivity(), R.color.white));
+ mFilteredRecyclerView.setToolbarSpinnerDrawable(R.drawable.arrow);
+ mFilteredRecyclerView.setToolbarLeftAndRightPadding(
+ getResources().getDimensionPixelSize(R.dimen.margin_filter_spinner),
+ getResources().getDimensionPixelSize(R.dimen.margin_none));
+
+ mFilteredRecyclerView.setFilterListener(new FilteredRecyclerView.FilterListener() {
+ @Override
+ public List<FilterCriteria> onLoadFilterCriteriaOptions(boolean refresh) {
+ ArrayList<FilterCriteria> list = new ArrayList<>();
+ Collections.addAll(list, PeopleListFilter.values());
+ // Only a private blog can have viewers
+ if (!isPrivate) {
+ list.remove(PeopleListFilter.VIEWERS);
+ }
+ return list;
+ }
+
+ @Override
+ public void onLoadFilterCriteriaOptionsAsync(FilteredRecyclerView.FilterCriteriaAsyncLoaderListener listener, boolean refresh) {
+ // no-op
+ }
+
+ @Override
+ public FilterCriteria onRecallSelection() {
+ mPeopleListFilter = AppPrefs.getPeopleListFilter();
+
+ // if viewers is not available for this blog, set the filter to TEAM
+ if (mPeopleListFilter == PeopleListFilter.VIEWERS && !isPrivate) {
+ mPeopleListFilter = PeopleListFilter.TEAM;
+ AppPrefs.setPeopleListFilter(mPeopleListFilter);
+ }
+ return mPeopleListFilter;
+ }
+
+ @Override
+ public void onLoadData() {
+ updatePeople(false);
+ }
+
+ @Override
+ public void onFilterSelected(int position, FilterCriteria criteria) {
+ mPeopleListFilter = (PeopleListFilter) criteria;
+ AppPrefs.setPeopleListFilter(mPeopleListFilter);
+ }
+
+ @Override
+ public String onShowEmptyViewMessage(EmptyViewMessageType emptyViewMsgType) {
+ int stringId = 0;
+ switch (emptyViewMsgType) {
+ case LOADING:
+ stringId = R.string.people_fetching;
+ break;
+ case NETWORK_ERROR:
+ stringId = R.string.no_network_message;
+ break;
+ case NO_CONTENT:
+ switch (mPeopleListFilter) {
+ case TEAM:
+ stringId = R.string.people_empty_list_filtered_users;
+ break;
+ case FOLLOWERS:
+ stringId = R.string.people_empty_list_filtered_followers;
+ break;
+ case EMAIL_FOLLOWERS:
+ stringId = R.string.people_empty_list_filtered_email_followers;
+ break;
+ case VIEWERS:
+ stringId = R.string.people_empty_list_filtered_viewers;
+ break;
+ }
+ break;
+ case GENERIC_ERROR:
+ switch (mPeopleListFilter) {
+ case TEAM:
+ stringId = R.string.error_fetch_users_list;
+ break;
+ case FOLLOWERS:
+ stringId = R.string.error_fetch_followers_list;
+ break;
+ case EMAIL_FOLLOWERS:
+ stringId = R.string.error_fetch_email_followers_list;
+ break;
+ case VIEWERS:
+ stringId = R.string.error_fetch_viewers_list;
+ break;
+ }
+ break;
+ }
+ return getString(stringId);
+ }
+
+ @Override
+ public void onShowCustomEmptyView(EmptyViewMessageType emptyViewMsgType) {
+
+ }
+ });
+
+ return rootView;
+ }
+
+ @Override
+ public void onResume() {
+ super.onResume();
+
+ updatePeople(false);
+ }
+
+ private void updatePeople(boolean loadMore) {
+ if (!NetworkUtils.isNetworkAvailable(getActivity())) {
+ mFilteredRecyclerView.updateEmptyView(EmptyViewMessageType.NETWORK_ERROR);
+ mFilteredRecyclerView.setRefreshing(false);
+ return;
+ }
+
+ if (mOnFetchPeopleListener != null) {
+ if (loadMore) {
+ boolean isFetching = mOnFetchPeopleListener.onFetchMorePeople(mPeopleListFilter);
+ if (isFetching) {
+ mFilteredRecyclerView.showLoadingProgress();
+ }
+ } else {
+ boolean isFetching = mOnFetchPeopleListener.onFetchFirstPage(mPeopleListFilter);
+ if (isFetching) {
+ mFilteredRecyclerView.updateEmptyView(EmptyViewMessageType.LOADING);
+ } else {
+ mFilteredRecyclerView.hideEmptyView();
+ mFilteredRecyclerView.setRefreshing(false);
+ }
+ refreshPeopleList(isFetching);
+ }
+ }
+ }
+
+ public void refreshPeopleList(boolean isFetching) {
+ if (!isAdded()) return;
+
+ List<Person> peopleList;
+ switch (mPeopleListFilter) {
+ case TEAM:
+ peopleList = PeopleTable.getUsers(mLocalTableBlogID);
+ break;
+ case FOLLOWERS:
+ peopleList = PeopleTable.getFollowers(mLocalTableBlogID);
+ break;
+ case EMAIL_FOLLOWERS:
+ peopleList = PeopleTable.getEmailFollowers(mLocalTableBlogID);
+ break;
+ case VIEWERS:
+ peopleList = PeopleTable.getViewers(mLocalTableBlogID);
+ break;
+ default:
+ peopleList = new ArrayList<>();
+ break;
+ }
+ PeopleAdapter peopleAdapter = (PeopleAdapter) mFilteredRecyclerView.getAdapter();
+ if (peopleAdapter == null) {
+ peopleAdapter = new PeopleAdapter(getActivity(), peopleList);
+ mFilteredRecyclerView.setAdapter(peopleAdapter);
+ } else {
+ peopleAdapter.setPeopleList(peopleList);
+ }
+
+ if (!peopleList.isEmpty()) {
+ // if the list is not empty, don't show any message
+ mFilteredRecyclerView.hideEmptyView();
+ } else if (!isFetching) {
+ // if we are not fetching and list is empty, show no content message
+ mFilteredRecyclerView.updateEmptyView(EmptyViewMessageType.NO_CONTENT);
+ }
+ }
+
+ public void fetchingRequestFinished(PeopleListFilter filter, boolean isFirstPage, boolean isSuccessful) {
+ if (mPeopleListFilter == filter) {
+ if (isFirstPage) {
+ mFilteredRecyclerView.setRefreshing(false);
+ if (!isSuccessful) {
+ mFilteredRecyclerView.updateEmptyView(EmptyViewMessageType.GENERIC_ERROR);
+ }
+ } else {
+ mFilteredRecyclerView.hideLoadingProgress();
+ }
+ }
+ }
+
+ // Container Activity must implement this interface
+ public interface OnPersonSelectedListener {
+ void onPersonSelected(Person person);
+ }
+
+ public interface OnFetchPeopleListener {
+ boolean onFetchFirstPage(PeopleListFilter filter);
+
+ boolean onFetchMorePeople(PeopleListFilter filter);
+ }
+
+ public class PeopleAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
+ private final LayoutInflater mInflater;
+ private List<Person> mPeopleList;
+ private int mAvatarSz;
+
+ public PeopleAdapter(Context context, List<Person> peopleList) {
+ mAvatarSz = context.getResources().getDimensionPixelSize(R.dimen.people_avatar_sz);
+ mInflater = LayoutInflater.from(context);
+ mPeopleList = peopleList;
+ setHasStableIds(true);
+ }
+
+ public void setPeopleList(List<Person> peopleList) {
+ mPeopleList = peopleList;
+ notifyDataSetChanged();
+ }
+
+ public Person getPerson(int position) {
+ if (mPeopleList == null) {
+ return null;
+ }
+ return mPeopleList.get(position);
+ }
+
+ @Override
+ public int getItemCount() {
+ if (mPeopleList == null) {
+ return 0;
+ }
+ return mPeopleList.size();
+ }
+
+ @Override
+ public long getItemId(int position) {
+ Person person = getPerson(position);
+ if (person == null) {
+ return -1;
+ }
+ return person.getPersonID();
+ }
+
+ @Override
+ public PeopleViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
+ View view = mInflater.inflate(R.layout.people_list_row, parent, false);
+
+ return new PeopleViewHolder(view);
+ }
+
+ @Override
+ public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
+ PeopleViewHolder peopleViewHolder = (PeopleViewHolder) holder;
+ final Person person = getPerson(position);
+
+ if (person != null) {
+ String avatarUrl = GravatarUtils.fixGravatarUrl(person.getAvatarUrl(), mAvatarSz);
+ peopleViewHolder.imgAvatar.setImageUrl(avatarUrl, WPNetworkImageView.ImageType.AVATAR);
+ peopleViewHolder.txtDisplayName.setText(StringUtils.unescapeHTML(person.getDisplayName()));
+ if (person.getRole() != null) {
+ peopleViewHolder.txtRole.setVisibility(View.VISIBLE);
+ peopleViewHolder.txtRole.setText(StringUtils.capitalize(person.getRole().toDisplayString()));
+ } else {
+ peopleViewHolder.txtRole.setVisibility(View.GONE);
+ }
+ if (!person.getUsername().isEmpty()) {
+ peopleViewHolder.txtUsername.setVisibility(View.VISIBLE);
+ peopleViewHolder.txtUsername.setText(String.format("@%s", person.getUsername()));
+ } else {
+ peopleViewHolder.txtUsername.setVisibility(View.GONE);
+ }
+ if (person.getPersonType() == Person.PersonType.USER
+ || person.getPersonType() == Person.PersonType.VIEWER) {
+ peopleViewHolder.txtSubscribed.setVisibility(View.GONE);
+ } else {
+ peopleViewHolder.txtSubscribed.setVisibility(View.VISIBLE);
+ String dateSubscribed = SimpleDateFormat.getDateInstance().format(person.getDateSubscribed());
+ String dateText = getString(R.string.follower_subscribed_since, dateSubscribed);
+ peopleViewHolder.txtSubscribed.setText(dateText);
+ }
+ }
+
+ // end of list is reached
+ if (position == getItemCount() - 1) {
+ updatePeople(true);
+ }
+ }
+
+ public class PeopleViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener {
+ private final WPNetworkImageView imgAvatar;
+ private final TextView txtDisplayName;
+ private final TextView txtUsername;
+ private final TextView txtRole;
+ private final TextView txtSubscribed;
+
+ public PeopleViewHolder(View view) {
+ super(view);
+ imgAvatar = (WPNetworkImageView) view.findViewById(R.id.person_avatar);
+ txtDisplayName = (TextView) view.findViewById(R.id.person_display_name);
+ txtUsername = (TextView) view.findViewById(R.id.person_username);
+ txtRole = (TextView) view.findViewById(R.id.person_role);
+ txtSubscribed = (TextView) view.findViewById(R.id.follower_subscribed_date);
+
+ itemView.setOnClickListener(this);
+ }
+
+ @Override
+ public void onClick(View v) {
+ if (mOnPersonSelectedListener != null) {
+ Person person = getPerson(getAdapterPosition());
+ mOnPersonSelectedListener.onPersonSelected(person);
+ }
+ }
+ }
+ }
+
+ // Taken from http://stackoverflow.com/a/27037230
+ private class PeopleItemDecoration extends RecyclerView.ItemDecoration {
+ private Drawable mDivider;
+
+ // use a custom drawable
+ public PeopleItemDecoration(Context context, int resId) {
+ mDivider = ContextCompat.getDrawable(context, resId);
+ }
+
+ @Override
+ public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {
+ int left = parent.getPaddingLeft();
+ int right = parent.getWidth() - parent.getPaddingRight();
+
+ int childCount = parent.getChildCount();
+ for (int i = 0; i < childCount; i++) {
+ View child = parent.getChildAt(i);
+
+ RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child.getLayoutParams();
+
+ int top = child.getBottom() + params.bottomMargin;
+ int bottom = top + mDivider.getIntrinsicHeight();
+
+ mDivider.setBounds(left, top, right, bottom);
+ mDivider.draw(c);
+ }
+ }
+ }
+}
diff --git a/WordPress/src/main/java/org/wordpress/android/ui/people/PeopleManagementActivity.java b/WordPress/src/main/java/org/wordpress/android/ui/people/PeopleManagementActivity.java
new file mode 100644
index 000000000..3c0e43c70
--- /dev/null
+++ b/WordPress/src/main/java/org/wordpress/android/ui/people/PeopleManagementActivity.java
@@ -0,0 +1,664 @@
+package org.wordpress.android.ui.people;
+
+import android.app.AlertDialog;
+import android.app.Fragment;
+import android.app.FragmentManager;
+import android.app.FragmentTransaction;
+import android.content.DialogInterface;
+import android.os.Bundle;
+import android.support.v7.app.ActionBar;
+import android.support.v7.app.AppCompatActivity;
+import android.support.v7.widget.Toolbar;
+import android.view.MenuItem;
+
+import org.wordpress.android.R;
+import org.wordpress.android.WordPress;
+import org.wordpress.android.analytics.AnalyticsTracker;
+import org.wordpress.android.datasets.PeopleTable;
+import org.wordpress.android.models.Blog;
+import org.wordpress.android.models.PeopleListFilter;
+import org.wordpress.android.models.Person;
+import org.wordpress.android.ui.people.utils.PeopleUtils;
+import org.wordpress.android.util.AnalyticsUtils;
+import org.wordpress.android.util.NetworkUtils;
+import org.wordpress.android.util.ToastUtils;
+
+import java.util.List;
+
+import de.greenrobot.event.EventBus;
+
+
+public class PeopleManagementActivity extends AppCompatActivity
+ implements PeopleListFragment.OnPersonSelectedListener, PeopleListFragment.OnFetchPeopleListener {
+ private static final String KEY_PEOPLE_LIST_FRAGMENT = "people-list-fragment";
+ private static final String KEY_PERSON_DETAIL_FRAGMENT = "person-detail-fragment";
+ private static final String KEY_PEOPLE_INVITE_FRAGMENT = "people-invite-fragment";
+ private static final String KEY_TITLE = "page-title";
+
+ private static final String KEY_USERS_END_OF_LIST_REACHED = "users-end-of-list-reached";
+ private static final String KEY_FOLLOWERS_END_OF_LIST_REACHED = "followers-end-of-list-reached";
+ private static final String KEY_EMAIL_FOLLOWERS_END_OF_LIST_REACHED = "email-followers-end-of-list-reached";
+ private static final String KEY_VIEWERS_END_OF_LIST_REACHED = "viewers-end-of-list-reached";
+
+ private static final String KEY_USERS_FETCH_REQUEST_IN_PROGRESS = "users-fetch-request-in-progress";
+ private static final String KEY_FOLLOWERS_FETCH_REQUEST_IN_PROGRESS = "followers-fetch-request-in-progress";
+ private static final String KEY_EMAIL_FOLLOWERS_FETCH_REQUEST_IN_PROGRESS = "email-followers-fetch-request-in-progress";
+ private static final String KEY_VIEWERS_FETCH_REQUEST_IN_PROGRESS = "viewers-fetch-request-in-progress";
+
+ private static final String KEY_HAS_REFRESHED_USERS = "has-refreshed-users";
+ private static final String KEY_HAS_REFRESHED_FOLLOWERS = "has-refreshed-followers";
+ private static final String KEY_HAS_REFRESHED_EMAIL_FOLLOWERS = "has-refreshed-email-followers";
+ private static final String KEY_HAS_REFRESHED_VIEWERS = "has-refreshed-viewers";
+
+ private static final String KEY_FOLLOWERS_LAST_FETCHED_PAGE = "followers-last-fetched-page";
+ private static final String KEY_EMAIL_FOLLOWERS_LAST_FETCHED_PAGE = "email-followers-last-fetched-page";
+
+ // End of list reached variables will be true when there is no more data to fetch
+ private boolean mUsersEndOfListReached;
+ private boolean mFollowersEndOfListReached;
+ private boolean mEmailFollowersEndOfListReached;
+ private boolean mViewersEndOfListReached;
+
+ // We only allow the lists to be refreshed once to avoid syncing and jumping animation issues
+ private boolean mHasRefreshedUsers;
+ private boolean mHasRefreshedFollowers;
+ private boolean mHasRefreshedEmailFollowers;
+ private boolean mHasRefreshedViewers;
+
+ // If we are currently making a request for a certain filter
+ private boolean mUsersFetchRequestInProgress;
+ private boolean mFollowersFetchRequestInProgress;
+ private boolean mEmailFollowersFetchRequestInProgress;
+ private boolean mViewersFetchRequestInProgress;
+
+ // Keep track of the last page we received from remote
+ private int mFollowersLastFetchedPage;
+ private int mEmailFollowersLastFetchedPage;
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ setContentView(R.layout.people_management_activity);
+
+ Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
+ setSupportActionBar(toolbar);
+ ActionBar actionBar = getSupportActionBar();
+ if (actionBar != null) {
+ actionBar.setHomeButtonEnabled(true);
+ actionBar.setDisplayHomeAsUpEnabled(true);
+ actionBar.setElevation(0);
+ }
+
+ Blog blog = WordPress.getCurrentBlog();
+ if (blog == null) {
+ ToastUtils.showToast(this, R.string.blog_not_found);
+ finish();
+ return;
+ }
+
+ FragmentManager fragmentManager = getFragmentManager();
+
+ if (savedInstanceState == null) {
+ // only delete cached people if there is a connection
+ if (NetworkUtils.isNetworkAvailable(this)) {
+ PeopleTable.deletePeopleExceptForFirstPage(blog.getLocalTableBlogId());
+ }
+
+ if (actionBar != null) {
+ actionBar.setTitle(R.string.people);
+ }
+
+ PeopleListFragment peopleListFragment = PeopleListFragment.newInstance(blog.getLocalTableBlogId());
+ peopleListFragment.setOnPersonSelectedListener(this);
+ peopleListFragment.setOnFetchPeopleListener(this);
+
+ mUsersEndOfListReached = false;
+ mFollowersEndOfListReached = false;
+ mEmailFollowersEndOfListReached = false;
+ mViewersEndOfListReached = false;
+
+ mHasRefreshedUsers = false;
+ mHasRefreshedFollowers = false;
+ mHasRefreshedEmailFollowers = false;
+ mHasRefreshedViewers = false;
+
+ mUsersFetchRequestInProgress = false;
+ mFollowersFetchRequestInProgress = false;
+ mEmailFollowersFetchRequestInProgress = false;
+ mViewersFetchRequestInProgress = false;
+ mFollowersLastFetchedPage = 0;
+ mEmailFollowersLastFetchedPage = 0;
+
+
+ fragmentManager.beginTransaction()
+ .add(R.id.fragment_container, peopleListFragment, KEY_PEOPLE_LIST_FRAGMENT)
+ .commit();
+ } else {
+ mUsersEndOfListReached = savedInstanceState.getBoolean(KEY_USERS_END_OF_LIST_REACHED);
+ mFollowersEndOfListReached = savedInstanceState.getBoolean(KEY_FOLLOWERS_END_OF_LIST_REACHED);
+ mEmailFollowersEndOfListReached = savedInstanceState.getBoolean(KEY_EMAIL_FOLLOWERS_END_OF_LIST_REACHED);
+ mViewersEndOfListReached = savedInstanceState.getBoolean(KEY_VIEWERS_END_OF_LIST_REACHED);
+
+ mHasRefreshedUsers = savedInstanceState.getBoolean(KEY_HAS_REFRESHED_USERS);
+ mHasRefreshedFollowers = savedInstanceState.getBoolean(KEY_HAS_REFRESHED_FOLLOWERS);
+ mHasRefreshedEmailFollowers = savedInstanceState.getBoolean(KEY_HAS_REFRESHED_EMAIL_FOLLOWERS);
+ mHasRefreshedViewers = savedInstanceState.getBoolean(KEY_HAS_REFRESHED_VIEWERS);
+
+ mUsersFetchRequestInProgress = savedInstanceState.getBoolean(KEY_USERS_FETCH_REQUEST_IN_PROGRESS);
+ mFollowersFetchRequestInProgress = savedInstanceState.getBoolean(KEY_FOLLOWERS_FETCH_REQUEST_IN_PROGRESS);
+ mEmailFollowersFetchRequestInProgress = savedInstanceState.getBoolean(KEY_EMAIL_FOLLOWERS_FETCH_REQUEST_IN_PROGRESS);
+ mViewersFetchRequestInProgress = savedInstanceState.getBoolean(KEY_VIEWERS_FETCH_REQUEST_IN_PROGRESS);
+
+ mFollowersLastFetchedPage = savedInstanceState.getInt(KEY_FOLLOWERS_LAST_FETCHED_PAGE);
+ mEmailFollowersLastFetchedPage = savedInstanceState.getInt(KEY_EMAIL_FOLLOWERS_LAST_FETCHED_PAGE);
+
+ CharSequence title = savedInstanceState.getCharSequence(KEY_TITLE);
+ if (actionBar != null && title != null) {
+ actionBar.setTitle(title);
+ }
+
+ PeopleListFragment peopleListFragment = getListFragment();
+ if (peopleListFragment != null) {
+ peopleListFragment.setOnPersonSelectedListener(this);
+ peopleListFragment.setOnFetchPeopleListener(this);
+ }
+ }
+ }
+
+ @Override
+ public void onSaveInstanceState(Bundle outState){
+ super.onSaveInstanceState(outState);
+ outState.putBoolean(KEY_USERS_END_OF_LIST_REACHED, mUsersEndOfListReached);
+ outState.putBoolean(KEY_FOLLOWERS_END_OF_LIST_REACHED, mFollowersEndOfListReached);
+ outState.putBoolean(KEY_EMAIL_FOLLOWERS_END_OF_LIST_REACHED, mEmailFollowersEndOfListReached);
+ outState.putBoolean(KEY_VIEWERS_END_OF_LIST_REACHED, mViewersEndOfListReached);
+
+ outState.putBoolean(KEY_HAS_REFRESHED_USERS, mHasRefreshedUsers);
+ outState.putBoolean(KEY_HAS_REFRESHED_FOLLOWERS, mHasRefreshedFollowers);
+ outState.putBoolean(KEY_HAS_REFRESHED_EMAIL_FOLLOWERS, mHasRefreshedEmailFollowers);
+ outState.putBoolean(KEY_HAS_REFRESHED_VIEWERS, mHasRefreshedViewers);
+
+ outState.putBoolean(KEY_USERS_FETCH_REQUEST_IN_PROGRESS, mUsersFetchRequestInProgress);
+ outState.putBoolean(KEY_FOLLOWERS_FETCH_REQUEST_IN_PROGRESS, mFollowersFetchRequestInProgress);
+ outState.putBoolean(KEY_EMAIL_FOLLOWERS_FETCH_REQUEST_IN_PROGRESS, mEmailFollowersFetchRequestInProgress);
+ outState.putBoolean(KEY_VIEWERS_FETCH_REQUEST_IN_PROGRESS, mViewersFetchRequestInProgress);
+
+ outState.putInt(KEY_FOLLOWERS_LAST_FETCHED_PAGE, mFollowersLastFetchedPage);
+ outState.putInt(KEY_EMAIL_FOLLOWERS_LAST_FETCHED_PAGE, mEmailFollowersLastFetchedPage);
+
+ ActionBar actionBar = getSupportActionBar();
+ if (actionBar != null) {
+ outState.putCharSequence(KEY_TITLE, actionBar.getTitle());
+ }
+ }
+
+ @Override
+ public void onStart() {
+ super.onStart();
+ EventBus.getDefault().register(this);
+ }
+
+ @Override
+ public void onStop() {
+ EventBus.getDefault().unregister(this);
+ super.onStop();
+ }
+
+ @Override
+ public void onBackPressed() {
+ if (!navigateBackToPeopleListFragment()) {
+ super.onBackPressed();
+ }
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(final MenuItem item) {
+ if (item.getItemId() == android.R.id.home) {
+ onBackPressed();
+ return true;
+ } else if (item.getItemId() == R.id.remove_person) {
+ confirmRemovePerson();
+ return true;
+ } else if (item.getItemId() == R.id.invite) {
+ FragmentManager fragmentManager = getFragmentManager();
+ Fragment peopleInviteFragment = fragmentManager.findFragmentByTag(KEY_PERSON_DETAIL_FRAGMENT);
+
+ if (peopleInviteFragment == null) {
+ Blog blog = WordPress.getCurrentBlog();
+ peopleInviteFragment = PeopleInviteFragment.newInstance(blog.getDotComBlogId());
+ }
+ if (!peopleInviteFragment.isAdded()) {
+ FragmentTransaction fragmentTransaction = getFragmentManager().beginTransaction();
+ fragmentTransaction.replace(R.id.fragment_container, peopleInviteFragment, KEY_PEOPLE_INVITE_FRAGMENT);
+ fragmentTransaction.addToBackStack(null);
+ fragmentTransaction.commit();
+ }
+ } else if (item.getItemId() == R.id.send_invitation) {
+ FragmentManager fragmentManager = getFragmentManager();
+ Fragment peopleInviteFragment = fragmentManager.findFragmentByTag(KEY_PEOPLE_INVITE_FRAGMENT);
+ if (peopleInviteFragment != null) {
+ ((InvitationSender) peopleInviteFragment).send();
+ }
+ }
+ return super.onOptionsItemSelected(item);
+ }
+
+ private boolean fetchUsersList(String dotComBlogId, final int localTableBlogId, final int offset) {
+ if (mUsersEndOfListReached || mUsersFetchRequestInProgress || !NetworkUtils.checkConnection(this)) {
+ return false;
+ }
+
+ mUsersFetchRequestInProgress = true;
+
+ PeopleUtils.fetchUsers(dotComBlogId, localTableBlogId, offset, new PeopleUtils.FetchUsersCallback() {
+ @Override
+ public void onSuccess(List<Person> peopleList, boolean isEndOfList) {
+ boolean isFreshList = (offset == 0);
+ mHasRefreshedUsers = true;
+ mUsersEndOfListReached = isEndOfList;
+ PeopleTable.saveUsers(peopleList, localTableBlogId, isFreshList);
+
+ PeopleListFragment peopleListFragment = getListFragment();
+ if (peopleListFragment != null) {
+ peopleListFragment.fetchingRequestFinished(PeopleListFilter.TEAM, isFreshList, true);
+ }
+
+ refreshOnScreenFragmentDetails();
+ mUsersFetchRequestInProgress = false;
+ }
+
+ @Override
+ public void onError() {
+ PeopleListFragment peopleListFragment = getListFragment();
+ if (peopleListFragment != null) {
+ boolean isFirstPage = offset == 0;
+ peopleListFragment.fetchingRequestFinished(PeopleListFilter.TEAM, isFirstPage, false);
+ }
+ mUsersFetchRequestInProgress = false;
+ ToastUtils.showToast(PeopleManagementActivity.this,
+ R.string.error_fetch_users_list,
+ ToastUtils.Duration.SHORT);
+ }
+ });
+
+ return true;
+ }
+
+ private boolean fetchFollowersList(String dotComBlogId, final int localTableBlogId, final int page) {
+ if (mFollowersEndOfListReached || mFollowersFetchRequestInProgress || !NetworkUtils.checkConnection(this)) {
+ return false;
+ }
+
+ mFollowersFetchRequestInProgress = true;
+
+ PeopleUtils.fetchFollowers(dotComBlogId, localTableBlogId, page, new PeopleUtils.FetchFollowersCallback() {
+ @Override
+ public void onSuccess(List<Person> peopleList, int pageFetched, boolean isEndOfList) {
+ boolean isFreshList = (page == 1);
+ mHasRefreshedFollowers = true;
+ mFollowersLastFetchedPage = pageFetched;
+ mFollowersEndOfListReached = isEndOfList;
+ PeopleTable.saveFollowers(peopleList, localTableBlogId, isFreshList);
+
+ PeopleListFragment peopleListFragment = getListFragment();
+ if (peopleListFragment != null) {
+ peopleListFragment.fetchingRequestFinished(PeopleListFilter.FOLLOWERS, isFreshList, true);
+ }
+
+ refreshOnScreenFragmentDetails();
+ mFollowersFetchRequestInProgress = false;
+ }
+
+ @Override
+ public void onError() {
+ PeopleListFragment peopleListFragment = getListFragment();
+ if (peopleListFragment != null) {
+ boolean isFirstPage = page == 1;
+ peopleListFragment.fetchingRequestFinished(PeopleListFilter.FOLLOWERS, isFirstPage, false);
+ }
+ mFollowersFetchRequestInProgress = false;
+ ToastUtils.showToast(PeopleManagementActivity.this,
+ R.string.error_fetch_followers_list,
+ ToastUtils.Duration.SHORT);
+ }
+ });
+
+ return true;
+ }
+
+ private boolean fetchEmailFollowersList(String dotComBlogId, final int localTableBlogId, final int page) {
+ if (mEmailFollowersEndOfListReached || mEmailFollowersFetchRequestInProgress || !NetworkUtils.checkConnection(this)) {
+ return false;
+ }
+
+ mEmailFollowersFetchRequestInProgress = true;
+
+ PeopleUtils.fetchEmailFollowers(dotComBlogId, localTableBlogId, page, new PeopleUtils.FetchFollowersCallback() {
+ @Override
+ public void onSuccess(List<Person> peopleList, int pageFetched, boolean isEndOfList) {
+ boolean isFreshList = (page == 1);
+ mHasRefreshedEmailFollowers = true;
+ mEmailFollowersLastFetchedPage = pageFetched;
+ mEmailFollowersEndOfListReached = isEndOfList;
+ PeopleTable.saveEmailFollowers(peopleList, localTableBlogId, isFreshList);
+
+ PeopleListFragment peopleListFragment = getListFragment();
+ if (peopleListFragment != null) {
+ peopleListFragment.fetchingRequestFinished(PeopleListFilter.EMAIL_FOLLOWERS, isFreshList, true);
+ }
+
+ refreshOnScreenFragmentDetails();
+ mEmailFollowersFetchRequestInProgress = false;
+ }
+
+ @Override
+ public void onError() {
+ PeopleListFragment peopleListFragment = getListFragment();
+ if (peopleListFragment != null) {
+ boolean isFirstPage = page == 1;
+ peopleListFragment.fetchingRequestFinished(PeopleListFilter.EMAIL_FOLLOWERS, isFirstPage, false);
+ }
+ mEmailFollowersFetchRequestInProgress = false;
+ ToastUtils.showToast(PeopleManagementActivity.this,
+ R.string.error_fetch_email_followers_list,
+ ToastUtils.Duration.SHORT);
+ }
+ });
+
+ return true;
+ }
+
+ private boolean fetchViewersList(String dotComBlogId, final int localTableBlogId, final int offset) {
+ if (mViewersEndOfListReached || mViewersFetchRequestInProgress || !NetworkUtils.checkConnection(this)) {
+ return false;
+ }
+
+ mViewersFetchRequestInProgress = true;
+
+ PeopleUtils.fetchViewers(dotComBlogId, localTableBlogId, offset, new PeopleUtils.FetchViewersCallback() {
+ @Override
+ public void onSuccess(List<Person> peopleList, boolean isEndOfList) {
+ boolean isFreshList = (offset == 0);
+ mHasRefreshedViewers = true;
+ mViewersEndOfListReached = isEndOfList;
+ PeopleTable.saveViewers(peopleList, localTableBlogId, isFreshList);
+
+ PeopleListFragment peopleListFragment = getListFragment();
+ if (peopleListFragment != null) {
+ peopleListFragment.fetchingRequestFinished(PeopleListFilter.VIEWERS, isFreshList, true);
+ }
+
+ refreshOnScreenFragmentDetails();
+ mViewersFetchRequestInProgress = false;
+ }
+
+ @Override
+ public void onError() {
+ PeopleListFragment peopleListFragment = getListFragment();
+ if (peopleListFragment != null) {
+ boolean isFirstPage = offset == 0;
+ peopleListFragment.fetchingRequestFinished(PeopleListFilter.VIEWERS, isFirstPage, false);
+ }
+ mViewersFetchRequestInProgress = false;
+ ToastUtils.showToast(PeopleManagementActivity.this,
+ R.string.error_fetch_viewers_list,
+ ToastUtils.Duration.SHORT);
+ }
+ });
+
+ return true;
+ }
+
+ @Override
+ public void onPersonSelected(Person person) {
+ PersonDetailFragment personDetailFragment = getDetailFragment();
+
+ long personID = person.getPersonID();
+ int localTableBlogID = person.getLocalTableBlogId();
+
+ if (personDetailFragment == null) {
+ personDetailFragment = PersonDetailFragment.newInstance(personID, localTableBlogID, person.getPersonType());
+ } else {
+ personDetailFragment.setPersonDetails(personID, localTableBlogID);
+ }
+ if (!personDetailFragment.isAdded()) {
+ AnalyticsUtils.trackWithCurrentBlogDetails(AnalyticsTracker.Stat.OPENED_PERSON);
+ FragmentTransaction fragmentTransaction = getFragmentManager().beginTransaction();
+ fragmentTransaction.replace(R.id.fragment_container, personDetailFragment, KEY_PERSON_DETAIL_FRAGMENT);
+ fragmentTransaction.addToBackStack(null);
+
+ ActionBar actionBar = getSupportActionBar();
+ if (actionBar != null) {
+ actionBar.setTitle("");
+ }
+
+ fragmentTransaction.commit();
+ }
+ }
+
+ public void onEventMainThread(RoleChangeDialogFragment.RoleChangeEvent event) {
+ if(!NetworkUtils.checkConnection(this)) {
+ return;
+ }
+
+ final Person person = PeopleTable.getUser(event.personID, event.localTableBlogId);
+ if (person == null || event.newRole == null || person.getRole() == event.newRole) {
+ return;
+ }
+
+ String blogId = WordPress.getCurrentRemoteBlogId();
+ if (blogId == null) {
+ return;
+ }
+
+ final PersonDetailFragment personDetailFragment = getDetailFragment();
+ if (personDetailFragment != null) {
+ // optimistically update the role
+ personDetailFragment.changeRole(event.newRole);
+ }
+
+ PeopleUtils.updateRole(blogId, person.getPersonID(), event.newRole, event.localTableBlogId,
+ new PeopleUtils.UpdateUserCallback() {
+ @Override
+ public void onSuccess(Person person) {
+ AnalyticsUtils.trackWithCurrentBlogDetails(AnalyticsTracker.Stat.PERSON_UPDATED);
+ PeopleTable.saveUser(person);
+ refreshOnScreenFragmentDetails();
+ }
+
+ @Override
+ public void onError() {
+ // change the role back to it's original value
+ if (personDetailFragment != null) {
+ personDetailFragment.refreshPersonDetails();
+ }
+ ToastUtils.showToast(PeopleManagementActivity.this,
+ R.string.error_update_role,
+ ToastUtils.Duration.LONG);
+ }
+ });
+ }
+
+ private void confirmRemovePerson() {
+ Person person = getCurrentPerson();
+ if (person == null) {
+ return;
+ }
+
+ AlertDialog.Builder builder = new AlertDialog.Builder(this, R.style.Calypso_AlertDialog);
+ builder.setTitle(getString(R.string.person_remove_confirmation_title, person.getDisplayName()));
+ if (person.getPersonType() == Person.PersonType.USER) {
+ builder.setMessage(getString(R.string.user_remove_confirmation_message, person.getDisplayName()));
+ } else if(person.getPersonType() == Person.PersonType.VIEWER) {
+ builder.setMessage(R.string.viewer_remove_confirmation_message);
+ } else {
+ builder.setMessage(R.string.follower_remove_confirmation_message);
+ }
+ builder.setNegativeButton(R.string.cancel, null);
+ builder.setPositiveButton(R.string.remove, new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ removeSelectedPerson();
+ }
+ });
+ builder.show();
+ }
+
+ private void removeSelectedPerson() {
+ if(!NetworkUtils.checkConnection(this)) {
+ return;
+ }
+
+ Person person = getCurrentPerson();
+ if (person == null) {
+ return;
+ }
+ String blogId = WordPress.getCurrentRemoteBlogId();
+ if (blogId == null) {
+ return;
+ }
+
+ final Person.PersonType personType = person.getPersonType();
+ final String displayName = person.getDisplayName();
+
+ PeopleUtils.RemovePersonCallback callback = new PeopleUtils.RemovePersonCallback() {
+ @Override
+ public void onSuccess(long personID, int localTableBlogId) {
+ if (personType == Person.PersonType.USER) {
+ AnalyticsUtils.trackWithCurrentBlogDetails(AnalyticsTracker.Stat.PERSON_REMOVED);
+ }
+
+ // remove the person from db, navigate back to list fragment and refresh it
+ PeopleTable.deletePerson(personID, localTableBlogId, personType);
+
+ String message = getString(R.string.person_removed, displayName);
+ ToastUtils.showToast(PeopleManagementActivity.this, message, ToastUtils.Duration.LONG);
+
+ navigateBackToPeopleListFragment();
+ refreshPeopleListFragment();
+ }
+
+ @Override
+ public void onError() {
+ int errorMessageRes;
+ switch (personType) {
+ case USER:
+ errorMessageRes = R.string.error_remove_user;
+ break;
+ case VIEWER:
+ errorMessageRes = R.string.error_remove_viewer;
+ break;
+ default:
+ errorMessageRes = R.string.error_remove_follower;
+ break;
+ }
+ ToastUtils.showToast(PeopleManagementActivity.this,
+ errorMessageRes,
+ ToastUtils.Duration.LONG);
+ }
+ };
+
+ if (personType == Person.PersonType.FOLLOWER || personType == Person.PersonType.EMAIL_FOLLOWER) {
+ PeopleUtils.removeFollower(blogId, person.getPersonID(), person.getLocalTableBlogId(),
+ personType, callback);
+ } else if(personType == Person.PersonType.VIEWER) {
+ PeopleUtils.removeViewer(blogId, person.getPersonID(), person.getLocalTableBlogId(), callback);
+ } else {
+ PeopleUtils.removeUser(blogId, person.getPersonID(), person.getLocalTableBlogId(), callback);
+ }
+ }
+
+ // This helper method is used after a successful network request
+ private void refreshOnScreenFragmentDetails() {
+ refreshPeopleListFragment();
+ refreshDetailFragment();
+ }
+
+ private void refreshPeopleListFragment() {
+ PeopleListFragment peopleListFragment = getListFragment();
+ if (peopleListFragment != null) {
+ peopleListFragment.refreshPeopleList(false);
+ }
+ }
+
+ private void refreshDetailFragment() {
+ PersonDetailFragment personDetailFragment = getDetailFragment();
+ if (personDetailFragment != null) {
+ personDetailFragment.refreshPersonDetails();
+ }
+ }
+
+ private boolean navigateBackToPeopleListFragment() {
+ FragmentManager fragmentManager = getFragmentManager();
+ if (fragmentManager.getBackStackEntryCount() > 0) {
+ fragmentManager.popBackStack();
+
+ ActionBar actionBar = getSupportActionBar();
+ if (actionBar != null) {
+ actionBar.setTitle(R.string.people);
+ }
+ return true;
+ }
+ return false;
+ }
+
+ private Person getCurrentPerson() {
+ PersonDetailFragment personDetailFragment = getDetailFragment();
+
+ if (personDetailFragment == null) {
+ return null;
+ }
+
+ return personDetailFragment.loadPerson();
+ }
+
+ @Override
+ public boolean onFetchFirstPage(PeopleListFilter filter) {
+ Blog blog = WordPress.getCurrentBlog();
+ if (filter == PeopleListFilter.TEAM && !mHasRefreshedUsers) {
+ return fetchUsersList(blog.getDotComBlogId(), blog.getLocalTableBlogId(), 0);
+ } else if (filter == PeopleListFilter.FOLLOWERS && !mHasRefreshedFollowers) {
+ return fetchFollowersList(blog.getDotComBlogId(), blog.getLocalTableBlogId(), 1);
+ } else if (filter == PeopleListFilter.EMAIL_FOLLOWERS && !mHasRefreshedEmailFollowers) {
+ return fetchEmailFollowersList(blog.getDotComBlogId(), blog.getLocalTableBlogId(), 1);
+ } else if (filter == PeopleListFilter.VIEWERS && !mHasRefreshedViewers) {
+ return fetchViewersList(blog.getDotComBlogId(), blog.getLocalTableBlogId(), 0);
+ }
+ return false;
+ }
+
+ @Override
+ public boolean onFetchMorePeople(PeopleListFilter filter) {
+ if (filter == PeopleListFilter.TEAM && !mUsersEndOfListReached) {
+ Blog blog = WordPress.getCurrentBlog();
+ int count = PeopleTable.getUsersCountForLocalBlogId(blog.getLocalTableBlogId());
+ return fetchUsersList(blog.getDotComBlogId(), blog.getLocalTableBlogId(), count);
+ } else if (filter == PeopleListFilter.FOLLOWERS && !mFollowersEndOfListReached) {
+ Blog blog = WordPress.getCurrentBlog();
+ int pageToFetch = mFollowersLastFetchedPage + 1;
+ return fetchFollowersList(blog.getDotComBlogId(), blog.getLocalTableBlogId(), pageToFetch);
+ } else if (filter == PeopleListFilter.EMAIL_FOLLOWERS && !mEmailFollowersEndOfListReached) {
+ Blog blog = WordPress.getCurrentBlog();
+ int pageToFetch = mEmailFollowersLastFetchedPage + 1;
+ return fetchEmailFollowersList(blog.getDotComBlogId(), blog.getLocalTableBlogId(), pageToFetch);
+ } else if (filter == PeopleListFilter.VIEWERS && !mViewersEndOfListReached) {
+ Blog blog = WordPress.getCurrentBlog();
+ int count = PeopleTable.getViewersCountForLocalBlogId(blog.getLocalTableBlogId());
+ return fetchViewersList(blog.getDotComBlogId(), blog.getLocalTableBlogId(), count);
+ }
+ return false;
+ }
+
+ private PeopleListFragment getListFragment() {
+ return (PeopleListFragment) getFragmentManager().findFragmentByTag(KEY_PEOPLE_LIST_FRAGMENT);
+ }
+
+ private PersonDetailFragment getDetailFragment() {
+ return (PersonDetailFragment) getFragmentManager().findFragmentByTag(KEY_PERSON_DETAIL_FRAGMENT);
+ }
+
+ public interface InvitationSender {
+ void send();
+ }
+}
diff --git a/WordPress/src/main/java/org/wordpress/android/ui/people/PersonDetailFragment.java b/WordPress/src/main/java/org/wordpress/android/ui/people/PersonDetailFragment.java
new file mode 100644
index 000000000..0687e23a4
--- /dev/null
+++ b/WordPress/src/main/java/org/wordpress/android/ui/people/PersonDetailFragment.java
@@ -0,0 +1,209 @@
+package org.wordpress.android.ui.people;
+
+import android.app.Fragment;
+import android.os.Bundle;
+import android.text.TextUtils;
+import android.view.LayoutInflater;
+import android.view.Menu;
+import android.view.MenuInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+
+import org.wordpress.android.R;
+import org.wordpress.android.WordPress;
+import org.wordpress.android.datasets.PeopleTable;
+import org.wordpress.android.models.Account;
+import org.wordpress.android.models.AccountHelper;
+import org.wordpress.android.models.Blog;
+import org.wordpress.android.models.Capability;
+import org.wordpress.android.models.Person;
+import org.wordpress.android.models.Role;
+import org.wordpress.android.util.AppLog;
+import org.wordpress.android.util.GravatarUtils;
+import org.wordpress.android.util.StringUtils;
+import org.wordpress.android.widgets.WPNetworkImageView;
+
+import java.text.SimpleDateFormat;
+
+public class PersonDetailFragment extends Fragment {
+ private static String ARG_PERSON_ID = "person_id";
+ private static String ARG_LOCAL_TABLE_BLOG_ID = "local_table_blog_id";
+ private static String ARG_PERSON_TYPE = "person_type";
+
+ private long mPersonID;
+ private int mLocalTableBlogID;
+ private Person.PersonType mPersonType;
+
+ private WPNetworkImageView mAvatarImageView;
+ private TextView mDisplayNameTextView;
+ private TextView mUsernameTextView;
+ private LinearLayout mRoleContainer;
+ private TextView mRoleTextView;
+ private LinearLayout mSubscribedDateContainer;
+ private TextView mSubscribedDateTitleView;
+ private TextView mSubscribedDateTextView;
+
+ public static PersonDetailFragment newInstance(long personID, int localTableBlogID, Person.PersonType personType) {
+ PersonDetailFragment personDetailFragment = new PersonDetailFragment();
+ Bundle bundle = new Bundle();
+ bundle.putLong(ARG_PERSON_ID, personID);
+ bundle.putInt(ARG_LOCAL_TABLE_BLOG_ID, localTableBlogID);
+ bundle.putSerializable(ARG_PERSON_TYPE, personType);
+ personDetailFragment.setArguments(bundle);
+ return personDetailFragment;
+ }
+
+ @Override
+ public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
+ inflater.inflate(R.menu.person_detail, menu);
+ super.onCreateOptionsMenu(menu, inflater);
+ }
+
+ @Override
+ public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
+ final ViewGroup rootView = (ViewGroup) inflater.inflate(R.layout.person_detail_fragment, container, false);
+
+ mPersonID = getArguments().getLong(ARG_PERSON_ID);
+ mLocalTableBlogID = getArguments().getInt(ARG_LOCAL_TABLE_BLOG_ID);
+ mPersonType = (Person.PersonType) getArguments().getSerializable(ARG_PERSON_TYPE);
+
+ mAvatarImageView = (WPNetworkImageView) rootView.findViewById(R.id.person_avatar);
+ mDisplayNameTextView = (TextView) rootView.findViewById(R.id.person_display_name);
+ mUsernameTextView = (TextView) rootView.findViewById(R.id.person_username);
+ mRoleContainer = (LinearLayout) rootView.findViewById(R.id.person_role_container);
+ mRoleTextView = (TextView) rootView.findViewById(R.id.person_role);
+ mSubscribedDateContainer = (LinearLayout) rootView.findViewById(R.id.subscribed_date_container);
+ mSubscribedDateTitleView = (TextView) rootView.findViewById(R.id.subscribed_date_title);
+ mSubscribedDateTextView = (TextView) rootView.findViewById(R.id.subscribed_date_text);
+
+ Account account = AccountHelper.getDefaultAccount();
+ boolean isCurrentUser = account.getUserId() == mPersonID;
+ Blog blog = WordPress.getBlog(mLocalTableBlogID);
+ if (!isCurrentUser && blog != null && blog.hasCapability(Capability.REMOVE_USERS)) {
+ setHasOptionsMenu(true);
+ }
+
+ return rootView;
+ }
+
+ @Override
+ public void onResume() {
+ super.onResume();
+
+ refreshPersonDetails();
+ }
+
+ public void refreshPersonDetails() {
+ if (!isAdded()) return;
+
+ Person person = loadPerson();
+ if (person != null) {
+ int avatarSz = getResources().getDimensionPixelSize(R.dimen.people_avatar_sz);
+ String avatarUrl = GravatarUtils.fixGravatarUrl(person.getAvatarUrl(), avatarSz);
+
+ mAvatarImageView.setImageUrl(avatarUrl, WPNetworkImageView.ImageType.AVATAR);
+ mDisplayNameTextView.setText(StringUtils.unescapeHTML(person.getDisplayName()));
+ if (person.getRole() != null) {
+ mRoleTextView.setText(StringUtils.capitalize(person.getRole().toDisplayString()));
+ }
+
+ if (!TextUtils.isEmpty(person.getUsername())) {
+ mUsernameTextView.setText(String.format("@%s", person.getUsername()));
+ }
+
+ if (mPersonType == Person.PersonType.USER) {
+ mRoleContainer.setVisibility(View.VISIBLE);
+ setupRoleContainerForCapability();
+ } else {
+ mRoleContainer.setVisibility(View.GONE);
+ }
+
+ if (mPersonType == Person.PersonType.USER || mPersonType == Person.PersonType.VIEWER) {
+ mSubscribedDateContainer.setVisibility(View.GONE);
+ } else {
+ mSubscribedDateContainer.setVisibility(View.VISIBLE);
+ if (mPersonType == Person.PersonType.FOLLOWER) {
+ mSubscribedDateTitleView.setText(R.string.title_follower);
+ } else if (mPersonType == Person.PersonType.EMAIL_FOLLOWER) {
+ mSubscribedDateTitleView.setText(R.string.title_email_follower);
+ }
+ String dateSubscribed = SimpleDateFormat.getDateInstance().format(person.getDateSubscribed());
+ String dateText = getString(R.string.follower_subscribed_since, dateSubscribed);
+ mSubscribedDateTextView.setText(dateText);
+ }
+
+ // Adds extra padding to display name for email followers to make it vertically centered
+ int padding = mPersonType == Person.PersonType.EMAIL_FOLLOWER
+ ? (int) getResources().getDimension(R.dimen.margin_small) : 0;
+ changeDisplayNameTopPadding(padding);
+ } else {
+ AppLog.w(AppLog.T.PEOPLE, "Person returned null from DB for personID: " + mPersonID
+ + " & localTableBlogID: " + mLocalTableBlogID);
+ }
+ }
+
+ public void setPersonDetails(long personID, int localTableBlogID) {
+ mPersonID = personID;
+ mLocalTableBlogID = localTableBlogID;
+ refreshPersonDetails();
+ }
+
+ // Checks current user's capabilities to decide whether she can change the role or not
+ private void setupRoleContainerForCapability() {
+ Blog blog = WordPress.getBlog(mLocalTableBlogID);
+ Account account = AccountHelper.getDefaultAccount();
+ boolean isCurrentUser = account.getUserId() == mPersonID;
+ boolean canChangeRole = (blog != null) && !isCurrentUser && blog.hasCapability(Capability.PROMOTE_USERS);
+ if (canChangeRole) {
+ mRoleContainer.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ showRoleChangeDialog();
+ }
+ });
+ } else {
+ // Remove the selectableItemBackground if the user can't be edited
+ clearRoleContainerBackground();
+ // Change transparency to give a visual cue to the user that it's disabled
+ mRoleContainer.setAlpha(0.5f);
+ }
+ }
+
+ private void showRoleChangeDialog() {
+ Person person = loadPerson();
+ if (person == null || person.getRole() == null) {
+ return;
+ }
+
+ RoleChangeDialogFragment dialog = RoleChangeDialogFragment.newInstance(person.getPersonID(),
+ person.getLocalTableBlogId(), person.getRole());
+ dialog.show(getFragmentManager(), null);
+ }
+
+ // used to optimistically update the role
+ public void changeRole(Role newRole) {
+ mRoleTextView.setText(newRole.toDisplayString());
+ }
+
+ @SuppressWarnings("deprecation")
+ private void clearRoleContainerBackground() {
+ if (android.os.Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.JELLY_BEAN) {
+ mRoleContainer.setBackgroundDrawable(null);
+ } else {
+ mRoleContainer.setBackground(null);
+ }
+ }
+
+ private void changeDisplayNameTopPadding(int newPadding) {
+ if (mDisplayNameTextView == null) {
+ return;
+ }
+ mDisplayNameTextView.setPadding(0, newPadding, 0 , 0);
+ }
+
+ public Person loadPerson() {
+ return PeopleTable.getPerson(mPersonID, mLocalTableBlogID, mPersonType);
+ }
+}
diff --git a/WordPress/src/main/java/org/wordpress/android/ui/people/RoleChangeDialogFragment.java b/WordPress/src/main/java/org/wordpress/android/ui/people/RoleChangeDialogFragment.java
new file mode 100644
index 000000000..438231fe3
--- /dev/null
+++ b/WordPress/src/main/java/org/wordpress/android/ui/people/RoleChangeDialogFragment.java
@@ -0,0 +1,148 @@
+package org.wordpress.android.ui.people;
+
+import android.app.AlertDialog;
+import android.app.Dialog;
+import android.app.DialogFragment;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.os.Bundle;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ArrayAdapter;
+import android.widget.RadioButton;
+import android.widget.TextView;
+
+import org.wordpress.android.R;
+import org.wordpress.android.models.Role;
+
+import de.greenrobot.event.EventBus;
+
+public class RoleChangeDialogFragment extends DialogFragment {
+ private static final String PERSON_ID_TAG = "person_id";
+ private static final String PERSON_LOCAL_TABLE_BLOG_ID_TAG = "local_table_blog_id";
+ private static final String ROLE_TAG = "role";
+
+ private RoleListAdapter mRoleListAdapter;
+
+ @Override
+ public void onSaveInstanceState(Bundle outState) {
+ super.onSaveInstanceState(outState);
+ Role role = mRoleListAdapter.getSelectedRole();
+ outState.putSerializable(ROLE_TAG, role);
+ }
+
+ public static RoleChangeDialogFragment newInstance(long personID, int localTableBlogId, Role role) {
+ RoleChangeDialogFragment roleChangeDialogFragment = new RoleChangeDialogFragment();
+ Bundle args = new Bundle();
+
+ args.putLong(PERSON_ID_TAG, personID);
+ args.putInt(PERSON_LOCAL_TABLE_BLOG_ID_TAG, localTableBlogId);
+ if (role != null) {
+ args.putSerializable(ROLE_TAG, role);
+ }
+
+ roleChangeDialogFragment.setArguments(args);
+ return roleChangeDialogFragment;
+ }
+
+ @Override
+ public Dialog onCreateDialog(Bundle savedInstanceState) {
+ AlertDialog.Builder builder = new AlertDialog.Builder(getActivity(), R.style.Calypso_AlertDialog);
+ builder.setTitle(R.string.role);
+ builder.setNegativeButton(R.string.cancel, null);
+ builder.setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ Role role = mRoleListAdapter.getSelectedRole();
+ Bundle args = getArguments();
+ if (args != null) {
+ long personID = args.getLong(PERSON_ID_TAG);
+ int localTableBlogId = args.getInt(PERSON_LOCAL_TABLE_BLOG_ID_TAG);
+ EventBus.getDefault().post(new RoleChangeEvent(personID, localTableBlogId, role));
+ }
+ }
+ });
+
+ if (mRoleListAdapter == null) {
+ final Role[] userRoles = Role.userRoles();
+ mRoleListAdapter = new RoleListAdapter(getActivity(), R.layout.role_list_row, userRoles);
+ }
+ if (savedInstanceState != null) {
+ Role savedRole = (Role) savedInstanceState.getSerializable(ROLE_TAG);
+ mRoleListAdapter.setSelectedRole(savedRole);
+ } else {
+ Bundle args = getArguments();
+ if (args != null) {
+ Role role = (Role) args.getSerializable(ROLE_TAG);
+ mRoleListAdapter.setSelectedRole(role);
+ }
+ }
+ builder.setAdapter(mRoleListAdapter, null);
+
+ return builder.create();
+ }
+
+ private class RoleListAdapter extends ArrayAdapter<Role> {
+ private Role mSelectedRole;
+
+ public RoleListAdapter(Context context, int resource, Role[] objects) {
+ super(context, resource, objects);
+ }
+
+ @Override
+ public View getView(final int position, View convertView, ViewGroup parent) {
+ if (convertView == null) {
+ convertView = View.inflate(getContext(), R.layout.role_list_row, null);
+ }
+
+ final RadioButton radioButton = (RadioButton) convertView.findViewById(R.id.radio);
+ TextView mainText = (TextView) convertView.findViewById(R.id.role_label);
+ Role role = getItem(position);
+ mainText.setText(role.toDisplayString());
+
+ if (radioButton != null) {
+ radioButton.setChecked(role == mSelectedRole);
+ radioButton.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ changeSelection(position);
+ }
+ });
+ }
+
+ convertView.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ changeSelection(position);
+ }
+ });
+
+ return convertView;
+ }
+
+ private void changeSelection(int position) {
+ mSelectedRole = getItem(position);
+ notifyDataSetChanged();
+ }
+
+ public Role getSelectedRole() {
+ return mSelectedRole;
+ }
+
+ public void setSelectedRole(Role role) {
+ mSelectedRole = role;
+ }
+ }
+
+ public static class RoleChangeEvent {
+ public final long personID;
+ public final int localTableBlogId;
+ public final Role newRole;
+
+ public RoleChangeEvent(long personID, int localTableBlogId, Role newRole) {
+ this.personID = personID;
+ this.localTableBlogId = localTableBlogId;
+ this.newRole = newRole;
+ }
+ }
+}
diff --git a/WordPress/src/main/java/org/wordpress/android/ui/people/RoleSelectDialogFragment.java b/WordPress/src/main/java/org/wordpress/android/ui/people/RoleSelectDialogFragment.java
new file mode 100644
index 000000000..9c39fc559
--- /dev/null
+++ b/WordPress/src/main/java/org/wordpress/android/ui/people/RoleSelectDialogFragment.java
@@ -0,0 +1,66 @@
+package org.wordpress.android.ui.people;
+
+import android.app.Activity;
+import android.app.AlertDialog;
+import android.app.Dialog;
+import android.app.DialogFragment;
+import android.app.Fragment;
+import android.content.DialogInterface;
+import android.os.Bundle;
+
+import org.wordpress.android.R;
+import org.wordpress.android.models.Role;
+
+public class RoleSelectDialogFragment extends DialogFragment {
+ private static final String IS_PRIVATE_TAG = "is_private";
+
+ @Override
+ public Dialog onCreateDialog(Bundle savedInstanceState) {
+ boolean isPrivateSite = getArguments().getBoolean(IS_PRIVATE_TAG);
+ final Role[] roles = Role.inviteRoles(isPrivateSite);
+ final String[] stringRoles = new String[roles.length];
+ for (int i = 0; i < roles.length; i++) {
+ stringRoles[i] = roles[i].toDisplayString();
+ }
+
+
+ AlertDialog.Builder builder = new AlertDialog.Builder(getActivity(), R.style.Calypso_AlertDialog);
+ builder.setTitle(R.string.role);
+ builder.setItems(stringRoles, new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ if (!isAdded()) {
+ return;
+ }
+
+ if (getTargetFragment() instanceof OnRoleSelectListener) {
+ ((OnRoleSelectListener) getTargetFragment()).onRoleSelected(roles[which]);
+ } else if (getActivity() instanceof OnRoleSelectListener) {
+ ((OnRoleSelectListener) getActivity()).onRoleSelected(roles[which]);
+ }
+ }
+ });
+
+ return builder.create();
+ }
+
+ public static <T extends Fragment & OnRoleSelectListener> void show(T parentFragment, int requestCode,
+ boolean isPrivateSite) {
+ RoleSelectDialogFragment roleChangeDialogFragment = new RoleSelectDialogFragment();
+ Bundle args = new Bundle();
+ args.putBoolean(IS_PRIVATE_TAG, isPrivateSite);
+ roleChangeDialogFragment.setArguments(args);
+ roleChangeDialogFragment.setTargetFragment(parentFragment, requestCode);
+ roleChangeDialogFragment.show(parentFragment.getFragmentManager(), null);
+ }
+
+ public static <T extends Activity & OnRoleSelectListener> void show(T parentActivity) {
+ RoleSelectDialogFragment roleChangeDialogFragment = new RoleSelectDialogFragment();
+ roleChangeDialogFragment.show(parentActivity.getFragmentManager(), null);
+ }
+
+ // Container Activity must implement this interface
+ public interface OnRoleSelectListener {
+ void onRoleSelected(Role newRole);
+ }
+}
diff --git a/WordPress/src/main/java/org/wordpress/android/ui/people/utils/PeopleUtils.java b/WordPress/src/main/java/org/wordpress/android/ui/people/utils/PeopleUtils.java
new file mode 100644
index 000000000..5c01c6ddf
--- /dev/null
+++ b/WordPress/src/main/java/org/wordpress/android/ui/people/utils/PeopleUtils.java
@@ -0,0 +1,527 @@
+package org.wordpress.android.ui.people.utils;
+
+import com.android.volley.VolleyError;
+import com.wordpress.rest.RestRequest;
+
+import org.json.JSONArray;
+import org.json.JSONException;
+import org.json.JSONObject;
+import org.wordpress.android.WordPress;
+import org.wordpress.android.models.Person;
+import org.wordpress.android.models.Role;
+import org.wordpress.android.ui.people.utils.PeopleUtils.ValidateUsernameCallback.ValidationResult;
+import org.wordpress.android.util.AppLog;
+import org.wordpress.android.util.AppLog.T;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+
+public class PeopleUtils {
+ // We limit followers we display to 1000 to avoid API performance issues
+ public static int FOLLOWER_PAGE_LIMIT = 50;
+ public static int FETCH_LIMIT = 20;
+
+ public static void fetchUsers(final String blogId, final int localTableBlogId, final int offset,
+ final FetchUsersCallback callback) {
+ com.wordpress.rest.RestRequest.Listener listener = new RestRequest.Listener() {
+ @Override
+ public void onResponse(JSONObject jsonObject) {
+ if (jsonObject != null && callback != null) {
+ try {
+ JSONArray jsonArray = jsonObject.getJSONArray("users");
+ List<Person> people = peopleListFromJSON(jsonArray, localTableBlogId, Person.PersonType.USER);
+ int numberOfUsers = jsonObject.optInt("found");
+ boolean isEndOfList = (people.size() + offset) >= numberOfUsers;
+ callback.onSuccess(people, isEndOfList);
+ }
+ catch (JSONException e) {
+ AppLog.e(T.API, "JSON exception occurred while parsing the response for sites/%s/users: " + e);
+ callback.onError();
+ }
+ }
+ }
+ };
+
+ RestRequest.ErrorListener errorListener = new RestRequest.ErrorListener() {
+ @Override
+ public void onErrorResponse(VolleyError volleyError) {
+ AppLog.e(T.API, volleyError);
+ if (callback != null) {
+ callback.onError();
+ }
+ }
+ };
+
+ Map<String, String> params = new HashMap<>();
+ params.put("number", Integer.toString(PeopleUtils.FETCH_LIMIT));
+ params.put("offset", Integer.toString(offset));
+ params.put("order_by", "display_name");
+ params.put("order", "ASC");
+ String path = String.format("sites/%s/users", blogId);
+ WordPress.getRestClientUtilsV1_1().get(path, params, null, listener, errorListener);
+ }
+
+ public static void fetchFollowers(final String blogId, final int localTableBlogId, final int page,
+ final FetchFollowersCallback callback) {
+ fetchFollowers(blogId, localTableBlogId, page, callback, false);
+ }
+
+ public static void fetchEmailFollowers(final String blogId, final int localTableBlogId, final int page,
+ final FetchFollowersCallback callback) {
+ fetchFollowers(blogId, localTableBlogId, page, callback, true);
+ }
+
+ private static void fetchFollowers(final String blogId, final int localTableBlogId, final int page,
+ final FetchFollowersCallback callback, final boolean isEmailFollower) {
+ com.wordpress.rest.RestRequest.Listener listener = new RestRequest.Listener() {
+ @Override
+ public void onResponse(JSONObject jsonObject) {
+ if (jsonObject != null && callback != null) {
+ try {
+ JSONArray jsonArray = jsonObject.getJSONArray("subscribers");
+ Person.PersonType personType = isEmailFollower ?
+ Person.PersonType.EMAIL_FOLLOWER : Person.PersonType.FOLLOWER;
+ List<Person> people = peopleListFromJSON(jsonArray, localTableBlogId, personType);
+ int pageFetched = jsonObject.optInt("page");
+ int numberOfPages = jsonObject.optInt("pages");
+ boolean isEndOfList = page >= numberOfPages || page >= FOLLOWER_PAGE_LIMIT;
+ callback.onSuccess(people, pageFetched, isEndOfList);
+ }
+ catch (JSONException e) {
+ AppLog.e(T.API, "JSON exception occurred while parsing the response for " +
+ "sites/%s/stats/followers: " + e);
+ callback.onError();
+ }
+ }
+ }
+ };
+
+ RestRequest.ErrorListener errorListener = new RestRequest.ErrorListener() {
+ @Override
+ public void onErrorResponse(VolleyError volleyError) {
+ AppLog.e(T.API, volleyError);
+ if (callback != null) {
+ callback.onError();
+ }
+ }
+ };
+
+ Map<String, String> params = new HashMap<>();
+ params.put("max", Integer.toString(FETCH_LIMIT));
+ params.put("page", Integer.toString(page));
+ params.put("type", isEmailFollower ? "email" : "wp_com");
+ String path = String.format("sites/%s/stats/followers", blogId);
+ WordPress.getRestClientUtilsV1_1().get(path, params, null, listener, errorListener);
+ }
+
+ public static void fetchViewers(final String blogId, final int localTableBlogId, final int offset,
+ final FetchViewersCallback callback) {
+ com.wordpress.rest.RestRequest.Listener listener = new RestRequest.Listener() {
+ @Override
+ public void onResponse(JSONObject jsonObject) {
+ if (jsonObject != null && callback != null) {
+ try {
+ JSONArray jsonArray = jsonObject.getJSONArray("viewers");
+ List<Person> people = peopleListFromJSON(jsonArray, localTableBlogId, Person.PersonType.VIEWER);
+ int numberOfUsers = jsonObject.optInt("found");
+ boolean isEndOfList = (people.size() + offset) >= numberOfUsers;
+ callback.onSuccess(people, isEndOfList);
+ }
+ catch (JSONException e) {
+ AppLog.e(T.API, "JSON exception occurred while parsing the response for " +
+ "sites/%s/viewers: " + e);
+ callback.onError();
+ }
+ }
+ }
+ };
+
+ RestRequest.ErrorListener errorListener = new RestRequest.ErrorListener() {
+ @Override
+ public void onErrorResponse(VolleyError volleyError) {
+ AppLog.e(T.API, volleyError);
+ if (callback != null) {
+ callback.onError();
+ }
+ }
+ };
+
+ int page = (offset / FETCH_LIMIT) + 1;
+ Map<String, String> params = new HashMap<>();
+ params.put("number", Integer.toString(FETCH_LIMIT));
+ params.put("page", Integer.toString(page));
+ String path = String.format("sites/%s/viewers", blogId);
+ WordPress.getRestClientUtilsV1_1().get(path, params, null, listener, errorListener);
+ }
+
+ public static void updateRole(final String blogId, long personID, Role newRole, final int localTableBlogId,
+ final UpdateUserCallback callback) {
+ com.wordpress.rest.RestRequest.Listener listener = new RestRequest.Listener() {
+ @Override
+ public void onResponse(JSONObject jsonObject) {
+ if (jsonObject != null && callback != null) {
+ try {
+ Person person = Person.userFromJSON(jsonObject, localTableBlogId);
+ if (person != null) {
+ callback.onSuccess(person);
+ } else {
+ AppLog.e(T.API, "Couldn't map jsonObject + " + jsonObject + " to person model.");
+ callback.onError();
+ }
+ } catch (JSONException e) {
+ callback.onError();
+ }
+ }
+ }
+ };
+
+ RestRequest.ErrorListener errorListener = new RestRequest.ErrorListener() {
+ @Override
+ public void onErrorResponse(VolleyError volleyError) {
+ AppLog.e(T.API, volleyError);
+ if (callback != null) {
+ callback.onError();
+ }
+ }
+ };
+
+ Map<String, String> params = new HashMap<>();
+ params.put("roles", newRole.toRESTString());
+ String path = String.format("sites/%s/users/%d", blogId, personID);
+ WordPress.getRestClientUtilsV1_1().post(path, params, null, listener, errorListener);
+ }
+
+ public static void removeUser(String blogId, final long personID, final int localTableBlogId,
+ final RemovePersonCallback callback) {
+ com.wordpress.rest.RestRequest.Listener listener = new RestRequest.Listener() {
+ @Override
+ public void onResponse(JSONObject jsonObject) {
+ if (jsonObject != null && callback != null) {
+ // check if the call was successful
+ boolean success = jsonObject.optBoolean("success");
+ if (success) {
+ callback.onSuccess(personID, localTableBlogId);
+ } else {
+ callback.onError();
+ }
+ }
+ }
+ };
+
+ RestRequest.ErrorListener errorListener = new RestRequest.ErrorListener() {
+ @Override
+ public void onErrorResponse(VolleyError volleyError) {
+ AppLog.e(T.API, volleyError);
+ if (callback != null) {
+ callback.onError();
+ }
+ }
+ };
+
+ String path = String.format("sites/%s/users/%d/delete", blogId, personID);
+ WordPress.getRestClientUtilsV1_1().post(path, listener, errorListener);
+ }
+
+ public static void removeFollower(String blogId, final long personID, final int localTableBlogId,
+ Person.PersonType personType, final RemovePersonCallback callback) {
+ com.wordpress.rest.RestRequest.Listener listener = new RestRequest.Listener() {
+ @Override
+ public void onResponse(JSONObject jsonObject) {
+ if (jsonObject != null && callback != null) {
+ // check if the call was successful
+ boolean success = jsonObject.optBoolean("deleted");
+ if (success) {
+ callback.onSuccess(personID, localTableBlogId);
+ } else {
+ callback.onError();
+ }
+ }
+ }
+ };
+
+ RestRequest.ErrorListener errorListener = new RestRequest.ErrorListener() {
+ @Override
+ public void onErrorResponse(VolleyError volleyError) {
+ AppLog.e(T.API, volleyError);
+ if (callback != null) {
+ callback.onError();
+ }
+ }
+ };
+
+ String path;
+ if (personType == Person.PersonType.EMAIL_FOLLOWER) {
+ path = String.format("sites/%s/email-followers/%d/delete", blogId, personID);
+ } else {
+ path = String.format("sites/%s/followers/%d/delete", blogId, personID);
+ }
+ WordPress.getRestClientUtilsV1_1().post(path, listener, errorListener);
+ }
+
+ public static void removeViewer(String blogId, final long personID, final int localTableBlogId,
+ final RemovePersonCallback callback) {
+ com.wordpress.rest.RestRequest.Listener listener = new RestRequest.Listener() {
+ @Override
+ public void onResponse(JSONObject jsonObject) {
+ if (jsonObject != null && callback != null) {
+ // check if the call was successful
+ boolean success = jsonObject.optBoolean("deleted");
+ if (success) {
+ callback.onSuccess(personID, localTableBlogId);
+ } else {
+ callback.onError();
+ }
+ }
+ }
+ };
+
+ RestRequest.ErrorListener errorListener = new RestRequest.ErrorListener() {
+ @Override
+ public void onErrorResponse(VolleyError volleyError) {
+ AppLog.e(T.API, volleyError);
+ if (callback != null) {
+ callback.onError();
+ }
+ }
+ };
+
+ String path = String.format("sites/%s/viewers/%d/delete", blogId, personID);
+ WordPress.getRestClientUtilsV1_1().post(path, listener, errorListener);
+ }
+
+ private static List<Person> peopleListFromJSON(JSONArray jsonArray, int localTableBlogId,
+ Person.PersonType personType) throws JSONException {
+ if (jsonArray == null) {
+ return null;
+ }
+
+ ArrayList<Person> peopleList = new ArrayList<>(jsonArray.length());
+
+ for (int i = 0; i < jsonArray.length(); i++) {
+ Person person;
+ if (personType == Person.PersonType.USER) {
+ person = Person.userFromJSON(jsonArray.optJSONObject(i), localTableBlogId);
+ } else if (personType == Person.PersonType.VIEWER) {
+ person = Person.viewerFromJSON(jsonArray.optJSONObject(i), localTableBlogId);
+ } else {
+ boolean isEmailFollower = (personType == Person.PersonType.EMAIL_FOLLOWER);
+ person = Person.followerFromJSON(jsonArray.optJSONObject(i), localTableBlogId, isEmailFollower);
+ }
+ if (person != null) {
+ peopleList.add(person);
+ }
+ }
+
+ return peopleList;
+ }
+
+ public interface FetchUsersCallback extends Callback {
+ void onSuccess(List<Person> peopleList, boolean isEndOfList);
+ }
+
+ public interface FetchFollowersCallback extends Callback {
+ void onSuccess(List<Person> peopleList, int pageFetched, boolean isEndOfList);
+ }
+
+ public interface FetchViewersCallback extends Callback {
+ void onSuccess(List<Person> peopleList, boolean isEndOfList);
+ }
+
+ public interface RemovePersonCallback extends Callback {
+ void onSuccess(long personID, int localTableBlogId);
+ }
+
+ public interface UpdateUserCallback extends Callback {
+ void onSuccess(Person person);
+ }
+
+ public interface Callback {
+ void onError();
+ }
+
+ public static void validateUsernames(final List<String> usernames, Role role, String dotComBlogId, final
+ ValidateUsernameCallback callback) {
+ com.wordpress.rest.RestRequest.Listener listener = new RestRequest.Listener() {
+ @Override
+ public void onResponse(JSONObject jsonObject) {
+ if (jsonObject != null && callback != null) {
+ JSONObject errors = jsonObject.optJSONObject("errors");
+
+ int errorredUsernameCount = 0;
+
+ if (errors != null) {
+ for (String username : usernames) {
+ JSONObject userError = errors.optJSONObject(username);
+
+ if (userError == null) {
+ continue;
+ }
+
+ errorredUsernameCount++;
+
+ switch (userError.optString("code")) {
+ case "invalid_input":
+ switch (userError.optString("message")) {
+ case "User not found":
+ callback.onUsernameValidation(username, ValidationResult.USER_NOT_FOUND);
+ continue;
+ case "Invalid email":
+ callback.onUsernameValidation(username, ValidationResult.INVALID_EMAIL);
+ continue;
+ }
+ break;
+ case "invalid_input_has_role":
+ callback.onUsernameValidation(username, ValidationResult.ALREADY_MEMBER);
+ continue;
+ case "invalid_input_following":
+ callback.onUsernameValidation(username, ValidationResult.ALREADY_FOLLOWING);
+ continue;
+ case "invalid_user_blocked_invites":
+ callback.onUsernameValidation(username, ValidationResult.BLOCKED_INVITES);
+ continue;
+ }
+
+ callback.onError();
+ callback.onValidationFinished();
+ return;
+ }
+ }
+
+ JSONArray succeededUsernames = jsonObject.optJSONArray("success");
+ if (succeededUsernames == null) {
+ callback.onError();
+ callback.onValidationFinished();
+ return;
+ }
+
+ int succeededUsernameCount = 0;
+
+ for (int i = 0; i < succeededUsernames.length(); i++) {
+ String username = succeededUsernames.optString(i);
+ if (usernames.contains(username)) {
+ succeededUsernameCount++;
+ callback.onUsernameValidation(username, ValidationResult.USER_FOUND);
+ }
+ }
+
+ if (errorredUsernameCount + succeededUsernameCount != usernames.size()) {
+ callback.onError();
+ callback.onValidationFinished();
+ }
+
+ callback.onValidationFinished();
+ }
+ }
+ };
+
+ RestRequest.ErrorListener errorListener = new RestRequest.ErrorListener() {
+ @Override
+ public void onErrorResponse(VolleyError volleyError) {
+ AppLog.e(AppLog.T.API, volleyError);
+ if (callback != null) {
+ callback.onError();
+ }
+ }
+ };
+
+ String path = String.format("sites/%s/invites/validate", dotComBlogId);
+ Map<String, String> params = new HashMap<>();
+ for (String username : usernames) {
+ params.put("invitees[" + username + "]", username); // specify an array key so to make the map key unique
+ }
+ params.put("role", role.toRESTString());
+ WordPress.getRestClientUtilsV1_1().post(path, params, null, listener, errorListener);
+ }
+
+ public interface ValidateUsernameCallback {
+ enum ValidationResult {
+ USER_NOT_FOUND,
+ ALREADY_MEMBER,
+ ALREADY_FOLLOWING,
+ BLOCKED_INVITES,
+ INVALID_EMAIL,
+ USER_FOUND
+ }
+
+ void onUsernameValidation(String username, ValidationResult validationResult);
+ void onValidationFinished();
+ void onError();
+ }
+
+ public static void sendInvitations(final List<String> usernames, Role role, String message, String dotComBlogId,
+ final InvitationsSendCallback callback) {
+ com.wordpress.rest.RestRequest.Listener listener = new RestRequest.Listener() {
+ @Override
+ public void onResponse(JSONObject jsonObject) {
+ if (callback == null) {
+ return;
+ }
+
+ if (jsonObject == null) {
+ callback.onError();
+ return;
+ }
+
+ Map<String, String> failedUsernames = new LinkedHashMap<>();
+
+ JSONObject errors = jsonObject.optJSONObject("errors");
+ if (errors != null) {
+ for (String username : usernames) {
+ JSONObject userError = errors.optJSONObject(username);
+
+ if (userError != null) {
+ failedUsernames.put(username, userError.optString("message"));
+ }
+ }
+ }
+
+ List<String> succeededUsernames = new ArrayList<>();
+ JSONArray succeededUsernamesJson = jsonObject.optJSONArray("sent");
+ if (succeededUsernamesJson == null) {
+ callback.onError();
+ return;
+ }
+
+ for (int i = 0; i < succeededUsernamesJson.length(); i++) {
+ String username = succeededUsernamesJson.optString(i);
+ if (usernames.contains(username)) {
+ succeededUsernames.add(username);
+ }
+ }
+
+ if (failedUsernames.size() + succeededUsernames.size() != usernames.size()) {
+ callback.onError();
+ }
+
+ callback.onSent(succeededUsernames, failedUsernames);
+ }
+ };
+
+ RestRequest.ErrorListener errorListener = new RestRequest.ErrorListener() {
+ @Override
+ public void onErrorResponse(VolleyError volleyError) {
+ AppLog.e(AppLog.T.API, volleyError);
+ if (callback != null) {
+ callback.onError();
+ }
+ }
+ };
+
+ String path = String.format("sites/%s/invites/new", dotComBlogId);
+ Map<String, String> params = new HashMap<>();
+ for (String username : usernames) {
+ params.put("invitees[" + username + "]", username); // specify an array key so to make the map key unique
+ }
+ params.put("role", role.toRESTString());
+ params.put("message", message);
+ WordPress.getRestClientUtilsV1_1().post(path, params, null, listener, errorListener);
+ }
+
+ public interface InvitationsSendCallback {
+ void onSent(List<String> succeededUsernames, Map<String, String> failedUsernameErrors);
+ void onError();
+ }
+}