aboutsummaryrefslogtreecommitdiff
path: root/WordPress/src/main/java/org/wordpress/android/ui/people/PeopleInviteFragment.java
diff options
context:
space:
mode:
Diffstat (limited to 'WordPress/src/main/java/org/wordpress/android/ui/people/PeopleInviteFragment.java')
-rw-r--r--WordPress/src/main/java/org/wordpress/android/ui/people/PeopleInviteFragment.java667
1 files changed, 667 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();
+ }
+}