package com.xabber.android.ui.fragment;
import android.app.Activity;
import android.app.Fragment;
import android.content.ActivityNotFoundException;
import android.content.ClipData;
import android.content.ClipboardManager;
import android.content.Context;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.v4.app.NavUtils;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.support.v7.widget.Toolbar;
import android.text.Editable;
import android.text.Spannable;
import android.text.TextWatcher;
import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.view.animation.Animation;
import android.view.animation.AnimationUtils;
import android.view.inputmethod.InputMethodManager;
import android.widget.EditText;
import android.widget.ImageButton;
import android.widget.ImageView;
import android.widget.PopupMenu;
import android.widget.PopupWindow;
import android.widget.Toast;
import com.xabber.android.R;
import com.xabber.android.data.Application;
import com.xabber.android.data.NetworkException;
import com.xabber.android.data.SettingsManager;
import com.xabber.android.data.account.AccountItem;
import com.xabber.android.data.account.AccountManager;
import com.xabber.android.data.database.messagerealm.MessageItem;
import com.xabber.android.data.database.messagerealm.SyncInfo;
import com.xabber.android.data.entity.AccountJid;
import com.xabber.android.data.entity.BaseEntity;
import com.xabber.android.data.entity.UserJid;
import com.xabber.android.data.extension.attention.AttentionManager;
import com.xabber.android.data.extension.cs.ChatStateManager;
import com.xabber.android.data.extension.file.FileUtils;
import com.xabber.android.data.extension.httpfileupload.HttpFileUploadManager;
import com.xabber.android.data.extension.httpfileupload.HttpUploadListener;
import com.xabber.android.data.extension.mam.LastHistoryLoadFinishedEvent;
import com.xabber.android.data.extension.mam.LastHistoryLoadStartedEvent;
import com.xabber.android.data.extension.mam.LoadHistorySettings;
import com.xabber.android.data.extension.mam.MamManager;
import com.xabber.android.data.extension.mam.PreviousHistoryLoadFinishedEvent;
import com.xabber.android.data.extension.mam.PreviousHistoryLoadStartedEvent;
import com.xabber.android.data.extension.muc.MUCManager;
import com.xabber.android.data.extension.muc.RoomChat;
import com.xabber.android.data.extension.muc.RoomState;
import com.xabber.android.data.extension.otr.OTRManager;
import com.xabber.android.data.extension.otr.SecurityLevel;
import com.xabber.android.data.log.LogManager;
import com.xabber.android.data.message.AbstractChat;
import com.xabber.android.data.message.MessageManager;
import com.xabber.android.data.message.MessageUpdateEvent;
import com.xabber.android.data.message.NewIncomingMessageEvent;
import com.xabber.android.data.message.RegularChat;
import com.xabber.android.data.message.chat.ChatManager;
import com.xabber.android.data.notification.NotificationManager;
import com.xabber.android.data.roster.RosterManager;
import com.xabber.android.ui.activity.ChatActivity;
import com.xabber.android.ui.activity.ConferenceAddActivity;
import com.xabber.android.ui.activity.ContactActivity;
import com.xabber.android.ui.activity.ContactEditActivity;
import com.xabber.android.ui.activity.ContactListActivity;
import com.xabber.android.ui.activity.FingerprintActivity;
import com.xabber.android.ui.activity.OccupantListActivity;
import com.xabber.android.ui.activity.QuestionActivity;
import com.xabber.android.ui.adapter.ChatMessageAdapter;
import com.xabber.android.ui.color.ColorManager;
import com.xabber.android.ui.dialog.BlockContactDialog;
import com.xabber.android.ui.dialog.ChatExportDialogFragment;
import com.xabber.android.ui.dialog.ChatHistoryClearDialog;
import com.xabber.android.ui.helper.ContactTitleInflater;
import com.xabber.android.ui.helper.PermissionsRequester;
import com.xabber.android.ui.preferences.ChatContactSettings;
import org.greenrobot.eventbus.EventBus;
import org.greenrobot.eventbus.Subscribe;
import org.greenrobot.eventbus.ThreadMode;
import org.jxmpp.jid.impl.JidCreate;
import java.util.Timer;
import java.util.TimerTask;
import github.ankushsachdeva.emojicon.EmojiconGridView;
import github.ankushsachdeva.emojicon.EmojiconsPopup;
import github.ankushsachdeva.emojicon.emoji.Emojicon;
import io.realm.RealmResults;
import io.realm.Sort;
public class ChatFragment extends Fragment implements PopupMenu.OnMenuItemClickListener,
View.OnClickListener, Toolbar.OnMenuItemClickListener,
ChatMessageAdapter.Message.MessageClickListener, HttpUploadListener,
ChatMessageAdapter.Listener {
public static final String ARGUMENT_ACCOUNT = "ARGUMENT_ACCOUNT";
public static final String ARGUMENT_USER = "ARGUMENT_USER";
private static final String SAVE_ACCOUNT = "com.xabber.android.ui.fragment.ARGUMENT_ACCOUNT";
private static final String SAVE_USER = "com.xabber.android.ui.fragment.ARGUMENT_USER";
private static final String LOG_TAG = ChatFragment.class.getSimpleName();
private final long STOP_TYPING_DELAY = 4000; // in ms
public static final int FILE_SELECT_ACTIVITY_REQUEST_CODE = 23;
private static final int PERMISSIONS_REQUEST_ATTACH_FILE = 24;
private static final int PERMISSIONS_REQUEST_WRITE_EXTERNAL_STORAGE = 26;
private static final int PERMISSIONS_REQUEST_EXPORT_CHAT = 27;
private AccountJid account;
private UserJid user;
private Toolbar toolbar;
private View contactTitleView;
private EditText inputView;
private ImageButton sendButton;
private ImageButton securityButton;
private ImageButton attachButton;
private View lastHistoryProgressBar;
private View previousHistoryProgressBar;
private RecyclerView realmRecyclerView;
private ChatMessageAdapter chatMessageAdapter;
private LinearLayoutManager layoutManager;
boolean isInputEmpty = true;
private boolean skipOnTextChanges = false;
private ChatViewerFragmentListener listener;
private Animation shakeAnimation = null;
private MessageItem clickedMessageItem;
private Timer stopTypingTimer = new Timer();
private boolean isRemoteHistoryRequested = false;
private int firstRemoteSyncedItemPosition = RecyclerView.NO_POSITION;
private RealmResults<SyncInfo> syncInfoResults;
private RealmResults<MessageItem> messageItems;
private boolean toBeScrolled;
public static ChatFragment newInstance(AccountJid account, UserJid user) {
ChatFragment fragment = new ChatFragment();
Bundle arguments = new Bundle();
arguments.putParcelable(ARGUMENT_ACCOUNT, account);
arguments.putParcelable(ARGUMENT_USER, user);
fragment.setArguments(arguments);
return fragment;
}
@Override
public void onAttach(Activity activity) {
super.onAttach(activity);
try {
listener = (ChatViewerFragmentListener) activity;
listener.registerChatFragment(this);
} catch (ClassCastException e) {
throw new ClassCastException(activity.toString()
+ " must implement ChatViewerFragmentListener");
}
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Bundle args = getArguments();
account = args.getParcelable(ARGUMENT_ACCOUNT);
user = args.getParcelable(ARGUMENT_USER);
if (savedInstanceState != null) {
account = savedInstanceState.getParcelable(SAVE_ACCOUNT);
user = savedInstanceState.getParcelable(SAVE_USER);
}
LogManager.i(this, "onCreate " + user);
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
super.onCreateView(inflater, container, savedInstanceState);
View view = inflater.inflate(R.layout.fragment_chat, container, false);
contactTitleView = view.findViewById(R.id.contact_title);
contactTitleView.findViewById(R.id.avatar).setOnClickListener(this);
toolbar = (Toolbar) view.findViewById(R.id.toolbar_default);
toolbar.inflateMenu(R.menu.toolbar_chat);
toolbar.setOverflowIcon(getResources().getDrawable(R.drawable.ic_overflow_menu_white_24dp));
toolbar.setOnMenuItemClickListener(this);
toolbar.setNavigationIcon(R.drawable.ic_arrow_left_white_24dp);
toolbar.setNavigationOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
NavUtils.navigateUpFromSameTask(getActivity());
}
});
setHasOptionsMenu(true);
sendButton = (ImageButton) view.findViewById(R.id.button_send_message);
sendButton.setColorFilter(ColorManager.getInstance().getAccountPainter().getGreyMain());
attachButton = (ImageButton) view.findViewById(R.id.button_attach);
attachButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
onAttachButtonPressed();
}
});
lastHistoryProgressBar = view.findViewById(R.id.chat_last_history_progress_bar);
previousHistoryProgressBar = view.findViewById(R.id.chat_previous_history_progress_bar);
securityButton = (ImageButton) view.findViewById(R.id.button_security);
securityButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
showSecurityMenu();
}
});
// to avoid strange bug on some 4.x androids
view.findViewById(R.id.input_layout).setBackgroundColor(ColorManager.getInstance().getChatInputBackgroundColor());
view.findViewById(R.id.button_send_message).setOnClickListener(
new View.OnClickListener() {
@Override
public void onClick(View v) {
sendMessage();
}
}
);
setUpInputView(view);
setUpEmoji(view);
realmRecyclerView = (RecyclerView) view.findViewById(R.id.chat_messages_recycler_view);
layoutManager = new LinearLayoutManager(getActivity());
realmRecyclerView.setLayoutManager(layoutManager);
layoutManager.setStackFromEnd(true);
realmRecyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
@Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
if (dy < 0) {
loadHistoryIfNeeded();
}
if (dy >= 0) {
toBeScrolled = false;
}
}
});
setChat(account, user);
return view;
}
public void setChat(AccountJid accountJid, UserJid userJid) {
this.account = accountJid;
this.user = userJid;
AbstractChat abstractChat = getChat();
if (!(abstractChat instanceof RegularChat)) {
securityButton.setVisibility(View.GONE);
}
messageItems = abstractChat.getMessages();
syncInfoResults = abstractChat.getSyncInfo();
chatMessageAdapter = new ChatMessageAdapter(getActivity(), messageItems, abstractChat, this);
realmRecyclerView.setAdapter(chatMessageAdapter);
restoreInputState();
updateContact();
}
@Override
public void onStart() {
super.onStart();
EventBus.getDefault().register(this);
AccountItem accountItem = AccountManager.getInstance().getAccount(this.account);
if (accountItem != null) {
LoadHistorySettings loadHistorySettings = accountItem.getLoadHistorySettings();
if (loadHistorySettings == LoadHistorySettings.all || loadHistorySettings == LoadHistorySettings.current) {
if (!isRemoteHistoryRequested) {
MamManager.getInstance().requestLastHistoryByUser(getChat());
}
}
}
}
@Override
public void onResume() {
super.onResume();
LogManager.i(this, "onResume");
updateContact();
restoreInputState();
restoreScrollState();
}
@Override
public void onPause() {
super.onPause();
ChatStateManager.getInstance().onPaused(account, user);
saveInputState();
saveScrollState();
}
@Override
public void onStop() {
super.onStop();
EventBus.getDefault().unregister(this);
}
@Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putParcelable(SAVE_ACCOUNT, account);
outState.putParcelable(SAVE_USER, user);
}
@Override
public void onDestroy() {
super.onDestroy();
chatMessageAdapter.release();
}
@Override
public void onDetach() {
super.onDetach();
if (listener != null) {
listener.unregisterChatFragment();
listener = null;
}
}
private void setUpInputView(View view) {
inputView = (EditText) view.findViewById(R.id.chat_input);
inputView.setOnKeyListener(new View.OnKeyListener() {
@Override
public boolean onKey(View view, int keyCode, KeyEvent event) {
if (SettingsManager.chatsSendByEnter()
&& event.getAction() == KeyEvent.ACTION_DOWN
&& keyCode == KeyEvent.KEYCODE_ENTER) {
sendMessage();
return true;
}
return false;
}
});
inputView.addTextChangedListener(new TextWatcher() {
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
if (!skipOnTextChanges && stopTypingTimer != null) {
stopTypingTimer.cancel();
}
}
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
}
@Override
public void afterTextChanged(Editable text) {
ChatFragment.this.afterTextChanged(text);
}
});
}
private void afterTextChanged(Editable text) {
setUpInputViewButtons();
if (skipOnTextChanges) {
return;
}
ChatStateManager.getInstance().onComposing(account, user, text);
stopTypingTimer = new Timer();
stopTypingTimer.schedule(new TimerTask() {
@Override
public void run() {
Application.getInstance().runOnUiThread(new Runnable() {
@Override
public void run() {
ChatStateManager.getInstance().onPaused(account, user);
}
});
}
}, STOP_TYPING_DELAY);
}
private void setUpEmoji(View view) {
final ImageButton emojiButton = (ImageButton) view.findViewById(R.id.button_emoticon);
final View rootView = view.findViewById(R.id.root_view);
// Give the topmost view of your activity layout hierarchy. This will be used to measure soft keyboard height
final EmojiconsPopup popup = new EmojiconsPopup(rootView, getActivity());
//Will automatically set size according to the soft keyboard size
popup.setSizeForSoftKeyboard();
//If the emoji popup is dismissed, change emojiButton to smiley icon
popup.setOnDismissListener(new PopupWindow.OnDismissListener() {
@Override
public void onDismiss() {
changeEmojiKeyboardIcon(emojiButton, R.drawable.ic_mood_black_24dp);
}
});
//If the text keyboard closes, also dismiss the emoji popup
popup.setOnSoftKeyboardOpenCloseListener(new EmojiconsPopup.OnSoftKeyboardOpenCloseListener() {
@Override
public void onKeyboardOpen(int keyBoardHeight) {
}
@Override
public void onKeyboardClose() {
if (popup.isShowing())
popup.dismiss();
}
});
//On emoji clicked, add it to edittext
popup.setOnEmojiconClickedListener(new EmojiconGridView.OnEmojiconClickedListener() {
@Override
public void onEmojiconClicked(Emojicon emojicon) {
if (inputView == null || emojicon == null) {
return;
}
int start = inputView.getSelectionStart();
int end = inputView.getSelectionEnd();
if (start < 0) {
inputView.append(emojicon.getEmoji());
} else {
inputView.getText().replace(Math.min(start, end),
Math.max(start, end), emojicon.getEmoji(), 0,
emojicon.getEmoji().length());
}
}
});
//On backspace clicked, emulate the KEYCODE_DEL key event
popup.setOnEmojiconBackspaceClickedListener(new EmojiconsPopup.OnEmojiconBackspaceClickedListener() {
@Override
public void onEmojiconBackspaceClicked(View v) {
KeyEvent event = new KeyEvent(
0, 0, 0, KeyEvent.KEYCODE_DEL, 0, 0, 0, 0, KeyEvent.KEYCODE_ENDCALL);
inputView.dispatchKeyEvent(event);
}
});
// To toggle between text keyboard and emoji keyboard keyboard(Popup)
emojiButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
//If popup is not showing => emoji keyboard is not visible, we need to show it
if(!popup.isShowing()){
//If keyboard is visible, simply show the emoji popup
if(popup.isKeyBoardOpen()){
popup.showAtBottom();
changeEmojiKeyboardIcon(emojiButton, R.drawable.ic_keyboard_black_24dp);
}
//else, open the text keyboard first and immediately after that show the emoji popup
else{
inputView.setFocusableInTouchMode(true);
inputView.requestFocus();
popup.showAtBottomPending();
final InputMethodManager inputMethodManager = (InputMethodManager) getActivity().getSystemService(Context.INPUT_METHOD_SERVICE);
inputMethodManager.showSoftInput(inputView, InputMethodManager.SHOW_IMPLICIT);
changeEmojiKeyboardIcon(emojiButton, R.drawable.ic_keyboard_black_24dp);
}
}
//If popup is showing, simply dismiss it to show the undelying text keyboard
else{
popup.dismiss();
}
}
});
}
public void saveScrollState() {
ChatManager.getInstance().setScrollState(account, user, layoutManager.onSaveInstanceState());
}
private void loadHistoryIfNeeded() {
AccountItem accountItem = AccountManager.getInstance().getAccount(this.account);
if (accountItem == null) {
return;
}
LoadHistorySettings loadHistorySettings = accountItem.getLoadHistorySettings();
if (loadHistorySettings != LoadHistorySettings.current
&& loadHistorySettings != LoadHistorySettings.all) {
return;
}
if (isRemoteHistoryRequested) {
return;
}
int visibleItemCount = layoutManager.getChildCount();
if (visibleItemCount == 0) {
return;
}
int firstVisibleItemPosition = layoutManager.findFirstVisibleItemPosition();
if (firstVisibleItemPosition / visibleItemCount <= 2) {
requestRemoteHistoryLoad();
return;
}
if (firstVisibleItemPosition < firstRemoteSyncedItemPosition) {
requestRemoteHistoryLoad();
return;
}
if (firstVisibleItemPosition - firstRemoteSyncedItemPosition < visibleItemCount * 2) {
requestRemoteHistoryLoad();
return;
}
}
private void requestRemoteHistoryLoad() {
if (!isRemoteHistoryRequested) {
AbstractChat chat = getChat();
if (chat != null) {
MamManager.getInstance().requestPreviousHistory(chat);
}
}
}
@Nullable
private AbstractChat getChat() {
return MessageManager.getInstance().getOrCreateChat(account, user);
}
@Subscribe(threadMode = ThreadMode.MAIN)
public void onEvent(LastHistoryLoadStartedEvent event) {
if (event.getAccount().equals(account) && event.getUser().equals(user)) {
lastHistoryProgressBar.setVisibility(View.VISIBLE);
isRemoteHistoryRequested = true;
}
}
@Subscribe(threadMode = ThreadMode.MAIN)
public void onEvent(LastHistoryLoadFinishedEvent event) {
if (event.getAccount().equals(account) && event.getUser().equals(user)) {
lastHistoryProgressBar.setVisibility(View.GONE);
isRemoteHistoryRequested = false;
}
}
@Subscribe(threadMode = ThreadMode.MAIN)
public void onEvent(PreviousHistoryLoadStartedEvent event) {
if (event.getAccount().equals(account) && event.getUser().equals(user)) {
LogManager.i(this, "PreviousHistoryLoadStartedEvent");
previousHistoryProgressBar.setVisibility(View.VISIBLE);
isRemoteHistoryRequested = true;
}
}
@Subscribe(threadMode = ThreadMode.MAIN)
public void onEvent(PreviousHistoryLoadFinishedEvent event) {
if (event.getAccount().equals(account) && event.getUser().equals(user)) {
LogManager.i(this, "PreviousHistoryLoadFinishedEvent");
isRemoteHistoryRequested = false;
previousHistoryProgressBar.setVisibility(View.GONE);
}
}
@Subscribe(threadMode = ThreadMode.MAIN)
public void onEvent(MessageUpdateEvent event) {
chatMessageAdapter.onChange();
}
@Subscribe(threadMode = ThreadMode.MAIN)
public void onEvent(NewIncomingMessageEvent event) {
if (event.getAccount().equals(account) && event.getUser().equals(user)) {
playIncomingAnimation();
}
}
private void onAttachButtonPressed() {
if (PermissionsRequester.requestFileReadPermissionIfNeeded(this, PERMISSIONS_REQUEST_ATTACH_FILE)) {
startFileSelection();
}
}
private void startFileSelection() {
Intent intent = (new Intent(Intent.ACTION_GET_CONTENT).setType("*/*").addCategory(Intent.CATEGORY_OPENABLE));
startActivityForResult(intent, FILE_SELECT_ACTIVITY_REQUEST_CODE);
}
@Override
public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
switch (requestCode) {
case PERMISSIONS_REQUEST_ATTACH_FILE :
if (PermissionsRequester.isPermissionGranted(grantResults)) {
startFileSelection();
} else {
onNoReadPermissionError();
}
break;
case PERMISSIONS_REQUEST_EXPORT_CHAT :
if (PermissionsRequester.isPermissionGranted(grantResults)) {
showExportChatDialog();
} else {
onNoWritePermissionError();
}
break;
}
}
private void onNoWritePermissionError() {
Toast.makeText(getActivity(), R.string.no_permission_to_write_files, Toast.LENGTH_SHORT).show();
}
private void onNoReadPermissionError() {
Toast.makeText(getActivity(), R.string.no_permission_to_read_files, Toast.LENGTH_SHORT).show();
}
@Override
public void onActivityResult(int requestCode, int resultCode, Intent result) {
if (requestCode != FILE_SELECT_ACTIVITY_REQUEST_CODE) {
return;
}
if (resultCode != Activity.RESULT_OK) {
return;
}
final Uri fileUri = result.getData();
final String path = FileUtils.getPath(getActivity(), fileUri);
LogManager.i(this, String.format("File uri: %s, path: %s", fileUri, path));
if (path == null) {
Toast.makeText(getActivity(), R.string.could_not_get_path_to_file, Toast.LENGTH_SHORT).show();
return;
}
uploadFile(path);
}
private void uploadFile(String path) {
HttpFileUploadManager.getInstance().uploadFile(account, user, path);
}
private void changeEmojiKeyboardIcon(ImageView iconToBeChanged, int drawableResourceId){
iconToBeChanged.setImageResource(drawableResourceId);
}
private void showSecurityMenu() {
PopupMenu popup = new PopupMenu(getActivity(), securityButton);
popup.inflate(R.menu.security);
popup.setOnMenuItemClickListener(this);
Menu menu = popup.getMenu();
SecurityLevel securityLevel = OTRManager.getInstance().getSecurityLevel(account, user);
if (securityLevel == SecurityLevel.plain) {
menu.findItem(R.id.action_start_encryption).setVisible(true)
.setEnabled(SettingsManager.securityOtrMode() != SettingsManager.SecurityOtrMode.disabled);
} else {
menu.findItem(R.id.action_restart_encryption).setVisible(true);
}
boolean isEncrypted = securityLevel != SecurityLevel.plain;
menu.findItem(R.id.action_stop_encryption).setEnabled(isEncrypted);
menu.findItem(R.id.action_verify_with_fingerprint).setEnabled(isEncrypted);
menu.findItem(R.id.action_verify_with_question).setEnabled(isEncrypted);
menu.findItem(R.id.action_verify_with_shared_secret).setEnabled(isEncrypted);
popup.show();
}
private void setUpInputViewButtons() {
boolean empty = inputView.getText().toString().trim().isEmpty();
if (empty != isInputEmpty) {
isInputEmpty = empty;
}
if (isInputEmpty) {
sendButton.setColorFilter(ColorManager.getInstance().getAccountPainter().getGreyMain());
sendButton.setEnabled(false);
securityButton.setVisibility(View.VISIBLE);
if (HttpFileUploadManager.getInstance().isFileUploadSupported(account)) {
attachButton.setVisibility(View.VISIBLE);
} else {
attachButton.setVisibility(View.GONE);
}
} else {
sendButton.setEnabled(true);
sendButton.setColorFilter(ColorManager.getInstance().getAccountPainter().getAccountSendButtonColor(account));
securityButton.setVisibility(View.GONE);
attachButton.setVisibility(View.GONE);
}
}
public void restoreInputState() {
skipOnTextChanges = true;
inputView.setText(ChatManager.getInstance().getTypedMessage(account, user));
inputView.setSelection(ChatManager.getInstance().getSelectionStart(account, user),
ChatManager.getInstance().getSelectionEnd(account, user));
skipOnTextChanges = false;
if (!inputView.getText().toString().isEmpty()) {
inputView.requestFocus();
}
}
public void saveInputState() {
ChatManager.getInstance().setTyped(account, user, inputView.getText().toString(),
inputView.getSelectionStart(), inputView.getSelectionEnd());
}
private void sendMessage() {
String text = inputView.getText().toString().trim();
if (text.isEmpty()) {
return;
}
clearInputText();
sendMessage(text);
listener.onMessageSent();
if (SettingsManager.chatsHideKeyboard() == SettingsManager.ChatsHideKeyboard.always
|| (getActivity().getResources().getBoolean(R.bool.landscape)
&& SettingsManager.chatsHideKeyboard() == SettingsManager.ChatsHideKeyboard.landscape)) {
ChatActivity.hideKeyboard(getActivity());
}
}
private void sendMessage(String text) {
MessageManager.getInstance().sendMessage(account, user, text);
scrollDown();
}
/**
* This method used for hardware menu button
*/
@Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
inflater.inflate(R.menu.toolbar_chat, menu);
setUpOptionsMenu(menu);
}
/**
* This method used for hardware menu button
*/
@Override
public boolean onOptionsItemSelected(MenuItem item) {
return onMenuItemClick(item);
}
private void setUpOptionsMenu(Menu menu) {
AbstractChat abstractChat = MessageManager.getInstance().getChat(account, user);
if (abstractChat instanceof RoomChat) {
RoomState chatState = ((RoomChat) abstractChat).getState();
if (chatState == RoomState.available) {
menu.findItem(R.id.action_list_of_occupants).setVisible(true);
}
if (chatState == RoomState.unavailable) {
menu.findItem(R.id.action_join_conference).setVisible(true);
} else {
menu.findItem(R.id.action_invite_to_chat).setVisible(true);
if (chatState == RoomState.error) {
menu.findItem(R.id.action_authorization_settings).setVisible(true);
} else {
menu.findItem(R.id.action_leave_conference).setVisible(true);
}
}
}
if (abstractChat instanceof RegularChat) {
menu.findItem(R.id.action_view_contact).setVisible(true);
menu.findItem(R.id.action_close_chat).setVisible(true);
menu.findItem(R.id.action_block_contact).setVisible(true);
}
}
public void updateContact() {
ContactTitleInflater.updateTitle(contactTitleView, getActivity(),
RosterManager.getInstance().getBestContact(account, user));
toolbar.setBackgroundColor(ColorManager.getInstance().getAccountPainter().getAccountMainColor(account));
setUpOptionsMenu(toolbar.getMenu());
updateSecurityButton();
}
private void scrollDown() {
LogManager.i(this, "scrollDown");
realmRecyclerView.scrollToPosition(chatMessageAdapter.getItemCount() - 1);
}
private void updateSecurityButton() {
SecurityLevel securityLevel = OTRManager.getInstance().getSecurityLevel(account, user);
if (securityButton != null) {
// strange null ptr happens
securityButton.setImageLevel(securityLevel.getImageLevel());
}
}
public boolean isEqual(BaseEntity chat) {
return chat != null && this.account.equals(chat.getAccount()) && this.user.equals(chat.getUser());
}
public void setInputText(String additional) {
skipOnTextChanges = true;
inputView.setText(additional);
inputView.setSelection(additional.length());
skipOnTextChanges = false;
}
public AccountJid getAccount() {
return account;
}
public UserJid getUser() {
return user;
}
private void clearInputText() {
skipOnTextChanges = true;
inputView.getText().clear();
skipOnTextChanges = false;
}
@Override
public boolean onMenuItemClick(MenuItem item) {
switch (item.getItemId()) {
/* security menu */
case R.id.action_start_encryption:
startEncryption(account, user);
return true;
case R.id.action_restart_encryption:
restartEncryption(account, user);
return true;
case R.id.action_stop_encryption:
stopEncryption(account, user);
return true;
case R.id.action_verify_with_fingerprint:
startActivity(FingerprintActivity.createIntent(getActivity(), account, user));
return true;
case R.id.action_verify_with_question:
startActivity(QuestionActivity.createIntent(getActivity(), account, user, true, false, null));
return true;
case R.id.action_verify_with_shared_secret:
startActivity(QuestionActivity.createIntent(getActivity(), account, user, false, false, null));
return true;
/* regular chat options menu */
case R.id.action_view_contact:
showContactInfo();
return true;
case R.id.action_chat_settings:
startActivity(ChatContactSettings.createIntent(getActivity(), account, user));
return true;
case R.id.action_authorization_settings:
startActivity(ConferenceAddActivity.createIntent(getActivity(), account, user.getBareUserJid()));
return true;
case R.id.action_close_chat:
closeChat(account, user);
return true;
case R.id.action_clear_history:
clearHistory(account, user);
return true;
case R.id.action_export_chat:
onExportChatClick();
return true;
case R.id.action_call_attention:
callAttention();
return true;
case R.id.action_block_contact:
BlockContactDialog.newInstance(account, user).show(getFragmentManager(), BlockContactDialog.class.getName());
return true;
/* conference specific options menu */
case R.id.action_join_conference:
MUCManager.getInstance().joinRoom(account, user.getJid().asEntityBareJidIfPossible(), true);
return true;
case R.id.action_invite_to_chat:
startActivity(ContactListActivity.createRoomInviteIntent(getActivity(), account, user.getBareUserJid()));
return true;
case R.id.action_leave_conference:
leaveConference(account, user);
return true;
case R.id.action_list_of_occupants:
startActivity(OccupantListActivity.createIntent(getActivity(), account, user));
return true;
/* message popup menu */
case R.id.action_message_repeat:
if (MessageItem.isUploadFileMessage(clickedMessageItem)) {
uploadFile(clickedMessageItem.getFilePath());
} else {
sendMessage(clickedMessageItem.getText());
}
return true;
case R.id.action_message_copy:
Spannable spannable = MessageItem.getSpannable(clickedMessageItem);
((ClipboardManager) getActivity().getSystemService(Context.CLIPBOARD_SERVICE))
.setPrimaryClip(ClipData.newPlainText(spannable, spannable));
return true;
case R.id.action_message_quote:
setInputText("> " + clickedMessageItem.getText() + "\n");
return true;
case R.id.action_message_remove:
MessageManager.getInstance().removeMessage(clickedMessageItem.getUniqueId());
return true;
case R.id.action_message_open_muc_private_chat:
UserJid occupantFullJid = null;
try {
occupantFullJid = UserJid.from(
JidCreate.domainFullFrom(user.getJid().asDomainBareJid(),
clickedMessageItem.getResource()));
MessageManager.getInstance().openChat(account, occupantFullJid);
startActivity(ChatActivity.createSpecificChatIntent(getActivity(), account, occupantFullJid));
} catch (UserJid.UserJidCreateException e) {
LogManager.exception(this, e);
}
return true;
default:
return false;
}
}
private void onExportChatClick() {
if (PermissionsRequester.requestFileWritePermissionIfNeeded(this, PERMISSIONS_REQUEST_EXPORT_CHAT)) {
showExportChatDialog();
}
}
private void showExportChatDialog() {
ChatExportDialogFragment.newInstance(account, user).show(getFragmentManager(), "CHAT_EXPORT");
}
private void stopEncryption(AccountJid account, UserJid user) {
try {
OTRManager.getInstance().endSession(account, user);
} catch (NetworkException e) {
Application.getInstance().onError(e);
}
}
private void restartEncryption(AccountJid account, UserJid user) {
try {
OTRManager.getInstance().refreshSession(account, user);
} catch (NetworkException e) {
Application.getInstance().onError(e);
}
}
private void startEncryption(AccountJid account, UserJid user) {
try {
OTRManager.getInstance().startSession(account, user);
} catch (NetworkException e) {
Application.getInstance().onError(e);
}
}
@Override
public void onClick(View v) {
if (v.getId() == R.id.avatar) {
showContactInfo();
}
}
private void showContactInfo() {
Intent intent;
if (MUCManager.getInstance().hasRoom(account, user)) {
intent = ContactActivity.createIntent(getActivity(), account, user);
} else {
intent = ContactEditActivity.createIntent(getActivity(), account, user);
}
startActivity(intent);
}
private void closeChat(AccountJid account, UserJid user) {
MessageManager.getInstance().closeChat(account, user);
NotificationManager.getInstance().removeMessageNotification(account, user);
listener.onCloseChat();
}
private void clearHistory(AccountJid account, UserJid user) {
ChatHistoryClearDialog.newInstance(account, user).show(getFragmentManager(), ChatHistoryClearDialog.class.getSimpleName());
}
private void leaveConference(AccountJid account, UserJid user) {
MUCManager.getInstance().leaveRoom(account, user.getJid().asEntityBareJidIfPossible());
closeChat(account, user);
}
private void callAttention() {
try {
AttentionManager.getInstance().sendAttention(account, user);
} catch (NetworkException e) {
Application.getInstance().onError(e);
}
}
@Override
public void onMessageClick(View caller, int position) {
int itemViewType = chatMessageAdapter.getItemViewType(position);
if (itemViewType == ChatMessageAdapter.VIEW_TYPE_INCOMING_MESSAGE
|| itemViewType == ChatMessageAdapter.VIEW_TYPE_OUTGOING_MESSAGE) {
clickedMessageItem = chatMessageAdapter.getMessageItem(position);
if (clickedMessageItem == null) {
LogManager.w(LOG_TAG, "onMessageClick null message item. Position: " + position);
return;
}
PopupMenu popup = new PopupMenu(getActivity(), caller);
popup.inflate(R.menu.item_message);
popup.setOnMenuItemClickListener(this);
final Menu menu = popup.getMenu();
if (clickedMessageItem.isError()) {
menu.findItem(R.id.action_message_repeat).setVisible(true);
}
if (MessageItem.isUploadFileMessage(clickedMessageItem)) {
menu.findItem(R.id.action_message_copy).setVisible(false);
menu.findItem(R.id.action_message_quote).setVisible(false);
menu.findItem(R.id.action_message_remove).setVisible(false);
}
if (clickedMessageItem.isIncoming() && MUCManager.getInstance()
.hasRoom(account, user.getJid().asEntityBareJidIfPossible())) {
menu.findItem(R.id.action_message_open_muc_private_chat).setVisible(true);
}
popup.show();
}
}
@Override
public void onMessageImageClick(View caller, int position) {
MessageItem messageItem = chatMessageAdapter.getMessageItem(position);
if (messageItem == null) {
LogManager.w(LOG_TAG, "onMessageImageClick: null message item. Position: " + position);
return;
}
Intent i = new Intent(Intent.ACTION_VIEW);
i.setData(Uri.parse(messageItem.getText()));
try {
startActivity(i);
// possible if image was not sent and don't have URL yet.
} catch (ActivityNotFoundException e) {
LogManager.exception(LOG_TAG, e);
}
}
public void playIncomingAnimation() {
if (shakeAnimation == null) {
shakeAnimation = AnimationUtils.loadAnimation(getActivity(), R.anim.shake);
}
toolbar.findViewById(R.id.name_holder).startAnimation(shakeAnimation);
}
@Override
public void onSuccessfullUpload(String getUrl) {
}
@Override
public void onMessageNumberChanged(int prevItemCount) {
int lastVisibleItemPosition = layoutManager.findLastVisibleItemPosition();
if (toBeScrolled || lastVisibleItemPosition == -1 || lastVisibleItemPosition == (prevItemCount - 1)) {
toBeScrolled = true;
scrollDown();
}
}
public void restoreScrollState() {
layoutManager.onRestoreInstanceState(ChatManager.getInstance().getScrollState(account, user));
}
@Override
public void onMessagesUpdated() {
updateFirstRemoteSyncedItemPosition();
loadHistoryIfNeeded();
}
private void updateFirstRemoteSyncedItemPosition() {
if (!syncInfoResults.isLoaded() || !messageItems.isLoaded() || syncInfoResults.isEmpty()) {
return;
}
SyncInfo syncInfo = syncInfoResults.first();
String firstMamMessageStanzaId = syncInfo.getFirstMamMessageStanzaId();
if (firstMamMessageStanzaId == null) {
return;
}
RealmResults<MessageItem> allSorted = messageItems.where()
.equalTo(MessageItem.Fields.STANZA_ID, firstMamMessageStanzaId)
.findAllSorted(MessageItem.Fields.TIMESTAMP, Sort.ASCENDING);
if (allSorted.isEmpty()) {
return;
}
String firstRemotelySyncedMessageUniqueId = allSorted.last().getUniqueId();
firstRemoteSyncedItemPosition = chatMessageAdapter.findMessagePosition(firstRemotelySyncedMessageUniqueId);
}
public interface ChatViewerFragmentListener {
void onCloseChat();
void onMessageSent();
void registerChatFragment(ChatFragment chatFragment);
void unregisterChatFragment();
}
}