Display if chat message delivered or not

This commit is contained in:
Sylvain Berfini 2012-09-12 18:09:11 +02:00
parent f4e4c9c9af
commit fc745e2c9a
13 changed files with 187 additions and 53 deletions

View file

@ -5,6 +5,13 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:orientation="horizontal" > android:orientation="horizontal" >
<ImageView
android:contentDescription="@string/content_description_message_status"
android:id="@+id/status"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:adjustViewBounds="true" />
<TextView <TextView
android:id="@+id/time" android:id="@+id/time"
android:textColor="@android:color/darker_gray" android:textColor="@android:color/darker_gray"

View file

@ -3,7 +3,7 @@
android:id="@+id/bubble" android:id="@+id/bubble"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:gravity="right" android:gravity="left"
android:orientation="vertical" > android:orientation="vertical" >
<TextView <TextView

View file

@ -13,13 +13,29 @@
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" /> android:layout_height="wrap_content" />
<TextView <LinearLayout
android:id="@+id/time" android:layout_width="wrap_content"
android:textColor="@android:color/darker_gray" android:layout_height="wrap_content"
android:textSize="12dp" android:layout_gravity="right"
android:singleLine="true" android:orientation="horizontal" >
android:paddingRight="5dp"
android:layout_width="wrap_content" <TextView
android:layout_height="wrap_content" /> android:id="@+id/time"
android:textColor="@android:color/darker_gray"
android:textSize="12dp"
android:singleLine="true"
android:paddingRight="5dp"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<ImageView
android:contentDescription="@string/content_description_message_status"
android:id="@+id/status"
android:layout_weight="1"
android:layout_width="0dip"
android:layout_height="wrap_content"
android:adjustViewBounds="true" />
</LinearLayout>
</LinearLayout> </LinearLayout>

View file

@ -301,6 +301,7 @@
<string name="content_description_next"></string> <string name="content_description_next"></string>
<string name="content_description_back"></string> <string name="content_description_back"></string>
<string name="content_description_setup_ok"></string> <string name="content_description_setup_ok"></string>
<string name="content_description_message_status"></string>
<string name="content_description_mark"></string> <string name="content_description_mark"></string>
<string name="setup_title">Account Setup Assistant</string> <string name="setup_title">Account Setup Assistant</string>

View file

@ -21,6 +21,8 @@ import java.util.List;
import org.linphone.LinphoneSimpleListener.LinphoneOnMessageReceivedListener; import org.linphone.LinphoneSimpleListener.LinphoneOnMessageReceivedListener;
import org.linphone.core.LinphoneAddress; import org.linphone.core.LinphoneAddress;
import org.linphone.core.LinphoneChatMessage;
import org.linphone.core.LinphoneChatMessage.State;
import org.linphone.core.LinphoneChatRoom; import org.linphone.core.LinphoneChatRoom;
import org.linphone.core.LinphoneCore; import org.linphone.core.LinphoneCore;
import org.linphone.ui.AvatarWithShadow; import org.linphone.ui.AvatarWithShadow;
@ -46,7 +48,7 @@ import android.widget.TextView;
/** /**
* @author Sylvain Berfini * @author Sylvain Berfini
*/ */
public class ChatFragment extends Fragment implements OnClickListener, LinphoneOnMessageReceivedListener { public class ChatFragment extends Fragment implements OnClickListener, LinphoneOnMessageReceivedListener, LinphoneChatMessage.StateListener {
private LinphoneChatRoom chatRoom; private LinphoneChatRoom chatRoom;
private View view; private View view;
private String sipUri; private String sipUri;
@ -57,6 +59,7 @@ public class ChatFragment extends Fragment implements OnClickListener, LinphoneO
private ScrollView messagesScrollView; private ScrollView messagesScrollView;
private int previousMessageID; private int previousMessageID;
private Handler mHandler = new Handler(); private Handler mHandler = new Handler();
private BubbleChat lastSentMessageBubble;
@Override @Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, public View onCreateView(LayoutInflater inflater, ViewGroup container,
@ -93,7 +96,7 @@ public class ChatFragment extends Fragment implements OnClickListener, LinphoneO
previousMessageID = -1; previousMessageID = -1;
ChatStorage chatStorage = LinphoneActivity.instance().getChatStorage(); ChatStorage chatStorage = LinphoneActivity.instance().getChatStorage();
for (ChatMessage msg : messagesList) { for (ChatMessage msg : messagesList) {
displayMessage(msg.getId(), msg.getMessage(), msg.getTimestamp(), msg.isIncoming(), messagesLayout); displayMessage(msg.getId(), msg.getMessage(), msg.getTimestamp(), msg.isIncoming(), msg.getStatus(), messagesLayout);
chatStorage.markMessageAsRead(msg.getId()); chatStorage.markMessageAsRead(msg.getId());
} }
LinphoneActivity.instance().updateMissedChatCount(); LinphoneActivity.instance().updateMissedChatCount();
@ -125,11 +128,14 @@ public class ChatFragment extends Fragment implements OnClickListener, LinphoneO
invalidate(); invalidate();
} }
private void displayMessage(final int id, final String message, final String time, final boolean isIncoming, final RelativeLayout layout) { private void displayMessage(final int id, final String message, final String time, final boolean isIncoming, final LinphoneChatMessage.State status, final RelativeLayout layout) {
mHandler.post(new Runnable() { mHandler.post(new Runnable() {
@Override @Override
public void run() { public void run() {
BubbleChat bubble = new BubbleChat(layout.getContext(), id, message, time, isIncoming, previousMessageID); BubbleChat bubble = new BubbleChat(layout.getContext(), id, message, time, isIncoming, status, previousMessageID);
if (!isIncoming) {
lastSentMessageBubble = bubble;
}
previousMessageID = id; previousMessageID = id;
layout.addView(bubble.getView()); layout.addView(bubble.getView());
registerForContextMenu(bubble.getView()); registerForContextMenu(bubble.getView());
@ -170,14 +176,15 @@ public class ChatFragment extends Fragment implements OnClickListener, LinphoneO
if (chatRoom != null && message != null && message.getText().length() > 0) { if (chatRoom != null && message != null && message.getText().length() > 0) {
String messageToSend = message.getText().toString(); String messageToSend = message.getText().toString();
message.setText(""); message.setText("");
chatRoom.sendMessage(messageToSend); LinphoneChatMessage chatMessage = chatRoom.createLinphoneChatMessage(messageToSend);
chatRoom.sendMessage(chatMessage, this);
if (LinphoneActivity.isInstanciated()) { if (LinphoneActivity.isInstanciated()) {
LinphoneActivity.instance().onMessageSent(sipUri, messageToSend); LinphoneActivity.instance().onMessageSent(sipUri, messageToSend);
} }
displayMessage(previousMessageID + 2, messageToSend, String.valueOf(System.currentTimeMillis()), false, messagesLayout); displayMessage(previousMessageID + 2, messageToSend, String.valueOf(System.currentTimeMillis()), false, State.InProgress, messagesLayout);
scrollToEnd(); scrollToEnd();
} }
} }
@ -195,10 +202,25 @@ public class ChatFragment extends Fragment implements OnClickListener, LinphoneO
public void onMessageReceived(LinphoneAddress from, String message) { public void onMessageReceived(LinphoneAddress from, String message) {
if (from.asStringUriOnly().equals(sipUri)) { if (from.asStringUriOnly().equals(sipUri)) {
int id = previousMessageID + 2; int id = previousMessageID + 2;
displayMessage(id, message, String.valueOf(System.currentTimeMillis()), true, messagesLayout); displayMessage(id, message, String.valueOf(System.currentTimeMillis()), true, null, messagesLayout);
scrollToEnd(); scrollToEnd();
} }
} }
@Override
public void onLinphoneChatMessageStateChanged(LinphoneChatMessage msg, State state) {
final String finalMessage = msg.getMessage();
final State finalState = state;
if (LinphoneActivity.isInstanciated() && state != State.InProgress) {
mHandler.post(new Runnable() {
@Override
public void run() {
LinphoneActivity.instance().onMessageStateChanged(sipUri, finalMessage, finalState.toInt());
lastSentMessageBubble.updateStatusView(finalState);
}
});
}
}
public String getSipUri() { public String getSipUri() {
return sipUri; return sipUri;

View file

@ -1,4 +1,6 @@
package org.linphone; package org.linphone;
import org.linphone.core.LinphoneChatMessage;
/* /*
ChatMessage.java ChatMessage.java
Copyright (C) 2012 Belledonne Communications, Grenoble, France Copyright (C) 2012 Belledonne Communications, Grenoble, France
@ -25,14 +27,16 @@ public class ChatMessage {
private String message; private String message;
private String timestamp; private String timestamp;
private boolean incoming; private boolean incoming;
private int status;
private int id; private int id;
public ChatMessage(int id, String message, String timestamp, boolean incoming) { public ChatMessage(int id, String message, String timestamp, boolean incoming, int status) {
super(); super();
this.id = id; this.id = id;
this.message = message; this.message = message;
this.timestamp = timestamp; this.timestamp = timestamp;
this.incoming = incoming; this.incoming = incoming;
this.status = status;
} }
public int getId() { public int getId() {
@ -62,4 +66,12 @@ public class ChatMessage {
public void setIncoming(boolean incoming) { public void setIncoming(boolean incoming) {
this.incoming = incoming; this.incoming = incoming;
} }
public void setStatus(int status) {
this.status = status;
}
public LinphoneChatMessage.State getStatus() {
return LinphoneChatMessage.State.fromInt(status);
}
} }

View file

@ -20,6 +20,8 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import org.linphone.core.LinphoneChatMessage;
import android.content.ContentValues; import android.content.ContentValues;
import android.content.Context; import android.content.Context;
import android.database.Cursor; import android.database.Cursor;
@ -48,6 +50,12 @@ public class ChatStorage {
db.close(); db.close();
} }
public void updateMessageStatus(String to, String message, int status) {
ContentValues values = new ContentValues();
values.put("status", status);
db.update(TABLE_NAME, values, "direction LIKE " + OUTGOING + " AND remoteContact LIKE \"" + to + "\" AND message LIKE \"" + message + "\"", null);
}
public int saveMessage(String from, String to, String message) { public int saveMessage(String from, String to, String message) {
ContentValues values = new ContentValues(); ContentValues values = new ContentValues();
if (from.equals("")) { if (from.equals("")) {
@ -55,11 +63,13 @@ public class ChatStorage {
values.put("remoteContact", to); values.put("remoteContact", to);
values.put("direction", OUTGOING); values.put("direction", OUTGOING);
values.put("read", READ); values.put("read", READ);
values.put("status", LinphoneChatMessage.State.InProgress.toInt());
} else if (to.equals("")) { } else if (to.equals("")) {
values.put("localContact", to); values.put("localContact", to);
values.put("remoteContact", from); values.put("remoteContact", from);
values.put("direction", INCOMING); values.put("direction", INCOMING);
values.put("read", NOT_READ); values.put("read", NOT_READ);
values.put("status", LinphoneChatMessage.State.Idle.toInt());
} }
values.put("message", message); values.put("message", message);
values.put("time", System.currentTimeMillis()); values.put("time", System.currentTimeMillis());
@ -77,8 +87,9 @@ public class ChatStorage {
int direction = c.getInt(c.getColumnIndex("direction")); int direction = c.getInt(c.getColumnIndex("direction"));
message = c.getString(c.getColumnIndex("message")); message = c.getString(c.getColumnIndex("message"));
timestamp = c.getString(c.getColumnIndex("time")); timestamp = c.getString(c.getColumnIndex("time"));
int status = c.getInt(c.getColumnIndex("status"));
ChatMessage chatMessage = new ChatMessage(id, message, timestamp, direction == INCOMING); ChatMessage chatMessage = new ChatMessage(id, message, timestamp, direction == INCOMING, status);
chatMessages.add(chatMessage); chatMessages.add(chatMessage);
} }
@ -117,7 +128,7 @@ public class ChatStorage {
class ChatHelper extends SQLiteOpenHelper { class ChatHelper extends SQLiteOpenHelper {
private static final int DATABASE_VERSION = 2; private static final int DATABASE_VERSION = 3;
private static final String DATABASE_NAME = "linphone-android"; private static final String DATABASE_NAME = "linphone-android";
ChatHelper(Context context) { ChatHelper(Context context) {
@ -126,7 +137,7 @@ public class ChatStorage {
@Override @Override
public void onCreate(SQLiteDatabase db) { public void onCreate(SQLiteDatabase db) {
db.execSQL("CREATE TABLE " + TABLE_NAME + " (id INTEGER PRIMARY KEY AUTOINCREMENT, localContact TEXT NOT NULL, remoteContact TEXT NOT NULL, direction INTEGER, message TEXT NOT NULL, time NUMERIC, read INTEGER);"); db.execSQL("CREATE TABLE " + TABLE_NAME + " (id INTEGER PRIMARY KEY AUTOINCREMENT, localContact TEXT NOT NULL, remoteContact TEXT NOT NULL, direction INTEGER, message TEXT NOT NULL, time NUMERIC, read INTEGER, status INTEGER);");
} }
@Override @Override

View file

@ -614,6 +614,10 @@ public class LinphoneActivity extends FragmentActivity implements OnClickListene
public void onMessageSent(String to, String message) { public void onMessageSent(String to, String message) {
getChatStorage().saveMessage("", to, message); getChatStorage().saveMessage("", to, message);
} }
public void onMessageStateChanged(String to, String message, int newState) {
getChatStorage().updateMessageStatus(to, message, newState);
}
@Override @Override
public void onRegistrationStateChanged(RegistrationState state) { public void onRegistrationStateChanged(RegistrationState state) {

View file

@ -372,7 +372,6 @@ public class StatusFragment extends Fragment {
zrtpToast = new Toast(getActivity()); zrtpToast = new Toast(getActivity());
zrtpToast.setGravity(Gravity.TOP | Gravity.RIGHT, 0, LinphoneUtils.pixelsToDpi(getResources(), 40)); zrtpToast.setGravity(Gravity.TOP | Gravity.RIGHT, 0, LinphoneUtils.pixelsToDpi(getResources(), 40));
zrtpToast.setDuration(Toast.LENGTH_LONG); zrtpToast.setDuration(Toast.LENGTH_LONG);
zrtpToast.setView(layout);
ImageView ok = (ImageView) layout.findViewById(R.id.toastOK); ImageView ok = (ImageView) layout.findViewById(R.id.toastOK);
ok.setOnClickListener(new OnClickListener() { ok.setOnClickListener(new OnClickListener() {
@ -419,8 +418,9 @@ public class StatusFragment extends Fragment {
}; };
zrtpToast.show(); zrtpToast.setView(layout);
hideZrtpToast = false; hideZrtpToast = false;
zrtpToast.show();
zrtpHack.start(); zrtpHack.start();
} }

View file

@ -0,0 +1,38 @@
package org.linphone.core;
public class LinphoneChatMessageImpl implements LinphoneChatMessage {
protected final long nativePtr;
private native void setUserData(long ptr);
private native String getMessage(long ptr);
private native LinphoneAddress getPeerAddress(long ptr);
protected LinphoneChatMessageImpl(long aNativePtr) {
nativePtr = aNativePtr;
setUserData();
}
public long getNativePtr() {
return nativePtr;
}
@Override
public Object getUserData() {
// TODO Auto-generated method stub
return null;
}
@Override
public void setUserData() {
setUserData(nativePtr);
}
@Override
public String getMessage() {
return getMessage(nativePtr);
}
@Override
public LinphoneAddress getPeerAddress() {
return getPeerAddress(nativePtr);
}
}

View file

@ -22,8 +22,10 @@ import org.linphone.core.LinphoneChatMessage.StateListener;
class LinphoneChatRoomImpl implements LinphoneChatRoom { class LinphoneChatRoomImpl implements LinphoneChatRoom {
protected final long nativePtr; protected final long nativePtr;
private native long createLinphoneChatMessage(long ptr, String message);
private native long getPeerAddress(long ptr); private native long getPeerAddress(long ptr);
private native void sendMessage(long ptr, String message); private native void sendMessage(long ptr, String message);
private native void sendMessage2(long ptr, long message, StateListener listener);
protected LinphoneChatRoomImpl(long aNativePtr) { protected LinphoneChatRoomImpl(long aNativePtr) {
nativePtr = aNativePtr; nativePtr = aNativePtr;
@ -36,9 +38,10 @@ class LinphoneChatRoomImpl implements LinphoneChatRoom {
public void sendMessage(String message) { public void sendMessage(String message) {
sendMessage(nativePtr,message); sendMessage(nativePtr,message);
} }
@Override @Override
public void sendMessage(LinphoneChatMessage msg, StateListener listener) { public void sendMessage(LinphoneChatMessage message, StateListener listener) {
// TODO To be implemened sendMessage2(nativePtr, message.getNativePtr(), listener);
} }
@Override @Override
@ -46,4 +49,8 @@ class LinphoneChatRoomImpl implements LinphoneChatRoom {
// ignore, deprecated. // ignore, deprecated.
} }
@Override
public LinphoneChatMessage createLinphoneChatMessage(String message) {
return new LinphoneChatMessageImpl(createLinphoneChatMessage(nativePtr, message));
}
} }

View file

@ -24,6 +24,7 @@ import java.util.Map.Entry;
import org.linphone.LinphoneUtils; import org.linphone.LinphoneUtils;
import org.linphone.R; import org.linphone.R;
import org.linphone.core.LinphoneChatMessage;
import android.content.Context; import android.content.Context;
import android.graphics.Color; import android.graphics.Color;
@ -32,6 +33,7 @@ import android.text.SpannableStringBuilder;
import android.text.style.ImageSpan; import android.text.style.ImageSpan;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.View; import android.view.View;
import android.widget.ImageView;
import android.widget.LinearLayout; import android.widget.LinearLayout;
import android.widget.RelativeLayout; import android.widget.RelativeLayout;
import android.widget.RelativeLayout.LayoutParams; import android.widget.RelativeLayout.LayoutParams;
@ -74,8 +76,9 @@ public class BubbleChat {
} }
private RelativeLayout view; private RelativeLayout view;
private ImageView statusView;
public BubbleChat(Context context, int id, String message, String time, boolean isIncoming, int previousID) { public BubbleChat(Context context, int id, String message, String time, boolean isIncoming, LinphoneChatMessage.State status, int previousID) {
view = new RelativeLayout(context); view = new RelativeLayout(context);
LayoutParams layoutParams = new LayoutParams(RelativeLayout.LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT); LayoutParams layoutParams = new LayoutParams(RelativeLayout.LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
@ -98,45 +101,43 @@ public class BubbleChat {
view.setLayoutParams(layoutParams); view.setLayoutParams(layoutParams);
if (context.getResources().getBoolean(R.bool.display_messages_time)) { if (context.getResources().getBoolean(R.bool.display_messages_time)) {
LinearLayout layout;
if (context.getResources().getBoolean(R.bool.display_time_aside)) { if (context.getResources().getBoolean(R.bool.display_time_aside)) {
LinearLayout layout;
if (isIncoming) { if (isIncoming) {
layout = (LinearLayout) LayoutInflater.from(context).inflate(R.layout.chat_bubble_alt_incoming, null); layout = (LinearLayout) LayoutInflater.from(context).inflate(R.layout.chat_bubble_alt_incoming, null);
} else { } else {
layout = (LinearLayout) LayoutInflater.from(context).inflate(R.layout.chat_bubble_alt_outgoing, null); layout = (LinearLayout) LayoutInflater.from(context).inflate(R.layout.chat_bubble_alt_outgoing, null);
} }
TextView msgView = (TextView) layout.findViewById(R.id.message);
if (context.getResources().getBoolean(R.bool.emoticons_in_messages)) {
msgView.setText(getSmiledText(context, message));
} else {
msgView.setText(message);
}
TextView timeView = (TextView) layout.findViewById(R.id.time);
timeView.setText(timestampToHumanDate(context, time));
view.addView(layout);
} else { } else {
LinearLayout layout;
if (isIncoming) { if (isIncoming) {
layout = (LinearLayout) LayoutInflater.from(context).inflate(R.layout.chat_bubble_incoming, null); layout = (LinearLayout) LayoutInflater.from(context).inflate(R.layout.chat_bubble_incoming, null);
} else { } else {
layout = (LinearLayout) LayoutInflater.from(context).inflate(R.layout.chat_bubble_outgoing, null); layout = (LinearLayout) LayoutInflater.from(context).inflate(R.layout.chat_bubble_outgoing, null);
} }
TextView msgView = (TextView) layout.findViewById(R.id.message);
if (context.getResources().getBoolean(R.bool.emoticons_in_messages)) {
msgView.setText(getSmiledText(context, message));
} else {
msgView.setText(message);
}
TextView timeView = (TextView) layout.findViewById(R.id.time);
timeView.setText(timestampToHumanDate(context, time));
view.addView(layout);
} }
TextView msgView = (TextView) layout.findViewById(R.id.message);
if (context.getResources().getBoolean(R.bool.emoticons_in_messages)) {
msgView.setText(getSmiledText(context, message));
} else {
msgView.setText(message);
}
TextView timeView = (TextView) layout.findViewById(R.id.time);
timeView.setText(timestampToHumanDate(context, time));
statusView = (ImageView) layout.findViewById(R.id.status);
if (statusView != null) {
if (status == LinphoneChatMessage.State.Delivered) {
statusView.setImageResource(R.drawable.led_connected); //FIXME
} else if (status == LinphoneChatMessage.State.NotDelivered) {
statusView.setImageResource(R.drawable.led_error); //FIXME
} else {
statusView.setImageResource(R.drawable.led_inprogress); //FIXME
}
}
view.addView(layout);
} else { } else {
TextView messageView = new TextView(context); TextView messageView = new TextView(context);
messageView.setId(id); messageView.setId(id);
@ -153,6 +154,21 @@ public class BubbleChat {
} }
} }
public void updateStatusView(LinphoneChatMessage.State status) {
if (statusView == null) {
return;
}
if (status == LinphoneChatMessage.State.Delivered) {
statusView.setImageResource(R.drawable.led_connected); //FIXME
} else if (status == LinphoneChatMessage.State.NotDelivered) {
statusView.setImageResource(R.drawable.led_error); //FIXME
} else {
statusView.setImageResource(R.drawable.led_inprogress); //FIXME
}
view.invalidate();
}
public View getView() { public View getView() {
return view; return view;
} }

@ -1 +1 @@
Subproject commit 3e7882dabf2b486f60d5e0516c50ce195ebd0ac5 Subproject commit d74083cfaa2d757ab16588490c1844b090687f18