package com.abewy.android.apps.klyph.messenger.fragment;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import org.apache.commons.lang3.StringUtils;
import util.EmojiUtil;
import android.annotation.TargetApi;
import android.app.Activity;
import android.app.AlertDialog;
import android.app.NotificationManager;
import android.content.ClipData;
import android.content.ClipboardManager;
import android.content.ComponentName;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
import android.os.Messenger;
import android.os.RemoteException;
import android.text.Editable;
import android.text.Spannable;
import android.text.SpannableString;
import android.text.TextWatcher;
import android.text.style.URLSpan;
import android.text.util.Linkify;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AbsListView;
import android.widget.AdapterView;
import android.widget.BaseAdapter;
import android.widget.EditText;
import android.widget.GridView;
import android.widget.ImageButton;
import android.widget.ImageView;
import android.widget.ListView;
import com.abewy.android.apps.klyph.core.KlyphSession;
import com.abewy.android.apps.klyph.core.fql.Friend;
import com.abewy.android.apps.klyph.core.fql.Message;
import com.abewy.android.apps.klyph.core.fql.MessageThread;
import com.abewy.android.apps.klyph.core.graph.GraphObject;
import com.abewy.android.apps.klyph.core.request.BaseAsyncRequest;
import com.abewy.android.apps.klyph.core.request.RequestError;
import com.abewy.android.apps.klyph.core.request.Response;
import com.abewy.android.apps.klyph.core.util.AlertUtil;
import com.abewy.android.apps.klyph.core.util.FacebookUtil;
import com.abewy.android.apps.klyph.messenger.R;
import com.abewy.android.apps.klyph.messenger.adapter.MultiObjectAdapter;
import com.abewy.android.apps.klyph.messenger.fragment.ConversationListFragment.ConversationListCallback;
import com.abewy.android.apps.klyph.messenger.request.AsyncRequest;
import com.abewy.android.apps.klyph.messenger.request.AsyncRequest.Query;
import com.abewy.android.apps.klyph.messenger.service.IMessengerCallback;
import com.abewy.android.apps.klyph.messenger.service.IMessengerService;
import com.abewy.android.apps.klyph.messenger.service.MessengerService;
import com.abewy.android.apps.klyph.messenger.service.PPresence;
import com.abewy.android.apps.klyph.messenger.service.PRosterEntry;
import com.abewy.net.ConnectionState;
import com.abewy.util.Android;
import com.abewy.util.PhoneUtil;
import com.crashlytics.android.Crashlytics;
public class ConversationFragment extends KlyphFragment
{
public static interface ConversationCallback
{
public void onMessageSent(String threadId, String message);
}
private ConversationCallback listener;
private static final int NO_PENDING_ACTION = -1;
private static final int SEND_REMOVE_MESSAGE = 1;
private EditText editText;
private ImageButton sendButton;
private ImageButton emojiButton;
private GridView emojiGrid;
private String recipientId;
private String recipientPicUrl;
private String mePicUrl;
private int pendingAction = NO_PENDING_ACTION;
private String pendingId;
private String title;
private boolean canSendMessage = false;
private TextWatcher textwatcher = new TextWatcher() {
@Override
public void onTextChanged(CharSequence s, int start, int before, int count)
{
if (s.length() > 0)
{
sendButton.setEnabled(canSendMessage);
}
else
{
sendButton.setEnabled(false);
}
}
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after)
{
}
@Override
public void afterTextChanged(Editable s)
{
}
};
public ConversationFragment()
{
setRequestType(Query.MESSAGES);
}
private void setThread(MessageThread thread)
{
title = "";
mePicUrl = null;
recipientPicUrl = null;
if (thread.isSingleUserConversation())
{
for (Friend friend : thread.getRecipients_friends())
{
String id = friend.getUid();
if (id.equals(KlyphSession.getSessionUserId()))
{
mePicUrl = friend.getPic();
}
else
{
recipientPicUrl = friend.getPic();
title = friend.getName();
getActivity().setTitle(title);
}
}
}
else
{
List<String> names = new ArrayList<String>();
for (Friend friend : thread.getRecipients_friends())
{
String id = friend.getUid();
if (!id.equals(KlyphSession.getSessionUserId()))
{
names.add(friend.getFirst_name());
}
}
title = StringUtils.join(names, ", ");
getActivity().setTitle(title);
}
canSendMessage = thread.getRecipients().size() == 2;
updateEditTextProperties();
}
public String getTitle()
{
return title;
}
private void noAvailableThread()
{
canSendMessage = true;
updateEditTextProperties();
}
private void updateEditTextProperties()
{
editText.setHint(canSendMessage ? R.string.send_message : R.string.soon);
editText.setEnabled(canSendMessage);
emojiButton.setEnabled(canSendMessage);
if (canSendMessage == false)
emojiGrid.setVisibility(View.GONE);
}
@Override
public void onViewCreated(View view, Bundle savedInstanceState)
{
editText = (EditText) view.findViewById(R.id.send_text_edit);
editText.addTextChangedListener(textwatcher);
editText.clearFocus();
sendButton = (ImageButton) view.findViewById(R.id.send_button);
sendButton.setEnabled(false);
sendButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v)
{
sendMessage();
}
});
emojiButton = (ImageButton) view.findViewById(R.id.emoji_button);
emojiButton.setEnabled(false);
emojiButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v)
{
toggleGridVisibility();
}
});
emojiGrid = (GridView) view.findViewById(R.id.emoji_grid);
emojiGrid.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> arg0, View arg1, int position, long arg3)
{
// editText.removeTextChangedListener(textwatcher);
editText.getText().append((String) emojiGrid.getAdapter().getItem(position) + " ");
EmojiUtil.convertTextToEmoji(editText);
// editText.addTextChangedListener(textwatcher);
// sendButton.setEnabled(true);
}
});
emojiGrid.setAdapter(new GridAdapter());
setListAdapter(new MultiObjectAdapter(getListView()));
registerForContextMenu(getListView());
defineEmptyText(R.string.empty_list_no_message);
getListView().setStackFromBottom(true);
getListView().setDrawSelectorOnTop(false);
getListView().setSelector(R.drawable.transparent_selector);
// getListView().setTranscriptMode(AbsListView.TRANSCRIPT_MODE_ALWAYS_SCROLL);
setListVisible(false);
setRequestType(Query.MESSAGES);
setLoadingObjectAsFirstItem(true);
super.onViewCreated(view, savedInstanceState);
}
public void loadThreadConversation(MessageThread thread)
{
title = "";
final NotificationManager notificationManager = (NotificationManager) getActivity().getSystemService(Context.NOTIFICATION_SERVICE);
notificationManager.cancelAll();
if (thread.isSingleUserConversation())
{
for (String id : thread.getRecipients())
{
if (!id.equals(KlyphSession.getSessionUserId()))
{
removeSendMessage(id);
break;
}
}
}
setElementId(thread.getThread_id());
setRecipientFromThread(thread);
setThread(thread);
editText.setText("");
emojiGrid.setVisibility(View.GONE);
clearAndRefresh();
}
private void setRecipientFromThread(MessageThread thread)
{
if (thread.getRecipients().size() == 2)
{
for (String id : thread.getRecipients())
{
if (!id.equals(KlyphSession.getSessionUserId()))
{
recipientId = id;
break;
}
}
}
}
public void loadFriendConversation(String friendId)
{
Log.d("ConversationFragment", "loadFriendConversation " + friendId);
final NotificationManager notificationManager = (NotificationManager) getActivity().getSystemService(Context.NOTIFICATION_SERVICE);
notificationManager.cancelAll();
removeSendMessage(friendId);
recipientId = friendId;
setElementId("0");
editText.setText("");
emojiGrid.setVisibility(View.GONE);
canSendMessage = true;
updateEditTextProperties();
getAdapter().clear();
setIsFirstLoad(true);
setListVisible(false);
new AsyncRequest(Query.THREAD_WITH_FRIEND, friendId, "", new BaseAsyncRequest.Callback() {
@Override
public void onComplete(Response response)
{
onRequestComplete(response);
}
}).execute();
}
private void removeSendMessage(String id)
{
if (mIRemoteService != null)
{
try
{
mIRemoteService.removeSavedMessages(id);
}
catch (RemoteException e)
{
}
}
else
{
pendingAction = SEND_REMOVE_MESSAGE;
pendingId = id;
}
}
private void onRequestComplete(final Response response)
{
if (getActivity() != null)
{
getActivity().runOnUiThread(new Runnable() {
@Override
public void run()
{
if (response.getError() == null)
{
onRequestSuccess(response.getGraphObjectList());
}
else
{
onRequestError(response.getError());
}
}
});
}
}
private void onRequestSuccess(List<GraphObject> result)
{
Log.d("ConversationFragment", "onRequestSuccess " + result.size());
// Check if view is created
if (getView() != null && getListView() != null)
{
if (result.size() > 0)
{
MessageThread thread = (MessageThread) result.get(0);
Log.d("ConversationFragment", "get thread " + thread.getSnippet_author());
setElementId(thread.getThread_id());
setThread(thread);
clearAndRefresh();
}
else
{
noAvailableThread();
populate(new ArrayList<GraphObject>());
}
}
}
private void onRequestError(RequestError error)
{
Log.d("ConversationFragment", "onRequestError " + error.getMessage());
noAvailableThread();
populate(new ArrayList<GraphObject>());
}
@Override
protected void populate(List<GraphObject> data)
{
if (isFirstLoad() == true)
{
for (GraphObject graphObject : data)
{
getAdapter().add(graphObject);
}
}
else
{
int n = data.size();
for (int i = 0; i < n; i++)
{
getAdapter().insert(data.get(i), i);
}
}
// getAdapter().notifyDataSetChanged();*/
endLoading();
getListView().setSelection(data.size()-1);
if (data.size() == 0)
setNoMoreData(true);
else
setOffset(((Message) data.get(0)).getCreated_time());
}
@Override
public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount)
{
if (!isLoading() && !isFirstLoad() && !hasNoMoreData())
{
boolean loadMore = firstVisibleItem == 0;
if (loadMore)
{
refresh();
}
}
}
@Override
protected int getCustomLayout()
{
return R.layout.fragment_conversation;
}
@Override
public void onListItemClick(ListView l, View v, int position, long id)
{
super.onListItemClick(l, v, position, id);
final Message message = (Message) getAdapter().getItem(position);
List<String> list = new ArrayList<String>();
int copyText = -1;
int downloadImage = -1;
String body = message.getBody();
if (body.length() > 0)
{
list.add(getString(R.string.copy_text));
copyText = list.size() - 1;
Spannable spannable = new SpannableString(body);
Linkify.addLinks(spannable, Linkify.WEB_URLS);
URLSpan[] urls = spannable.getSpans(0, spannable.length(), URLSpan.class);
if (urls.length > 0)
{
for (URLSpan urlSpan : urls)
{
list.add(urlSpan.getURL());
}
}
}
/*
* if (message.getAttachment() != null)
* {
* list.add(getString(R.string.download_image));
* downloadImage = list.size() - 1;
* }
*/
final int fcopyText = copyText;
final int fdownloadImage = downloadImage;
final String[] items = list.toArray(new String[0]);
// For Api 8 to 10
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
builder.setItems(items, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which)
{
if (which == fcopyText)
{
handleCopyTextAction(message);
}
else if (which == fdownloadImage)
{
handleDownloadAction(message);
}
else
{
handleUrlAction(items[which]);
}
}
});
builder.create().show();
}
@TargetApi(11)
private void handleCopyTextAction(Message message)
{
if (Android.isMinAPI(11))
{
ClipboardManager clipboard = (ClipboardManager) getActivity().getSystemService(Context.CLIPBOARD_SERVICE);
ClipData clip = ClipData.newPlainText("Message", message.getBody());
clipboard.setPrimaryClip(clip);
}
else
{
android.text.ClipboardManager clipboard = (android.text.ClipboardManager) getActivity().getSystemService(Context.CLIPBOARD_SERVICE);
clipboard.setText(message.getBody());
}
}
private void handleDownloadAction(Message message)
{
}
private void handleUrlAction(String url)
{
PhoneUtil.openURL(getActivity(), url);
}
private static class GridAdapter extends BaseAdapter
{
@Override
public int getCount()
{
return EmojiUtil.EMOJIS.size();
}
@Override
public Object getItem(int position)
{
return EmojiUtil.EMOJIS.keySet().toArray()[position];
}
@Override
public long getItemId(int position)
{
return position;
}
@Override
public View getView(int position, View convertView, ViewGroup parent)
{
if (convertView == null)
{
LayoutInflater inflater = (LayoutInflater) parent.getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
convertView = inflater.inflate(R.layout.item_emoji, parent, false);
}
ImageView imageView = (ImageView) convertView.findViewById(R.id.emoji_image);
imageView.setImageResource(EmojiUtil.EMOJIS.get(getItem(position)));
return convertView;
}
}
// ___ Chat service features
public void connectToService()
{
Log.d("ConversationFragment", "connectToService");
getActivity().getApplicationContext().bindService(new Intent(getActivity(), MessengerService.class), mConnection, Context.BIND_AUTO_CREATE);
getActivity().getApplicationContext().bindService(new Intent(IMessengerService.class.getName()), mSecondConnection, Context.BIND_AUTO_CREATE);
mIsBound = true;
}
public void disconnectFromService()
{
try
{
getActivity().getApplicationContext().unbindService(mConnection);
}
catch (IllegalArgumentException e)
{
}
}
private void sendMessage()
{
if (!ConnectionState.getInstance(getActivity()).isOnline())
{
AlertUtil.showAlert(getActivity(), R.string.error, R.string.request_connexion_error, R.string.ok);
return;
}
String message = editText.getText().toString();
Bundle bundle = new Bundle();
bundle.putString("to", recipientId);
bundle.putString("message", message);
try
{
android.os.Message msg = android.os.Message.obtain(null, MessengerService.SEND_MSG);
msg.setData(bundle);
msg.replyTo = mMessenger;
mService.send(msg);
}
catch (RemoteException e)
{
Log.d("ConversationFragment", "RemoteException can send message " + e.getMessage());
}
editText.setText("");
String now = String.valueOf(new Date().getTime());
now = now.substring(0, now.length() - 3);
Message m = new Message();
m.setAuthor_id(KlyphSession.getSessionUserId());
m.setAuthor_name(KlyphSession.getSessionUserName());
m.setAuthor_pic(mePicUrl != null && mePicUrl.length() > 0 ? mePicUrl
: FacebookUtil.getProfilePictureURLForId(KlyphSession.getSessionUserId()));
m.setCreated_time(now);
m.setBody(message);
getAdapter().add(m);
getAdapter().notifyDataSetChanged();
getListView().setSelection(getAdapter().getCount() - 1);
listener.onMessageSent(getElementId(), message);
}
private void onMessageReceived(Bundle data)
{
String uid = data.getString("participant");
if (uid.equals(recipientId))
{
String date = data.getString("date");
date = date.substring(0, date.length() - 3);
Message msg = new Message();
msg.setAuthor_id(uid);
msg.setAuthor_name(data.getString("from"));
msg.setCreated_time(date);
msg.setAuthor_pic(recipientPicUrl != null && recipientPicUrl.length() > 0 ? recipientPicUrl : FacebookUtil.getProfilePictureURLForId(uid));
msg.setBody(data.getString("body"));
getListView().setTranscriptMode(AbsListView.TRANSCRIPT_MODE_ALWAYS_SCROLL);
getAdapter().add(msg);
getAdapter().notifyDataSetChanged();
getListView().setTranscriptMode(AbsListView.TRANSCRIPT_MODE_NORMAL);
}
}
private void toggleGridVisibility()
{
emojiGrid.setVisibility(emojiGrid.getVisibility() == View.VISIBLE ? View.GONE : View.VISIBLE);
}
// ___ Service communication
/** Messenger for communicating with service. */
Messenger mService = null;
/** Flag indicating whether we have called bind on the service. */
boolean mIsBound;
/**
* Handler of incoming messages from service.
*/
class IncomingHandler extends Handler
{
@Override
public void handleMessage(android.os.Message msg)
{
switch (msg.what)
{
case MessengerService.REPORT_MESSAGE_RECEIVED:
Log.d("ConvearsationFragment", "REPORT_MESSAGE_RECEIVED");
onMessageReceived(msg.getData());
break;
default:
super.handleMessage(msg);
}
}
}
/**
* Target we publish for clients to send messages to IncomingHandler.
*/
final Messenger mMessenger = new Messenger(new IncomingHandler());
private IMessengerService mIRemoteService;
private IMessengerCallback mCallback = new IMessengerCallback.Stub() {
@Override
public void onPresenceChange(PPresence presence) throws RemoteException
{
if (getActivity() != null)
{
getActivity().runOnUiThread(new Runnable() {
@Override
public void run()
{
Log.d("ConversationFragment", "onPresenceChange");
}
});
}
}
@Override
public void onRosterUpdated(List<PRosterEntry> roster) throws RemoteException
{
if (getActivity() != null)
{
getActivity().runOnUiThread(new Runnable() {
@Override
public void run()
{
Log.d("ConversationFragment", "onRosterUpdated");
}
});
}
}
};
/**
* Class for interacting with the main interface of the service.
*/
private ServiceConnection mConnection = new ServiceConnection() {
public void onServiceConnected(ComponentName className, IBinder service)
{
// This is called when the connection with the service has been
// established, giving us the service object we can use to
// interact with the service. We are communicating with our
// service through an IDL interface, so get a client-side
// representation of that from the raw service object.
Log.d("ConvearsationFragment", "register connection 1");
mService = new Messenger(service);
// We want to monitor the service for as long as we are
// connected to it.
try
{
android.os.Message msg = android.os.Message.obtain(null,
MessengerService.REGISTER_CLIENT);
msg.replyTo = mMessenger;
mService.send(msg);
}
catch (RemoteException e)
{
// In this case the service has crashed before we could even
// do anything with it; we can count on soon being
// disconnected (and then reconnected if it can be restarted)
// so there is no need to do anything here.
Crashlytics.log(Log.DEBUG, "ConversationFragment",
"Can't connect to service " + e.getMessage());
}
}
public void onServiceDisconnected(ComponentName className)
{
// This is called when the connection with the service has been
// unexpectedly disconnected -- that is, its process crashed.
mService = null;
}
};
private ServiceConnection mSecondConnection = new ServiceConnection() {
public void onServiceConnected(ComponentName className, IBinder service)
{
// This is called when the connection with the service has been
// established, giving us the service object we can use to
// interact with the service. We are communicating with our
// service through an IDL interface, so get a client-side
// representation of that from the raw service object.
Log.d("ConversationFragment", "register connection 2");
mIRemoteService = IMessengerService.Stub.asInterface(service);
try
{
mIRemoteService.registerCallback(mCallback);
}
catch (RemoteException e)
{
Log.d("ConversationFragment", "registerCallback error");
}
if (pendingAction == SEND_REMOVE_MESSAGE)
{
try
{
mIRemoteService.removeSavedMessages(pendingId);
}
catch (RemoteException e)
{
}
finally
{
pendingAction = NO_PENDING_ACTION;
pendingId = null;
}
}
}
public void onServiceDisconnected(ComponentName className)
{
try
{
mIRemoteService.unregisterCallback(mCallback);
}
catch (RemoteException e)
{
Log.d("ConversationFragment", "unregisterCallback error");
}
// This is called when the connection with the service has been
// unexpectedly disconnected -- that is, its process crashed.
mIRemoteService = null;
}
};
void doBindService()
{
// Establish a connection with the service. We use an explicit
// class name because there is no reason to be able to let other
// applications replace our component.
getActivity().getApplicationContext().bindService(new Intent(getActivity(), MessengerService.class), mConnection, Context.BIND_AUTO_CREATE);
getActivity().getApplicationContext().bindService(new Intent(IMessengerService.class.getName()), mSecondConnection, Context.BIND_AUTO_CREATE);
mIsBound = true;
}
void doUnbindService()
{
if (mIsBound)
{
// If we have received the service, and hence registered with
// it, then now is the time to unregister.
if (mService != null)
{
try
{
android.os.Message msg = android.os.Message.obtain(null, MessengerService.UNREGISTER_CLIENT);
msg.replyTo = mMessenger;
mService.send(msg);
}
catch (RemoteException e)
{
// There is nothing special we need to do if the service
// has crashed.
}
}
// Detach our existing connection.
try
{
getActivity().getApplicationContext().unbindService(mConnection);
}
catch (IllegalArgumentException e)
{
}
try
{
getActivity().getApplicationContext().unbindService(mSecondConnection);
}
catch (IllegalArgumentException e)
{
}
mIsBound = false;
}
}
@Override
public void onAttach(Activity activity)
{
super.onAttach(activity);
if (activity instanceof ConversationListCallback)
listener = (ConversationCallback) activity;
}
@Override
public void onDetach()
{
super.onDetach();
listener = null;
}
@Override
public void onDestroy()
{
Log.d("ConversationFragment", "onDestroy");
doUnbindService();
mConnection = null;
mSecondConnection = null;
mIRemoteService = null;
mService = null;
// mCallback = null;
super.onDestroy();
editText = null;
sendButton = null;
emojiButton = null;
emojiGrid = null;
listener = null;
}
}