Improve chat display for long conversations

This commit is contained in:
Sylvain Berfini 2013-04-02 16:05:44 +02:00
parent 83110a31c0
commit 71fda6cced
15 changed files with 228 additions and 83 deletions

25
.gitignore vendored
View file

@ -1,12 +1,13 @@
libs
obj
gen
bin
doc
default.properties
local.properties
project.properties
tests/*$py.class
tests/build.xml
res/.DS_Store
bc-android.keystore
libs
obj
gen
bin
doc
default.properties
local.properties
project.properties
tests/*$py.class
tests/build.xml
res/.DS_Store
bc-android.keystore
res/raw/lpconfig.xsd

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4 KiB

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4 KiB

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.5 KiB

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.2 KiB

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.9 KiB

After

Width:  |  Height:  |  Size: 3 KiB

View file

@ -31,7 +31,7 @@
</LinearLayout>
<ScrollView
<org.linphone.ui.LinphoneScrollView
android:id="@+id/chatScrollView"
android:paddingTop="5dp"
android:layout_weight="1"
@ -47,7 +47,7 @@
</RelativeLayout>
</ScrollView>
</org.linphone.ui.LinphoneScrollView>
<RelativeLayout
android:id="@+id/messageLayout"

View file

@ -31,7 +31,7 @@
</LinearLayout>
<ScrollView
<org.linphone.ui.LinphoneScrollView
android:id="@+id/chatScrollView"
android:paddingTop="5dp"
android:layout_weight="1"
@ -47,7 +47,7 @@
</RelativeLayout>
</ScrollView>
</org.linphone.ui.LinphoneScrollView>
<RelativeLayout
android:id="@+id/messageLayout"

View file

@ -31,7 +31,7 @@
</LinearLayout>
<ScrollView
<org.linphone.ui.LinphoneScrollView
android:id="@+id/chatScrollView"
android:paddingTop="5dp"
android:layout_weight="1"
@ -47,7 +47,7 @@
</RelativeLayout>
</ScrollView>
</org.linphone.ui.LinphoneScrollView>
<RelativeLayout
android:id="@+id/messageLayout"

View file

@ -31,7 +31,7 @@
</LinearLayout>
<ScrollView
<org.linphone.ui.LinphoneScrollView
android:id="@+id/chatScrollView"
android:paddingTop="5dp"
android:layout_weight="1"
@ -47,7 +47,7 @@
</RelativeLayout>
</ScrollView>
</org.linphone.ui.LinphoneScrollView>
<RelativeLayout
android:id="@+id/messageLayout"

View file

@ -41,6 +41,8 @@ import org.linphone.core.LinphoneChatRoom;
import org.linphone.core.LinphoneCore;
import org.linphone.ui.AvatarWithShadow;
import org.linphone.ui.BubbleChat;
import org.linphone.ui.LinphoneScrollView;
import org.linphone.ui.ScrollViewListener;
import android.annotation.SuppressLint;
import android.app.Activity;
@ -79,7 +81,6 @@ import android.widget.EditText;
import android.widget.ImageView;
import android.widget.ProgressBar;
import android.widget.RelativeLayout;
import android.widget.ScrollView;
import android.widget.TextView;
import android.widget.Toast;
@ -99,6 +100,7 @@ public class ChatFragment extends Fragment implements OnClickListener, LinphoneC
private static final int SIZE_SMALL = 500;
private static final int SIZE_MEDIUM = 1000;
private static final int SIZE_LARGE = 1500;
private static final int MESSAGES_STEP = 20;
private LinphoneChatRoom chatRoom;
private View view;
@ -108,11 +110,13 @@ public class ChatFragment extends Fragment implements OnClickListener, LinphoneC
private TextView sendImage, sendMessage, contactName;
private AvatarWithShadow contactPicture;
private RelativeLayout messagesLayout, uploadLayout, textLayout;
private ScrollView messagesScrollView;
private LinphoneScrollView messagesScrollView;
private int previousMessageID;
private Handler mHandler = new Handler();
private BubbleChat lastSentMessageBubble;
private HashMap<Integer, String> latestImageMessages;
private int messagesFilterLimit = 0;
private List<ChatMessage> messagesList;
private ProgressBar progressBar;
private int bytesSent;
@ -148,7 +152,7 @@ public class ChatFragment extends Fragment implements OnClickListener, LinphoneC
textLayout = (RelativeLayout) view.findViewById(R.id.messageLayout);
messagesLayout = (RelativeLayout) view.findViewById(R.id.messages);
messagesScrollView = (ScrollView) view.findViewById(R.id.chatScrollView);
messagesScrollView = (LinphoneScrollView) view.findViewById(R.id.chatScrollView);
progressBar = (ProgressBar) view.findViewById(R.id.progressbar);
sendImage = (TextView) view.findViewById(R.id.sendPicture);
@ -171,6 +175,9 @@ public class ChatFragment extends Fragment implements OnClickListener, LinphoneC
}
});
if (savedInstanceState != null) {
messagesFilterLimit = savedInstanceState.getInt("messagesFilterLimit");
}
displayChat(displayName, pictureUri);
LinphoneCore lc = LinphoneManager.getLcIfManagerNotDestroyedOrNull();
@ -223,10 +230,15 @@ public class ChatFragment extends Fragment implements OnClickListener, LinphoneC
return view;
}
private void refreshMessages() {
messagesList = LinphoneActivity.instance().getChatMessages(sipUri);
}
@Override
public void onSaveInstanceState(Bundle outState) {
outState.putString("fileToUploadPath", fileToUploadPath);
outState.putParcelable("imageToUpload", imageToUpload);
outState.putInt("messagesFilterLimit", messagesFilterLimit);
super.onSaveInstanceState(outState);
}
@ -255,32 +267,73 @@ public class ChatFragment extends Fragment implements OnClickListener, LinphoneC
public void showKeyboardVisibleMode() {
LinphoneActivity.instance().hideMenu(true);
contactPicture.setVisibility(View.GONE);
scrollToEnd();
//scrollToEnd();
}
public void hideKeyboardVisibleMode() {
LinphoneActivity.instance().hideMenu(false);
contactPicture.setVisibility(View.VISIBLE);
scrollToEnd();
//scrollToEnd();
}
private void invalidate() {
messagesLayout.removeAllViews();
List<ChatMessage> messagesList = LinphoneActivity.instance().getChatMessages(sipUri);
refreshMessages();
previousMessageID = -1;
ChatStorage chatStorage = LinphoneActivity.instance().getChatStorage();
for (ChatMessage msg : messagesList) {
if (msg.getMessage() != null) {
displayMessage(msg.getId(), msg.getMessage(), msg.getTimestamp(), msg.isIncoming(), msg.getStatus(), messagesLayout);
} else {
displayImageMessage(msg.getId(), msg.getImage(), msg.getTimestamp(), msg.isIncoming(), msg.getStatus(), messagesLayout);
}
chatStorage.markMessageAsRead(msg.getId());
}
LinphoneActivity.instance().updateMissedChatCount();
scrollToEnd();
if (messagesFilterLimit == 0) {
if (messagesList.size() > MESSAGES_STEP)
messagesFilterLimit = MESSAGES_STEP;
else
messagesFilterLimit = messagesList.size();
} else {
if (messagesFilterLimit + MESSAGES_STEP <= messagesList.size())
messagesFilterLimit += MESSAGES_STEP;
else
messagesFilterLimit = messagesList.size();
}
invalidate(messagesFilterLimit);
}
private void invalidate(final int limit) {
messagesLayout.removeAllViews();
mHandler.post(new Runnable() {
@Override
public void run() {
previousMessageID = -1;
ChatStorage chatStorage = LinphoneActivity.instance().getChatStorage();
for (int i = messagesList.size() - limit; i < messagesList.size(); i++) {
ChatMessage msg = messagesList.get(i);
if (msg.getMessage() != null) {
displayMessage(msg.getId(), msg.getMessage(), msg.getTimestamp(), msg.isIncoming(), msg.getStatus(), messagesLayout);
} else {
displayImageMessage(msg.getId(), msg.getImage(), msg.getTimestamp(), msg.isIncoming(), msg.getStatus(), messagesLayout);
}
if (!msg.isRed())
chatStorage.markMessageAsRead(msg.getId());
}
LinphoneActivity.instance().updateMissedChatCount();
if (limit < messagesList.size()) {
messagesScrollView.setScrollViewListener(new ScrollViewListener() {
@Override
public void OnScrollToTop(final int previousHeight) {
invalidate();mHandler.postDelayed(new Runnable() {
@Override
public void run() {
//Scroll to latest saw message
messagesScrollView.scrollTo(0, messagesLayout.getChildAt(MESSAGES_STEP-1).getBottom());
}
}, 300);
}
});
} else {
messagesScrollView.setScrollViewListener(null);
}
}
});
}
private void displayChat(String displayName, String pictureUri) {
@ -288,8 +341,7 @@ public class ChatFragment extends Fragment implements OnClickListener, LinphoneC
contactName.setText(LinphoneUtils.getUsernameFromAddress(sipUri));
} else if (displayName == null) {
contactName.setText(sipUri);
}
else {
} else {
contactName.setText(displayName);
}
@ -299,44 +351,37 @@ public class ChatFragment extends Fragment implements OnClickListener, LinphoneC
contactPicture.setImageResource(R.drawable.unknown_small);
}
messagesScrollView.post(new Runnable() {
@Override
public void run() {
scrollToEnd();
}
});
if (messagesFilterLimit == 0)
invalidate();
else {
invalidate(messagesFilterLimit);
}
invalidate();
scrollToEnd();
}
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() {
@Override
public void run() {
BubbleChat bubble = new BubbleChat(layout.getContext(), id, message, null, time, isIncoming, status, previousMessageID);
if (!isIncoming) {
lastSentMessageBubble = bubble;
}
previousMessageID = id;
layout.addView(bubble.getView());
registerForContextMenu(bubble.getView());
}
});
private void displayMessage(int id, String message, String time, boolean isIncoming, LinphoneChatMessage.State status, RelativeLayout layout) {
BubbleChat bubble = new BubbleChat(layout.getContext(), id, message, null, time, isIncoming, status, previousMessageID);
if (!isIncoming) {
lastSentMessageBubble = bubble;
}
View v = bubble.getView();
previousMessageID = id;
layout.addView(v);
registerForContextMenu(v);
}
private void displayImageMessage(final int id, final Bitmap image, final String time, final boolean isIncoming, final LinphoneChatMessage.State status, final RelativeLayout layout) {
mHandler.post(new Runnable() {
@Override
public void run() {
BubbleChat bubble = new BubbleChat(layout.getContext(), id, null, image, time, isIncoming, status, previousMessageID);
if (!isIncoming) {
lastSentMessageBubble = bubble;
}
previousMessageID = id;
layout.addView(bubble.getView());
registerForContextMenu(bubble.getView());
}
});
private void displayImageMessage(int id, Bitmap image, String time, boolean isIncoming, LinphoneChatMessage.State status, RelativeLayout layout) {
BubbleChat bubble = new BubbleChat(layout.getContext(), id, null, image, time, isIncoming, status, previousMessageID);
if (!isIncoming) {
lastSentMessageBubble = bubble;
}
View v = bubble.getView();
previousMessageID = id;
layout.addView(v);
registerForContextMenu(v);
}
public void changeDisplayedChat(String newSipUri, String displayName, String pictureUri) {
@ -351,6 +396,7 @@ public class ChatFragment extends Fragment implements OnClickListener, LinphoneC
LinphoneActivity.instance().getChatStorage().deleteDraft(sipUri);
}
messagesFilterLimit = 0;
sipUri = newSipUri;
if (LinphoneActivity.isInstanciated()) {
String draft = LinphoneActivity.instance().getChatStorage().getDraft(sipUri);
@ -444,7 +490,6 @@ public class ChatFragment extends Fragment implements OnClickListener, LinphoneC
LinphoneActivity.instance().selectMenu(FragmentsAvailable.CHAT);
LinphoneActivity.instance().updateChatFragment(this);
}
scrollToEnd();
if (LinphoneActivity.isInstanciated()) {
String draft = LinphoneActivity.instance().getChatStorage().getDraft(sipUri);
@ -503,7 +548,7 @@ public class ChatFragment extends Fragment implements OnClickListener, LinphoneC
}
private void scrollToEnd() {
mHandler.postDelayed(new Runnable() {
messagesScrollView.postDelayed(new Runnable() {
@Override
public void run() {
messagesScrollView.fullScroll(View.FOCUS_DOWN);
@ -519,14 +564,24 @@ public class ChatFragment extends Fragment implements OnClickListener, LinphoneC
}
}
public void onMessageReceived(int id, LinphoneAddress from, LinphoneChatMessage message) {
public void onMessageReceived(final int id, LinphoneAddress from, final LinphoneChatMessage message) {
if (from.asStringUriOnly().equals(sipUri)) {
if (message.getText() != null) {
displayMessage(id, message.getText(), String.valueOf(System.currentTimeMillis()), true, null, messagesLayout);
mHandler.post(new Runnable() {
@Override
public void run() {
displayMessage(id, message.getText(), String.valueOf(System.currentTimeMillis()), true, null, messagesLayout);
}
});
} else if (message.getExternalBodyUrl() != null) {
byte[] rawImage = LinphoneActivity.instance().getChatStorage().getRawImageFromMessage(id);
Bitmap bm = BitmapFactory.decodeByteArray(rawImage, 0, rawImage.length);
displayImageMessage(id, bm, String.valueOf(System.currentTimeMillis()), true, null, messagesLayout);
final Bitmap bm = BitmapFactory.decodeByteArray(rawImage, 0, rawImage.length);
mHandler.post(new Runnable() {
@Override
public void run() {
displayImageMessage(id, bm, String.valueOf(System.currentTimeMillis()), true, null, messagesLayout);
}
});
}
scrollToEnd();
}

View file

@ -33,8 +33,9 @@ public class ChatMessage {
private int status;
private int id;
private Bitmap image;
private boolean isRed;
public ChatMessage(int id, String message, byte[] rawImage, String timestamp, boolean incoming, int status) {
public ChatMessage(int id, String message, byte[] rawImage, String timestamp, boolean incoming, int status, boolean red) {
super();
this.id = id;
this.message = message;
@ -42,6 +43,7 @@ public class ChatMessage {
this.incoming = incoming;
this.status = status;
this.image = rawImage != null ? BitmapFactory.decodeByteArray(rawImage, 0, rawImage.length) : null;
this.isRed = red;
}
public int getId() {
@ -83,4 +85,8 @@ public class ChatMessage {
public Bitmap getImage() {
return image;
}
public boolean isRed() {
return isRed;
}
}

View file

@ -193,8 +193,9 @@ public class ChatStorage {
timestamp = c.getString(c.getColumnIndex("time"));
int status = c.getInt(c.getColumnIndex("status"));
byte[] rawImage = c.getBlob(c.getColumnIndex("image"));
int read = c.getInt(c.getColumnIndex("read"));
ChatMessage chatMessage = new ChatMessage(id, message, rawImage, timestamp, direction == INCOMING, status);
ChatMessage chatMessage = new ChatMessage(id, message, rawImage, timestamp, direction == INCOMING, status, read == READ);
chatMessages.add(chatMessage);
} catch (Exception e) {
e.printStackTrace();

View file

@ -0,0 +1,56 @@
package org.linphone.ui;
/*
LinphoneScrollView.java
Copyright (C) 2013 Belledonne Communications, Grenoble, France
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/
import android.content.Context;
import android.util.AttributeSet;
import android.widget.ScrollView;
/**
* @author Sylvain Berfini
*/
public class LinphoneScrollView extends ScrollView {
private ScrollViewListener scrollViewListener = null;
public LinphoneScrollView(Context context) {
super(context);
}
public LinphoneScrollView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
public LinphoneScrollView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public void setScrollViewListener(ScrollViewListener scrollViewListener) {
this.scrollViewListener = scrollViewListener;
}
@Override
protected void onScrollChanged(int x, int y, int oldx, int oldy) {
super.onScrollChanged(x, y, oldx, oldy);
if (y >= getMeasuredHeight() && scrollViewListener != null) {
//scrollViewListener.OnScrollToBottom();
}
else if (y == 0 && scrollViewListener != null) {
scrollViewListener.OnScrollToTop(getMeasuredHeight());
}
}
}

View file

@ -0,0 +1,26 @@
package org.linphone.ui;
/*
ScrollViewListener.java
Copyright (C) 2013 Belledonne Communications, Grenoble, France
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/
/**
* @author Sylvain Berfini
*/
public interface ScrollViewListener {
void OnScrollToTop(int previousHeight);
}