/**
* Wire
* Copyright (C) 2016 Wire Swiss GmbH
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.waz.zclient.pages.main.pickuser;
import android.Manifest;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.support.v4.app.ActivityCompat;
import android.support.v7.app.AlertDialog;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.support.v7.widget.Toolbar;
import android.text.TextUtils;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.animation.Animation;
import android.widget.LinearLayout;
import android.widget.ListView;
import android.widget.TextView;
import android.widget.Toast;
import com.waz.api.ContactDetails;
import com.waz.api.ContactMethod;
import com.waz.api.Contacts;
import com.waz.api.IConversation;
import com.waz.api.NetworkMode;
import com.waz.api.User;
import com.waz.api.UserSearchResult;
import com.waz.zclient.BaseActivity;
import com.waz.zclient.OnBackPressedListener;
import com.waz.zclient.R;
import com.waz.zclient.controllers.accentcolor.AccentColorObserver;
import com.waz.zclient.controllers.currentfocus.IFocusController;
import com.waz.zclient.controllers.globallayout.KeyboardVisibilityObserver;
import com.waz.zclient.controllers.navigation.NavigationController;
import com.waz.zclient.controllers.permission.RequestPermissionsObserver;
import com.waz.zclient.controllers.tracking.events.connect.EnteredSearchEvent;
import com.waz.zclient.controllers.tracking.events.connect.OpenedConversationEvent;
import com.waz.zclient.controllers.tracking.events.connect.OpenedGenericInviteMenuEvent;
import com.waz.zclient.controllers.tracking.events.connect.SentConnectRequestEvent;
import com.waz.zclient.controllers.tracking.screens.ApplicationScreen;
import com.waz.zclient.core.api.scala.ModelObserver;
import com.waz.zclient.core.controllers.tracking.attributes.ConversationType;
import com.waz.zclient.core.stores.conversation.ConversationChangeRequester;
import com.waz.zclient.core.stores.conversation.InboxLoadRequester;
import com.waz.zclient.core.stores.conversation.OnInboxLoadedListener;
import com.waz.zclient.core.stores.network.DefaultNetworkAction;
import com.waz.zclient.core.stores.pickuser.PickUserStoreObserver;
import com.waz.zclient.pages.BaseFragment;
import com.waz.zclient.pages.main.participants.dialog.DialogLaunchMode;
import com.waz.zclient.pages.main.participants.views.ChatheadWithTextFooter;
import com.waz.zclient.pages.main.pickuser.controller.IPickUserController;
import com.waz.zclient.pages.main.pickuser.controller.PickUserControllerSearchObserver;
import com.waz.zclient.pages.main.pickuser.controller.PickUserDataState;
import com.waz.zclient.pages.main.pickuser.views.ContactRowView;
import com.waz.zclient.pages.main.pickuser.views.SearchBoxView;
import com.waz.zclient.pages.main.pickuser.views.UserRowView;
import com.waz.zclient.tracking.GlobalTrackingController;
import com.waz.zclient.ui.animation.fragment.FadeAnimation;
import com.waz.zclient.ui.startui.ConversationQuickMenu;
import com.waz.zclient.ui.startui.ConversationQuickMenuCallback;
import com.waz.zclient.ui.text.TypefaceTextView;
import com.waz.zclient.ui.utils.KeyboardUtils;
import com.waz.zclient.ui.views.ZetaButton;
import com.waz.zclient.utils.IntentUtils;
import com.waz.zclient.utils.LayoutSpec;
import com.waz.zclient.utils.PermissionUtils;
import com.waz.zclient.utils.StringUtils;
import com.waz.zclient.utils.TrackingUtils;
import com.waz.zclient.utils.ViewUtils;
import com.waz.zclient.utils.device.DeviceDetector;
import com.waz.zclient.views.DefaultPageTransitionAnimation;
import com.waz.zclient.views.LoadingIndicatorView;
import com.waz.zclient.views.PickableElement;
import hugo.weaving.DebugLog;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
public class PickUserFragment extends BaseFragment<PickUserFragment.Container> implements View.OnClickListener,
OnInboxLoadedListener,
PickUserStoreObserver,
KeyboardVisibilityObserver,
AccentColorObserver,
ConversationQuickMenuCallback,
OnBackPressedListener,
SearchResultOnItemTouchListener.Callback,
SearchResultAdapter.Callback,
PickUserControllerSearchObserver,
RequestPermissionsObserver {
public static final String TAG = PickUserFragment.class.getName();
public static final String ARGUMENT_ADD_TO_CONVERSATION = "ARGUMENT_ADD_TO_CONVERSATION";
public static final String ARGUMENT_GROUP_CONVERSATION = "ARGUMENT_GROUP_CONVERSATION";
public static final int NUM_SEARCH_RESULTS_LIST = 30;
public static final int NUM_SEARCH_RESULTS_TOP_USERS = 24;
public static final int NUM_SEARCH_RESULTS_ADD_TO_CONV = 1000;
private static final int DEFAULT_SELECTED_INVITE_METHOD = 0;
private static final int SHOW_KEYBOARD_THRETHOLD = 10;
private RecyclerView searchResultRecyclerView;
private SearchResultAdapter searchResultAdapter;
// Saves user from which a pending connect request is loaded
private User pendingFromUser;
private Toolbar toolbar;
private TextView toolbarHeader;
private View divider;
private TypefaceTextView errorMessageViewHeader;
private LinearLayout errorMessageViewSendInvite;
private TypefaceTextView errorMessageViewBody;
private LinearLayout errorMessageViewContainer;
private ConversationQuickMenu conversationQuickMenu;
private ZetaButton userSelectionConfirmationButton;
private View userSelectionConfirmationContainer;
private View genericInviteContainer;
private ZetaButton genericInviteButton;
private SearchBoxView searchBoxView;
private boolean isKeyboardVisible;
private boolean searchBoxIsEmpty = true;
private long showLoadingBarDelay;
private boolean lastInputIsKeyboardDoneAction;
private AlertDialog dialog;
private static final boolean SHOW_INVITE = true;
final public SearchBoxView.Callback searchBoxViewCallback = new SearchBoxView.Callback() {
@Override
public void onRemovedTokenSpan(PickableElement element) {
User user = getStoreFactory().getPickUserStore().getUser(element.id());
getControllerFactory().getPickUserController().removeUser(user);
if (isAddingToConversation()) {
setConversationQuickMenuVisible(false);
}
}
@Override
public void onKeyboardDoneAction() {
getControllerFactory().getPickUserController().notifyKeyboardDoneAction();
}
@Override
public void onFocusChange(boolean hasFocus) {
setFocusByCurrentPickerDestination();
}
@Override
public void onClearButton() {
closeStartUI();
}
@Override
public void afterTextChanged(String s) {
if (getControllerFactory().isTornDown()) {
return;
}
getControllerFactory().getPickUserController().setSearchFilter(searchBoxView.getSearchFilter());
}
};
private final ModelObserver<UserSearchResult> usersSearchModelObserver = new ModelObserver<UserSearchResult>() {
@Override
public void updated(UserSearchResult model) {
if (getContainer() != null) {
getContainer().getLoadingViewIndicator().hide();
}
if (model.getAll().length == 0) {
PickUserDataState dataState = getDataState(getControllerFactory().getPickUserController().hasSelectedUsers());
if (dataState == PickUserDataState.SHOW_ALL_USERS_TO_ADD_TO_CONVERSATION) {
handleEmptySearchResult(getString(R.string.people_picker__error_message__no_users_to_add_to_conversation), "", false);
} else {
handleEmptySearchResult(getString(R.string.people_picker__error_message__no_results), "", false);
}
} else {
hideErrorMessage();
searchResultAdapter.setSearchResult(model.getAll(), null, null);
}
}
};
public static PickUserFragment newInstance(boolean addToConversation) {
PickUserFragment fragment = new PickUserFragment();
Bundle args = new Bundle();
args.putBoolean(ARGUMENT_ADD_TO_CONVERSATION, addToConversation);
args.putBoolean(ARGUMENT_GROUP_CONVERSATION, false);
fragment.setArguments(args);
return fragment;
}
public static PickUserFragment newInstance(boolean addToConversation, boolean groupConversation) {
PickUserFragment fragment = new PickUserFragment();
Bundle args = new Bundle();
args.putBoolean(ARGUMENT_ADD_TO_CONVERSATION, addToConversation);
args.putBoolean(ARGUMENT_GROUP_CONVERSATION, groupConversation);
fragment.setArguments(args);
return fragment;
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//
// Lifecycle
//
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
@Override
public Animation onCreateAnimation(int transit, boolean enter, int nextAnim) {
if (nextAnim == 0 ||
getContainer() == null ||
getControllerFactory().isTornDown()) {
return super.onCreateAnimation(transit, enter, nextAnim);
}
if (getControllerFactory().getPickUserController().isHideWithoutAnimations()) {
return new DefaultPageTransitionAnimation(0, ViewUtils.getOrientationIndependentDisplayHeight(getActivity()), enter, 0, 0, 1f);
}
if (enter) {
// Fade animation in participants dialog on tablet
if (LayoutSpec.isTablet(getActivity()) && isAddingToConversation()) {
return new FadeAnimation(getResources().getInteger(R.integer.open_new_conversation__top_conversation__animation_duration), 0f, 1f);
}
return new DefaultPageTransitionAnimation(0,
getResources().getDimensionPixelSize(R.dimen.open_new_conversation__thread_list__max_top_distance),
enter,
getResources().getInteger(R.integer.framework_animation_duration_long),
getResources().getInteger(R.integer.framework_animation_duration_medium),
1f);
}
return new DefaultPageTransitionAnimation(0,
getResources().getDimensionPixelSize(R.dimen.open_new_conversation__thread_list__max_top_distance),
enter,
getResources().getInteger(R.integer.framework_animation_duration_medium),
0,
1f);
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup viewContainer, Bundle savedInstanceState) {
View rootView = inflater.inflate(R.layout.fragment_pick_user, viewContainer, false);
toolbarHeader = ViewUtils.getView(rootView, R.id.ttv__pickuser__add_header);
toolbar = ViewUtils.getView(rootView, R.id.t_pickuser_toolbar);
toolbar.setNavigationOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
closeStartUI();
}
});
divider = ViewUtils.getView(rootView, R.id.v__pickuser__divider);
searchResultAdapter = new SearchResultAdapter(this);
searchResultAdapter.setTopUsersOnItemTouchListener(new SearchResultOnItemTouchListener(getActivity(), this));
searchResultRecyclerView = ViewUtils.getView(rootView, R.id.rv__pickuser__header_list_view);
searchResultRecyclerView.setLayoutManager(new LinearLayoutManager(getActivity()));
searchResultRecyclerView.setAdapter(searchResultAdapter);
searchResultRecyclerView.addOnItemTouchListener(new SearchResultOnItemTouchListener(getActivity(), this));
if (isAddingToConversation()) {
searchResultRecyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
@Override
public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
if (newState == RecyclerView.SCROLL_STATE_DRAGGING &&
getControllerFactory().getGlobalLayoutController().isKeyboardVisible()) {
KeyboardUtils.hideKeyboard(getActivity());
}
}
});
}
searchBoxView = ViewUtils.getView(rootView, R.id.sbv__search_box);
searchBoxView.applyLightTheme(!getControllerFactory().getThemeController().isDarkTheme());
searchBoxView.setAccentColor(getControllerFactory().getAccentColorController().getColor());
searchBoxView.setCallback(searchBoxViewCallback);
conversationQuickMenu = ViewUtils.getView(rootView, R.id.cqm__pickuser__quick_menu);
conversationQuickMenu.setCallback(this);
conversationQuickMenu.setVisibility(View.GONE);
userSelectionConfirmationContainer = ViewUtils.getView(rootView, R.id.fl__pickuser__confirmation_button_container);
userSelectionConfirmationContainer.setVisibility(View.GONE);
userSelectionConfirmationButton = ViewUtils.getView(rootView, R.id.zb__pickuser__confirmation_button);
genericInviteContainer = ViewUtils.getView(rootView, R.id.fl__pickuser__generic_invite__container);
genericInviteButton = ViewUtils.getView(rootView, R.id.zb__pickuser__generic_invite);
genericInviteButton.setIsFilled(false);
// Error message
errorMessageViewContainer = ViewUtils.getView(rootView, R.id.fl_pickuser__error_message_container);
errorMessageViewContainer.setVisibility(View.GONE);
errorMessageViewHeader = ViewUtils.getView(rootView, R.id.ttv_pickuser__error_header);
errorMessageViewBody = ViewUtils.getView(rootView, R.id.ttv_pickuser__error_body);
errorMessageViewSendInvite = ViewUtils.getView(rootView, R.id.ll_pickuser__error_invite);
showLoadingBarDelay = getResources().getInteger(R.integer.people_picker__loading_bar__show_delay);
if (isAddingToConversation()) {
genericInviteContainer.setVisibility(View.GONE);
searchBoxView.setHintText(getString(R.string.pick_user_search__add_to_conversation));
searchBoxView.showClearButton(false);
divider.setVisibility(View.VISIBLE);
toolbar.setVisibility(View.VISIBLE);
toolbarHeader.setText(getArguments().getBoolean(ARGUMENT_GROUP_CONVERSATION) ?
getString(R.string.people_picker__toolbar_header__group) :
getString(R.string.people_picker__toolbar_header__one_to_one));
userSelectionConfirmationButton.setText(getArguments().getBoolean(ARGUMENT_GROUP_CONVERSATION) ?
getString(R.string.people_picker__confirm_button_title__add_to_conversation) :
getString(R.string.people_picker__confirm_button_title__create_conversation));
ViewUtils.setHeight(searchBoxView, getResources().getDimensionPixelSize(R.dimen.searchbox__height__with_toolbar));
} else {
// Use constant style for left side start ui
int textColor;
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
//noinspection deprecation
textColor = getResources().getColor(R.color.text__primary_dark);
} else {
textColor = getResources().getColor(R.color.text__primary_dark, getContext().getTheme());
}
errorMessageViewHeader.setTextColor(textColor);
errorMessageViewBody.setTextColor(textColor);
TextView errorMessageIcon = ViewUtils.getView(rootView, R.id.gtv_pickuser__error_icon);
errorMessageIcon.setTextColor(textColor);
TextView errorMessageSublabel = ViewUtils.getView(rootView, R.id.ttv_pickuser__error_sublabel);
errorMessageSublabel.setTextColor(textColor);
searchResultAdapter.setDarkTheme(true);
searchBoxView.forceDarkTheme();
searchBoxView.setHintText(getString(R.string.pick_user_search_focus));
searchBoxView.showClearButton(true);
toolbar.setVisibility(View.GONE);
divider.setVisibility(View.GONE);
}
return rootView;
}
@Override
public void onStart() {
super.onStart();
getStoreFactory().getPickUserStore().addPickUserStoreObserver(this);
getControllerFactory().getGlobalLayoutController().addKeyboardVisibilityObserver(this);
getControllerFactory().getAccentColorController().addAccentColorObserver(this);
getControllerFactory().getPickUserController().addPickUserSearchControllerObserver(this);
getControllerFactory().getRequestPermissionsController().addObserver(this);
if (isAddingToConversation() && !getArguments().getBoolean(ARGUMENT_GROUP_CONVERSATION)) {
new Handler().post(new Runnable() {
@Override
public void run() {
if (getControllerFactory() == null || getControllerFactory().isTornDown()) {
return;
}
for (User user : getControllerFactory().getPickUserController().getSelectedUsers()) {
searchBoxView.addUser(user);
}
}
});
} else {
getControllerFactory().getPickUserController().setSearchFilter("");
}
loadStartUi();
usersSearchModelObserver.resumeListening();
usersSearchModelObserver.forceUpdate();
boolean hasShareContactsEnabled = getControllerFactory().getUserPreferencesController().hasShareContactsEnabled();
boolean hasContactsReadPermission = PermissionUtils.hasSelfPermissions(getContext(), Manifest.permission.READ_CONTACTS);
if (hasShareContactsEnabled && !hasContactsReadPermission) {
ActivityCompat.requestPermissions(getActivity(), new String[]{Manifest.permission.READ_CONTACTS}, PermissionUtils.REQUEST_READ_CONTACTS);
}
}
@Override
public void onResume() {
super.onResume();
genericInviteButton.setOnClickListener(this);
userSelectionConfirmationButton.setOnClickListener(this);
errorMessageViewSendInvite.setOnClickListener(this);
if (!isAddingToConversation()) {
new Handler().postDelayed(new Runnable() {
@Override
public void run() {
if (getStoreFactory() == null || getStoreFactory().isTornDown()) {
return;
}
final int numberOfActiveConversations = getStoreFactory().getConversationStore().getNumberOfActiveConversations();
if (searchBoxView == null ||
numberOfActiveConversations <= SHOW_KEYBOARD_THRETHOLD) {
return;
}
searchBoxView.setFocus();
KeyboardUtils.showKeyboard(getActivity());
}
}, getResources().getInteger(R.integer.people_picker__keyboard__show_delay));
}
}
@Override
public void onPause() {
genericInviteButton.setOnClickListener(null);
userSelectionConfirmationButton.setOnClickListener(null);
errorMessageViewSendInvite.setOnClickListener(null);
super.onPause();
}
@Override
public void onStop() {
getContainer().getLoadingViewIndicator().hide();
getStoreFactory().getPickUserStore().removePickUserStoreObserver(this);
getControllerFactory().getGlobalLayoutController().removeKeyboardVisibilityObserver(this);
getControllerFactory().getAccentColorController().removeAccentColorObserver(this);
getControllerFactory().getPickUserController().removePickUserSearchControllerObserver(this);
getControllerFactory().getRequestPermissionsController().removeObserver(this);
usersSearchModelObserver.pauseListening();
super.onStop();
}
@Override
public void onDestroyView() {
errorMessageViewHeader = null;
errorMessageViewSendInvite = null;
errorMessageViewBody = null;
errorMessageViewContainer = null;
searchResultAdapter.reset();
searchResultRecyclerView = null;
conversationQuickMenu = null;
userSelectionConfirmationContainer = null;
userSelectionConfirmationButton = null;
genericInviteButton = null;
genericInviteContainer = null;
searchBoxView = null;
toolbar = null;
toolbarHeader = null;
super.onDestroyView();
}
@Override
public boolean onBackPressed() {
if (isKeyboardVisible) {
KeyboardUtils.hideKeyboard(getActivity());
} else if (getControllerFactory().getPickUserController().isShowingUserProfile()) {
getControllerFactory().getPickUserController().hideUserProfile();
return true;
}
return isKeyboardVisible;
}
@Override
public void onConnectRequestInboxConversationsLoaded(List<IConversation> conversations,
InboxLoadRequester inboxLoadRequester) {
for (IConversation conversation : conversations) {
if (conversation.getId().equals(pendingFromUser.getConversation().getId())) {
getContainer().showIncomingPendingConnectRequest(conversation);
return;
}
}
}
@Override
public void onTopUsersUpdated(User[] users) {
if (isAddingToConversation()) {
return;
}
getContainer().getLoadingViewIndicator().hide();
PickUserDataState dataState = getDataState(getControllerFactory().getPickUserController().hasSelectedUsers());
switch (dataState) {
case SHOW_ALL_USERS_TO_ADD_TO_CONVERSATION:
if (users == null || users.length == 0) {
handleEmptySearchResult(getString(R.string.people_picker__error_message__no_users_to_add_to_conversation), "", false);
} else {
hideErrorMessage();
searchResultAdapter.setSearchResult(users, null, null);
}
break;
case SHOW_TOP_USERS_AS_LIST:
if (users == null || users.length == 0) {
if (TextUtils.isEmpty(getControllerFactory().getPickUserController().getSearchFilter())) {
UserSearchResult usersSearchResult = getStoreFactory().getZMessagingApiStore()
.getApi()
.search()
.getConnectionsByName("", NUM_SEARCH_RESULTS_ADD_TO_CONV, new String[0]);
usersSearchModelObserver.setAndUpdate(usersSearchResult);
} else {
handleEmptySearchResult(getString(R.string.people_picker__error_message__no_results), "", false);
}
} else {
hideErrorMessage();
searchResultAdapter.setSearchResult(users, null, null);
}
break;
case SHOW_TOP_USERS_AND_RECOMMENDED:
searchResultAdapter.setTopUsers(users);
getStoreFactory().getPickUserStore().loadContacts();
break;
}
}
@Override
public void onSearchResultsUpdated(User[] connectedUsers, User[] otherUsers, IConversation[] conversations) {
getContainer().getLoadingViewIndicator().hide();
String errorHeader = getString(R.string.people_picker__error_message__no_results);
String errorBody;
if (isAddingToConversation()) {
errorBody = getString(R.string.people_picker__error_message__no_results__shared_contacts__add_to_conversation);
} else {
errorBody = getControllerFactory().getPickUserController().searchInputIsInvalidEmail() ?
getString(R.string.people_picker__error_message__no_results__invalid_email__no_shared_contacts) :
getString(R.string.people_picker__error_message__no_results__no_shared_contacts);
}
switch (getDataState(getControllerFactory().getPickUserController().hasSelectedUsers())) {
case SHOW_SEARCH_RESULTS_TO_ADD_TO_CONVERSATION:
if (connectedUsers.length == 0) {
handleEmptySearchResult(errorHeader, errorBody, !lastInputIsKeyboardDoneAction);
} else {
hideErrorMessage();
searchResultAdapter.setSearchResult(connectedUsers, null, null);
}
break;
case SHOW_SEARCH_RESULTS:
if (getControllerFactory().getPickUserController().hasSelectedUsers()) {
// Hide group conversations if there are selected users
if (connectedUsers.length == 0) {
handleEmptySearchResult(errorHeader, errorBody, !lastInputIsKeyboardDoneAction);
} else {
hideErrorMessage();
searchResultAdapter.setSearchResult(connectedUsers, null, null);
}
} else {
if (connectedUsers.length == 0 &&
otherUsers.length == 0 &&
conversations.length == 0 &&
!searchResultAdapter.hasContacts()) {
handleEmptySearchResult(errorHeader, errorBody, !lastInputIsKeyboardDoneAction);
} else {
hideErrorMessage();
searchResultAdapter.setSearchResult(connectedUsers, otherUsers, conversations);
}
}
break;
}
}
@Override
public void onContactsUpdated(Contacts contacts) {
if (contacts == null ||
contacts.size() == 0) {
return;
}
hideErrorMessage();
searchResultAdapter.setContacts(contacts);
}
@Override
public void onSearchContactsUpdated(Contacts contacts) {
if (contacts == null) {
return;
}
searchResultAdapter.setContacts(contacts);
}
@Override
public void onKeyboardVisibilityChanged(boolean keyboardIsVisible, int keyboardHeight, View currentFocus) {
int color;
isKeyboardVisible = keyboardIsVisible;
if (keyboardIsVisible || !searchBoxIsEmpty) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
//noinspection deprecation
color = getResources().getColor(R.color.people_picker__loading__color);
} else {
color = getResources().getColor(R.color.people_picker__loading__color, getContext().getTheme());
}
} else {
color = getControllerFactory().getAccentColorController().getColor();
}
getContainer().getLoadingViewIndicator().setColor(color);
if (isAddingToConversation()) {
return;
}
int inviteVisibility = keyboardIsVisible || getControllerFactory().getPickUserController().hasSelectedUsers() ? View.GONE : View.VISIBLE;
genericInviteContainer.setVisibility(inviteVisibility);
}
@Override
public void onAccentColorHasChanged(Object sender, int color) {
conversationQuickMenu.setAccentColor(color);
userSelectionConfirmationButton.setAccentColor(color);
searchResultAdapter.setAccentColor(color);
genericInviteButton.setAccentColor(color, true);
searchBoxView.setAccentColor(color);
}
//////////////////////////////////////////////////////////////////////////////////////////
//
// ConversationQuickMenuCallback
//
//////////////////////////////////////////////////////////////////////////////////////////
@Override
public void onConversationButtonClicked() {
KeyboardUtils.hideKeyboard(getActivity());
List<User> users = getControllerFactory().getPickUserController().getSelectedUsers();
getContainer().onSelectedUsers(users, ConversationChangeRequester.START_CONVERSATION);
}
@Override
public void onVideoCallButtonClicked() {
KeyboardUtils.hideKeyboard(getActivity());
List<User> users = getControllerFactory().getPickUserController().getSelectedUsers();
if (users.size() > 1) {
throw new IllegalStateException("A video call cannot be started with more than one user. The button should not be visible " +
"if multiple users are selected.");
}
getContainer().onSelectedUsers(users, ConversationChangeRequester.START_CONVERSATION_FOR_VIDEO_CALL);
}
@Override
public void onCallButtonClicked() {
KeyboardUtils.hideKeyboard(getActivity());
List<User> users = getControllerFactory().getPickUserController().getSelectedUsers();
getContainer().onSelectedUsers(users, ConversationChangeRequester.START_CONVERSATION_FOR_CALL);
// TODO: Uncomment when call issue is resolved with https://wearezeta.atlassian.net/browse/CM-675
//getStoreFactory().getGroupCallingStore().startCall();
}
@Override
public void onCameraButtonClicked() {
KeyboardUtils.hideKeyboard(getActivity());
List<User> users = getControllerFactory().getPickUserController().getSelectedUsers();
getContainer().onSelectedUsers(users, ConversationChangeRequester.START_CONVERSATION_FOR_CAMERA);
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//
// PickUserControllerSearchObserver - Search actions
//
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
@Override
public void onSearchBoxIsEmpty() {
searchBoxIsEmpty = true;
lastInputIsKeyboardDoneAction = false;
setConversationQuickMenuVisible(false);
loadStartUi();
}
@Override
public void onSearchBoxHasNewSearchFilter(String filter) {
searchBoxIsEmpty = false;
lastInputIsKeyboardDoneAction = false;
loadStartUi();
}
@Override
public void onKeyboardDoneAction() {
lastInputIsKeyboardDoneAction = true;
List<User> users = getControllerFactory().getPickUserController().getSelectedUsers();
int minUsers = 1;
if (isAddingToConversation() && !getArguments().getBoolean(ARGUMENT_GROUP_CONVERSATION)) {
minUsers = 2;
}
if (users.size() >= minUsers) {
KeyboardUtils.hideKeyboard(getActivity());
getContainer().onSelectedUsers(users, ConversationChangeRequester.START_CONVERSATION);
}
if (searchResultRecyclerView != null &&
searchResultRecyclerView.getVisibility() != View.VISIBLE &&
errorMessageViewContainer.getVisibility() != View.VISIBLE) {
showErrorMessage();
}
}
@Override
public void onSelectedUserAdded(List<User> selectedUsers, User addedUser) {
changeUserSelectedState(addedUser, true);
updateConversationButtonLabel();
conversationQuickMenu.showVideoCallButton(shouldVideoCallButtonBeDisplayed());
searchBoxView.addUser(addedUser);
}
private boolean shouldVideoCallButtonBeDisplayed() {
return getControllerFactory().getPickUserController().getSelectedUsers().size() == 1 &&
DeviceDetector.isVideoCallingEnabled();
}
@Override
public void onSelectedUserRemoved(List<User> selectedUsers, User removedUser) {
changeUserSelectedState(removedUser, false);
updateConversationButtonLabel();
conversationQuickMenu.showVideoCallButton(shouldVideoCallButtonBeDisplayed());
searchBoxView.removeUser(removedUser);
}
private void changeUserSelectedState(User user, boolean selected) {
changeUserSelectedState(searchResultRecyclerView, user, selected);
if (!searchResultAdapter.hasTopUsers()) {
return;
}
for (int i = 0; i < searchResultRecyclerView.getChildCount(); i++) {
View rowView = searchResultRecyclerView.getChildAt(i);
if (rowView instanceof RecyclerView) {
changeUserSelectedState(((RecyclerView) rowView), user, selected);
}
}
}
private void changeUserSelectedState(RecyclerView rv, User user, boolean selected) {
for (int i = 0; i < rv.getChildCount(); i++) {
View rowView = rv.getChildAt(i);
if (rowView instanceof UserRowView) {
UserRowView userRowView = (UserRowView) rowView;
if (userRowView.getUser() != null &&
userRowView.getUser().equals(user)) {
userRowView.setSelected(selected);
}
}
}
}
//////////////////////////////////////////////////////////////////////////////////////////
//
// SearchResultOnItemTouchListener.Callback
//
//////////////////////////////////////////////////////////////////////////////////////////
@Override
public void onUserClicked(User user, int position, View anchorView) {
if (user == null ||
user.isMe() ||
getControllerFactory() == null ||
getControllerFactory().isTornDown()) {
return;
}
TrackingUtils.onUserSelectedInStartUI(((BaseActivity) getActivity()).injectJava(GlobalTrackingController.class),
user,
anchorView instanceof ChatheadWithTextFooter,
isAddingToConversation(),
position,
searchResultAdapter);
// Selecting user from search results toggles user token and confirmation button
if (user.getConnectionStatus() == User.ConnectionStatus.ACCEPTED) {
if (anchorView.isSelected()) {
getControllerFactory().getPickUserController().addUser(user);
} else {
getControllerFactory().getPickUserController().removeUser(user);
}
setConversationQuickMenuVisible(getControllerFactory().getPickUserController().hasSelectedUsers());
return;
}
if (anchorView instanceof ContactRowView &&
user.getConnectionStatus() == User.ConnectionStatus.UNCONNECTED) {
return;
}
showUser(user, anchorView);
}
@Override
public void onUserDoubleClicked(User user, int position, View anchorView) {
if (!(anchorView instanceof ChatheadWithTextFooter)) {
return;
}
if (user == null ||
user.isMe() ||
user.getConnectionStatus() != User.ConnectionStatus.ACCEPTED ||
getControllerFactory().getPickUserController().hasSelectedUsers()) {
return;
}
((BaseActivity) getActivity()).injectJava(GlobalTrackingController.class).tagEvent(new OpenedConversationEvent(ConversationType.ONE_TO_ONE_CONVERSATION.name(),
OpenedConversationEvent.Context.TOPUSER_DOUBLETAP,
(position + 1)));
getStoreFactory().getConversationStore().setCurrentConversation(user.getConversation(),
ConversationChangeRequester.START_CONVERSATION);
}
@Override
public void onConversationClicked(IConversation conversation, int position) {
KeyboardUtils.hideKeyboard(getActivity());
((BaseActivity) getActivity()).injectJava(GlobalTrackingController.class).tagEvent(new OpenedConversationEvent(ConversationType.GROUP_CONVERSATION.name(),
OpenedConversationEvent.Context.SEARCH,
searchResultAdapter.getConversationInternalPosition(position)));
getStoreFactory().getConversationStore().setCurrentConversation(conversation,
ConversationChangeRequester.START_CONVERSATION);
}
//////////////////////////////////////////////////////////////////////////////////////////
//
// SearchResultAdapter.Callback
//
//////////////////////////////////////////////////////////////////////////////////////////
@Override
public Set<User> getSelectedUsers() {
if (getControllerFactory() == null ||
getControllerFactory().isTornDown()) {
return null;
}
return new HashSet<>(getControllerFactory().getPickUserController().getSelectedUsers());
}
@Override
public void onContactListUserClicked(User user) {
if (getStoreFactory() == null ||
getStoreFactory().isTornDown() ||
user == null) {
return;
}
// ACCEPTED user connection status handled by onUserClicked callback
switch (user.getConnectionStatus()) {
case UNCONNECTED:
User me = getStoreFactory().getProfileStore().getSelfUser();
String myName = me != null ? me.getName() : "";
String message = getString(R.string.connect__message, user.getName(), myName);
user.connect(message);
((BaseActivity) getActivity()).injectJava(GlobalTrackingController.class).tagEvent(new SentConnectRequestEvent(SentConnectRequestEvent.EventContext.INVITE_CONTACT_LIST, user.getCommonConnectionsCount()));
break;
}
}
@Override
public void onContactListContactClicked(final ContactDetails contactDetails) {
getStoreFactory().getNetworkStore().doIfHasInternetOrNotifyUser(new DefaultNetworkAction() {
@Override
public void execute(NetworkMode networkMode) {
final int contactMethodsCount = contactDetails.getContactMethods().size();
final ContactMethod[] contactMethods = contactDetails.getContactMethods().toArray(new ContactMethod[contactMethodsCount]);
if (contactMethodsCount == 1 &&
contactMethods[0].getKind() == ContactMethod.Kind.SMS) {
// Launch SMS app directly if contact only has phone numner
final String number = contactMethods[0].getStringRepresentation();
sendSMSInvite(number);
((BaseActivity) getActivity()).injectJava(GlobalTrackingController.class).tagEvent(new OpenedGenericInviteMenuEvent(OpenedGenericInviteMenuEvent.EventContext.ADDRESSBOOK));
return;
}
final CharSequence[] itemNames = new CharSequence[contactMethodsCount];
for (int i = 0; i < contactMethodsCount; i++) {
ContactMethod contactMethod = contactMethods[i];
itemNames[i] = contactMethod.getStringRepresentation();
}
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
builder.setTitle(getResources().getString(R.string.people_picker__contact_list__invite_dialog__title))
.setPositiveButton(getResources().getText(R.string.confirmation_menu__confirm_done),
new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialogInterface, int i) {
ListView lv = dialog.getListView();
int selected = lv.getCheckedItemPosition();
ContactMethod selectedContactMethod = null;
if (selected >= 0) {
selectedContactMethod = contactMethods[selected];
}
if (selectedContactMethod == null) {
return;
}
if (selectedContactMethod.getKind() == ContactMethod.Kind.SMS) {
final String number = String.valueOf(itemNames[selected]);
sendSMSInvite(number);
((BaseActivity) getActivity()).injectJava(GlobalTrackingController.class).tagEvent(new OpenedGenericInviteMenuEvent(OpenedGenericInviteMenuEvent.EventContext.ADDRESSBOOK));
} else {
selectedContactMethod.invite(" ", null);
Toast.makeText(getActivity(),
getResources().getString(R.string.people_picker__invite__sent_feedback),
Toast.LENGTH_LONG).show();
boolean fromSearch = TextUtils.isEmpty(getControllerFactory().getPickUserController().getSearchFilter());
TrackingUtils.tagSentInviteToContactEvent(((BaseActivity) getActivity()).injectJava(GlobalTrackingController.class),
selectedContactMethod.getKind(),
contactDetails.hasBeenInvited(),
fromSearch);
}
}
})
.setNegativeButton(getResources().getText(R.string.confirmation_menu__cancel),
new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialogInterface, int i) {
dialogInterface.cancel();
}
})
.setSingleChoiceItems(itemNames,
DEFAULT_SELECTED_INVITE_METHOD,
null);
dialog = builder.create();
dialog.show();
((BaseActivity) getActivity()).injectJava(GlobalTrackingController.class).onApplicationScreen(ApplicationScreen.SEND_PERSONAL_INVITE_MENU);
}
});
}
private void sendSMSInvite(String number) {
User me = getStoreFactory().getProfileStore().getSelfUser();
if (me != null) {
String smsBody;
String username = me.getUsername();
if (TextUtils.isEmpty(username)) {
smsBody = getString(R.string.people_picker__invite__share_text__body, me.getName());
} else {
smsBody = getString(R.string.people_picker__invite__share_text__body, StringUtils.formatHandle(username));
}
Intent intent = new Intent(Intent.ACTION_SENDTO, Uri.fromParts("sms", number, ""));
intent.putExtra("sms_body", smsBody);
startActivity(intent);
}
}
@Override
public int getDestination() {
return IPickUserController.STARTUI;
}
//////////////////////////////////////////////////////////////////////////////////////////
//
// List and grid helpers
//
//////////////////////////////////////////////////////////////////////////////////////////
@DebugLog
public void loadStartUi() {
if (searchResultAdapter.getItemCount() == 0) {
getContainer().getLoadingViewIndicator().show(LoadingIndicatorView.SPINNER);
} else {
getContainer().getLoadingViewIndicator().show(LoadingIndicatorView.INFINITE_LOADING_BAR, showLoadingBarDelay);
}
String filter = getControllerFactory().getPickUserController().getSearchFilter();
boolean hasSelectedUsers = getControllerFactory().getPickUserController().hasSelectedUsers();
switch (getDataState(hasSelectedUsers)) {
case SHOW_ALL_USERS_TO_ADD_TO_CONVERSATION:
filter = "";
case SHOW_SEARCH_RESULTS_TO_ADD_TO_CONVERSATION:
String[] excludedUsers = getStoreFactory().getPickUserStore().getExcludedUsers();
UserSearchResult usersSearchResult = getStoreFactory().getZMessagingApiStore()
.getApi()
.search()
.getConnectionsByName(filter, NUM_SEARCH_RESULTS_ADD_TO_CONV, excludedUsers);
usersSearchModelObserver.setAndUpdate(usersSearchResult);
break;
case SHOW_SEARCH_RESULTS:
getStoreFactory().getPickUserStore().loadSearchByFilter(filter, NUM_SEARCH_RESULTS_LIST, false);
if (!hasSelectedUsers) {
getStoreFactory().getPickUserStore().searchContacts(filter);
}
((BaseActivity) getActivity()).injectJava(GlobalTrackingController.class).tagEvent(new EnteredSearchEvent(isAddingToConversation(), filter));
break;
case SHOW_TOP_USERS_AS_LIST:
case SHOW_TOP_USERS_AND_RECOMMENDED:
boolean excludeConversationParticipants = isAddingToConversation() &&
getArguments().getBoolean(ARGUMENT_GROUP_CONVERSATION);
getStoreFactory().getPickUserStore().loadTopUserList(NUM_SEARCH_RESULTS_TOP_USERS, excludeConversationParticipants);
break;
}
}
private void handleEmptySearchResult(String errorHeader, String errorBody, boolean showBlankScreen) {
setErrorMessage(errorHeader, errorBody);
if (showBlankScreen) {
errorMessageViewContainer.setVisibility(View.INVISIBLE);
searchResultRecyclerView.setClickable(false);
searchResultRecyclerView.setVisibility(View.GONE);
} else {
showErrorMessage();
}
}
private void setErrorMessage(String header, String body) {
errorMessageViewHeader.setText(header);
if (body != null) {
errorMessageViewBody.setText(body);
errorMessageViewBody.setVisibility(View.VISIBLE);
} else {
errorMessageViewBody.setVisibility(View.GONE);
}
if (SHOW_INVITE && !isAddingToConversation()) {
errorMessageViewSendInvite.setVisibility(View.VISIBLE);
} else {
errorMessageViewSendInvite.setVisibility(View.GONE);
}
}
private void showErrorMessage() {
errorMessageViewContainer.setVisibility(View.VISIBLE);
// Set isClickable as ListView continues to receive click events with GONE visibility
searchResultRecyclerView.setClickable(false);
searchResultRecyclerView.setVisibility(View.GONE);
}
private void hideErrorMessage() {
errorMessageViewContainer.setVisibility(View.GONE);
searchResultRecyclerView.setClickable(true);
searchResultRecyclerView.setVisibility(View.VISIBLE);
errorMessageViewHeader.setText("");
}
private IPickUserController.Destination getCurrentPickerDestination() {
return getContainer().getCurrentPickerDestination();
}
private void sendGenericInvite(final boolean fromSearch) {
if (getControllerFactory() == null || getControllerFactory().isTornDown() ||
getStoreFactory() == null || getStoreFactory().isTornDown()) {
return;
}
User self = getStoreFactory().getProfileStore().getSelfUser();
String name = self != null && self.getDisplayName() != null ? self.getDisplayName() : "";
String username = self != null && self.getUsername() != null ? self.getUsername() : "";
Intent sharingIntent = IntentUtils.getInviteIntent(getString(R.string.people_picker__invite__share_text__header, name),
getString(R.string.people_picker__invite__share_text__body, StringUtils.formatHandle(username)));
startActivity(Intent.createChooser(sharingIntent, getString(R.string.people_picker__invite__share_details_dialog)));
OpenedGenericInviteMenuEvent.EventContext eventContext = fromSearch ?
OpenedGenericInviteMenuEvent.EventContext.NO_RESULTS :
OpenedGenericInviteMenuEvent.EventContext.BANNER;
((BaseActivity) getActivity()).injectJava(GlobalTrackingController.class).tagEvent(new OpenedGenericInviteMenuEvent(eventContext));
((BaseActivity) getActivity()).injectJava(GlobalTrackingController.class).onApplicationScreen(ApplicationScreen.SEND_GENERIC_INVITE_MENU);
}
private void showUser(User user, View anchorView) {
switch (user.getConnectionStatus()) {
case ACCEPTED:
if (isAddingToConversation()) {
ArrayList<User> users = new ArrayList<>();
users.add(user);
getContainer().onSelectedUsers(users, ConversationChangeRequester.START_CONVERSATION);
} else {
// Go to 1:1 conversation
IConversation conversation = user.getConversation();
if (conversation != null) {
KeyboardUtils.hideKeyboard(getActivity());
getStoreFactory().getConversationStore().setCurrentConversation(conversation,
ConversationChangeRequester.START_CONVERSATION);
}
}
break;
case PENDING_FROM_USER:
case BLOCKED:
case IGNORED:
case CANCELLED:
case UNCONNECTED:
KeyboardUtils.hideKeyboard(getActivity());
getControllerFactory().getConversationScreenController().setPopoverLaunchedMode(DialogLaunchMode.SEARCH);
getControllerFactory().getPickUserController().showUserProfile(user, anchorView);
break;
case PENDING_FROM_OTHER:
// Show inbox
KeyboardUtils.hideKeyboard(getActivity());
// Load inbox requests to retrieve conversation
pendingFromUser = user;
getStoreFactory().getConversationStore().loadConnectRequestInboxConversations(this,
InboxLoadRequester.INBOX_SHOW_SPECIFIC);
break;
}
}
private void setConversationQuickMenuVisible(boolean show) {
if (getView() == null ||
getControllerFactory() == null ||
getControllerFactory().isTornDown()) {
return;
}
if (isAddingToConversation()) {
int numberOfSelectedUsers = getControllerFactory().getPickUserController().getSelectedUsers().size();
boolean visible = getArguments().getBoolean(ARGUMENT_GROUP_CONVERSATION) ? numberOfSelectedUsers > 0 : numberOfSelectedUsers > 1;
userSelectionConfirmationContainer.setVisibility(visible ? View.VISIBLE : View.GONE);
} else {
boolean visible = show || getControllerFactory().getPickUserController().hasSelectedUsers();
conversationQuickMenu.setVisibility(visible ? View.VISIBLE : View.GONE);
genericInviteContainer.setVisibility(visible || isKeyboardVisible ? View.GONE : View.VISIBLE);
}
}
private void updateConversationButtonLabel() {
String label = getControllerFactory().getPickUserController().getSelectedUsers().size() > 1 ?
getString(R.string.conversation_quick_menu__conversation_button__group_label) :
getString(R.string.conversation_quick_menu__conversation_button__single_label);
conversationQuickMenu.setConversationButtonText(label);
}
@Override
public void onClick(View view) {
switch (view.getId()) {
case R.id.zb__pickuser__confirmation_button:
KeyboardUtils.hideKeyboard(getActivity());
List<User> users = getControllerFactory().getPickUserController().getSelectedUsers();
getContainer().onSelectedUsers(users, ConversationChangeRequester.START_CONVERSATION);
break;
case R.id.zb__pickuser__generic_invite:
sendGenericInvite(false);
break;
case R.id.ll_pickuser__error_invite:
sendGenericInvite(true);
break;
}
}
private void setFocusByCurrentPickerDestination() {
// Don't trigger setting focus in closed split mode on tablet portrait, search is not visible then
if (LayoutSpec.isTablet(getActivity()) &&
ViewUtils.isInPortrait(getActivity()) &&
getControllerFactory().getNavigationController().getPagerPosition() == NavigationController.SECOND_PAGE) {
return;
}
if (getCurrentPickerDestination() == IPickUserController.Destination.CONVERSATION_LIST &&
(LayoutSpec.isTablet(getActivity()) || getControllerFactory().getNavigationController().getPagerPosition() == NavigationController.FIRST_PAGE)) {
getControllerFactory().getFocusController().setFocus(IFocusController.CONVERSATION_LIST_SEARCHBOX);
} else if (getCurrentPickerDestination() == IPickUserController.Destination.PARTICIPANTS) {
getControllerFactory().getFocusController().setFocus(IFocusController.PARTICIPANTS_SEARCHBOX);
}
}
private PickUserDataState getDataState(boolean hasSelectedUsers) {
PickUserDataState dataState = PickUserDataState.SHOW_TOP_USERS_AND_RECOMMENDED;
if (!TextUtils.isEmpty(getControllerFactory().getPickUserController().getSearchFilter())) {
if (isAddingToConversation()) {
dataState = PickUserDataState.SHOW_SEARCH_RESULTS_TO_ADD_TO_CONVERSATION;
} else {
dataState = PickUserDataState.SHOW_SEARCH_RESULTS;
}
} else if (isAddingToConversation()) {
dataState = PickUserDataState.SHOW_ALL_USERS_TO_ADD_TO_CONVERSATION;
} else if (hasSelectedUsers) {
dataState = PickUserDataState.SHOW_TOP_USERS_AS_LIST;
}
return dataState;
}
private boolean isAddingToConversation() {
return getArguments().getBoolean(ARGUMENT_ADD_TO_CONVERSATION);
}
private void closeStartUI() {
KeyboardUtils.hideKeyboard(getActivity());
getControllerFactory().getPickUserController().setSearchFilter("");
getControllerFactory().getPickUserController().hidePickUser(getCurrentPickerDestination(), true);
}
public interface Container {
void showIncomingPendingConnectRequest(IConversation conversation);
void onSelectedUsers(List<User> users, ConversationChangeRequester requester);
LoadingIndicatorView getLoadingViewIndicator();
IPickUserController.Destination getCurrentPickerDestination();
}
@Override
public void onRequestPermissionsResult(int requestCode, int[] grantResults) {
if (requestCode == PermissionUtils.REQUEST_READ_CONTACTS &&
grantResults.length > 0 &&
grantResults[0] == PackageManager.PERMISSION_GRANTED) {
//Changing the value of the shareContacts seems to be the
//only way to trigger a refresh on the sync engine...
boolean oldConfig = getControllerFactory().getUserPreferencesController().hasShareContactsEnabled();
getControllerFactory().getUserPreferencesController().setShareContactsEnabled(!oldConfig);
getControllerFactory().getUserPreferencesController().setShareContactsEnabled(oldConfig);
}
}
}