package com.quickblox.sample.chat.ui.activity;
import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.os.PersistableBundle;
import android.support.design.widget.Snackbar;
import android.support.v7.app.ActionBar;
import android.text.TextUtils;
import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.EditText;
import android.widget.LinearLayout;
import android.widget.ProgressBar;
import com.quickblox.chat.QBChatService;
import com.quickblox.chat.model.QBAttachment;
import com.quickblox.chat.model.QBChatMessage;
import com.quickblox.chat.model.QBChatDialog;
import com.quickblox.chat.model.QBDialogType;
import com.quickblox.core.QBEntityCallback;
import com.quickblox.core.exception.QBResponseException;
import com.quickblox.sample.chat.R;
import com.quickblox.sample.chat.ui.adapter.AttachmentPreviewAdapter;
import com.quickblox.sample.chat.ui.adapter.ChatAdapter;
import com.quickblox.sample.chat.ui.widget.AttachmentPreviewAdapterView;
import com.quickblox.sample.chat.utils.chat.ChatHelper;
import com.quickblox.sample.chat.utils.qb.PaginationHistoryListener;
import com.quickblox.sample.chat.utils.qb.QbChatDialogMessageListenerImp;
import com.quickblox.sample.chat.utils.qb.QbDialogHolder;
import com.quickblox.sample.chat.utils.qb.QbDialogUtils;
import com.quickblox.sample.chat.utils.qb.VerboseQbChatConnectionListener;
import com.quickblox.sample.core.ui.dialog.ProgressDialogFragment;
import com.quickblox.sample.core.utils.Toaster;
import com.quickblox.sample.core.utils.imagepick.ImagePickHelper;
import com.quickblox.sample.core.utils.imagepick.OnImagePickedListener;
import com.quickblox.users.model.QBUser;
import org.jivesoftware.smack.ConnectionListener;
import org.jivesoftware.smack.SmackException;
import org.jivesoftware.smack.XMPPException;
import java.io.File;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import se.emilsjolander.stickylistheaders.StickyListHeadersListView;
public class ChatActivity extends BaseActivity implements OnImagePickedListener {
private static final String TAG = ChatActivity.class.getSimpleName();
private static final int REQUEST_CODE_ATTACHMENT = 721;
private static final int REQUEST_CODE_SELECT_PEOPLE = 752;
private static final String PROPERTY_SAVE_TO_HISTORY = "save_to_history";
public static final String EXTRA_DIALOG_ID = "dialogId";
private ProgressBar progressBar;
private StickyListHeadersListView messagesListView;
private EditText messageEditText;
private LinearLayout attachmentPreviewContainerLayout;
private Snackbar snackbar;
private ChatAdapter chatAdapter;
private AttachmentPreviewAdapter attachmentPreviewAdapter;
private ConnectionListener chatConnectionListener;
private QBChatDialog qbChatDialog;
private ArrayList<QBChatMessage> unShownMessages;
private int skipPagination = 0;
private ChatMessageListener chatMessageListener;
public static void startForResult(Activity activity, int code, QBChatDialog dialogId) {
Intent intent = new Intent(activity, ChatActivity.class);
intent.putExtra(ChatActivity.EXTRA_DIALOG_ID, dialogId);
activity.startActivityForResult(intent, code);
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_chat);
Log.v(TAG, "onCreate ChatActivity on Thread ID = " + Thread.currentThread().getId());
qbChatDialog = (QBChatDialog) getIntent().getSerializableExtra(EXTRA_DIALOG_ID);
Log.v(TAG, "deserialized dialog = " + qbChatDialog);
qbChatDialog.initForChat(QBChatService.getInstance());
chatMessageListener = new ChatMessageListener();
qbChatDialog.addMessageListener(chatMessageListener);
initChatConnectionListener();
initViews();
initChat();
}
@Override
public void onSaveInstanceState(Bundle outState, PersistableBundle outPersistentState) {
if (qbChatDialog != null) {
outState.putString(EXTRA_DIALOG_ID, qbChatDialog.getDialogId());
}
super.onSaveInstanceState(outState, outPersistentState);
}
@Override
protected void onRestoreInstanceState(Bundle savedInstanceState) {
super.onRestoreInstanceState(savedInstanceState);
if (qbChatDialog == null) {
qbChatDialog = QbDialogHolder.getInstance().getChatDialogById(savedInstanceState.getString(EXTRA_DIALOG_ID));
}
}
@Override
protected void onResume() {
super.onResume();
ChatHelper.getInstance().addConnectionListener(chatConnectionListener);
}
@Override
protected void onPause() {
super.onPause();
ChatHelper.getInstance().removeConnectionListener(chatConnectionListener);
}
@Override
public void onBackPressed() {
releaseChat();
sendDialogId();
super.onBackPressed();
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.activity_chat, menu);
MenuItem menuItemLeave = menu.findItem(R.id.menu_chat_action_leave);
MenuItem menuItemAdd = menu.findItem(R.id.menu_chat_action_add);
MenuItem menuItemDelete = menu.findItem(R.id.menu_chat_action_delete);
if (qbChatDialog.getType() == QBDialogType.PRIVATE) {
menuItemLeave.setVisible(false);
menuItemAdd.setVisible(false);
} else {
menuItemDelete.setVisible(false);
}
return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
int id = item.getItemId();
switch (id) {
case R.id.menu_chat_action_info:
ChatInfoActivity.start(this, qbChatDialog);
return true;
case R.id.menu_chat_action_add:
SelectUsersActivity.startForResult(this, REQUEST_CODE_SELECT_PEOPLE, qbChatDialog);
return true;
case R.id.menu_chat_action_leave:
leaveGroupChat();
return true;
case R.id.menu_chat_action_delete:
deleteChat();
return true;
case android.R.id.home:
onBackPressed();
return true;
default:
return super.onOptionsItemSelected(item);
}
}
private void sendDialogId() {
Intent result = new Intent();
result.putExtra(EXTRA_DIALOG_ID, qbChatDialog.getDialogId());
setResult(RESULT_OK, result);
}
private void leaveGroupChat() {
ProgressDialogFragment.show(getSupportFragmentManager());
ChatHelper.getInstance().exitFromDialog(qbChatDialog, new QBEntityCallback<QBChatDialog>() {
@Override
public void onSuccess(QBChatDialog qbDialog, Bundle bundle) {
ProgressDialogFragment.hide(getSupportFragmentManager());
QbDialogHolder.getInstance().deleteDialog(qbDialog);
finish();
}
@Override
public void onError(QBResponseException e) {
ProgressDialogFragment.hide(getSupportFragmentManager());
showErrorSnackbar(R.string.error_leave_chat, e, new View.OnClickListener() {
@Override
public void onClick(View v) {
leaveGroupChat();
}
});
}
});
}
@SuppressWarnings("unchecked")
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (resultCode == RESULT_OK) {
if (requestCode == REQUEST_CODE_SELECT_PEOPLE) {
ArrayList<QBUser> selectedUsers = (ArrayList<QBUser>) data.getSerializableExtra(
SelectUsersActivity.EXTRA_QB_USERS);
updateDialog(selectedUsers);
}
}
}
@Override
public void onImagePicked(int requestCode, File file) {
switch (requestCode) {
case REQUEST_CODE_ATTACHMENT:
attachmentPreviewAdapter.add(file);
break;
}
}
@Override
public void onImagePickError(int requestCode, Exception e) {
showErrorSnackbar(0, e, null);
}
@Override
public void onImagePickClosed(int requestCode) {
// ignore
}
@Override
protected View getSnackbarAnchorView() {
return findViewById(R.id.list_chat_messages);
}
public void onSendChatClick(View view) {
int totalAttachmentsCount = attachmentPreviewAdapter.getCount();
Collection<QBAttachment> uploadedAttachments = attachmentPreviewAdapter.getUploadedAttachments();
if (!uploadedAttachments.isEmpty()) {
if (uploadedAttachments.size() == totalAttachmentsCount) {
for (QBAttachment attachment : uploadedAttachments) {
sendChatMessage(null, attachment);
}
} else {
Toaster.shortToast(R.string.chat_wait_for_attachments_to_upload);
}
}
String text = messageEditText.getText().toString().trim();
if (!TextUtils.isEmpty(text)) {
sendChatMessage(text, null);
}
}
public void onAttachmentsClick(View view) {
new ImagePickHelper().pickAnImage(this, REQUEST_CODE_ATTACHMENT);
}
public void showMessage(QBChatMessage message) {
if (chatAdapter != null) {
chatAdapter.add(message);
scrollMessageListDown();
} else {
if (unShownMessages == null) {
unShownMessages = new ArrayList<>();
}
unShownMessages.add(message);
}
}
private void initViews() {
actionBar.setDisplayHomeAsUpEnabled(true);
messagesListView = _findViewById(R.id.list_chat_messages);
messageEditText = _findViewById(R.id.edit_chat_message);
progressBar = _findViewById(R.id.progress_chat);
attachmentPreviewContainerLayout = _findViewById(R.id.layout_attachment_preview_container);
attachmentPreviewAdapter = new AttachmentPreviewAdapter(this,
new AttachmentPreviewAdapter.OnAttachmentCountChangedListener() {
@Override
public void onAttachmentCountChanged(int count) {
attachmentPreviewContainerLayout.setVisibility(count == 0 ? View.GONE : View.VISIBLE);
}
},
new AttachmentPreviewAdapter.OnAttachmentUploadErrorListener() {
@Override
public void onAttachmentUploadError(QBResponseException e) {
showErrorSnackbar(0, e, new View.OnClickListener() {
@Override
public void onClick(View v) {
onAttachmentsClick(v);
}
});
}
});
AttachmentPreviewAdapterView previewAdapterView = _findViewById(R.id.adapter_view_attachment_preview);
previewAdapterView.setAdapter(attachmentPreviewAdapter);
}
private void sendChatMessage(String text, QBAttachment attachment) {
QBChatMessage chatMessage = new QBChatMessage();
if (attachment != null) {
chatMessage.addAttachment(attachment);
} else {
chatMessage.setBody(text);
}
chatMessage.setProperty(PROPERTY_SAVE_TO_HISTORY, "1");
chatMessage.setDateSent(System.currentTimeMillis() / 1000);
chatMessage.setMarkable(true);
if (!QBDialogType.PRIVATE.equals(qbChatDialog.getType()) && !qbChatDialog.isJoined()){
Toaster.shortToast("You're still joining a group chat, please wait a bit");
return;
}
try {
qbChatDialog.sendMessage(chatMessage);
if (QBDialogType.PRIVATE.equals(qbChatDialog.getType())) {
showMessage(chatMessage);
}
if (attachment != null) {
attachmentPreviewAdapter.remove(attachment);
} else {
messageEditText.setText("");
}
} catch (SmackException.NotConnectedException e) {
Log.w(TAG, e);
Toaster.shortToast("Can't send a message, You are not connected to chat");
}
}
private void initChat() {
switch (qbChatDialog.getType()) {
case GROUP:
case PUBLIC_GROUP:
joinGroupChat();
break;
case PRIVATE:
loadDialogUsers();
break;
default:
Toaster.shortToast(String.format("%s %s", getString(R.string.chat_unsupported_type), qbChatDialog.getType().name()));
finish();
break;
}
}
private void joinGroupChat() {
progressBar.setVisibility(View.VISIBLE);
ChatHelper.getInstance().join(qbChatDialog, new QBEntityCallback<Void>() {
@Override
public void onSuccess(Void result, Bundle b) {
if (snackbar != null) {
snackbar.dismiss();
}
loadDialogUsers();
}
@Override
public void onError(QBResponseException e) {
progressBar.setVisibility(View.GONE);
snackbar = showErrorSnackbar(R.string.connection_error, e, null);
}
});
}
private void leaveGroupDialog() {
try {
ChatHelper.getInstance().leaveChatDialog(qbChatDialog);
} catch (XMPPException | SmackException.NotConnectedException e) {
Log.w(TAG, e);
}
}
private void releaseChat() {
qbChatDialog.removeMessageListrener(chatMessageListener);
if (!QBDialogType.PRIVATE.equals(qbChatDialog.getType())) {
leaveGroupDialog();
}
}
private void updateDialog(final ArrayList<QBUser> selectedUsers) {
ChatHelper.getInstance().updateDialogUsers(qbChatDialog, selectedUsers,
new QBEntityCallback<QBChatDialog>() {
@Override
public void onSuccess(QBChatDialog dialog, Bundle args) {
qbChatDialog = dialog;
loadDialogUsers();
}
@Override
public void onError(QBResponseException e) {
showErrorSnackbar(R.string.chat_info_add_people_error, e,
new View.OnClickListener() {
@Override
public void onClick(View v) {
updateDialog(selectedUsers);
}
});
}
}
);
}
private void loadDialogUsers() {
ChatHelper.getInstance().getUsersFromDialog(qbChatDialog, new QBEntityCallback<ArrayList<QBUser>>() {
@Override
public void onSuccess(ArrayList<QBUser> users, Bundle bundle) {
setChatNameToActionBar();
loadChatHistory();
}
@Override
public void onError(QBResponseException e) {
showErrorSnackbar(R.string.chat_load_users_error, e,
new View.OnClickListener() {
@Override
public void onClick(View v) {
loadDialogUsers();
}
});
}
});
}
private void setChatNameToActionBar() {
String chatName = QbDialogUtils.getDialogName(qbChatDialog);
ActionBar ab = getSupportActionBar();
if (ab != null) {
ab.setTitle(chatName);
ab.setDisplayHomeAsUpEnabled(true);
ab.setHomeButtonEnabled(true);
}
}
private void loadChatHistory() {
ChatHelper.getInstance().loadChatHistory(qbChatDialog, skipPagination, new QBEntityCallback<ArrayList<QBChatMessage>>() {
@Override
public void onSuccess(ArrayList<QBChatMessage> messages, Bundle args) {
// The newest messages should be in the end of list,
// so we need to reverse list to show messages in the right order
Collections.reverse(messages);
if (chatAdapter == null) {
chatAdapter = new ChatAdapter(ChatActivity.this, qbChatDialog, messages);
chatAdapter.setPaginationHistoryListener(new PaginationHistoryListener() {
@Override
public void downloadMore() {
loadChatHistory();
}
});
chatAdapter.setOnItemInfoExpandedListener(new ChatAdapter.OnItemInfoExpandedListener() {
@Override
public void onItemInfoExpanded(final int position) {
if (isLastItem(position)) {
// HACK need to allow info textview visibility change so posting it via handler
runOnUiThread(new Runnable() {
@Override
public void run() {
messagesListView.setSelection(position);
}
});
} else {
messagesListView.smoothScrollToPosition(position);
}
}
private boolean isLastItem(int position) {
return position == chatAdapter.getCount() - 1;
}
});
if (unShownMessages != null && !unShownMessages.isEmpty()) {
List<QBChatMessage> chatList = chatAdapter.getList();
for (QBChatMessage message : unShownMessages) {
if (!chatList.contains(message)) {
chatAdapter.add(message);
}
}
}
messagesListView.setAdapter(chatAdapter);
messagesListView.setAreHeadersSticky(false);
messagesListView.setDivider(null);
} else {
chatAdapter.addList(messages);
messagesListView.setSelection(messages.size());
}
progressBar.setVisibility(View.GONE);
}
@Override
public void onError(QBResponseException e) {
progressBar.setVisibility(View.GONE);
skipPagination -= ChatHelper.CHAT_HISTORY_ITEMS_PER_PAGE;
snackbar = showErrorSnackbar(R.string.connection_error, e, null);
}
});
skipPagination += ChatHelper.CHAT_HISTORY_ITEMS_PER_PAGE;
}
private void scrollMessageListDown() {
messagesListView.setSelection(messagesListView.getCount() - 1);
}
private void deleteChat() {
ChatHelper.getInstance().deleteDialog(qbChatDialog, new QBEntityCallback<Void>() {
@Override
public void onSuccess(Void aVoid, Bundle bundle) {
setResult(RESULT_OK);
finish();
}
@Override
public void onError(QBResponseException e) {
showErrorSnackbar(R.string.dialogs_deletion_error, e,
new View.OnClickListener() {
@Override
public void onClick(View v) {
deleteChat();
}
});
}
});
}
private void initChatConnectionListener() {
chatConnectionListener = new VerboseQbChatConnectionListener(getSnackbarAnchorView()) {
@Override
public void reconnectionSuccessful() {
super.reconnectionSuccessful();
skipPagination = 0;
switch (qbChatDialog.getType()) {
case GROUP:
chatAdapter = null;
// Join active room if we're in Group Chat
runOnUiThread(new Runnable() {
@Override
public void run() {
joinGroupChat();
}
});
break;
}
}
};
}
public class ChatMessageListener extends QbChatDialogMessageListenerImp {
@Override
public void processMessage(String s, QBChatMessage qbChatMessage, Integer integer) {
showMessage(qbChatMessage);
}
}
}