chat bubbles. yeah

Daniel Gultsch created

Change summary

AndroidManifest.xml                                  |  1 
gen/de/gultsch/chat/R.java                           | 30 ++-
res/drawable/message_border.xml                      |  6 
res/layout/conversation_list_row.xml                 |  2 
res/layout/fragment_conversation.xml                 | 85 +++++------
res/layout/message_sent.xml                          | 53 +++++++
res/values/strings.xml                               |  2 
src/de/gultsch/chat/entities/Conversation.java       | 11 +
src/de/gultsch/chat/entities/Message.java            |  2 
src/de/gultsch/chat/persistance/DatabaseBackend.java | 32 +++-
src/de/gultsch/chat/ui/ConversationActivity.java     |  3 
src/de/gultsch/chat/ui/ConversationFragment.java     | 96 +++++++++++++
src/de/gultsch/chat/ui/XmppActivity.java             |  4 
src/de/gultsch/chat/utils/Beautifier.java            | 25 +++
14 files changed, 273 insertions(+), 79 deletions(-)

Detailed changes

AndroidManifest.xml 🔗

@@ -9,6 +9,7 @@
         android:targetSdkVersion="19" />
 
     <uses-permission android:name="android.permission.READ_CONTACTS" />
+    <uses-permission android:name="android.permission.READ_PROFILE"/>
     
     <application
         android:allowBackup="true"

gen/de/gultsch/chat/R.java 🔗

@@ -32,29 +32,33 @@ public final class R {
         public static final int ic_action_unsecure=0x7f020004;
         public static final int ic_launcher=0x7f020005;
         public static final int ic_profile=0x7f020006;
-        public static final int section_header=0x7f020007;
+        public static final int message_border=0x7f020007;
+        public static final int profilemock=0x7f020008;
+        public static final int section_header=0x7f020009;
     }
     public static final class id {
-        public static final int action_accounts=0x7f0a001b;
-        public static final int action_add=0x7f0a0017;
-        public static final int action_archive=0x7f0a001a;
-        public static final int action_details=0x7f0a0019;
-        public static final int action_security=0x7f0a0018;
-        public static final int action_settings=0x7f0a001c;
+        public static final int action_accounts=0x7f0a001e;
+        public static final int action_add=0x7f0a001a;
+        public static final int action_archive=0x7f0a001d;
+        public static final int action_details=0x7f0a001c;
+        public static final int action_security=0x7f0a001b;
+        public static final int action_settings=0x7f0a001f;
         public static final int contact_display_name=0x7f0a0009;
         public static final int contact_divider=0x7f0a000b;
         public static final int contact_jid=0x7f0a000a;
         public static final int contact_photo=0x7f0a0008;
         public static final int conversation_image=0x7f0a000c;
         public static final int conversation_lastmsg=0x7f0a000e;
+        public static final int conversation_lastupdate=0x7f0a000f;
         public static final int conversation_name=0x7f0a000d;
         public static final int create_new_contact=0x7f0a0007;
-        public static final int duration=0x7f0a000f;
-        public static final int editText1=0x7f0a0011;
-        public static final int imageButton1=0x7f0a0012;
         public static final int jabber_contacts=0x7f0a0005;
         public static final int jabber_contacts_header=0x7f0a0004;
         public static final int list=0x7f0a0015;
+        public static final int message_body=0x7f0a0018;
+        public static final int message_photo=0x7f0a0017;
+        public static final int message_time=0x7f0a0019;
+        public static final int messages_view=0x7f0a0013;
         public static final int new_contact_header=0x7f0a0006;
         public static final int new_conversation_search=0x7f0a0000;
         public static final int phone_contacts=0x7f0a0003;
@@ -62,7 +66,8 @@ public final class R {
         public static final int scrollView1=0x7f0a0001;
         public static final int selected_conversation=0x7f0a0016;
         public static final int slidingpanelayout=0x7f0a0014;
-        public static final int textView1=0x7f0a0013;
+        public static final int textSendButton=0x7f0a0012;
+        public static final int textinput=0x7f0a0011;
         public static final int textsend=0x7f0a0010;
     }
     public static final class layout {
@@ -71,6 +76,7 @@ public final class R {
         public static final int conversation_list_row=0x7f030002;
         public static final int fragment_conversation=0x7f030003;
         public static final int fragment_conversations_overview=0x7f030004;
+        public static final int message_sent=0x7f030005;
     }
     public static final class menu {
         public static final int conversations=0x7f090000;
@@ -84,6 +90,8 @@ public final class R {
         public static final int action_secure=0x7f070006;
         public static final int action_settings=0x7f070001;
         public static final int app_name=0x7f070000;
+        public static final int just_now=0x7f070008;
+        public static final int sending=0x7f070009;
         public static final int title_activity_new_conversation=0x7f070007;
     }
     public static final class style {

res/drawable/message_border.xml 🔗

@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="UTF-8"?>
+  <shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle">
+  <corners android:radius="2dp"/>
+  <padding android:left="1.5dp" android:right="1.5dp" android:top="1.5dp" android:bottom="1.5dp"/>
+  <solid android:color="#cecece"/>
+</shape>

res/layout/conversation_list_row.xml 🔗

@@ -38,7 +38,7 @@
 	        android:paddingTop="3dp"/>
 
 	    <TextView
-	        android:id="@+id/duration"
+	        android:id="@+id/conversation_lastupdate"
 	        android:layout_width="wrap_content"
 	        android:layout_height="wrap_content"
 	        android:layout_alignBaseline="@+id/conversation_name"

res/layout/fragment_conversation.xml 🔗

@@ -1,64 +1,55 @@
 <?xml version="1.0" encoding="utf-8"?>
 <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools"
     android:layout_width="match_parent"
     android:layout_height="match_parent"
-    android:background="#f9f9f9">
-    
+    android:background="#e5e5e5" >
+
     <RelativeLayout
+        android:background="#eee"
         android:id="@+id/textsend"
         android:layout_width="fill_parent"
         android:layout_height="wrap_content"
         android:layout_alignParentBottom="true"
         android:layout_alignParentLeft="true" >
-    
-    <EditText
-        android:id="@+id/editText1"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:layout_alignParentLeft="true"
-        android:layout_toLeftOf="@+id/imageButton1"
-        android:ems="10"
-        android:inputType="textMultiLine"
-        android:minLines="1"
-        android:background="#ffffff"
-        android:layout_marginTop="12dp"
-        android:layout_marginBottom="12dp"
-        android:layout_marginLeft="8dp"
-        android:layout_marginRight="8dp">
-    </EditText>
 
-    <ImageButton
-        android:id="@+id/imageButton1"
-        android:layout_width="48dp"
-        android:layout_height="48dp"
-        android:layout_alignParentRight="true"
-        android:layout_centerVertical="true"
-        android:background="?android:selectableItemBackground"
-        android:src="@drawable/ic_action_send_now" />
+        <EditText
+            android:id="@+id/textinput"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:minHeight="48dp"
+            android:layout_alignParentLeft="true"
+            android:paddingBottom="12dp"
+            android:paddingLeft="8dp"
+            android:paddingRight="8dp"
+            android:paddingTop="12dp"
+            android:layout_toLeftOf="@+id/textSendButton"
+            android:background="#eee"
+            android:ems="10"
+            android:inputType="textMultiLine"
+            android:minLines="1" >
+        </EditText>
 
+        <ImageButton
+            android:id="@+id/textSendButton"
+            android:layout_width="48dp"
+            android:layout_height="48dp"
+            android:layout_alignParentRight="true"
+            android:layout_centerVertical="true"
+            android:background="?android:selectableItemBackground"
+            android:src="@drawable/ic_action_send_now" />
     </RelativeLayout>
-    
-    <ScrollView
-        android:id="@+id/scrollView1"
+
+    <ListView
+        android:id="@+id/messages_view"
         android:layout_width="fill_parent"
-        android:layout_height="fill_parent"
+        android:layout_height="wrap_content"
         android:layout_above="@+id/textsend"
         android:layout_alignParentLeft="true"
-        android:layout_alignParentTop="true"
-        android:background="#e5e5e5" >
-
-        <LinearLayout
-            android:layout_width="fill_parent"
-            android:layout_height="fill_parent"
-            android:background="#e5e5e5"
-            android:orientation="vertical" >
-
-            <TextView
-                android:id="@+id/textView1"
-                android:layout_width="wrap_content"
-                android:layout_height="wrap_content"
-                android:text="TextView" />
-        </LinearLayout>
-    </ScrollView>
+        android:background="#e5e5e5"
+        tools:listitem="@layout/message_sent"
+        android:divider="@null"
+		android:dividerHeight="0dp">
+    </ListView>
 
-</RelativeLayout>
+</RelativeLayout>

res/layout/message_sent.xml 🔗

@@ -0,0 +1,53 @@
+<?xml version="1.0" encoding="utf-8"?>
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="fill_parent"
+    android:layout_height="wrap_content"
+    android:orientation="vertical"
+    android:padding="8dp">
+
+    <LinearLayout
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:background="@drawable/message_border"
+        android:layout_toLeftOf="@+id/message_photo"
+     	android:layout_alignParentLeft="true"
+     	android:layout_alignParentBottom="true"
+     	android:minHeight="48dp"
+     	>
+<LinearLayout
+    android:layout_width="fill_parent"
+    android:layout_height="fill_parent"
+    android:orientation="vertical"
+    android:background="#ededed"
+    android:padding="5dp">   
+
+    <TextView
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:text="Hi, how are you?"
+        android:textSize="16sp"
+        android:id="@+id/message_body"
+        android:textColor="#333333"/>
+    <TextView
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:paddingTop="1dp"
+        android:text="@string/sending"
+        android:textColor="#8e8e8e"
+        android:textSize="12sp"
+        android:id="@+id/message_time"/>
+
+	</LinearLayout>
+</LinearLayout>
+    <ImageView
+        android:id="@+id/message_photo"
+        android:layout_width="48dp"
+        android:layout_height="48dp"
+        android:layout_alignParentBottom="true"
+        android:layout_alignParentRight="true"
+        android:layout_marginLeft="-1.5dp"
+        android:padding="0dp"
+        android:src="@drawable/ic_profile"
+        android:scaleType="fitXY"/>
+	
+</RelativeLayout>

res/values/strings.xml 🔗

@@ -9,4 +9,6 @@
     <string name="action_details">Show details</string>
     <string name="action_secure">Secure conversation</string>
     <string name="title_activity_new_conversation">New Conversation</string>
+    <string name="just_now">just now</string>
+    <string name="sending">sending&#8230;</string>
 </resources>

src/de/gultsch/chat/entities/Conversation.java 🔗

@@ -1,5 +1,6 @@
 package de.gultsch.chat.entities;
 
+import java.util.ArrayList;
 import java.util.List;
 
 import android.content.ContentValues;
@@ -9,6 +10,9 @@ import android.net.Uri;
 public class Conversation extends AbstractEntity {
 
 	private static final long serialVersionUID = -6727528868973996739L;
+	
+	public static final String TABLENAME = "conversations";
+	
 	public static final int STATUS_AVAILABLE = 0;
 	public static final int STATUS_ARCHIVED = 1;
 	public static final int STATUS_DELETED = 2;
@@ -27,7 +31,7 @@ public class Conversation extends AbstractEntity {
 	private int status;
 	private long created;
 
-	private transient List<Message> messages;
+	private transient List<Message> messages = null;
 
 	public Conversation(String name, Uri profilePhoto, Account account,
 			String contactJid) {
@@ -48,6 +52,7 @@ public class Conversation extends AbstractEntity {
 	}
 
 	public List<Message> getMessages() {
+		if (messages == null) this.messages = new ArrayList<Message>(); //prevent null pointer
 		return messages;
 	}
 
@@ -81,6 +86,10 @@ public class Conversation extends AbstractEntity {
 	public int getStatus() {
 		return this.status;
 	}
+	
+	public long getCreated() {
+		return this.created;
+	}
 
 	public ContentValues getContentValues() {
 		ContentValues values = new ContentValues();

src/de/gultsch/chat/entities/Message.java 🔗

@@ -6,6 +6,8 @@ import android.database.Cursor;
 public class Message extends AbstractEntity {
 
 	private static final long serialVersionUID = 7222081895167103025L;
+	
+	public static final String TABLENAME = "messages";
 
 	public static final int STATUS_RECIEVED = 0;
 	public static final int STATUS_UNSEND = 1;

src/de/gultsch/chat/persistance/DatabaseBackend.java 🔗

@@ -4,15 +4,16 @@ import java.util.ArrayList;
 import java.util.List;
 
 import de.gultsch.chat.entities.Conversation;
+import de.gultsch.chat.entities.Message;
 import android.content.Context;
 import android.database.Cursor;
 import android.database.sqlite.SQLiteDatabase;
 import android.database.sqlite.SQLiteOpenHelper;
 
 public class DatabaseBackend extends SQLiteOpenHelper {
-	
+
 	private static DatabaseBackend instance = null;
-	
+
 	private static final String DATABASE_NAME = "history";
 	private static final int DATABASE_VERSION = 1;
 
@@ -22,7 +23,13 @@ public class DatabaseBackend extends SQLiteOpenHelper {
 
 	@Override
 	public void onCreate(SQLiteDatabase db) {
-		db.execSQL("create table conversations (uuid TEXT, name TEXT, profilePhotoUri TEXT, accountUuid TEXT, contactJid TEXT, created NUMBER, status NUMBER)");
+		db.execSQL("create table " + Conversation.TABLENAME + " ("
+				+ Conversation.UUID + " TEXT, " + Conversation.NAME + " TEXT, "
+				+ Conversation.PHOTO_URI + " TEXT, " + Conversation.ACCOUNT
+				+ " TEXT, " + Conversation.CONTACT + " TEXT, "
+				+ Conversation.CREATED + " NUMBER, " + Conversation.STATUS
+				+ " NUMBER)");
+		db.execSQL("create table "+Message.TABLENAME+ "()");
 	}
 
 	@Override
@@ -30,23 +37,23 @@ public class DatabaseBackend extends SQLiteOpenHelper {
 		// TODO Auto-generated method stub
 
 	}
-	
+
 	public static synchronized DatabaseBackend getInstance(Context context) {
 		if (instance == null) {
 			instance = new DatabaseBackend(context);
 		}
 		return instance;
 	}
-	
+
 	public void addConversation(Conversation conversation) {
 		SQLiteDatabase db = this.getWritableDatabase();
 		db.insert("conversations", null, conversation.getContentValues());
 	}
-	
-	
+
 	public int getConversationCount() {
 		SQLiteDatabase db = this.getReadableDatabase();
-		Cursor cursor = db.rawQuery("select count(uuid) as count from conversations",null);
+		Cursor cursor = db.rawQuery(
+				"select count(uuid) as count from conversations", null);
 		cursor.moveToFirst();
 		return cursor.getInt(0);
 	}
@@ -54,9 +61,12 @@ public class DatabaseBackend extends SQLiteOpenHelper {
 	public List<Conversation> getConversations(int status) {
 		List<Conversation> list = new ArrayList<Conversation>();
 		SQLiteDatabase db = this.getReadableDatabase();
-		String[] selectionArgs = {""+status};
-		Cursor cursor = db.rawQuery("select * from conversations where status = ? order by created desc", selectionArgs);
-		while(cursor.moveToNext()) {
+		String[] selectionArgs = { "" + status };
+		Cursor cursor = db
+				.rawQuery(
+						"select * from conversations where status = ? order by created desc",
+						selectionArgs);
+		while (cursor.moveToNext()) {
 			list.add(Conversation.fromCursor(cursor));
 		}
 		return list;

src/de/gultsch/chat/ui/ConversationActivity.java 🔗

@@ -9,6 +9,7 @@ import de.gultsch.chat.entities.Account;
 import de.gultsch.chat.entities.Contact;
 import de.gultsch.chat.entities.Conversation;
 import de.gultsch.chat.persistance.DatabaseBackend;
+import de.gultsch.chat.utils.Beautifier;
 import android.os.Bundle;
 import android.app.FragmentTransaction;
 import android.content.Context;
@@ -60,6 +61,8 @@ public class ConversationActivity extends XmppActivity {
 				}
 				((TextView) view.findViewById(R.id.conversation_name))
 						.setText(getItem(position).getName());
+				((TextView) view.findViewById(R.id.conversation_lastupdate))
+				.setText(Beautifier.readableTimeDifference(getItem(position).getCreated()));
 				((ImageView) view.findViewById(R.id.conversation_image))
 						.setImageURI(getItem(position).getProfilePhotoUri());
 				return view;

src/de/gultsch/chat/ui/ConversationFragment.java 🔗

@@ -2,24 +2,108 @@ package de.gultsch.chat.ui;
 
 import de.gultsch.chat.R;
 import de.gultsch.chat.entities.Conversation;
+import de.gultsch.chat.entities.Message;
+import de.gultsch.chat.utils.Beautifier;
 import android.app.Fragment;
+import android.content.Context;
+import android.database.Cursor;
+import android.graphics.Typeface;
+import android.net.Uri;
 import android.os.Bundle;
+import android.provider.ContactsContract.Profile;
+import android.util.Log;
 import android.view.LayoutInflater;
 import android.view.View;
+import android.view.View.OnClickListener;
 import android.view.ViewGroup;
+import android.widget.ArrayAdapter;
+import android.widget.EditText;
+import android.widget.ListAdapter;
+import android.widget.ListView;
+import android.widget.ImageButton;
+import android.widget.ImageView;
+import android.widget.TextView;
 
 public class ConversationFragment extends Fragment {
-	
+
 	Conversation conversation;
-	
+
 	public void setConversation(Conversation conv) {
 		this.conversation = conv;
 	}
-	
+
 	@Override
-    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
-        return inflater.inflate(R.layout.fragment_conversation, container, false);
-    }
+	public View onCreateView(final LayoutInflater inflater, ViewGroup container,
+			Bundle savedInstanceState) {
+		
+		String[] mProjection = new String[]
+			    {
+			        Profile._ID,
+			        Profile.PHOTO_THUMBNAIL_URI
+			    };
+			Cursor mProfileCursor = getActivity().getContentResolver().query(
+			        Profile.CONTENT_URI,
+			        mProjection ,
+			        null,
+			        null,
+			        null);
+			
+		mProfileCursor.moveToFirst();
+		final Uri profilePicture = Uri.parse(mProfileCursor.getString(1));
+		
+		Log.d("gultsch","found user profile pic "+profilePicture.toString());
+		
+		final View view = inflater.inflate(R.layout.fragment_conversation, container,
+				false);
+		((ImageButton) view.findViewById(R.id.textSendButton))
+				.setOnClickListener(new OnClickListener() {
+
+					@Override
+					public void onClick(View v) {
+						EditText chatMsg = (EditText) view.findViewById(R.id.textinput);
+						if (chatMsg.getText().length() < 1) return;
+						Message message = new Message(conversation,chatMsg.getText().toString(),
+								Message.ENCRYPTION_NONE);
+						XmppActivity activity = (XmppActivity) getActivity();
+						activity.xmppConnectionService.sendMessage(message);
+						conversation.getMessages().add(message);
+						chatMsg.setText("");
+						
+						ListView messagesView = (ListView) view.findViewById(R.id.messages_view);
+						ArrayAdapter<Message> adapter = (ArrayAdapter<Message>) messagesView.getAdapter();
+						adapter.notifyDataSetChanged();
+						
+						messagesView.setSelection(conversation.getMessages().size() -1);
+					}
+				});
+
+		ListView messagesView = (ListView) view
+				.findViewById(R.id.messages_view);
+		messagesView.setAdapter(new ArrayAdapter<Message>(this.getActivity()
+				.getApplicationContext(), R.layout.message_sent,
+				this.conversation.getMessages()) {
+
+			@Override
+			public View getView(int position, View view, ViewGroup parent) {
+				Message item = getItem(position);
+				if ((item.getStatus() != Message.STATUS_RECIEVED)
+						|| (item.getStatus() == Message.STATUS_SEND)) {
+					view = (View) inflater.inflate(R.layout.message_sent, null);
+					((ImageView) view.findViewById(R.id.message_photo)).setImageURI(profilePicture);
+				}
+				((TextView) view.findViewById(R.id.message_body)).setText(item.getBody());
+				TextView time = (TextView) view.findViewById(R.id.message_time);
+				if (item.getStatus() == Message.STATUS_UNSEND) {
+					time.setTypeface(null, Typeface.ITALIC);
+				} else {
+					time.setText(Beautifier.readableTimeDifference(item.getTimeSent()));
+				}
+				return view;
+			}
+		});
+
+		return view;
+	}
 
 	public Conversation getConversation() {
 		return conversation;

src/de/gultsch/chat/ui/XmppActivity.java 🔗

@@ -10,8 +10,8 @@ import android.content.ServiceConnection;
 import android.os.IBinder;
 
 public abstract class XmppActivity extends Activity {
-	protected XmppConnectionService xmppConnectionService;
-	protected boolean xmppConnectionServiceBound = false;
+	public XmppConnectionService xmppConnectionService;
+	public boolean xmppConnectionServiceBound = false;
 	protected boolean handledViewIntent = false;
 	protected ServiceConnection mConnection = new ServiceConnection() {
 

src/de/gultsch/chat/utils/Beautifier.java 🔗

@@ -0,0 +1,25 @@
+package de.gultsch.chat.utils;
+
+import java.text.SimpleDateFormat;
+import java.util.Date;
+
+public class Beautifier {
+	public static String readableTimeDifference(long time) {
+		if (time==0) {
+			return "just now";
+		}
+		Date date = new Date(time);
+		long difference = (System.currentTimeMillis() - time) / 1000;
+		if (difference<60) {
+			return "just now";
+		} else if (difference<60*10) {
+			return difference / 60 + " min ago";
+		} else if (difference<60*60*24) {
+			SimpleDateFormat sdf = new SimpleDateFormat("HH:mm");
+			return sdf.format(date);
+		} else {
+			SimpleDateFormat sdf = new SimpleDateFormat("M/D");
+			return sdf.format(date);
+		}
+	}
+}