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); } } }