/*
* Created by Itzik Braun on 12/3/2015.
* Copyright (c) 2015 deluge. All rights reserved.
*
* Last Modification at: 3/12/15 4:27 PM
*/
package com.braunster.chatsdk.Utils.helper;
import android.app.Activity;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.provider.MediaStore;
import android.view.View;
import android.view.animation.Animation;
import android.view.animation.AnimationUtils;
import android.widget.ListView;
import android.widget.ProgressBar;
import com.braunster.chatsdk.R;
import com.braunster.chatsdk.Utils.ImageUtils;
import com.braunster.chatsdk.Utils.Utils;
import com.braunster.chatsdk.Utils.sorter.MessageSorter;
import com.braunster.chatsdk.activities.ChatSDKLocationActivity;
import com.braunster.chatsdk.adapter.ChatSDKMessagesListAdapter;
import com.braunster.chatsdk.dao.BMessage;
import com.braunster.chatsdk.dao.BMessageDao;
import com.braunster.chatsdk.dao.BThread;
import com.braunster.chatsdk.dao.core.DaoCore;
import com.braunster.chatsdk.network.BDefines;
import com.braunster.chatsdk.network.BNetworkManager;
import com.braunster.chatsdk.object.BError;
import com.braunster.chatsdk.object.ChatSDKThreadPool;
import com.braunster.chatsdk.object.Cropper;
import com.braunster.chatsdk.view.ChatMessageBoxView;
import com.github.johnpersano.supertoasts.SuperCardToast;
import com.google.android.gms.maps.model.LatLng;
import com.soundcloud.android.crop.BuildConfig;
import com.soundcloud.android.crop.Crop;
import org.apache.commons.lang3.StringUtils;
import org.jdeferred.DoneCallback;
import org.jdeferred.FailCallback;
import org.jdeferred.ProgressCallback;
import java.io.File;
import java.lang.ref.WeakReference;
import java.util.Collections;
import java.util.List;
import de.greenrobot.dao.query.QueryBuilder;
import timber.log.Timber;
public class ChatSDKChatHelper implements ChatMessageBoxView.MessageBoxOptionsListener, ChatMessageBoxView.MessageSendListener{
public static final int ERROR = 1991, NOT_HANDLED = 1992, HANDLED = 1993;
/** The key to get the shared file uri. This is used when the activity is opened to share and image or a file with the chat users.
* Example can be found in ContactsFragment that use click mode share with contact. */
public static final String SHARED_FILE_URI = "share_file_uri";
/** The key to get shared text, this is used when the activity is open to share text with the chat user.
* Example can be found in ContactsFragment that use click mode share with contact. */
public static final String SHARED_TEXT = "shared_text";
public static final String LAT = "lat", LNG = "lng";
/** The key to get the shared file path. This is used when the activity is opened to share and image or a file with the chat users.
*/
public static final String SHARED_FILE_PATH = "shared_file_path";
public static final String SHARE_LOCATION = "share_location";
public static final String READ_COUNT = "read_count";
public static final String FILE_NAME = "file_name";
/** The key to get the path of the last captured image path in case the activity is destroyed while capturing.*/
public static final String SELECTED_FILE_PATH = "captured_photo_path";
private Cropper crop;
protected static final int PHOTO_PICKER_ID = 100;
protected static final int CAPTURE_IMAGE = 101;
public static final int PICK_LOCATION = 102;
public static final int ADD_USERS = 103;
/** to keep track if the share asked for was for filled so we wont try sharing again.*/
private static final String SHARED = "shared";
private boolean shared = false;
/** The amount of messages that was loaded for this thread,
* When we load more then the default messages amount we want to keep the amount so we could load them again if the list needs to be re-created.*/
public static final String LOADED_MESSAGES_AMOUNT = "LoadedMessagesAmount";
private int loadedMessagesAmount = 0;
/** The selected file that is picked to be sent.
* This is also the path to the camera output.*/
private String selectedFilePath = "";
private String mediaType = "";
/**
* The file name of the image that was picked and cropped
**/
private String mFileName;
/** Keeping track of the amount of messages that was read in this thread.*/
private int readCount = 0;
private static final String TAG = ChatSDKChatHelper.class.getSimpleName();
private static final boolean DEBUG = false;
private WeakReference<Activity> activity;
private BThread thread;
private ChatSDKUiHelper uiHelper;
private ListView listMessages;
private ChatMessageBoxView messageBoxView;
private ProgressBar progressBar;
private ChatSDKMessagesListAdapter messagesListAdapter;
public ChatSDKChatHelper(Activity activity, BThread thread, ChatSDKUiHelper uiHelper) {
this.activity = new WeakReference<Activity>(activity);
this.thread = thread;
this.uiHelper = uiHelper;
}
public void integrateUI(ChatMessageBoxView messageBoxView, ChatSDKMessagesListAdapter messagesListAdapter, ListView listView, ProgressBar progressBar) {
integrateUI(true, messageBoxView, messagesListAdapter, listView, progressBar);
}
public void integrateUI(boolean autoSend, ChatMessageBoxView messageBoxView, ChatSDKMessagesListAdapter messagesListAdapter, ListView listView, ProgressBar progressBar) {
this.listMessages = listView;
this.progressBar = progressBar;
this.messagesListAdapter = messagesListAdapter;
this.messageBoxView = messageBoxView;
if (autoSend)
messageBoxView.setMessageSendListener(this);
messageBoxView.setMessageBoxOptionsListener(this);
}
/** Load messages from the database and saving the current position of the list.*/
public void loadMessagesAndRetainCurrentPos(){
loadMessages(true, false, 0, 0);
}
public void loadMessages(final boolean retain, final boolean hideListView, final int offsetOrPos, final int amountToLoad){
if (messagesListAdapter == null || listMessages == null || progressBar == null || activity == null)
return;
if (thread == null)
{
return;
}
if (hideListView)
{
listMessages.setVisibility(View.INVISIBLE);
progressBar.setVisibility(View.VISIBLE);
}
else progressBar.setVisibility(View.INVISIBLE);
ChatSDKThreadPool.getInstance().execute(new Runnable() {
@Override
public void run() {
if (!hasActivity())
return;
final int oldDataSize = messagesListAdapter.getCount();
List<BMessage> messages;
// Loading messages
// Load with fixed limit
if (amountToLoad > 0)
messages = getMessagesForThreadForEntityID(thread.getId(), amountToLoad);
// we allread loaded messages so we load more then the default limit.
else if (messagesListAdapter.getCount() > BDefines.MAX_MESSAGES_TO_PULL + 1)
messages = getMessagesForThreadForEntityID(thread.getId(), messagesListAdapter.getCount());
//This value is saved in the savedInstanceState so we could check if there was more loaded messages then normal before.
else if (loadedMessagesAmount > BDefines.MAX_MESSAGES_TO_PULL + 1)
messages = getMessagesForThreadForEntityID(thread.getId(), loadedMessagesAmount);
//Loading with default limit.
else
messages = getMessagesForThreadForEntityID(thread.getId());
// Sorting the message by date to make sure the list looks ok.
Collections.sort(messages, new MessageSorter(MessageSorter.ORDER_TYPE_DESC));
loadedMessagesAmount = messages.size();
markAsRead(messages);
// Setting the new message to the adapter.
final List<ChatSDKMessagesListAdapter.MessageListItem> list = messagesListAdapter.makeList(messages);
if (list.size() == 0)
{
activity.get().runOnUiThread(new Runnable() {
@Override
public void run() {
progressBar.setVisibility(View.INVISIBLE);
listMessages.setVisibility(View.VISIBLE);
}
});
return;
}
activity.get().runOnUiThread(new Runnable() {
@Override
public void run() {
messagesListAdapter.setListData(list);
// restoring the old position after the load is done.
if (retain)
{
int newDataSize = messagesListAdapter.getCount();
final int index = listMessages.getFirstVisiblePosition() + newDataSize - oldDataSize + offsetOrPos;
View v = listMessages.getChildAt(0);
final int top = (v == null) ? -1 : v.getTop();
listMessages.post(new Runnable() {
@Override
public void run() {
listMessages.setSelectionFromTop(index, top);
if (listMessages.getVisibility() == View.INVISIBLE)
animateListView();
}
});
}
// If list view is visible smooth scroll else dirty.
else scrollListTo(offsetOrPos, !hideListView);
}
});
}
});
}
/**
* Get all messages for given thread id ordered Ascending/Descending
*/
public List<BMessage> getMessagesForThreadForEntityID(Long id) {
return getMessagesForThreadForEntityID(id, BDefines.MAX_MESSAGES_TO_PULL);
}
/**
* Get all messages for given thread id ordered Ascending/Descending
*/
public List<BMessage> getMessagesForThreadForEntityID(Long id, int limit) {
List<BMessage> list ;
QueryBuilder<BMessage> qb = DaoCore.daoSession.queryBuilder(BMessage.class);
qb.where(BMessageDao.Properties.ThreadDaoId.eq(id));
// Making sure no null messages infected the sort.
qb.where(BMessageDao.Properties.Date.isNotNull());
qb.where(BMessageDao.Properties.Sender.isNotNull());
qb.orderDesc(BMessageDao.Properties.Date);
if (limit != -1)
qb.limit(limit);
list = qb.list();
return list;
}
public void markAsRead(List<BMessage> messages){
for (BMessage m : messages)
{
m.setIsRead(true);
DaoCore.updateEntity(m);
readCount++;
}
}
public void markAsRead(BMessage message){
message.setIsRead(true);
DaoCore.updateEntity(message);
readCount++;
}
public void loadMessagesAndScrollBottom(){
loadMessages(false, true, - 1, 0);
}
public void loadMessages(int scrollingPos){
loadMessages(false, true, scrollingPos, 0);
}
public void scrollListTo(final int pos, final boolean smooth) {
if (!hasActivity())
return;
if (listMessages == null)
return;
listMessages.post(new Runnable() {
@Override
public void run() {
// Select the last row so it will scroll into view...
int position = pos;
if (pos == -1)
position = messagesListAdapter.getCount()-1;
if (smooth)
listMessages.smoothScrollToPosition(position);
else
listMessages.setSelection(position);
if (listMessages.getVisibility() == View.INVISIBLE)
animateListView();
}
});
}
public void animateListView(){
if (!hasActivity())
return;
if (listMessages == null)
return;
if (DEBUG) Timber.v("animateListView");
listMessages.setAnimation(AnimationUtils.loadAnimation(activity.get(), R.anim.fade_in_expand));
listMessages.getAnimation().setAnimationListener(new Animation.AnimationListener() {
@Override
public void onAnimationStart(Animation animation) {
if (DEBUG) Timber.v("onAnimationStart");
if (progressBar!= null)
progressBar.setVisibility(View.INVISIBLE);
}
@Override
public void onAnimationEnd(Animation animation) {
if (DEBUG) Timber.v("onAnimationEnd");
listMessages.setVisibility(View.VISIBLE);
}
@Override
public void onAnimationRepeat(Animation animation) {
}
});
listMessages.getAnimation().start();
}
public void handleResult(int requestCode, int resultCode, Intent data) {
if (DEBUG) Timber.v("onActivityResult");
if (!hasActivity()) return;
/* Pick photo logic*/
if (requestCode == PHOTO_PICKER_ID)
{
processPickedPhoto(resultCode, data);
}
else if (requestCode == Crop.REQUEST_CROP + PHOTO_PICKER_ID) {
processCroppedPhoto(resultCode, data);
}
/* Pick location logic*/
else if (requestCode == PICK_LOCATION)
{
processPickedLocation(resultCode, data);
}
/* Capture image logic*/
else if (requestCode == CAPTURE_IMAGE)
{
if (resultCode == Activity.RESULT_OK) {
sendImageMessage(selectedFilePath);
}
}
}
private void processCroppedPhoto(int resultCode, Intent data){
if (resultCode == Crop.RESULT_ERROR)
{
uiHelper.dismissProgressCard();
return;
}
try
{
// If enabled we will save the image to the app
// directory in gallery else we will save it in the cache dir.
File dir;
if (BDefines.Options.SaveImagesToDir)
dir = Utils.ImageSaver.getAlbumStorageDir(activity.get(), Utils.ImageSaver.IMAGE_DIR_NAME);
else
dir = this.activity.get().getCacheDir();
if (dir == null)
{
uiHelper.dismissProgressCard();
uiHelper.showAlertToast(R.string.unable_to_fetch_image);
return;
}
File image = new File(dir, mFileName + ".jpeg");
selectedFilePath = image.getPath();
// Scanning the image so it would be visible in the gallery images.
if (BDefines.Options.SaveImagesToDir)
ImageUtils.scanFilePathForGallery(activity.get(), selectedFilePath);
sendImageMessage(image.getPath());
}
catch (NullPointerException e){
uiHelper.showAlertToast(R.string.unable_to_fetch_image);
}
}
private void processPickedPhoto(int resultCode, Intent data){
switch (resultCode)
{
case Activity.RESULT_OK:
Uri uri = data.getData();
mFileName = DaoCore.generateEntity();
// If enabled we will save the image to the app
// directory in gallery else we will save it in the cache dir.
File dir;
if (BDefines.Options.SaveImagesToDir)
dir = Utils.ImageSaver.getAlbumStorageDir(activity.get(), Utils.ImageSaver.IMAGE_DIR_NAME);
else
dir = this.activity.get().getCacheDir();
if (dir == null)
{
uiHelper.dismissProgressCard();
uiHelper.showAlertToast(R.string.unable_to_fetch_image);
return;
}
Uri outputUri = Uri.fromFile(new File(dir, mFileName + ".jpeg"));
crop = new Cropper(uri);
Intent cropIntent = crop.getAdjustIntent(this.activity.get(), outputUri);
int request = Crop.REQUEST_CROP + PHOTO_PICKER_ID;
activity.get().startActivityForResult(cropIntent, request);
return;
case Activity.RESULT_CANCELED:
uiHelper.dismissProgressCard();
}
}
private void processPickedLocation(int resultCode, Intent data){
if (resultCode == Activity.RESULT_CANCELED) {
if (data.getExtras() == null)
return;
if (data.getExtras().containsKey(ChatSDKLocationActivity.ERROR))
uiHelper.showAlertToast(data.getExtras().getString(ChatSDKLocationActivity.ERROR));
}
else if (resultCode == Activity.RESULT_OK) {
if (DEBUG)
Timber.d("Zoom level: %s", data.getFloatExtra(ChatSDKLocationActivity.ZOOM, 0.0f));
// Send the message, Params Latitude, Longitude, Base64 Representation of the image of the location, threadId.
sendLocationMessage(data);
}
}
private void sendingMessageToast(){
// Just to be sure it's initialized.
uiHelper.initCardToast();
uiHelper.showProgressCard("Sending...");
}
public void onSavedInstanceBundle(Bundle outState){
if (StringUtils.isNotEmpty(selectedFilePath))
{
outState.putString(SELECTED_FILE_PATH, selectedFilePath);
}
outState.putInt(LOADED_MESSAGES_AMOUNT, loadedMessagesAmount);
outState.putBoolean(SHARED, shared);
SuperCardToast.onSaveState(outState);
outState.putInt(READ_COUNT, readCount);
outState.putString(FILE_NAME, mFileName);
}
public void restoreSavedInstance(Bundle savedInstanceState){
if (savedInstanceState == null)
return;
if (!hasActivity())
return;
selectedFilePath = savedInstanceState.getString(SELECTED_FILE_PATH);
savedInstanceState.remove(SELECTED_FILE_PATH);
shared = savedInstanceState.getBoolean(SHARED);
loadedMessagesAmount = savedInstanceState.getInt(LOADED_MESSAGES_AMOUNT, 0);
readCount = savedInstanceState.getInt(READ_COUNT);
mFileName = savedInstanceState.getString(FILE_NAME);
SuperCardToast.onRestoreState(savedInstanceState, activity.get());
}
@Override
public void onSendPressed(String text) {
sendMessageWithText();
}
@Override
public void onLocationPressed() {
if (!hasActivity())
return;
Intent intent = new Intent(activity.get(), uiHelper.shareLocationActivity);
activity.get().startActivityForResult(intent, PICK_LOCATION);
}
@Override
public void onTakePhotoPressed() {
if (!hasActivity())
return;
Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
File file, dir = Utils.ImageSaver.getAlbumStorageDir(activity.get(), Utils.ImageSaver.IMAGE_DIR_NAME);
if (dir == null)
{
uiHelper.dismissProgressCard();
uiHelper.showAlertToast(R.string.unable_to_catch_image);
return;
}
if(dir.exists())
{
file = new File(dir, DaoCore.generateEntity() + ".jpg");
selectedFilePath = file.getPath();
intent.putExtra(MediaStore.EXTRA_OUTPUT, Uri.fromFile(file));
}
// start the image capture Intent
activity.get().startActivityForResult(intent, CAPTURE_IMAGE);
}
@Override
public void onPickImagePressed() {
if (!hasActivity())
return;
Intent intent = new Intent();
intent.setType("image/*");
intent.setAction(Intent.ACTION_GET_CONTENT);
activity.get().startActivityForResult(Intent.createChooser(intent,
"Complete action using"), PHOTO_PICKER_ID);
}
@Override
public boolean onOptionButtonPressed() {
return false;
}
/** Send text message logic.*/
public void sendMessageWithText(){
sendMessageWithText(messageBoxView.getMessageText(), true);
}
/** Send text message
* FIXME the messages does not added to the row anymore because we are getting the date from firebase server. Need to find a different way, Maybe new item mode for the row that wont have any date.
* @param text the text to send.
* @param clearEditText if true clear the message edit text.*/
public void sendMessageWithText(String text, boolean clearEditText){
if (DEBUG) Timber.v("sendTextMessage, Text: %s, Clear: %s", text, String.valueOf(clearEditText));
if (StringUtils.isEmpty(text) || StringUtils.isBlank(text))
{
if (!uiHelper.getAlertToast().isShowing()) {
uiHelper.getAlertToast().setText("Cant send empty message!");
uiHelper.getAlertToast().show();
}
return;
}
// Clear all white space from message
text = text.trim();
BNetworkManager.sharedManager().getNetworkAdapter().sendMessageWithText(text, thread.getId())
.then(new DoneCallback<BMessage>() {
@Override
public void onDone(BMessage message) {
}
}, new FailCallback<BError>() {
@Override
public void onFail(BError error) {
uiHelper.showAlertToast(R.string.unable_to_send_message);
}
}, new ProgressCallback<BMessage>() {
@Override
public void onProgress(BMessage message) {
// Adding the message after it was prepared bt the NetworkAdapter.
if (messagesListAdapter != null)
messagesListAdapter.addRow(message);
}
});
if (clearEditText && messageBoxView!=null)
messageBoxView.clearText();
}
/** Send an image message.
* @param filePath the path to the image file that need to be sent.*/
public void sendImageMessage(final String filePath){
if (DEBUG) Timber.v("sendImageMessage, Path: %s", filePath);
sendingMessageToast();
BNetworkManager.sharedManager().getNetworkAdapter().sendMessageWithImage(filePath, thread.getId())
.then(new DoneCallback<BMessage>() {
@Override
public void onDone(BMessage message) {
if (DEBUG) Timber.v("Image is sent");
uiHelper.dismissProgressCardWithSmallDelay();
}
}, new FailCallback<BError>() {
@Override
public void onFail(BError error) {
uiHelper.dismissProgressCardWithSmallDelay();
uiHelper.showAlertToast(R.string.unable_to_send_image_message);
}
}, new ProgressCallback<BMessage>() {
@Override
public void onProgress(BMessage message) {
// Adding the message after it was prepared bt the NetworkAdapter.
if (messagesListAdapter != null)
{
messagesListAdapter.addRow(message);
scrollListTo(messagesListAdapter.getCount(), true);
}
}
});
}
public void sendLocationMessage(final Intent data){
sendingMessageToast();
BNetworkManager.sharedManager().getNetworkAdapter().sendMessageWithLocation(data.getExtras().getString(ChatSDKLocationActivity.SNAP_SHOT_PATH, null),
new LatLng(data.getDoubleExtra(ChatSDKLocationActivity.LANITUDE, 0), data.getDoubleExtra(ChatSDKLocationActivity.LONGITUDE, 0)),
thread.getId())
.then(new DoneCallback<BMessage>() {
@Override
public void onDone(BMessage message) {
if (DEBUG) Timber.v("Image is sent");
uiHelper.dismissProgressCardWithSmallDelay();
}
}, new FailCallback<BError>() {
@Override
public void onFail(BError error) {
uiHelper.dismissProgressCardWithSmallDelay();
uiHelper.showAlertToast(R.string.unable_to_send_location_message);
}
}, new ProgressCallback<BMessage>() {
@Override
public void onProgress(BMessage message) {
// Adding the message after it was prepared bt the NetworkAdapter.
String path = data.getExtras().getString(ChatSDKLocationActivity.SNAP_SHOT_PATH, "");
if (StringUtils.isNotBlank(path))
{
if (messagesListAdapter != null)
{
messagesListAdapter.addRow(message);
scrollListTo(messagesListAdapter.getCount(), true);
}
}
}
});
}
/** Check the intent if carries some data that received from another app to share on this chat.*/
public void checkIfWantToShare(Intent intent){
if (DEBUG) Timber.v("checkIfWantToShare");
if (!hasActivity())
return;
if (shared)
return;
if (intent.getExtras() == null || intent.getExtras().isEmpty())
{
return;
}
if (intent.getExtras().containsKey(SHARED_FILE_URI))
{
if (DEBUG) Timber.i("Want to share URI");
try{
String path = Utils.getRealPathFromURI(activity.get(), (Uri) intent.getExtras().get(SHARED_FILE_URI));
if (DEBUG) Timber.d("Path from uri: " + path);
uiHelper.showProgressCard(R.string.sending);
sendImageMessage(path);
}
catch (NullPointerException e){
uiHelper.showAlertToast(R.string.unable_to_fetch_image);
}
// removing the key so we wont send again,
intent.getExtras().remove(SHARED_FILE_URI);
intent.removeExtra(SHARED_FILE_URI);
shared = true;
}
else if (intent.getExtras().containsKey(SHARED_TEXT))
{
if (DEBUG) Timber.i("Want to share Text");
String text =intent.getExtras().getString(SHARED_TEXT);
// removing the key so we wont send again,
intent.getExtras().remove(SHARED_TEXT);
sendMessageWithText(text, false);
intent.removeExtra(SHARED_TEXT);
shared = true;
}
else if (intent.getExtras().containsKey(SHARED_FILE_PATH))
{
if (DEBUG) Timber.i("Want to share File from path");
uiHelper.showProgressCard("Sending...");
String path =intent.getStringExtra(SHARED_FILE_PATH);
// removing the key so we wont send again,
intent.getExtras().remove(SHARED_FILE_PATH);
sendImageMessage(path);
intent.removeExtra(SHARED_FILE_PATH);
shared = true;
}
else if (intent.getExtras().containsKey(SHARE_LOCATION)){
if (DEBUG) Timber.i("Want to share Location");
// FIXME pull text from string resource for language control
uiHelper.showProgressCard(R.string.sending);
BNetworkManager.sharedManager().getNetworkAdapter().sendMessageWithLocation(intent.getExtras().getString(SHARE_LOCATION, null),
new LatLng(intent.getDoubleExtra(LAT, 0), intent.getDoubleExtra(LNG, 0)),
thread.getId())
.done(new DoneCallback<BMessage>() {
@Override
public void onDone(BMessage message) {
if (DEBUG) Timber.v("Image is sent");
uiHelper.dismissProgressCardWithSmallDelay();
}
})
.fail(new FailCallback<BError>() {
@Override
public void onFail(BError bError) {
uiHelper.dismissProgressCardWithSmallDelay();
uiHelper.showAlertToast(R.string.unable_to_send_location_message);
}
});
intent.removeExtra(SHARE_LOCATION);
shared = true;
}
}
public String getSelectedFilePath() {
return selectedFilePath;
}
public boolean isLoactionMedia(){
return StringUtils.isNotEmpty(getSelectedFilePath()) && StringUtils.isNotBlank(getSelectedFilePath());
}
public void setThread(BThread thread) {
this.thread = thread;
}
public void setListMessages(ListView listMessages) {
this.listMessages = listMessages;
}
public void setMessageBoxView(ChatMessageBoxView messageBoxView) {
this.messageBoxView = messageBoxView;
}
public void setProgressBar(ProgressBar progressBar) {
this.progressBar = progressBar;
}
public void setMessagesListAdapter(ChatSDKMessagesListAdapter messagesListAdapter) {
this.messagesListAdapter = messagesListAdapter;
}
public int getReadCount() {
return readCount;
}
public boolean hasActivity(){
return activity != null && activity.get() != null;
}
}