[WIP] Made chat works

This commit is contained in:
ChronosX88 2019-03-28 14:51:49 +04:00
parent 61e660329d
commit 6a5fdcb733
36 changed files with 634 additions and 73 deletions

View File

@ -2,11 +2,11 @@
"formatVersion": 1, "formatVersion": 1,
"database": { "database": {
"version": 2, "version": 2,
"identityHash": "aa6543fc56b99224739cb0b53a63d48e", "identityHash": "a24b31a8e1f482a72f55843041945d5b",
"entities": [ "entities": [
{ {
"tableName": "messages", "tableName": "messages",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `type` INTEGER NOT NULL, `chatID` TEXT, `sender` TEXT, `timestamp` INTEGER NOT NULL, `text` TEXT)", "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `type` INTEGER NOT NULL, `chatID` TEXT, `sender` TEXT, `timestamp` INTEGER NOT NULL, `text` TEXT, `isSent` INTEGER NOT NULL, `isRead` INTEGER NOT NULL)",
"fields": [ "fields": [
{ {
"fieldPath": "id", "fieldPath": "id",
@ -43,6 +43,18 @@
"columnName": "text", "columnName": "text",
"affinity": "TEXT", "affinity": "TEXT",
"notNull": false "notNull": false
},
{
"fieldPath": "isSent",
"columnName": "isSent",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "isRead",
"columnName": "isRead",
"affinity": "INTEGER",
"notNull": true
} }
], ],
"primaryKey": { "primaryKey": {
@ -102,7 +114,7 @@
"views": [], "views": [],
"setupQueries": [ "setupQueries": [
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)", "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, \"aa6543fc56b99224739cb0b53a63d48e\")" "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, \"a24b31a8e1f482a72f55843041945d5b\")"
] ]
} }
} }

View File

@ -18,6 +18,9 @@
<category android:name="android.intent.category.LAUNCHER" /> <category android:name="android.intent.category.LAUNCHER" />
</intent-filter> </intent-filter>
</activity> </activity>
<activity
android:name=".views.ChatActivity"
android:theme="@style/NoWindowActionBar"/>
</application> </application>
<uses-permission android:name="android.permission.INTERNET"/> <uses-permission android:name="android.permission.INTERNET"/>
</manifest> </manifest>

View File

@ -0,0 +1,7 @@
package io.github.chronosx88.influence.contracts;
import android.view.View;
public interface ItemClickListener {
void onItemClick(View view, int position);
}

View File

@ -0,0 +1,9 @@
package io.github.chronosx88.influence.contracts.chatactivity;
import net.tomp2p.peers.PeerAddress;
import io.github.chronosx88.influence.models.roomEntities.MessageEntity;
public interface ChatLogicContract {
void sendMessage(PeerAddress address, MessageEntity message);
}

View File

@ -0,0 +1,6 @@
package io.github.chronosx88.influence.contracts.chatactivity;
public interface ChatPresenterContract {
void sendMessage(String text);
void updateAdapter();
}

View File

@ -0,0 +1,10 @@
package io.github.chronosx88.influence.contracts.chatactivity;
import java.util.List;
import io.github.chronosx88.influence.models.roomEntities.MessageEntity;
public interface ChatViewContract {
void updateMessageList(MessageEntity message);
void updateMessageList(List<MessageEntity> messages);
}

View File

@ -1,5 +1,7 @@
package io.github.chronosx88.influence.contracts.chatlist; package io.github.chronosx88.influence.contracts.chatlist;
import android.content.Intent;
import java.util.List; import java.util.List;
import io.github.chronosx88.influence.helpers.ChatListAdapter; import io.github.chronosx88.influence.helpers.ChatListAdapter;
@ -7,5 +9,6 @@ import io.github.chronosx88.influence.models.roomEntities.ChatEntity;
public interface ChatListViewContract { public interface ChatListViewContract {
void setRecycleAdapter(ChatListAdapter adapter); void setRecycleAdapter(ChatListAdapter adapter);
void startActivity(Intent intent);
void updateChatList(ChatListAdapter adapter, List<ChatEntity> chats); void updateChatList(ChatListAdapter adapter, List<ChatEntity> chats);
} }

View File

@ -1,5 +0,0 @@
package io.github.chronosx88.influence.contracts.main;
public interface MainViewContract {
//
}

View File

@ -1,4 +1,4 @@
package io.github.chronosx88.influence.contracts.main; package io.github.chronosx88.influence.contracts.mainactivity;
public interface MainLogicContract { public interface MainLogicContract {
void initPeer(); void initPeer();

View File

@ -1,4 +1,4 @@
package io.github.chronosx88.influence.contracts.main; package io.github.chronosx88.influence.contracts.mainactivity;
public interface MainPresenterContract { public interface MainPresenterContract {
void initPeer(); void initPeer();

View File

@ -0,0 +1,5 @@
package io.github.chronosx88.influence.contracts.mainactivity;
public interface MainViewContract {
//
}

View File

@ -0,0 +1,84 @@
package io.github.chronosx88.influence.helpers;
import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.List;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
import de.hdodenhof.circleimageview.CircleImageView;
import io.github.chronosx88.influence.R;
import io.github.chronosx88.influence.models.roomEntities.MessageEntity;
public class ChatAdapter extends RecyclerView.Adapter<ChatAdapter.ViewHolder> {
private final static int RIGHT_ITEM = 0;
private final static int LEFT_ITEM = 1;
private final static int TECHNICAL_MESSAGE = 2; // TODO
private final static Context context = AppHelper.getContext();
private ArrayList<MessageEntity> messages = new ArrayList<>();
@NonNull
@Override
public ChatAdapter.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
if(viewType == RIGHT_ITEM) {
return new ChatAdapter.ViewHolder(LayoutInflater.from(context).inflate(R.layout.message_right_item, parent, false));
} else {
return new ChatAdapter.ViewHolder(LayoutInflater.from(context).inflate(R.layout.message_left_item, parent, false));
}
}
public void addMessage(MessageEntity message) {
messages.add(message);
}
public void addMessages(List<MessageEntity> messages) {
this.messages.addAll(messages);
}
@Override
public void onBindViewHolder(@NonNull ChatAdapter.ViewHolder holder, int position) {
// Setting message text
holder.messageText.setText(messages.get(position).text);
// Setting message time (HOUR:MINUTE)
Calendar calendar = Calendar.getInstance();
calendar.setTime(new Date(messages.get(position).timestamp));
String time = calendar.get(Calendar.HOUR) + ":" + calendar.get(Calendar.MINUTE);
holder.messageTime.setText(time);
}
@Override
public int getItemCount() {
return messages.size();
}
@Override
public int getItemViewType(int position) {
if(messages.get(position).sender.equals(AppHelper.getPeerID())) {
return RIGHT_ITEM;
} else {
return LEFT_ITEM;
}
}
class ViewHolder extends RecyclerView.ViewHolder {
TextView messageText;
CircleImageView profileImage;
TextView messageTime;
public ViewHolder(View itemView) {
super(itemView);
messageText = itemView.findViewById(R.id.message_text);
profileImage = itemView.findViewById(R.id.profile_image);
messageTime = itemView.findViewById(R.id.message_time);
}
}
}

View File

@ -12,11 +12,17 @@ import java.util.List;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView; import androidx.recyclerview.widget.RecyclerView;
import io.github.chronosx88.influence.R; import io.github.chronosx88.influence.R;
import io.github.chronosx88.influence.contracts.ItemClickListener;
import io.github.chronosx88.influence.models.roomEntities.ChatEntity; import io.github.chronosx88.influence.models.roomEntities.ChatEntity;
public class ChatListAdapter extends RecyclerView.Adapter<ChatListAdapter.ChatListViewHolder> { public class ChatListAdapter extends RecyclerView.Adapter<ChatListAdapter.ChatListViewHolder> {
List<ChatEntity> chatList = new ArrayList<>(); List<ChatEntity> chatList = new ArrayList<>();
public int onClickPosition = -1; public int onClickPosition = -1;
private ItemClickListener itemClickListener;
public ChatListAdapter(ItemClickListener itemClickListener) {
this.itemClickListener = itemClickListener;
}
@NonNull @NonNull
@Override @Override
@ -41,6 +47,10 @@ public class ChatListAdapter extends RecyclerView.Adapter<ChatListAdapter.ChatLi
holder.onLongClick(position); holder.onLongClick(position);
} }
public ChatEntity getChatEntity(int position) {
return chatList.get(position);
}
@Override @Override
public int getItemCount() { public int getItemCount() {
return chatList.size(); return chatList.size();
@ -53,6 +63,9 @@ public class ChatListAdapter extends RecyclerView.Adapter<ChatListAdapter.ChatLi
super(itemView); super(itemView);
chatName = itemView.findViewById(R.id.chat_name); chatName = itemView.findViewById(R.id.chat_name);
itemView.setOnCreateContextMenuListener(this); itemView.setOnCreateContextMenuListener(this);
itemView.setOnClickListener((v) -> {
itemClickListener.onItemClick(v, getAdapterPosition());
});
} }
public void onLongClick(int position) { public void onLongClick(int position) {

View File

@ -37,15 +37,43 @@ public class LocalDBWrapper {
* @param chatID ID of the chat in which need to create a message * @param chatID ID of the chat in which need to create a message
* @param sender Message sender (username) * @param sender Message sender (username)
* @param text Message text (or technical info if technical message type) * @param text Message text (or technical info if technical message type)
* @return * @return Message ID (in local DB)
*/ */
public static boolean createMessageEntry(int type, String chatID, String sender, String text) { public static long createMessageEntry(int type, String chatID, String sender, String text) {
List<ChatEntity> chatEntities = AppHelper.getChatDB().chatDao().getChatByChatID(chatID); List<ChatEntity> chatEntities = AppHelper.getChatDB().chatDao().getChatByChatID(chatID);
if(chatEntities.size() < 1) { if(chatEntities.size() < 1) {
Log.e(LOG_TAG, "Failed to create message entry because chat " + chatID + " doesn't exists!"); Log.e(LOG_TAG, "Failed to create message entry because chat " + chatID + " doesn't exists!");
return false; return -1;
} }
dbInstance.messageDao().insertMessage(new MessageEntity(type, chatID, sender, new Date().getTime(), text)); MessageEntity message = new MessageEntity(type, chatID, sender, new Date().getTime(), text, false, false);
return true; return dbInstance.messageDao().insertMessage(message);
}
public static MessageEntity getMessageByID(long id) {
List<MessageEntity> messages = dbInstance.messageDao().getMessageByID(id);
if(messages.isEmpty()) {
return null;
}
return messages.get(0);
}
public static List<MessageEntity> getMessagesByChatID(String chatID) {
List<MessageEntity> messages = dbInstance.messageDao().getMessagesByChatID(chatID);
if(messages.isEmpty()) {
return null;
}
return messages;
}
public static ChatEntity getChatByChatID(String chatID) {
List<ChatEntity> chats = dbInstance.chatDao().getChatByChatID(chatID);
if(chats.isEmpty()) {
return null;
}
return chats.get(0);
}
public static void updateChatEntry(long id, boolean isSent) {
dbInstance.messageDao().updateMessage(id, isSent);
} }
} }

View File

@ -0,0 +1,5 @@
package io.github.chronosx88.influence.helpers;
public class MessageTypes {
public static final int USUAL_MESSAGE = 0x0;
}

View File

@ -6,25 +6,21 @@ import com.google.gson.Gson;
import com.google.gson.JsonObject; import com.google.gson.JsonObject;
import com.google.gson.JsonParser; import com.google.gson.JsonParser;
import net.tomp2p.dht.FutureGet;
import net.tomp2p.dht.FuturePut;
import net.tomp2p.dht.FutureRemove;
import net.tomp2p.dht.PeerDHT; import net.tomp2p.dht.PeerDHT;
import net.tomp2p.futures.FuturePing;
import net.tomp2p.peers.Number160; import net.tomp2p.peers.Number160;
import net.tomp2p.peers.Number640; import net.tomp2p.peers.Number640;
import net.tomp2p.peers.PeerAddress; import net.tomp2p.peers.PeerAddress;
import net.tomp2p.storage.Data; import net.tomp2p.storage.Data;
import java.io.IOException; import java.io.IOException;
import java.util.List;
import java.util.Map; import java.util.Map;
import io.github.chronosx88.influence.contracts.observer.NetworkObserver; import io.github.chronosx88.influence.contracts.observer.NetworkObserver;
import io.github.chronosx88.influence.helpers.actions.NetworkActions; import io.github.chronosx88.influence.helpers.actions.NetworkActions;
import io.github.chronosx88.influence.helpers.actions.UIActions; import io.github.chronosx88.influence.helpers.actions.UIActions;
import io.github.chronosx88.influence.models.NewChatRequestMessage; import io.github.chronosx88.influence.models.NewChatRequestMessage;
import io.github.chronosx88.influence.models.roomEntities.ChatEntity; import io.github.chronosx88.influence.models.SendMessage;
import io.github.chronosx88.influence.models.SuccessfullySentMessage;
public class NetworkHandler implements NetworkObserver { public class NetworkHandler implements NetworkObserver {
private final static String LOG_TAG = "NetworkHandler"; private final static String LOG_TAG = "NetworkHandler";
@ -54,6 +50,19 @@ public class NetworkHandler implements NetworkObserver {
ObservableUtils.notifyUI(UIActions.SUCCESSFUL_CREATE_CHAT); ObservableUtils.notifyUI(UIActions.SUCCESSFUL_CREATE_CHAT);
break; break;
} }
case NetworkActions.NEW_MESSAGE: {
SendMessage sendMessage = gson.fromJson((String) object, SendMessage.class);
long messageID = LocalDBWrapper.createMessageEntry(sendMessage.getMessageType(), sendMessage.getChatID(), sendMessage.getSenderID(), sendMessage.getText());
ObservableUtils.notifyUI(UIActions.MESSAGE_RECEIVED, messageID);
sendMessageReceived(sendMessage);
break;
}
case NetworkActions.MESSAGE_SENT: {
SuccessfullySentMessage successfullySentMessage = gson.fromJson((String) object, SuccessfullySentMessage.class);
LocalDBWrapper.updateChatEntry(successfullySentMessage.getMessageID(), true);
}
} }
}).start(); }).start();
} }
@ -64,7 +73,6 @@ public class NetworkHandler implements NetworkObserver {
} }
private void handleIncomingChatRequest(String chatID, PeerAddress chatStarterAddress) { private void handleIncomingChatRequest(String chatID, PeerAddress chatStarterAddress) {
NewChatRequestMessage newChatRequestMessage = new NewChatRequestMessage(chatID, AppHelper.getPeerID(), peerDHT.peerAddress()); NewChatRequestMessage newChatRequestMessage = new NewChatRequestMessage(chatID, AppHelper.getPeerID(), peerDHT.peerAddress());
newChatRequestMessage.setAction(NetworkActions.SUCCESSFULL_CREATE_CHAT); newChatRequestMessage.setAction(NetworkActions.SUCCESSFULL_CREATE_CHAT);
@ -115,7 +123,7 @@ public class NetworkHandler implements NetworkObserver {
.awaitUninterruptibly(); .awaitUninterruptibly();
} }
ObservableUtils.notifyUI(UIActions.NEW_CHAT); ObservableUtils.notifyUI(UIActions.SUCCESSFUL_CREATE_CHAT);
} }
} }
} }
@ -132,19 +140,25 @@ public class NetworkHandler implements NetworkObserver {
e.printStackTrace(); e.printStackTrace();
} }
LocalDBWrapper.createChatEntry( /*LocalDBWrapper.createChatEntry(
newChatRequestMessage.getChatID(), newChatRequestMessage.getMessageID(),
newChatRequestMessage.getSenderID(), newChatRequestMessage.getSenderID(),
newChatRequestMessage.getSenderPeerAddress() newChatRequestMessage.getSenderPeerAddress()
); );*/
Log.i(LOG_TAG, "Chat " + newChatRequestMessage.getChatID() + " successfully accepted!");
peerDHT.remove(Number160.createHash(AppHelper.getPeerID() + "_pendingAcceptedChats")) peerDHT.remove(Number160.createHash(AppHelper.getPeerID() + "_pendingAcceptedChats"))
.contentKey(Number160.createHash(newChatRequestMessage.getChatID())) .contentKey(Number160.createHash(newChatRequestMessage.getChatID()))
.start() .start()
.awaitUninterruptibly(); .awaitUninterruptibly();
ObservableUtils.notifyUI(UIActions.NEW_CHAT); ObservableUtils.notifyUI(UIActions.SUCCESSFUL_CREATE_CHAT);
} }
} }
} }
private void sendMessageReceived(SendMessage sendMessage) {
P2PUtils.send(sendMessage.getSenderPeerAddress(), gson.toJson(new SuccessfullySentMessage(AppHelper.getPeerID(), AppHelper.getPeerDHT().peerAddress(), sendMessage.getMessageID())));
}
} }

View File

@ -8,4 +8,18 @@ public class ObservableUtils {
jsonObject.addProperty("action", action); jsonObject.addProperty("action", action);
AppHelper.getObservable().notifyUIObservers(jsonObject); AppHelper.getObservable().notifyUIObservers(jsonObject);
} }
public static void notifyUI(int action, String additional) {
JsonObject jsonObject = new JsonObject();
jsonObject.addProperty("action", action);
jsonObject.addProperty("additional", additional);
AppHelper.getObservable().notifyUIObservers(jsonObject);
}
public static void notifyUI(int action, long additional) {
JsonObject jsonObject = new JsonObject();
jsonObject.addProperty("action", action);
jsonObject.addProperty("additional", additional);
AppHelper.getObservable().notifyUIObservers(jsonObject);
}
} }

View File

@ -5,6 +5,7 @@ import com.google.gson.Gson;
import net.tomp2p.dht.FutureGet; import net.tomp2p.dht.FutureGet;
import net.tomp2p.dht.FuturePut; import net.tomp2p.dht.FuturePut;
import net.tomp2p.dht.PeerDHT; import net.tomp2p.dht.PeerDHT;
import net.tomp2p.futures.FutureDirect;
import net.tomp2p.futures.FuturePing; import net.tomp2p.futures.FuturePing;
import net.tomp2p.peers.Number160; import net.tomp2p.peers.Number160;
import net.tomp2p.peers.Number640; import net.tomp2p.peers.Number640;
@ -59,4 +60,14 @@ public class P2PUtils {
} }
return null; return null;
} }
public static boolean send(PeerAddress address, String data) {
FutureDirect futureDirect = peerDHT
.peer()
.sendDirect(address)
.object(data)
.start()
.awaitUninterruptibly();
return futureDirect.isSuccess();
}
} }

View File

@ -10,4 +10,5 @@ public class UIActions {
public static final int NEW_CHAT = 0x6; public static final int NEW_CHAT = 0x6;
public static final int PEER_NOT_EXIST = 0x7; public static final int PEER_NOT_EXIST = 0x7;
public static final int SUCCESSFUL_CREATE_CHAT = 0x8; public static final int SUCCESSFUL_CREATE_CHAT = 0x8;
public static final int MESSAGE_RECEIVED = 0x9;
} }

View File

@ -0,0 +1,33 @@
package io.github.chronosx88.influence.logic;
import com.google.gson.Gson;
import net.tomp2p.peers.PeerAddress;
import io.github.chronosx88.influence.contracts.chatactivity.ChatLogicContract;
import io.github.chronosx88.influence.helpers.AppHelper;
import io.github.chronosx88.influence.helpers.P2PUtils;
import io.github.chronosx88.influence.models.SendMessage;
import io.github.chronosx88.influence.models.roomEntities.MessageEntity;
public class ChatLogic implements ChatLogicContract {
private static Gson gson = new Gson();
@Override
public void sendMessage(PeerAddress address, MessageEntity message) {
new Thread(() -> {
P2PUtils
.send(address, gson.toJson(
new SendMessage(
AppHelper.getPeerID(),
AppHelper.getPeerDHT().peerAddress(),
message.id,
message.timestamp,
message.type,
message.chatID,
message.text
)
));
}).start();
// TODO: put message into DHT if user is offline
}
}

View File

@ -7,7 +7,6 @@ import android.util.Log;
import com.google.gson.Gson; import com.google.gson.Gson;
import com.google.gson.JsonObject; import com.google.gson.JsonObject;
import net.tomp2p.dht.FuturePut;
import net.tomp2p.dht.PeerBuilderDHT; import net.tomp2p.dht.PeerBuilderDHT;
import net.tomp2p.dht.PeerDHT; import net.tomp2p.dht.PeerDHT;
import net.tomp2p.futures.FutureBootstrap; import net.tomp2p.futures.FutureBootstrap;
@ -33,7 +32,7 @@ import java.security.spec.DSAPublicKeySpec;
import java.security.spec.InvalidKeySpecException; import java.security.spec.InvalidKeySpecException;
import java.util.UUID; import java.util.UUID;
import io.github.chronosx88.influence.contracts.main.MainLogicContract; import io.github.chronosx88.influence.contracts.mainactivity.MainLogicContract;
import io.github.chronosx88.influence.helpers.AppHelper; import io.github.chronosx88.influence.helpers.AppHelper;
import io.github.chronosx88.influence.helpers.DSAKey; import io.github.chronosx88.influence.helpers.DSAKey;
import io.github.chronosx88.influence.helpers.KeyPairManager; import io.github.chronosx88.influence.helpers.KeyPairManager;

View File

@ -0,0 +1,64 @@
package io.github.chronosx88.influence.models;
import net.tomp2p.peers.PeerAddress;
import java.io.Serializable;
import io.github.chronosx88.influence.helpers.actions.NetworkActions;
public class SendMessage extends BasicNetworkMessage implements Serializable {
private long messageID;
private long timestamp;
private int messageType;
private String chatID;
private String text;
public SendMessage(String senderID, PeerAddress senderPeerAddress, long messageID,long timestamp, int messageType, String chatID, String text) {
super(NetworkActions.NEW_MESSAGE, senderID, senderPeerAddress);
this.messageID = messageID;
this.timestamp = timestamp;
this.messageType = messageType;
this.chatID = chatID;
this.text = text;
}
public long getTimestamp() {
return timestamp;
}
public int getMessageType() {
return messageType;
}
public String getChatID() {
return chatID;
}
public String getText() {
return text;
}
public void setTimestamp(long timestamp) {
this.timestamp = timestamp;
}
public void setMessageType(int messageType) {
this.messageType = messageType;
}
public void setChatID(String chatID) {
this.chatID = chatID;
}
public void setText(String text) {
this.text = text;
}
public long getMessageID() {
return messageID;
}
public void setMessageID(long messageID) {
this.messageID = messageID;
}
}

View File

@ -0,0 +1,24 @@
package io.github.chronosx88.influence.models;
import net.tomp2p.peers.PeerAddress;
import java.io.Serializable;
import io.github.chronosx88.influence.helpers.actions.NetworkActions;
public class SuccessfullySentMessage extends BasicNetworkMessage implements Serializable {
private long messageID;
public SuccessfullySentMessage(String senderID, PeerAddress senderPeerAddress, long messageID) {
super(NetworkActions.MESSAGE_SENT, senderID, senderPeerAddress);
this.messageID = messageID;
}
public long getMessageID() {
return messageID;
}
public void setMessageID(long messageID) {
this.messageID = messageID;
}
}

View File

@ -10,10 +10,10 @@ import io.github.chronosx88.influence.models.roomEntities.MessageEntity;
@Dao @Dao
public interface MessageDao { public interface MessageDao {
@Insert @Insert
void insertMessage(MessageEntity chatModel); long insertMessage(MessageEntity chatModel);
@Query("DELETE FROM messages WHERE id = :msgID") @Query("DELETE FROM messages WHERE id = :msgID")
void deleteMessage(String msgID); void deleteMessage(long msgID);
@Query("DELETE FROM messages WHERE chatID = :chatID") @Query("DELETE FROM messages WHERE chatID = :chatID")
void deleteMessagesByChatID(String chatID); void deleteMessagesByChatID(String chatID);
@ -22,5 +22,11 @@ public interface MessageDao {
List<MessageEntity> getMessagesByChatID(String chatID); List<MessageEntity> getMessagesByChatID(String chatID);
@Query("SELECT * FROM messages WHERE id = :id") @Query("SELECT * FROM messages WHERE id = :id")
List<MessageEntity> getMessageByID(String id); List<MessageEntity> getMessageByID(long id);
@Query("UPDATE messages SET isSent = :isSent WHERE id = :msgID")
void updateMessage(long msgID, boolean isSent);
@Query("UPDATE messages SET text = :text WHERE id = :msgID")
void updateMessage(long msgID, String text);
} }

View File

@ -7,41 +7,23 @@ import androidx.room.PrimaryKey;
@Entity(tableName = "messages") @Entity(tableName = "messages")
public class MessageEntity { public class MessageEntity {
@PrimaryKey(autoGenerate = true) public int id; @PrimaryKey(autoGenerate = true) public long id;
@ColumnInfo public int type; @ColumnInfo public int type;
@ColumnInfo public String chatID; @ColumnInfo public String chatID;
@ColumnInfo public String sender; @ColumnInfo public String sender;
@ColumnInfo public long timestamp; @ColumnInfo public long timestamp;
@ColumnInfo public String text; @ColumnInfo public String text;
@ColumnInfo public boolean isSent;
@ColumnInfo public boolean isRead;
public MessageEntity(int type, String chatID, String sender, long timestamp, String text) { public MessageEntity(int type, String chatID, String sender, long timestamp, String text, boolean isSent, boolean isRead) {
this.type = type; this.type = type;
this.chatID = chatID; this.chatID = chatID;
this.sender = sender; this.sender = sender;
this.timestamp = timestamp; this.timestamp = timestamp;
this.text = text; this.text = text;
} this.isSent = isSent;
this.isRead = isRead;
public int getId() {
return id;
}
public int getType() { return type; }
public String getChatID() {
return chatID;
}
public long getTimestamp() {
return timestamp;
}
public String getSender() {
return sender;
}
public String getText() {
return text;
} }
@NonNull @NonNull

View File

@ -1,5 +1,6 @@
package io.github.chronosx88.influence.presenters; package io.github.chronosx88.influence.presenters;
import android.content.Intent;
import android.view.MenuItem; import android.view.MenuItem;
import net.tomp2p.dht.FutureRemove; import net.tomp2p.dht.FutureRemove;
@ -10,8 +11,10 @@ import io.github.chronosx88.influence.contracts.chatlist.ChatListPresenterContra
import io.github.chronosx88.influence.contracts.chatlist.ChatListViewContract; import io.github.chronosx88.influence.contracts.chatlist.ChatListViewContract;
import io.github.chronosx88.influence.helpers.AppHelper; import io.github.chronosx88.influence.helpers.AppHelper;
import io.github.chronosx88.influence.helpers.ChatListAdapter; import io.github.chronosx88.influence.helpers.ChatListAdapter;
import io.github.chronosx88.influence.helpers.LocalDBWrapper;
import io.github.chronosx88.influence.logic.ChatListLogic; import io.github.chronosx88.influence.logic.ChatListLogic;
import io.github.chronosx88.influence.models.roomEntities.ChatEntity; import io.github.chronosx88.influence.models.roomEntities.ChatEntity;
import io.github.chronosx88.influence.views.ChatActivity;
public class ChatListPresenter implements ChatListPresenterContract { public class ChatListPresenter implements ChatListPresenterContract {
private ChatListViewContract view; private ChatListViewContract view;
@ -20,7 +23,9 @@ public class ChatListPresenter implements ChatListPresenterContract {
public ChatListPresenter(ChatListViewContract view) { public ChatListPresenter(ChatListViewContract view) {
this.view = view; this.view = view;
chatListAdapter = new ChatListAdapter(); chatListAdapter = new ChatListAdapter((v, p)-> {
openChat(chatListAdapter.getChatEntity(p).chatID);
});
this.logic = new ChatListLogic(); this.logic = new ChatListLogic();
this.view.setRecycleAdapter(chatListAdapter); this.view.setRecycleAdapter(chatListAdapter);
} }
@ -32,7 +37,10 @@ public class ChatListPresenter implements ChatListPresenterContract {
@Override @Override
public void openChat(String chatID) { public void openChat(String chatID) {
// TODO Intent intent = new Intent(AppHelper.getContext(), ChatActivity.class);
intent.putExtra("chatID", chatID);
intent.putExtra("contactUsername", LocalDBWrapper.getChatByChatID(chatID).name);
view.startActivity(intent);
} }
@Override @Override
@ -43,6 +51,7 @@ public class ChatListPresenter implements ChatListPresenterContract {
new Thread(() -> { new Thread(() -> {
ChatEntity chat = chatListAdapter.getItem(chatListAdapter.onClickPosition); ChatEntity chat = chatListAdapter.getItem(chatListAdapter.onClickPosition);
AppHelper.getChatDB().chatDao().deleteChat(chat.chatID); AppHelper.getChatDB().chatDao().deleteChat(chat.chatID);
AppHelper.getChatDB().messageDao().deleteMessagesByChatID(chat.chatID);
view.updateChatList(chatListAdapter, logic.loadAllChats()); view.updateChatList(chatListAdapter, logic.loadAllChats());
}).start(); }).start();
} }

View File

@ -0,0 +1,62 @@
package io.github.chronosx88.influence.presenters;
import com.google.gson.Gson;
import com.google.gson.JsonObject;
import net.tomp2p.peers.PeerAddress;
import java.util.ArrayList;
import java.util.List;
import io.github.chronosx88.influence.contracts.chatactivity.ChatLogicContract;
import io.github.chronosx88.influence.contracts.chatactivity.ChatPresenterContract;
import io.github.chronosx88.influence.contracts.chatactivity.ChatViewContract;
import io.github.chronosx88.influence.contracts.observer.Observer;
import io.github.chronosx88.influence.helpers.AppHelper;
import io.github.chronosx88.influence.helpers.LocalDBWrapper;
import io.github.chronosx88.influence.helpers.MessageTypes;
import io.github.chronosx88.influence.helpers.Serializer;
import io.github.chronosx88.influence.helpers.actions.UIActions;
import io.github.chronosx88.influence.logic.ChatLogic;
import io.github.chronosx88.influence.models.roomEntities.MessageEntity;
public class ChatPresenter implements ChatPresenterContract, Observer {
private ChatLogicContract logic;
private ChatViewContract view;
private PeerAddress receiverAddress;
private String chatID;
private Gson gson;
public ChatPresenter(ChatViewContract view, String chatID) {
this.logic = new ChatLogic();
this.view = view;
this.chatID = chatID;
this.receiverAddress = (PeerAddress) Serializer.deserialize(LocalDBWrapper.getChatByChatID(chatID).getPeerAddress());
AppHelper.getObservable().register(this);
gson = new Gson();
}
@Override
public void sendMessage(String text) {
long messageID = LocalDBWrapper.createMessageEntry(MessageTypes.USUAL_MESSAGE, chatID, AppHelper.getPeerID(), text);
MessageEntity message = LocalDBWrapper.getMessageByID(messageID);
logic.sendMessage(receiverAddress, message);
view.updateMessageList(message);
}
@Override
public void handleEvent(JsonObject object) {
switch (object.get("action").getAsInt()) {
case UIActions.MESSAGE_RECEIVED: {
MessageEntity messageEntity = LocalDBWrapper.getMessageByID(object.get("additional").getAsInt());
view.updateMessageList(messageEntity);
}
}
}
@Override
public void updateAdapter() {
List<MessageEntity> entities = LocalDBWrapper.getMessagesByChatID(chatID);
view.updateMessageList(entities == null ? new ArrayList<>() : entities);
}
}

View File

@ -1,8 +1,8 @@
package io.github.chronosx88.influence.presenters; package io.github.chronosx88.influence.presenters;
import io.github.chronosx88.influence.contracts.main.MainLogicContract; import io.github.chronosx88.influence.contracts.mainactivity.MainLogicContract;
import io.github.chronosx88.influence.contracts.main.MainPresenterContract; import io.github.chronosx88.influence.contracts.mainactivity.MainPresenterContract;
import io.github.chronosx88.influence.contracts.main.MainViewContract; import io.github.chronosx88.influence.contracts.mainactivity.MainViewContract;
import io.github.chronosx88.influence.logic.MainLogic; import io.github.chronosx88.influence.logic.MainLogic;
public class MainPresenter implements MainPresenterContract { public class MainPresenter implements MainPresenterContract {

View File

@ -0,0 +1,72 @@
package io.github.chronosx88.influence.views;
import android.content.Intent;
import android.os.Bundle;
import android.widget.EditText;
import android.widget.ImageButton;
import android.widget.TextView;
import java.util.List;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.widget.Toolbar;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import io.github.chronosx88.influence.R;
import io.github.chronosx88.influence.contracts.chatactivity.ChatViewContract;
import io.github.chronosx88.influence.helpers.ChatAdapter;
import io.github.chronosx88.influence.models.roomEntities.MessageEntity;
import io.github.chronosx88.influence.presenters.ChatPresenter;
public class ChatActivity extends AppCompatActivity implements ChatViewContract {
private ChatAdapter chatAdapter;
private RecyclerView messageList;
private ImageButton sendMessageButton;
private EditText messageTextEdit;
private TextView contactUsernameTextView;
private ChatPresenter presenter;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_chat);
Intent intent = getIntent();
presenter = new ChatPresenter(this, intent.getStringExtra("chatID"));
Toolbar toolbar = findViewById(R.id.toolbar_chat_activity);
setSupportActionBar(toolbar);
messageList = findViewById(R.id.message_list);
chatAdapter = new ChatAdapter();
presenter.updateAdapter();
messageList.setAdapter(chatAdapter);
messageList.setLayoutManager(new LinearLayoutManager(this));
contactUsernameTextView = findViewById(R.id.appbar_username);
messageTextEdit = findViewById(R.id.message_input);
sendMessageButton = findViewById(R.id.send_button);
sendMessageButton.setOnClickListener((v) -> {
presenter.sendMessage(messageTextEdit.getText().toString());
});
contactUsernameTextView.setText(intent.getStringExtra("contactUsername"));
}
@Override
public void updateMessageList(MessageEntity message) {
runOnUiThread(() -> {
chatAdapter.addMessage(message);
chatAdapter.notifyDataSetChanged();
});
}
@Override
public void updateMessageList(List<MessageEntity> messages) {
runOnUiThread(() -> {
chatAdapter.addMessages(messages);
chatAdapter.notifyDataSetChanged();
});
}
}

View File

@ -13,8 +13,8 @@ import androidx.appcompat.app.AppCompatActivity;
import androidx.fragment.app.Fragment; import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentTransaction; import androidx.fragment.app.FragmentTransaction;
import io.github.chronosx88.influence.R; import io.github.chronosx88.influence.R;
import io.github.chronosx88.influence.contracts.main.MainPresenterContract; import io.github.chronosx88.influence.contracts.mainactivity.MainPresenterContract;
import io.github.chronosx88.influence.contracts.main.MainViewContract; import io.github.chronosx88.influence.contracts.mainactivity.MainViewContract;
import io.github.chronosx88.influence.contracts.observer.Observer; import io.github.chronosx88.influence.contracts.observer.Observer;
import io.github.chronosx88.influence.helpers.AppHelper; import io.github.chronosx88.influence.helpers.AppHelper;
import io.github.chronosx88.influence.helpers.actions.UIActions; import io.github.chronosx88.influence.helpers.actions.UIActions;

View File

@ -68,8 +68,10 @@ public class ChatListFragment extends Fragment implements ChatListViewContract,
@Override @Override
public void handleEvent(JsonObject object) { public void handleEvent(JsonObject object) {
switch (object.get("action").getAsInt()) { switch (object.get("action").getAsInt()) {
case UIActions.SUCCESSFUL_CREATE_CHAT:
case UIActions.NEW_CHAT: { case UIActions.NEW_CHAT: {
presenter.updateChatList(); presenter.updateChatList();
break;
} }
} }
} }

View File

@ -0,0 +1,5 @@
<vector android:height="24dp" android:tint="#008577"
android:viewportHeight="24.0" android:viewportWidth="24.0"
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="#FF000000" android:pathData="M2.01,21L23,12 2.01,3 2,10l15,2 -15,2z"/>
</vector>

View File

@ -0,0 +1,75 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:background="#e6e6e6">
<com.google.android.material.appbar.AppBarLayout
android:id="@+id/appbar_chat_activity"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<androidx.appcompat.widget.Toolbar
android:id="@+id/toolbar_chat_activity"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/colorPrimary"
android:theme="@style/Base.ThemeOverlay.AppCompat.Dark.ActionBar"
app:popupTheme="@style/MenuStyle">
<de.hdodenhof.circleimageview.CircleImageView
android:layout_width="40dp"
android:layout_height="40dp"
android:src="@mipmap/ic_launcher"
android:id="@+id/profile_image_chat_activity"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/appbar_username"
android:textSize="18sp"
android:layout_marginLeft="25dp"
android:textColor="#FFFFFF"
android:textStyle="bold"
android:layout_marginStart="25dp" />
</androidx.appcompat.widget.Toolbar>
</com.google.android.material.appbar.AppBarLayout>
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/message_list"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_below="@id/appbar_chat_activity"
android:layout_above="@+id/chat_activity_bottom_container"/>
<RelativeLayout
android:id="@+id/chat_activity_bottom_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:background="#FFFFFF"
android:orientation="horizontal"
android:padding="5dp">
<EditText
android:id="@+id/message_input"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:layout_toLeftOf="@+id/send_button"
android:background="@android:color/transparent"
android:hint="Type a message..." />
<ImageButton
android:id="@+id/send_button"
android:layout_width="40dp"
android:layout_height="40dp"
android:layout_alignParentEnd="true"
android:layout_alignParentRight="true"
android:background="@drawable/ic_send_green_24dp" />
</RelativeLayout>
</RelativeLayout>

View File

@ -11,27 +11,27 @@
<de.hdodenhof.circleimageview.CircleImageView <de.hdodenhof.circleimageview.CircleImageView
android:layout_width="40dp" android:layout_width="40dp"
android:layout_height="40dp" android:layout_height="40dp"
android:id="@+id/contact_profile_image" android:id="@+id/profile_image"
android:src="@mipmap/ic_launcher"/> android:src="@mipmap/ic_launcher"/>
<TextView <TextView
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:background="@drawable/message_item_left_background" android:background="@drawable/message_item_left_background"
android:layout_toRightOf="@id/contact_profile_image" android:layout_toRightOf="@id/profile_image"
android:text="Null" android:text="Null"
android:paddingEnd="50dp" android:paddingEnd="50dp"
android:layout_marginStart="4dp" android:layout_marginStart="4dp"
android:layout_marginTop="7dp" android:layout_marginTop="7dp"
android:id="@+id/message_text_left" android:id="@+id/message_text"
android:textSize="18sp" android:textSize="18sp"
android:padding="8dp"/> android:padding="8dp"/>
<TextView <TextView
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:id="@+id/message_time_left" android:id="@+id/message_time"
android:layout_alignBottom="@+id/message_text_left" android:layout_alignBottom="@+id/message_text"
android:layout_alignRight="@+id/message_text_left" android:layout_alignRight="@+id/message_text"
android:paddingBottom="3dp" android:paddingBottom="3dp"
android:paddingRight="4dp" android:paddingRight="4dp"
android:text="12:00"/> android:text="12:00"/>

View File

@ -25,7 +25,7 @@
android:layout_toLeftOf="@id/profile_image" android:layout_toLeftOf="@id/profile_image"
android:layout_marginEnd="4dp" android:layout_marginEnd="4dp"
android:layout_marginTop="7dp" android:layout_marginTop="7dp"
android:id="@+id/message_text_right" android:id="@+id/message_text"
android:textSize="18sp" android:textSize="18sp"
android:paddingEnd="50dp" android:paddingEnd="50dp"
android:textColor="#ffffff" android:textColor="#ffffff"
@ -33,9 +33,9 @@
<TextView <TextView
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:id="@+id/message_time_right" android:id="@+id/message_time"
android:layout_alignBottom="@+id/message_text_right" android:layout_alignBottom="@+id/message_text"
android:layout_alignRight="@+id/message_text_right" android:layout_alignRight="@+id/message_text"
android:paddingBottom="3dp" android:paddingBottom="3dp"
android:textColor="#ffffff" android:textColor="#ffffff"
android:paddingRight="4dp" android:paddingRight="4dp"

View File

@ -32,4 +32,12 @@
<item name="android:topDark">@android:color/transparent</item> <item name="android:topDark">@android:color/transparent</item>
</style> </style>
<style name="MenuStyle" parent="Theme.AppCompat.Light">
<item name="android:background">@android:color/white</item>
</style>
<style name="NoWindowActionBar" parent="AppTheme">
<item name="windowNoTitle">true</item>
</style>
</resources> </resources>