From 6a5fdcb733b72ce18f58d1614c150eb7a551c992 Mon Sep 17 00:00:00 2001 From: ChronosX88 Date: Thu, 28 Mar 2019 14:51:49 +0400 Subject: [PATCH] [WIP] Made chat works --- .../2.json | 18 +++- app/src/main/AndroidManifest.xml | 3 + .../contracts/ItemClickListener.java | 7 ++ .../chatactivity/ChatLogicContract.java | 9 ++ .../chatactivity/ChatPresenterContract.java | 6 ++ .../chatactivity/ChatViewContract.java | 10 +++ .../chatlist/ChatListViewContract.java | 3 + .../contracts/main/MainViewContract.java | 5 -- .../MainLogicContract.java | 2 +- .../MainPresenterContract.java | 2 +- .../mainactivity/MainViewContract.java | 5 ++ .../influence/helpers/ChatAdapter.java | 84 +++++++++++++++++++ .../influence/helpers/ChatListAdapter.java | 13 +++ .../influence/helpers/LocalDBWrapper.java | 38 +++++++-- .../influence/helpers/MessageTypes.java | 5 ++ .../influence/helpers/NetworkHandler.java | 38 ++++++--- .../influence/helpers/ObservableUtils.java | 14 ++++ .../influence/helpers/P2PUtils.java | 11 +++ .../influence/helpers/actions/UIActions.java | 1 + .../chronosx88/influence/logic/ChatLogic.java | 33 ++++++++ .../chronosx88/influence/logic/MainLogic.java | 3 +- .../influence/models/SendMessage.java | 64 ++++++++++++++ .../models/SuccessfullySentMessage.java | 24 ++++++ .../influence/models/daos/MessageDao.java | 12 ++- .../models/roomEntities/MessageEntity.java | 30 ++----- .../presenters/ChatListPresenter.java | 13 ++- .../influence/presenters/ChatPresenter.java | 62 ++++++++++++++ .../influence/presenters/MainPresenter.java | 6 +- .../influence/views/ChatActivity.java | 72 ++++++++++++++++ .../influence/views/MainActivity.java | 4 +- .../views/fragments/ChatListFragment.java | 2 + .../main/res/drawable/ic_send_green_24dp.xml | 5 ++ app/src/main/res/layout/activity_chat.xml | 75 +++++++++++++++++ app/src/main/res/layout/message_left_item.xml | 12 +-- .../main/res/layout/message_right_item.xml | 8 +- app/src/main/res/values/styles.xml | 8 ++ 36 files changed, 634 insertions(+), 73 deletions(-) create mode 100644 app/src/main/java/io/github/chronosx88/influence/contracts/ItemClickListener.java create mode 100644 app/src/main/java/io/github/chronosx88/influence/contracts/chatactivity/ChatLogicContract.java create mode 100644 app/src/main/java/io/github/chronosx88/influence/contracts/chatactivity/ChatPresenterContract.java create mode 100644 app/src/main/java/io/github/chronosx88/influence/contracts/chatactivity/ChatViewContract.java delete mode 100644 app/src/main/java/io/github/chronosx88/influence/contracts/main/MainViewContract.java rename app/src/main/java/io/github/chronosx88/influence/contracts/{main => mainactivity}/MainLogicContract.java (57%) rename app/src/main/java/io/github/chronosx88/influence/contracts/{main => mainactivity}/MainPresenterContract.java (58%) create mode 100644 app/src/main/java/io/github/chronosx88/influence/contracts/mainactivity/MainViewContract.java create mode 100644 app/src/main/java/io/github/chronosx88/influence/helpers/ChatAdapter.java create mode 100644 app/src/main/java/io/github/chronosx88/influence/helpers/MessageTypes.java create mode 100644 app/src/main/java/io/github/chronosx88/influence/logic/ChatLogic.java create mode 100644 app/src/main/java/io/github/chronosx88/influence/models/SendMessage.java create mode 100644 app/src/main/java/io/github/chronosx88/influence/models/SuccessfullySentMessage.java create mode 100644 app/src/main/java/io/github/chronosx88/influence/presenters/ChatPresenter.java create mode 100644 app/src/main/java/io/github/chronosx88/influence/views/ChatActivity.java create mode 100644 app/src/main/res/drawable/ic_send_green_24dp.xml create mode 100644 app/src/main/res/layout/activity_chat.xml diff --git a/app/schemas/io.github.chronosx88.influence.helpers.RoomHelper/2.json b/app/schemas/io.github.chronosx88.influence.helpers.RoomHelper/2.json index 03323fe..4454e73 100644 --- a/app/schemas/io.github.chronosx88.influence.helpers.RoomHelper/2.json +++ b/app/schemas/io.github.chronosx88.influence.helpers.RoomHelper/2.json @@ -2,11 +2,11 @@ "formatVersion": 1, "database": { "version": 2, - "identityHash": "aa6543fc56b99224739cb0b53a63d48e", + "identityHash": "a24b31a8e1f482a72f55843041945d5b", "entities": [ { "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": [ { "fieldPath": "id", @@ -43,6 +43,18 @@ "columnName": "text", "affinity": "TEXT", "notNull": false + }, + { + "fieldPath": "isSent", + "columnName": "isSent", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isRead", + "columnName": "isRead", + "affinity": "INTEGER", + "notNull": true } ], "primaryKey": { @@ -102,7 +114,7 @@ "views": [], "setupQueries": [ "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\")" ] } } \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 633096d..53e778d 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -18,6 +18,9 @@ + \ No newline at end of file diff --git a/app/src/main/java/io/github/chronosx88/influence/contracts/ItemClickListener.java b/app/src/main/java/io/github/chronosx88/influence/contracts/ItemClickListener.java new file mode 100644 index 0000000..1788dfd --- /dev/null +++ b/app/src/main/java/io/github/chronosx88/influence/contracts/ItemClickListener.java @@ -0,0 +1,7 @@ +package io.github.chronosx88.influence.contracts; + +import android.view.View; + +public interface ItemClickListener { + void onItemClick(View view, int position); +} \ No newline at end of file diff --git a/app/src/main/java/io/github/chronosx88/influence/contracts/chatactivity/ChatLogicContract.java b/app/src/main/java/io/github/chronosx88/influence/contracts/chatactivity/ChatLogicContract.java new file mode 100644 index 0000000..ab26d70 --- /dev/null +++ b/app/src/main/java/io/github/chronosx88/influence/contracts/chatactivity/ChatLogicContract.java @@ -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); +} diff --git a/app/src/main/java/io/github/chronosx88/influence/contracts/chatactivity/ChatPresenterContract.java b/app/src/main/java/io/github/chronosx88/influence/contracts/chatactivity/ChatPresenterContract.java new file mode 100644 index 0000000..9668c33 --- /dev/null +++ b/app/src/main/java/io/github/chronosx88/influence/contracts/chatactivity/ChatPresenterContract.java @@ -0,0 +1,6 @@ +package io.github.chronosx88.influence.contracts.chatactivity; + +public interface ChatPresenterContract { + void sendMessage(String text); + void updateAdapter(); +} diff --git a/app/src/main/java/io/github/chronosx88/influence/contracts/chatactivity/ChatViewContract.java b/app/src/main/java/io/github/chronosx88/influence/contracts/chatactivity/ChatViewContract.java new file mode 100644 index 0000000..4600a7a --- /dev/null +++ b/app/src/main/java/io/github/chronosx88/influence/contracts/chatactivity/ChatViewContract.java @@ -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 messages); +} diff --git a/app/src/main/java/io/github/chronosx88/influence/contracts/chatlist/ChatListViewContract.java b/app/src/main/java/io/github/chronosx88/influence/contracts/chatlist/ChatListViewContract.java index 69baa1a..acf1740 100644 --- a/app/src/main/java/io/github/chronosx88/influence/contracts/chatlist/ChatListViewContract.java +++ b/app/src/main/java/io/github/chronosx88/influence/contracts/chatlist/ChatListViewContract.java @@ -1,5 +1,7 @@ package io.github.chronosx88.influence.contracts.chatlist; +import android.content.Intent; + import java.util.List; import io.github.chronosx88.influence.helpers.ChatListAdapter; @@ -7,5 +9,6 @@ import io.github.chronosx88.influence.models.roomEntities.ChatEntity; public interface ChatListViewContract { void setRecycleAdapter(ChatListAdapter adapter); + void startActivity(Intent intent); void updateChatList(ChatListAdapter adapter, List chats); } diff --git a/app/src/main/java/io/github/chronosx88/influence/contracts/main/MainViewContract.java b/app/src/main/java/io/github/chronosx88/influence/contracts/main/MainViewContract.java deleted file mode 100644 index 189e9a9..0000000 --- a/app/src/main/java/io/github/chronosx88/influence/contracts/main/MainViewContract.java +++ /dev/null @@ -1,5 +0,0 @@ -package io.github.chronosx88.influence.contracts.main; - -public interface MainViewContract { - // -} diff --git a/app/src/main/java/io/github/chronosx88/influence/contracts/main/MainLogicContract.java b/app/src/main/java/io/github/chronosx88/influence/contracts/mainactivity/MainLogicContract.java similarity index 57% rename from app/src/main/java/io/github/chronosx88/influence/contracts/main/MainLogicContract.java rename to app/src/main/java/io/github/chronosx88/influence/contracts/mainactivity/MainLogicContract.java index 0d9517b..497828a 100644 --- a/app/src/main/java/io/github/chronosx88/influence/contracts/main/MainLogicContract.java +++ b/app/src/main/java/io/github/chronosx88/influence/contracts/mainactivity/MainLogicContract.java @@ -1,4 +1,4 @@ -package io.github.chronosx88.influence.contracts.main; +package io.github.chronosx88.influence.contracts.mainactivity; public interface MainLogicContract { void initPeer(); diff --git a/app/src/main/java/io/github/chronosx88/influence/contracts/main/MainPresenterContract.java b/app/src/main/java/io/github/chronosx88/influence/contracts/mainactivity/MainPresenterContract.java similarity index 58% rename from app/src/main/java/io/github/chronosx88/influence/contracts/main/MainPresenterContract.java rename to app/src/main/java/io/github/chronosx88/influence/contracts/mainactivity/MainPresenterContract.java index 3ff5969..23ff888 100644 --- a/app/src/main/java/io/github/chronosx88/influence/contracts/main/MainPresenterContract.java +++ b/app/src/main/java/io/github/chronosx88/influence/contracts/mainactivity/MainPresenterContract.java @@ -1,4 +1,4 @@ -package io.github.chronosx88.influence.contracts.main; +package io.github.chronosx88.influence.contracts.mainactivity; public interface MainPresenterContract { void initPeer(); diff --git a/app/src/main/java/io/github/chronosx88/influence/contracts/mainactivity/MainViewContract.java b/app/src/main/java/io/github/chronosx88/influence/contracts/mainactivity/MainViewContract.java new file mode 100644 index 0000000..8f290ff --- /dev/null +++ b/app/src/main/java/io/github/chronosx88/influence/contracts/mainactivity/MainViewContract.java @@ -0,0 +1,5 @@ +package io.github.chronosx88.influence.contracts.mainactivity; + +public interface MainViewContract { + // +} diff --git a/app/src/main/java/io/github/chronosx88/influence/helpers/ChatAdapter.java b/app/src/main/java/io/github/chronosx88/influence/helpers/ChatAdapter.java new file mode 100644 index 0000000..4ead9c5 --- /dev/null +++ b/app/src/main/java/io/github/chronosx88/influence/helpers/ChatAdapter.java @@ -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 { + 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 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 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); + } + } +} diff --git a/app/src/main/java/io/github/chronosx88/influence/helpers/ChatListAdapter.java b/app/src/main/java/io/github/chronosx88/influence/helpers/ChatListAdapter.java index 90771a5..29942aa 100644 --- a/app/src/main/java/io/github/chronosx88/influence/helpers/ChatListAdapter.java +++ b/app/src/main/java/io/github/chronosx88/influence/helpers/ChatListAdapter.java @@ -12,11 +12,17 @@ import java.util.List; import androidx.annotation.NonNull; import androidx.recyclerview.widget.RecyclerView; import io.github.chronosx88.influence.R; +import io.github.chronosx88.influence.contracts.ItemClickListener; import io.github.chronosx88.influence.models.roomEntities.ChatEntity; public class ChatListAdapter extends RecyclerView.Adapter { List chatList = new ArrayList<>(); public int onClickPosition = -1; + private ItemClickListener itemClickListener; + + public ChatListAdapter(ItemClickListener itemClickListener) { + this.itemClickListener = itemClickListener; + } @NonNull @Override @@ -41,6 +47,10 @@ public class ChatListAdapter extends RecyclerView.Adapter { + itemClickListener.onItemClick(v, getAdapterPosition()); + }); } public void onLongClick(int position) { diff --git a/app/src/main/java/io/github/chronosx88/influence/helpers/LocalDBWrapper.java b/app/src/main/java/io/github/chronosx88/influence/helpers/LocalDBWrapper.java index 5061110..ca4472f 100644 --- a/app/src/main/java/io/github/chronosx88/influence/helpers/LocalDBWrapper.java +++ b/app/src/main/java/io/github/chronosx88/influence/helpers/LocalDBWrapper.java @@ -37,15 +37,43 @@ public class LocalDBWrapper { * @param chatID ID of the chat in which need to create a message * @param sender Message sender (username) * @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 chatEntities = AppHelper.getChatDB().chatDao().getChatByChatID(chatID); if(chatEntities.size() < 1) { 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)); - return true; + MessageEntity message = new MessageEntity(type, chatID, sender, new Date().getTime(), text, false, false); + return dbInstance.messageDao().insertMessage(message); + } + + public static MessageEntity getMessageByID(long id) { + List messages = dbInstance.messageDao().getMessageByID(id); + if(messages.isEmpty()) { + return null; + } + return messages.get(0); + } + + public static List getMessagesByChatID(String chatID) { + List messages = dbInstance.messageDao().getMessagesByChatID(chatID); + if(messages.isEmpty()) { + return null; + } + return messages; + } + + public static ChatEntity getChatByChatID(String chatID) { + List 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); } } diff --git a/app/src/main/java/io/github/chronosx88/influence/helpers/MessageTypes.java b/app/src/main/java/io/github/chronosx88/influence/helpers/MessageTypes.java new file mode 100644 index 0000000..579ae88 --- /dev/null +++ b/app/src/main/java/io/github/chronosx88/influence/helpers/MessageTypes.java @@ -0,0 +1,5 @@ +package io.github.chronosx88.influence.helpers; + +public class MessageTypes { + public static final int USUAL_MESSAGE = 0x0; +} diff --git a/app/src/main/java/io/github/chronosx88/influence/helpers/NetworkHandler.java b/app/src/main/java/io/github/chronosx88/influence/helpers/NetworkHandler.java index c8ae371..cbc8259 100644 --- a/app/src/main/java/io/github/chronosx88/influence/helpers/NetworkHandler.java +++ b/app/src/main/java/io/github/chronosx88/influence/helpers/NetworkHandler.java @@ -6,25 +6,21 @@ import com.google.gson.Gson; import com.google.gson.JsonObject; 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.futures.FuturePing; import net.tomp2p.peers.Number160; import net.tomp2p.peers.Number640; import net.tomp2p.peers.PeerAddress; import net.tomp2p.storage.Data; import java.io.IOException; -import java.util.List; import java.util.Map; import io.github.chronosx88.influence.contracts.observer.NetworkObserver; import io.github.chronosx88.influence.helpers.actions.NetworkActions; import io.github.chronosx88.influence.helpers.actions.UIActions; 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 { private final static String LOG_TAG = "NetworkHandler"; @@ -54,6 +50,19 @@ public class NetworkHandler implements NetworkObserver { ObservableUtils.notifyUI(UIActions.SUCCESSFUL_CREATE_CHAT); 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(); } @@ -64,7 +73,6 @@ public class NetworkHandler implements NetworkObserver { } - private void handleIncomingChatRequest(String chatID, PeerAddress chatStarterAddress) { NewChatRequestMessage newChatRequestMessage = new NewChatRequestMessage(chatID, AppHelper.getPeerID(), peerDHT.peerAddress()); newChatRequestMessage.setAction(NetworkActions.SUCCESSFULL_CREATE_CHAT); @@ -115,7 +123,7 @@ public class NetworkHandler implements NetworkObserver { .awaitUninterruptibly(); } - ObservableUtils.notifyUI(UIActions.NEW_CHAT); + ObservableUtils.notifyUI(UIActions.SUCCESSFUL_CREATE_CHAT); } } } @@ -132,19 +140,25 @@ public class NetworkHandler implements NetworkObserver { e.printStackTrace(); } - LocalDBWrapper.createChatEntry( - newChatRequestMessage.getChatID(), + /*LocalDBWrapper.createChatEntry( + newChatRequestMessage.getMessageID(), newChatRequestMessage.getSenderID(), newChatRequestMessage.getSenderPeerAddress() - ); + );*/ + + Log.i(LOG_TAG, "Chat " + newChatRequestMessage.getChatID() + " successfully accepted!"); peerDHT.remove(Number160.createHash(AppHelper.getPeerID() + "_pendingAcceptedChats")) .contentKey(Number160.createHash(newChatRequestMessage.getChatID())) .start() .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()))); + } } diff --git a/app/src/main/java/io/github/chronosx88/influence/helpers/ObservableUtils.java b/app/src/main/java/io/github/chronosx88/influence/helpers/ObservableUtils.java index 6a02596..182bda3 100644 --- a/app/src/main/java/io/github/chronosx88/influence/helpers/ObservableUtils.java +++ b/app/src/main/java/io/github/chronosx88/influence/helpers/ObservableUtils.java @@ -8,4 +8,18 @@ public class ObservableUtils { jsonObject.addProperty("action", action); 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); + } } diff --git a/app/src/main/java/io/github/chronosx88/influence/helpers/P2PUtils.java b/app/src/main/java/io/github/chronosx88/influence/helpers/P2PUtils.java index adcfb1b..dfb48d9 100644 --- a/app/src/main/java/io/github/chronosx88/influence/helpers/P2PUtils.java +++ b/app/src/main/java/io/github/chronosx88/influence/helpers/P2PUtils.java @@ -5,6 +5,7 @@ import com.google.gson.Gson; import net.tomp2p.dht.FutureGet; import net.tomp2p.dht.FuturePut; import net.tomp2p.dht.PeerDHT; +import net.tomp2p.futures.FutureDirect; import net.tomp2p.futures.FuturePing; import net.tomp2p.peers.Number160; import net.tomp2p.peers.Number640; @@ -59,4 +60,14 @@ public class P2PUtils { } return null; } + + public static boolean send(PeerAddress address, String data) { + FutureDirect futureDirect = peerDHT + .peer() + .sendDirect(address) + .object(data) + .start() + .awaitUninterruptibly(); + return futureDirect.isSuccess(); + } } diff --git a/app/src/main/java/io/github/chronosx88/influence/helpers/actions/UIActions.java b/app/src/main/java/io/github/chronosx88/influence/helpers/actions/UIActions.java index 459b8ed..b791d6f 100644 --- a/app/src/main/java/io/github/chronosx88/influence/helpers/actions/UIActions.java +++ b/app/src/main/java/io/github/chronosx88/influence/helpers/actions/UIActions.java @@ -10,4 +10,5 @@ public class UIActions { public static final int NEW_CHAT = 0x6; public static final int PEER_NOT_EXIST = 0x7; public static final int SUCCESSFUL_CREATE_CHAT = 0x8; + public static final int MESSAGE_RECEIVED = 0x9; } diff --git a/app/src/main/java/io/github/chronosx88/influence/logic/ChatLogic.java b/app/src/main/java/io/github/chronosx88/influence/logic/ChatLogic.java new file mode 100644 index 0000000..88cdea9 --- /dev/null +++ b/app/src/main/java/io/github/chronosx88/influence/logic/ChatLogic.java @@ -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 + } +} diff --git a/app/src/main/java/io/github/chronosx88/influence/logic/MainLogic.java b/app/src/main/java/io/github/chronosx88/influence/logic/MainLogic.java index 4e955be..a281c3e 100644 --- a/app/src/main/java/io/github/chronosx88/influence/logic/MainLogic.java +++ b/app/src/main/java/io/github/chronosx88/influence/logic/MainLogic.java @@ -7,7 +7,6 @@ import android.util.Log; import com.google.gson.Gson; import com.google.gson.JsonObject; -import net.tomp2p.dht.FuturePut; import net.tomp2p.dht.PeerBuilderDHT; import net.tomp2p.dht.PeerDHT; import net.tomp2p.futures.FutureBootstrap; @@ -33,7 +32,7 @@ import java.security.spec.DSAPublicKeySpec; import java.security.spec.InvalidKeySpecException; 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.DSAKey; import io.github.chronosx88.influence.helpers.KeyPairManager; diff --git a/app/src/main/java/io/github/chronosx88/influence/models/SendMessage.java b/app/src/main/java/io/github/chronosx88/influence/models/SendMessage.java new file mode 100644 index 0000000..388402d --- /dev/null +++ b/app/src/main/java/io/github/chronosx88/influence/models/SendMessage.java @@ -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; + } +} diff --git a/app/src/main/java/io/github/chronosx88/influence/models/SuccessfullySentMessage.java b/app/src/main/java/io/github/chronosx88/influence/models/SuccessfullySentMessage.java new file mode 100644 index 0000000..da24345 --- /dev/null +++ b/app/src/main/java/io/github/chronosx88/influence/models/SuccessfullySentMessage.java @@ -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; + } +} diff --git a/app/src/main/java/io/github/chronosx88/influence/models/daos/MessageDao.java b/app/src/main/java/io/github/chronosx88/influence/models/daos/MessageDao.java index a93d7c8..5bbb1ca 100644 --- a/app/src/main/java/io/github/chronosx88/influence/models/daos/MessageDao.java +++ b/app/src/main/java/io/github/chronosx88/influence/models/daos/MessageDao.java @@ -10,10 +10,10 @@ import io.github.chronosx88.influence.models.roomEntities.MessageEntity; @Dao public interface MessageDao { @Insert - void insertMessage(MessageEntity chatModel); + long insertMessage(MessageEntity chatModel); @Query("DELETE FROM messages WHERE id = :msgID") - void deleteMessage(String msgID); + void deleteMessage(long msgID); @Query("DELETE FROM messages WHERE chatID = :chatID") void deleteMessagesByChatID(String chatID); @@ -22,5 +22,11 @@ public interface MessageDao { List getMessagesByChatID(String chatID); @Query("SELECT * FROM messages WHERE id = :id") - List getMessageByID(String id); + List 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); } diff --git a/app/src/main/java/io/github/chronosx88/influence/models/roomEntities/MessageEntity.java b/app/src/main/java/io/github/chronosx88/influence/models/roomEntities/MessageEntity.java index facffe6..479b4cb 100644 --- a/app/src/main/java/io/github/chronosx88/influence/models/roomEntities/MessageEntity.java +++ b/app/src/main/java/io/github/chronosx88/influence/models/roomEntities/MessageEntity.java @@ -7,41 +7,23 @@ import androidx.room.PrimaryKey; @Entity(tableName = "messages") public class MessageEntity { - @PrimaryKey(autoGenerate = true) public int id; + @PrimaryKey(autoGenerate = true) public long id; @ColumnInfo public int type; @ColumnInfo public String chatID; @ColumnInfo public String sender; @ColumnInfo public long timestamp; @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.chatID = chatID; this.sender = sender; this.timestamp = timestamp; this.text = text; - } - - 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; + this.isSent = isSent; + this.isRead = isRead; } @NonNull diff --git a/app/src/main/java/io/github/chronosx88/influence/presenters/ChatListPresenter.java b/app/src/main/java/io/github/chronosx88/influence/presenters/ChatListPresenter.java index b62c90b..b42c15b 100644 --- a/app/src/main/java/io/github/chronosx88/influence/presenters/ChatListPresenter.java +++ b/app/src/main/java/io/github/chronosx88/influence/presenters/ChatListPresenter.java @@ -1,5 +1,6 @@ package io.github.chronosx88.influence.presenters; +import android.content.Intent; import android.view.MenuItem; 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.helpers.AppHelper; 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.models.roomEntities.ChatEntity; +import io.github.chronosx88.influence.views.ChatActivity; public class ChatListPresenter implements ChatListPresenterContract { private ChatListViewContract view; @@ -20,7 +23,9 @@ public class ChatListPresenter implements ChatListPresenterContract { public ChatListPresenter(ChatListViewContract view) { this.view = view; - chatListAdapter = new ChatListAdapter(); + chatListAdapter = new ChatListAdapter((v, p)-> { + openChat(chatListAdapter.getChatEntity(p).chatID); + }); this.logic = new ChatListLogic(); this.view.setRecycleAdapter(chatListAdapter); } @@ -32,7 +37,10 @@ public class ChatListPresenter implements ChatListPresenterContract { @Override 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 @@ -43,6 +51,7 @@ public class ChatListPresenter implements ChatListPresenterContract { new Thread(() -> { ChatEntity chat = chatListAdapter.getItem(chatListAdapter.onClickPosition); AppHelper.getChatDB().chatDao().deleteChat(chat.chatID); + AppHelper.getChatDB().messageDao().deleteMessagesByChatID(chat.chatID); view.updateChatList(chatListAdapter, logic.loadAllChats()); }).start(); } diff --git a/app/src/main/java/io/github/chronosx88/influence/presenters/ChatPresenter.java b/app/src/main/java/io/github/chronosx88/influence/presenters/ChatPresenter.java new file mode 100644 index 0000000..626a00b --- /dev/null +++ b/app/src/main/java/io/github/chronosx88/influence/presenters/ChatPresenter.java @@ -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 entities = LocalDBWrapper.getMessagesByChatID(chatID); + view.updateMessageList(entities == null ? new ArrayList<>() : entities); + } +} diff --git a/app/src/main/java/io/github/chronosx88/influence/presenters/MainPresenter.java b/app/src/main/java/io/github/chronosx88/influence/presenters/MainPresenter.java index c51f552..c24d6a7 100644 --- a/app/src/main/java/io/github/chronosx88/influence/presenters/MainPresenter.java +++ b/app/src/main/java/io/github/chronosx88/influence/presenters/MainPresenter.java @@ -1,8 +1,8 @@ package io.github.chronosx88.influence.presenters; -import io.github.chronosx88.influence.contracts.main.MainLogicContract; -import io.github.chronosx88.influence.contracts.main.MainPresenterContract; -import io.github.chronosx88.influence.contracts.main.MainViewContract; +import io.github.chronosx88.influence.contracts.mainactivity.MainLogicContract; +import io.github.chronosx88.influence.contracts.mainactivity.MainPresenterContract; +import io.github.chronosx88.influence.contracts.mainactivity.MainViewContract; import io.github.chronosx88.influence.logic.MainLogic; public class MainPresenter implements MainPresenterContract { diff --git a/app/src/main/java/io/github/chronosx88/influence/views/ChatActivity.java b/app/src/main/java/io/github/chronosx88/influence/views/ChatActivity.java new file mode 100644 index 0000000..8180435 --- /dev/null +++ b/app/src/main/java/io/github/chronosx88/influence/views/ChatActivity.java @@ -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 messages) { + runOnUiThread(() -> { + chatAdapter.addMessages(messages); + chatAdapter.notifyDataSetChanged(); + }); + } +} diff --git a/app/src/main/java/io/github/chronosx88/influence/views/MainActivity.java b/app/src/main/java/io/github/chronosx88/influence/views/MainActivity.java index b9edbd6..100d6da 100644 --- a/app/src/main/java/io/github/chronosx88/influence/views/MainActivity.java +++ b/app/src/main/java/io/github/chronosx88/influence/views/MainActivity.java @@ -13,8 +13,8 @@ import androidx.appcompat.app.AppCompatActivity; import androidx.fragment.app.Fragment; import androidx.fragment.app.FragmentTransaction; import io.github.chronosx88.influence.R; -import io.github.chronosx88.influence.contracts.main.MainPresenterContract; -import io.github.chronosx88.influence.contracts.main.MainViewContract; +import io.github.chronosx88.influence.contracts.mainactivity.MainPresenterContract; +import io.github.chronosx88.influence.contracts.mainactivity.MainViewContract; import io.github.chronosx88.influence.contracts.observer.Observer; import io.github.chronosx88.influence.helpers.AppHelper; import io.github.chronosx88.influence.helpers.actions.UIActions; diff --git a/app/src/main/java/io/github/chronosx88/influence/views/fragments/ChatListFragment.java b/app/src/main/java/io/github/chronosx88/influence/views/fragments/ChatListFragment.java index fcf4251..0038231 100644 --- a/app/src/main/java/io/github/chronosx88/influence/views/fragments/ChatListFragment.java +++ b/app/src/main/java/io/github/chronosx88/influence/views/fragments/ChatListFragment.java @@ -68,8 +68,10 @@ public class ChatListFragment extends Fragment implements ChatListViewContract, @Override public void handleEvent(JsonObject object) { switch (object.get("action").getAsInt()) { + case UIActions.SUCCESSFUL_CREATE_CHAT: case UIActions.NEW_CHAT: { presenter.updateChatList(); + break; } } } diff --git a/app/src/main/res/drawable/ic_send_green_24dp.xml b/app/src/main/res/drawable/ic_send_green_24dp.xml new file mode 100644 index 0000000..475c0fa --- /dev/null +++ b/app/src/main/res/drawable/ic_send_green_24dp.xml @@ -0,0 +1,5 @@ + + + diff --git a/app/src/main/res/layout/activity_chat.xml b/app/src/main/res/layout/activity_chat.xml new file mode 100644 index 0000000..a8b9192 --- /dev/null +++ b/app/src/main/res/layout/activity_chat.xml @@ -0,0 +1,75 @@ + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/message_left_item.xml b/app/src/main/res/layout/message_left_item.xml index 0b43940..9b2a1cd 100644 --- a/app/src/main/res/layout/message_left_item.xml +++ b/app/src/main/res/layout/message_left_item.xml @@ -11,27 +11,27 @@ diff --git a/app/src/main/res/layout/message_right_item.xml b/app/src/main/res/layout/message_right_item.xml index 0ccac27..7ed414a 100644 --- a/app/src/main/res/layout/message_right_item.xml +++ b/app/src/main/res/layout/message_right_item.xml @@ -25,7 +25,7 @@ android:layout_toLeftOf="@id/profile_image" android:layout_marginEnd="4dp" android:layout_marginTop="7dp" - android:id="@+id/message_text_right" + android:id="@+id/message_text" android:textSize="18sp" android:paddingEnd="50dp" android:textColor="#ffffff" @@ -33,9 +33,9 @@ @android:color/transparent + + + +