diff --git a/.idea/misc.xml b/.idea/misc.xml
index 503aca7..33952c6 100644
--- a/.idea/misc.xml
+++ b/.idea/misc.xml
@@ -55,7 +55,7 @@
-
+
diff --git a/app/src/main/java/org/communiquons/android/comunic/client/data/conversations/ConversationMessage.java b/app/src/main/java/org/communiquons/android/comunic/client/data/conversations/ConversationMessage.java
index bf720dd..fee1a7e 100644
--- a/app/src/main/java/org/communiquons/android/comunic/client/data/conversations/ConversationMessage.java
+++ b/app/src/main/java/org/communiquons/android/comunic/client/data/conversations/ConversationMessage.java
@@ -1,5 +1,6 @@
package org.communiquons.android.comunic.client.data.conversations;
+import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import java.util.Objects;
@@ -46,7 +47,7 @@ public class ConversationMessage {
*
* @param conversation_id The ID of the conversation
*/
- public void setConversation_id(int conversation_id) {
+ void setConversation_id(int conversation_id) {
this.conversation_id = conversation_id;
}
@@ -64,7 +65,7 @@ public class ConversationMessage {
*
* @param user_id The ID of the user
*/
- public void setUser_id(int user_id) {
+ void setUser_id(int user_id) {
this.user_id = user_id;
}
@@ -99,6 +100,19 @@ public class ConversationMessage {
return image_path;
}
+ /**
+ * Get the path of the image associated with the content
+ *
+ * Warning ! if no image path were specified, "null" will be returned, but as a string instead
+ * of an empty pointer
+ *
+ * @return The path of the image
+ */
+ @NonNull
+ public String getImagePathNotNull() {
+ return image_path != null ? image_path : "null";
+ }
+
/**
* Set the content of the message
*
@@ -122,7 +136,7 @@ public class ConversationMessage {
*
* @param time_insert The time of insertion of the message
*/
- public void setTime_insert(int time_insert) {
+ void setTime_insert(int time_insert) {
this.time_insert = time_insert;
}
diff --git a/app/src/main/java/org/communiquons/android/comunic/client/data/conversations/ConversationMessagesDbHelper.java b/app/src/main/java/org/communiquons/android/comunic/client/data/conversations/ConversationMessagesDbHelper.java
new file mode 100644
index 0000000..9993149
--- /dev/null
+++ b/app/src/main/java/org/communiquons/android/comunic/client/data/conversations/ConversationMessagesDbHelper.java
@@ -0,0 +1,228 @@
+package org.communiquons.android.comunic.client.data.conversations;
+
+import android.content.ContentValues;
+import android.database.Cursor;
+import android.database.sqlite.SQLiteDatabase;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+import android.util.Log;
+
+import org.communiquons.android.comunic.client.data.DatabaseHelper;
+import org.communiquons.android.comunic.client.data.DatabaseContract.ConversationsMessagesSchema;
+
+import java.util.ArrayList;
+
+/**
+ * Conversation messages database helper
+ *
+ * @author Pierre HUBERT
+ * Created by pierre on 12/16/17.
+ */
+
+class ConversationMessagesDbHelper {
+
+ /**
+ * Debug tag
+ */
+ private static final String TAG = "ConversationMessagesDbH";
+
+ /**
+ * Database helper object
+ */
+ private DatabaseHelper dbHelper;
+
+ /**
+ * Conversations messages table name
+ */
+ private static final String TABLE_NAME = ConversationsMessagesSchema.TABLE_NAME;
+
+ /**
+ * Conversation messages table column
+ */
+ private final String[] columns = {
+ ConversationsMessagesSchema.COLUMN_NAME_MESSAGE_ID,
+ ConversationsMessagesSchema.COLUMN_NAME_CONVERSATION_ID,
+ ConversationsMessagesSchema.COLUMN_NAME_USER_ID,
+ ConversationsMessagesSchema.COLUMN_NAME_IMAGE_PATH,
+ ConversationsMessagesSchema.COLUMN_NAME_MESSAGE,
+ ConversationsMessagesSchema.COLUMN_NAME_TIME_INSERT
+ };
+
+ /**
+ * Class constructor
+ *
+ * @param dbHelper Database helper
+ */
+ ConversationMessagesDbHelper(@NonNull DatabaseHelper dbHelper){
+ this.dbHelper = dbHelper;
+ }
+
+
+ /**
+ * Get the last message stored of a conversation
+ *
+ * @param conversationID The ID of the target conversation
+ * @return The last message of the conversation or null if no message were found
+ */
+ @Nullable
+ ConversationMessage getLast(int conversationID){
+
+ ConversationMessage message = null;
+
+ SQLiteDatabase db = dbHelper.getReadableDatabase();
+
+ //Prepare the query on the database
+ String selection = ConversationsMessagesSchema.COLUMN_NAME_CONVERSATION_ID + " = ?";
+ String[] selectionArgs = {""+conversationID};
+ String orderBy = ConversationsMessagesSchema.COLUMN_NAME_MESSAGE_ID + " DESC";
+ String limit = "1";
+
+ //Perform the request
+ Cursor response = db.query(TABLE_NAME, columns, selection, selectionArgs, null,
+ null, orderBy, limit);
+
+ //Check for response
+ if(response.getCount() != 0){
+ response.moveToFirst();
+ message = getMessageFromCursorPos(response);
+ }
+
+ response.close();
+ db.close();
+
+ return message;
+ }
+
+ /**
+ * Insert a list of messages into the database
+ *
+ * @param list The list of messages to insert
+ * @return The result of the operation
+ */
+ boolean insertMultiple(ArrayList list){
+
+ SQLiteDatabase db = dbHelper.getWritableDatabase();
+
+ boolean success = true;
+
+ //Process the list of messages
+ for(ConversationMessage message : list){
+
+ //Try to insert the message
+ if(!insertOne(db, message))
+ success = false;
+
+ }
+
+ db.close();
+
+ return success;
+ }
+
+ /**
+ * Get an interval of messages from the database
+ *
+ * @param conv The conversation ID
+ * @param start The ID of the oldest message to fetch
+ * @param end The ID of the newest message to fetch
+ * @return The list of messages | null in case of failure
+ */
+ ArrayList getInterval(int conv, int start, int end){
+
+ SQLiteDatabase db = dbHelper.getReadableDatabase();
+
+ //Perform a request on the database
+ String selection = ConversationsMessagesSchema.COLUMN_NAME_CONVERSATION_ID + " = ? AND " +
+ " " + ConversationsMessagesSchema.COLUMN_NAME_MESSAGE_ID + " >= ? AND " +
+ ConversationsMessagesSchema.COLUMN_NAME_MESSAGE_ID + " <= ?";
+ String[] selectionArgs = {
+ ""+conv,
+ ""+start,
+ ""+end
+ };
+ String order = ConversationsMessagesSchema.COLUMN_NAME_MESSAGE_ID;
+ Cursor cur = db.query(TABLE_NAME, columns, selection, selectionArgs,
+ null, null, order);
+
+ //Process each response
+ ArrayList list = new ArrayList<>();
+ cur.moveToFirst();
+ while(!cur.isAfterLast()){
+ list.add(getMessageFromCursorPos(cur));
+ cur.moveToNext();
+ }
+
+ //Close objects
+ cur.close();
+ db.close();
+
+ return list;
+ }
+
+ /**
+ * Insert a single message into the database
+ *
+ * @param db Database object (with writeable access)
+ * @param message The message to insert into the database
+ * @return TRUE in case of success / FALSE else
+ */
+ private boolean insertOne(SQLiteDatabase db, ConversationMessage message){
+
+ //Perform the query
+ return db.insert(TABLE_NAME, null, getContentValues(message)) != -1;
+
+ }
+
+ /**
+ * Fill a message object based on a current cursor positin
+ *
+ * @param cursor The response cursor
+ * @return ConversationMessage object
+ */
+ private ConversationMessage getMessageFromCursorPos(Cursor cursor){
+
+ ConversationMessage message = new ConversationMessage();
+
+ //Query database result
+ message.setId(cursor.getInt(cursor.getColumnIndexOrThrow(
+ ConversationsMessagesSchema.COLUMN_NAME_MESSAGE_ID)));
+
+ message.setConversation_id(cursor.getInt(cursor.getColumnIndexOrThrow(
+ ConversationsMessagesSchema.COLUMN_NAME_CONVERSATION_ID)));
+
+ message.setUser_id(cursor.getInt(cursor.getColumnIndexOrThrow(
+ ConversationsMessagesSchema.COLUMN_NAME_USER_ID)));
+
+ message.setImage_path(cursor.getString(cursor.getColumnIndexOrThrow(
+ ConversationsMessagesSchema.COLUMN_NAME_IMAGE_PATH)));
+
+ message.setContent(cursor.getString(cursor.getColumnIndexOrThrow(
+ ConversationsMessagesSchema.COLUMN_NAME_MESSAGE)));
+
+ message.setTime_insert(cursor.getInt(cursor.getColumnIndexOrThrow(
+ ConversationsMessagesSchema.COLUMN_NAME_TIME_INSERT)));
+
+ return message;
+ }
+
+ /**
+ * Generate a ContentValue from a message object
+ *
+ * @param message The message to convert
+ * @return The generated ContentValues
+ */
+ private ContentValues getContentValues(ConversationMessage message){
+
+ //Generate the ContentValues and return it
+ ContentValues cv = new ContentValues();
+ cv.put(ConversationsMessagesSchema.COLUMN_NAME_MESSAGE_ID, message.getId());
+ cv.put(ConversationsMessagesSchema.COLUMN_NAME_CONVERSATION_ID,
+ message.getConversation_id());
+ cv.put(ConversationsMessagesSchema.COLUMN_NAME_USER_ID, message.getUser_id());
+ cv.put(ConversationsMessagesSchema.COLUMN_NAME_IMAGE_PATH, message.getImagePathNotNull());
+ cv.put(ConversationsMessagesSchema.COLUMN_NAME_MESSAGE, message.getContent());
+ cv.put(ConversationsMessagesSchema.COLUMN_NAME_TIME_INSERT, message.getTime_insert());
+ return cv;
+
+ }
+}
diff --git a/app/src/main/java/org/communiquons/android/comunic/client/data/conversations/ConversationMessagesHelper.java b/app/src/main/java/org/communiquons/android/comunic/client/data/conversations/ConversationMessagesHelper.java
new file mode 100644
index 0000000..e590af5
--- /dev/null
+++ b/app/src/main/java/org/communiquons/android/comunic/client/data/conversations/ConversationMessagesHelper.java
@@ -0,0 +1,181 @@
+package org.communiquons.android.comunic.client.data.conversations;
+
+import android.content.Context;
+import android.support.annotation.Nullable;
+import android.util.Log;
+
+import org.communiquons.android.comunic.client.api.APIRequest;
+import org.communiquons.android.comunic.client.api.APIRequestParameters;
+import org.communiquons.android.comunic.client.api.APIResponse;
+import org.communiquons.android.comunic.client.data.DatabaseHelper;
+import org.json.JSONArray;
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import java.util.ArrayList;
+
+/**
+ * Conversation messages helper
+ *
+ * @author Pierre HUBERT
+ * Created by pierre on 12/16/17.
+ */
+
+public class ConversationMessagesHelper {
+
+ /**
+ * Debug tag
+ */
+ private static final String TAG = "ConversationMessagesHel";
+
+ /**
+ * Conversations messages database helper
+ */
+ private ConversationMessagesDbHelper mDbHelper;
+
+ /**
+ * Context of execution of the application
+ */
+ private Context mContext;
+
+ /**
+ * Public constructor of the helper
+ *
+ * @param context The context of execution of the application
+ * @param dbHelper Database helper associated with the context
+ */
+ public ConversationMessagesHelper(Context context, DatabaseHelper dbHelper){
+ mContext = context;
+ mDbHelper = new ConversationMessagesDbHelper(dbHelper);
+ }
+
+ /**
+ * Get the latest messages of a conversation
+ *
+ * @param conversation_id The ID of the conversation to refresh
+ * @return The ID of the last message available in the database
+ */
+ int refresh_conversation(int conversation_id){
+
+ //Get the ID of the last message available in the database
+ int last_message_id = getLastIDFromDb(conversation_id);
+
+ //Perform a request on the database
+ ArrayList new_messages = downloadNew(conversation_id, last_message_id);
+
+ //Check for errors
+ if(new_messages == null){
+ //An error occurred
+ return -1;
+ }
+
+ //Add the new messages to the database (if any)
+ if(new_messages.size() > 0) {
+ mDbHelper.insertMultiple(new_messages);
+ }
+
+ //Get the last message ID from database again
+ return getLastIDFromDb(conversation_id);
+ }
+
+ /**
+ * Fetch messages from the database
+ *
+ * @param conv Conversation ID
+ * @param start The ID of the oldest message to fetch
+ * @param end The ID of the last message to fetch
+ * @return The message of the interval, or null in case of failure
+ */
+ @Nullable
+ ArrayList getInDb(int conv, int start, int end){
+
+ return mDbHelper.getInterval(conv, start, end);
+
+ }
+
+ /**
+ * Get the ID of the last message of the conversation from the database
+ *
+ * @param conversation_id Target conversation
+ * @return The ID of the last message available in the database or 0 in case of failure
+ */
+ private int getLastIDFromDb(int conversation_id){
+
+ //Get the id of the last message available in the database
+ ConversationMessage last_message = mDbHelper.getLast(conversation_id);
+
+ //Check if there isn't any message
+ if(last_message == null)
+ return 0; //There is no message in the database yet
+
+ //Return the ID of the last message available
+ else
+ return last_message.getId();
+ }
+
+ /**
+ * Download the latest messages available in the API
+ *
+ * @param conversationID The ID of the target conversation
+ * @param last_message_id The ID of the last known message (0 for none)
+ * @return null in case of failure, an empty array if there is no new messages available of the
+ * list of new messages for the specified conversation
+ */
+ @Nullable
+ private ArrayList downloadNew(int conversationID, int last_message_id){
+
+ //Prepare a request on the API
+ APIRequestParameters params = new APIRequestParameters(mContext,
+ "conversations/refresh_single");
+ params.addParameter("conversationID", ""+conversationID);
+ params.addParameter("last_message_id", ""+last_message_id);
+
+ ArrayList list = new ArrayList<>();
+
+ try {
+ //Perform the request
+ APIResponse response = new APIRequest().exec(params);
+
+ //Get the list of new messages
+ JSONArray messages = response.getJSONArray();
+
+ for(int i = 0; i < messages.length(); i++){
+
+ //Convert the message into a message object
+ list.add(getMessageObject(conversationID, messages.getJSONObject(i)));
+
+ }
+
+ } catch (Exception e){
+ Log.e(TAG, "Couldn't refresh the list of messages!");
+ e.printStackTrace();
+ return null;
+ }
+
+ return list;
+ }
+
+ /**
+ * Convert a JSON object into a conversation message element
+ *
+ * @param convID The ID of the current conversation
+ * @param obj The target object
+ * @return Generation Conversation Message object
+ * @throws JSONException If the JSON objected couldn't be decoded
+ */
+ private ConversationMessage getMessageObject(int convID, JSONObject obj) throws JSONException{
+
+ ConversationMessage message = new ConversationMessage();
+
+ //Get the message values
+ message.setId(obj.getInt("ID"));
+ message.setConversation_id(convID);
+ message.setUser_id(obj.getInt("ID_user"));
+ message.setImage_path(obj.getString("image_path"));
+ message.setContent(obj.getString("message"));
+ message.setTime_insert(obj.getInt("time_insert"));
+
+ return message;
+
+ }
+}
diff --git a/app/src/main/java/org/communiquons/android/comunic/client/data/conversations/ConversationRefreshRunnable.java b/app/src/main/java/org/communiquons/android/comunic/client/data/conversations/ConversationRefreshRunnable.java
new file mode 100644
index 0000000..d707d4a
--- /dev/null
+++ b/app/src/main/java/org/communiquons/android/comunic/client/data/conversations/ConversationRefreshRunnable.java
@@ -0,0 +1,163 @@
+package org.communiquons.android.comunic.client.data.conversations;
+
+import android.content.Context;
+import android.support.annotation.NonNull;
+import android.util.Log;
+
+import java.util.ArrayList;
+
+/**
+ * Runnable that refresh in the background the list of messages
+ *
+ * @author Pierre HUBERT
+ * Created by pierre on 12/16/17.
+ */
+
+public class ConversationRefreshRunnable implements Runnable {
+
+ /**
+ * Debug tag
+ */
+ private String TAG = "ConversationRefreshRunn";
+
+ /**
+ * The ID of the conversation
+ */
+ private int conversation_id;
+
+ /**
+ * The ID of the last message available
+ */
+ private int last_message_id;
+
+ /**
+ * Conversation message helper
+ */
+ private ConversationMessagesHelper convMessHelper;
+
+ /**
+ * Set to true to make the thread exit
+ */
+ private boolean quit = false;
+
+ /**
+ * Object that helps to make breaks between refreshes
+ */
+ private final Object object = new Object();
+
+ /**
+ * Create a new conversation refresh runnable
+ *
+ * @param conversation_id The ID of the conversation
+ * @param last_message_id The ID of the last message already present in the list (set to 0
+ * for no message)
+ * @param conversationMessagesHelper Conversation message helper to get access to the database
+ * and to be able to query the API through helper
+ */
+ public ConversationRefreshRunnable(int conversation_id, int last_message_id,
+ @NonNull ConversationMessagesHelper conversationMessagesHelper){
+ this.conversation_id = conversation_id;
+ this.last_message_id = last_message_id;
+ this.convMessHelper = conversationMessagesHelper;
+ }
+
+ /**
+ * onMessagesChangeListener
+ *
+ * This interface is used to perform callback actions on the UI Thread to add messages
+ * to a list for example
+ *
+ * This method also changes in the conversations
+ */
+ public interface onMessagesChangeListener {
+
+ /**
+ * Add new messages to a previous list of messages
+ *
+ * @param messages The new messagess
+ */
+ void onAddMessage(@NonNull ArrayList messages);
+
+ /**
+ * This method is called when there is not any message in the conversation
+ *
+ * Warning ! This method may be called several time
+ */
+ void onNoMessage();
+
+ /**
+ * This method is called when an error occur on a request on the database and / or on the
+ * remote server
+ */
+ void onError();
+
+ }
+
+ @Override
+ public void run() {
+ //Log action
+ Log.v(TAG, "Started conversation refresh runnable.");
+
+ synchronized (object) {
+ //Loop that execute indefinitely until the fragment is stopped
+ while (!quit) {
+
+ //Refresh the list of message from the server - the function return the ID of the last
+ // message available
+ int lastMessageInDb = convMessHelper.refresh_conversation(conversation_id);
+
+ //If the last message in the database is newer than the last message of the caller
+ if (lastMessageInDb > last_message_id) {
+
+ //Fetch all the messages available in the database since the last request
+ ArrayList newMessages = convMessHelper.getInDb(
+ conversation_id,
+ last_message_id + 1,
+ lastMessageInDb
+ );
+
+ //Check for errors
+ if(newMessages == null){
+
+ //Callback : an error occurred.
+ Log.e(TAG, "Couldn't get the list of new messages from local database !");
+
+ }
+ else {
+ //Use the callback to send the messages to the UI thread
+ for(ConversationMessage message: newMessages)
+ Log.v(TAG, "Message: " + message.getContent());
+
+ //Update the ID of the last message fetched
+ last_message_id = lastMessageInDb;
+ }
+
+ }
+
+ if(lastMessageInDb == -1){
+
+ //Callback : an error occurred
+ Log.e(TAG, "Couldn't get the list of new messages !");
+
+ }
+
+ //Make a small break
+ try {
+ object.wait(500);
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ break;
+ }
+ }
+ }
+
+ Log.v(TAG, "Stopped conversation refresh runnable.");
+ }
+
+ /**
+ * Make the thread quit safely (does not interrupt currently running operation)
+ */
+ public void quitSafely(){
+ quit = true;
+ }
+}
diff --git a/app/src/main/java/org/communiquons/android/comunic/client/fragments/ConversationFragment.java b/app/src/main/java/org/communiquons/android/comunic/client/fragments/ConversationFragment.java
index 2140c7a..a3933c9 100644
--- a/app/src/main/java/org/communiquons/android/comunic/client/fragments/ConversationFragment.java
+++ b/app/src/main/java/org/communiquons/android/comunic/client/fragments/ConversationFragment.java
@@ -9,6 +9,12 @@ import android.view.ViewGroup;
import android.widget.Toast;
import org.communiquons.android.comunic.client.R;
+import org.communiquons.android.comunic.client.data.DatabaseHelper;
+import org.communiquons.android.comunic.client.data.conversations.ConversationMessage;
+import org.communiquons.android.comunic.client.data.conversations.ConversationMessagesHelper;
+import org.communiquons.android.comunic.client.data.conversations.ConversationRefreshRunnable;
+
+import java.util.ArrayList;
/**
* Conversation fragment
@@ -36,11 +42,36 @@ public class ConversationFragment extends Fragment {
*/
private int conversation_id;
+ /**
+ * The last available message id
+ */
+ private int last_message_id = 0;
+
+ /**
+ * The list of messages
+ */
+ private ArrayList messagesList;
+
+ /**
+ * Conversation refresh runnable
+ */
+ private ConversationRefreshRunnable refreshRunnable;
+
+ /**
+ * Conversation messages helper
+ */
+ private ConversationMessagesHelper convMessHelper;
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
+ //Database helper
+ DatabaseHelper dbHelper = new DatabaseHelper(getActivity());
+
+ //Set conversation message helper
+ convMessHelper = new ConversationMessagesHelper(getActivity(), dbHelper);
+
//Get the conversation ID
conversation_id = getArguments().getInt(ARG_CONVERSATION_ID);
@@ -52,14 +83,31 @@ public class ConversationFragment extends Fragment {
@Nullable
@Override
- public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, Bundle savedInstanceState) {
+ public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container,
+ Bundle savedInstanceState) {
return inflater.inflate(R.layout.fragment_conversation, container, false);
}
@Override
public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
+ }
- Toast.makeText(getActivity(), "Open : " + conversation_id, Toast.LENGTH_SHORT).show();
+ @Override
+ public void onResume() {
+ super.onResume();
+
+ refreshRunnable = new ConversationRefreshRunnable(conversation_id, last_message_id,
+ convMessHelper);
+
+ //Create and start the thread
+ new Thread(refreshRunnable).start();
+ }
+
+ @Override
+ public void onPause() {
+ super.onPause();
+
+ refreshRunnable.quitSafely();
}
}