From be1b342e8a717d9841265aac19da5669190ac24a Mon Sep 17 00:00:00 2001 From: ChronosX88 Date: Mon, 20 May 2019 23:16:06 +0400 Subject: [PATCH] Now basic chat functional are working! Fully replaced with XMPP. --- app/build.gradle | 3 + .../2.json | 60 ++---- .../chronosx88/influence/XMPPConnection.java | 24 +-- .../influence/XMPPConnectionService.java | 15 +- .../influence/contracts/CoreContracts.kt | 30 ++- .../contracts/observer/IObservable.java | 7 +- .../contracts/observer/IObserver.java | 5 +- .../influence/helpers/ChatAdapter.java | 105 ----------- .../influence/helpers/ChatListAdapter.java | 84 --------- .../influence/helpers/JavaSerializer.java | 52 +++++ .../influence/helpers/KeyPairManager.java | 4 +- .../influence/helpers/LocalDBWrapper.java | 2 +- .../influence/helpers/ObservableActions.java | 22 +++ .../influence/helpers/ObservableUtils.java | 42 +++-- .../influence/helpers/RoomTypeConverter.java | 6 +- .../influence/logic/ChatListLogic.java | 2 +- .../chronosx88/influence/logic/ChatLogic.java | 177 ++---------------- .../chronosx88/influence/logic/MainLogic.java | 64 +------ .../influence/logic/SettingsLogic.kt | 14 +- .../influence/models/GenericDialog.java | 78 ++++++++ .../influence/models/GenericMessage.java | 59 ++++++ .../influence/models/GenericUser.java | 47 +++++ .../models/roomEntities/ChatEntity.java | 14 +- .../influence/observable/MainObservable.java | 39 +--- .../presenters/ChatListPresenter.java | 56 ------ .../influence/presenters/ChatPresenter.kt | 90 ++++----- .../presenters/DialogListPresenter.java | 96 ++++++++++ .../influence/presenters/MainPresenter.kt | 82 ++++---- .../influence/presenters/SettingsPresenter.kt | 34 +--- .../influence/views/ChatActivity.kt | 62 +++--- .../influence/views/LoginActivity.java | 5 +- .../influence/views/MainActivity.java | 75 ++++---- .../views/fragments/ChatListFragment.kt | 76 -------- .../views/fragments/DialogListFragment.kt | 39 ++++ .../views/fragments/SettingsFragment.java | 8 +- app/src/main/res/layout/activity_chat.xml | 42 ++--- app/src/main/res/layout/chatlist_fragment.xml | 4 +- app/src/main/res/values-ru-rRU/strings.xml | 1 + app/src/main/res/values/strings.xml | 1 + 39 files changed, 718 insertions(+), 908 deletions(-) delete mode 100644 app/src/main/java/io/github/chronosx88/influence/helpers/ChatAdapter.java delete mode 100644 app/src/main/java/io/github/chronosx88/influence/helpers/ChatListAdapter.java create mode 100644 app/src/main/java/io/github/chronosx88/influence/helpers/JavaSerializer.java create mode 100644 app/src/main/java/io/github/chronosx88/influence/helpers/ObservableActions.java create mode 100644 app/src/main/java/io/github/chronosx88/influence/models/GenericDialog.java create mode 100644 app/src/main/java/io/github/chronosx88/influence/models/GenericMessage.java create mode 100644 app/src/main/java/io/github/chronosx88/influence/models/GenericUser.java delete mode 100644 app/src/main/java/io/github/chronosx88/influence/presenters/ChatListPresenter.java create mode 100644 app/src/main/java/io/github/chronosx88/influence/presenters/DialogListPresenter.java delete mode 100644 app/src/main/java/io/github/chronosx88/influence/views/fragments/ChatListFragment.kt create mode 100644 app/src/main/java/io/github/chronosx88/influence/views/fragments/DialogListFragment.kt diff --git a/app/build.gradle b/app/build.gradle index f309182..37c75c3 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -64,6 +64,9 @@ dependencies { implementation 'org.igniterealtime.smack:smack-android:4.3.3' implementation 'org.igniterealtime.smack:smack-extensions:4.3.3' + implementation 'com.github.stfalcon:chatkit:0.3.3' + implementation 'net.sourceforge.streamsupport:streamsupport:1.7.0' + } repositories { mavenCentral() 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 31a7610..390f447 100644 --- a/app/schemas/io.github.chronosx88.influence.helpers.RoomHelper/2.json +++ b/app/schemas/io.github.chronosx88.influence.helpers.RoomHelper/2.json @@ -2,39 +2,27 @@ "formatVersion": 1, "database": { "version": 2, - "identityHash": "81501115d10a6dc46002667323359631", + "identityHash": "2409c873b47ccd635ed7d10e4d8604f8", "entities": [ { "tableName": "messages", - "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`messageID` TEXT NOT NULL, `type` INTEGER NOT NULL, `chatID` TEXT, `senderID` TEXT, `username` TEXT, `timestamp` INTEGER NOT NULL, `text` TEXT, `isSent` INTEGER NOT NULL, `isRead` INTEGER NOT NULL, PRIMARY KEY(`messageID`))", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`messageID` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `jid` TEXT, `senderJid` TEXT, `timestamp` INTEGER NOT NULL, `text` TEXT, `isSent` INTEGER NOT NULL, `isRead` INTEGER NOT NULL)", "fields": [ { "fieldPath": "messageID", "columnName": "messageID", - "affinity": "TEXT", - "notNull": true - }, - { - "fieldPath": "type", - "columnName": "type", "affinity": "INTEGER", "notNull": true }, { - "fieldPath": "chatID", - "columnName": "chatID", + "fieldPath": "jid", + "columnName": "jid", "affinity": "TEXT", "notNull": false }, { - "fieldPath": "senderID", - "columnName": "senderID", - "affinity": "TEXT", - "notNull": false - }, - { - "fieldPath": "username", - "columnName": "username", + "fieldPath": "senderJid", + "columnName": "senderJid", "affinity": "TEXT", "notNull": false }, @@ -67,55 +55,43 @@ "columnNames": [ "messageID" ], - "autoGenerate": false + "autoGenerate": true }, "indices": [], "foreignKeys": [] }, { "tableName": "chats", - "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`chatID` TEXT NOT NULL, `name` TEXT, `metadataRef` TEXT, `membersRef` TEXT, `bannedUsers` TEXT, `chunkCursor` INTEGER NOT NULL, PRIMARY KEY(`chatID`))", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`jid` TEXT NOT NULL, `chatName` TEXT, `users` TEXT, `unreadMessagesCount` INTEGER NOT NULL, PRIMARY KEY(`jid`))", "fields": [ { - "fieldPath": "chatID", - "columnName": "chatID", + "fieldPath": "jid", + "columnName": "jid", "affinity": "TEXT", "notNull": true }, { - "fieldPath": "name", - "columnName": "name", + "fieldPath": "chatName", + "columnName": "chatName", "affinity": "TEXT", "notNull": false }, { - "fieldPath": "metadataRef", - "columnName": "metadataRef", + "fieldPath": "users", + "columnName": "users", "affinity": "TEXT", "notNull": false }, { - "fieldPath": "membersRef", - "columnName": "membersRef", - "affinity": "TEXT", - "notNull": false - }, - { - "fieldPath": "bannedUsers", - "columnName": "bannedUsers", - "affinity": "TEXT", - "notNull": false - }, - { - "fieldPath": "chunkCursor", - "columnName": "chunkCursor", + "fieldPath": "unreadMessagesCount", + "columnName": "unreadMessagesCount", "affinity": "INTEGER", "notNull": true } ], "primaryKey": { "columnNames": [ - "chatID" + "jid" ], "autoGenerate": false }, @@ -126,7 +102,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, \"81501115d10a6dc46002667323359631\")" + "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, \"2409c873b47ccd635ed7d10e4d8604f8\")" ] } } \ No newline at end of file diff --git a/app/src/main/java/io/github/chronosx88/influence/XMPPConnection.java b/app/src/main/java/io/github/chronosx88/influence/XMPPConnection.java index 6bad8f7..3688686 100644 --- a/app/src/main/java/io/github/chronosx88/influence/XMPPConnection.java +++ b/app/src/main/java/io/github/chronosx88/influence/XMPPConnection.java @@ -56,9 +56,6 @@ public class XMPPConnection implements ConnectionListener { public enum ConnectionState { CONNECTED, - AUTHENTICATED, - CONNECTING, - DISCONNECTING, DISCONNECTED } @@ -68,6 +65,8 @@ public class XMPPConnection implements ConnectionListener { } public XMPPConnection(Context context) { + this.prefs = PreferenceManager.getDefaultSharedPreferences(context); + this.context = context; String jid = prefs.getString("jid", null); String password = prefs.getString("pass", null); if(jid != null && password != null) { @@ -78,8 +77,6 @@ public class XMPPConnection implements ConnectionListener { credentials.password = password; } networkHandler = new NetworkHandler(context); - prefs = PreferenceManager.getDefaultSharedPreferences(context); - this.context = context; } public void connect() throws XMPPException, SmackException, IOException { @@ -97,6 +94,9 @@ public class XMPPConnection implements ConnectionListener { connection = new XMPPTCPConnection(conf); connection.addConnectionListener(this); + if(credentials.jabberHost.equals("") && credentials.password.equals("") && credentials.username.equals("")){ + throw new IOException(); + } try { connection.connect(); connection.login(credentials.username, credentials.password); @@ -121,26 +121,28 @@ public class XMPPConnection implements ConnectionListener { @Override public void connected(org.jivesoftware.smack.XMPPConnection connection) { - XMPPConnectionService.connectionState = ConnectionState.CONNECTED; + XMPPConnectionService.CONNECTION_STATE = ConnectionState.CONNECTED; } @Override public void authenticated(org.jivesoftware.smack.XMPPConnection connection, boolean resumed) { - XMPPConnectionService.sessionState = SessionState.LOGGED_IN; + XMPPConnectionService.SESSION_STATE = SessionState.LOGGED_IN; prefs.edit().putBoolean("logged_in", true).apply(); + context.sendBroadcast(new Intent(XMPPConnectionService.INTENT_AUTHENTICATED)); + AppHelper.setJid(credentials.username + "@" + credentials.jabberHost); } @Override public void connectionClosed() { - XMPPConnectionService.connectionState = ConnectionState.DISCONNECTED; - XMPPConnectionService.sessionState = SessionState.LOGGED_OUT; + XMPPConnectionService.CONNECTION_STATE = ConnectionState.DISCONNECTED; + XMPPConnectionService.SESSION_STATE = SessionState.LOGGED_OUT; prefs.edit().putBoolean("logged_in", false).apply(); } @Override public void connectionClosedOnError(Exception e) { - XMPPConnectionService.connectionState = ConnectionState.DISCONNECTED; - XMPPConnectionService.sessionState = SessionState.LOGGED_OUT; + XMPPConnectionService.CONNECTION_STATE = ConnectionState.DISCONNECTED; + XMPPConnectionService.SESSION_STATE = SessionState.LOGGED_OUT; prefs.edit().putBoolean("logged_in", false).apply(); Log.e(LOG_TAG, "Connection closed, exception occurred"); e.printStackTrace(); diff --git a/app/src/main/java/io/github/chronosx88/influence/XMPPConnectionService.java b/app/src/main/java/io/github/chronosx88/influence/XMPPConnectionService.java index 5e9b00b..c9ea545 100644 --- a/app/src/main/java/io/github/chronosx88/influence/XMPPConnectionService.java +++ b/app/src/main/java/io/github/chronosx88/influence/XMPPConnectionService.java @@ -29,6 +29,8 @@ import org.jivesoftware.smack.XMPPException; import java.io.IOException; +import io.github.chronosx88.influence.helpers.AppHelper; + public class XMPPConnectionService extends Service { public static final String INTENT_NEW_MESSAGE = "io.github.chronosx88.intents.new_message"; public static final String INTENT_SEND_MESSAGE = "io.github.chronosx88.intents.send_message"; @@ -40,18 +42,16 @@ public class XMPPConnectionService extends Service { public static final String MESSAGE_BODY = "message_body"; public static final String MESSAGE_RECIPIENT = "message_recipient"; - public static XMPPConnection.ConnectionState connectionState = XMPPConnection.ConnectionState.DISCONNECTED; - public static XMPPConnection.SessionState sessionState = XMPPConnection.SessionState.LOGGED_OUT; + public static XMPPConnection.ConnectionState CONNECTION_STATE = XMPPConnection.ConnectionState.DISCONNECTED; + public static XMPPConnection.SessionState SESSION_STATE = XMPPConnection.SessionState.LOGGED_OUT; private Thread thread; private Handler threadHandler; private boolean isThreadAlive = false; private XMPPConnection connection; - private Context context; + private Context context = AppHelper.getContext(); - public XMPPConnectionService(Context context) { - this.context = context; - } + public XMPPConnectionService() { } @Override public IBinder onBind(Intent intent) { return null; } @@ -89,9 +89,8 @@ public class XMPPConnectionService extends Service { connection.connect(); } catch (IOException | SmackException | XMPPException e) { Intent intent = new Intent(INTENT_AUTHENTICATION_FAILED); - + context.sendBroadcast(intent); e.printStackTrace(); - //Stop the service all together. stopSelf(); } } diff --git a/app/src/main/java/io/github/chronosx88/influence/contracts/CoreContracts.kt b/app/src/main/java/io/github/chronosx88/influence/contracts/CoreContracts.kt index cfef030..35d4bb0 100644 --- a/app/src/main/java/io/github/chronosx88/influence/contracts/CoreContracts.kt +++ b/app/src/main/java/io/github/chronosx88/influence/contracts/CoreContracts.kt @@ -1,9 +1,12 @@ package io.github.chronosx88.influence.contracts +import android.content.Context import android.content.Intent -import android.view.MenuItem +import com.stfalcon.chatkit.dialogs.DialogsListAdapter +import com.stfalcon.chatkit.messages.MessagesListAdapter -import io.github.chronosx88.influence.helpers.ChatListAdapter +import io.github.chronosx88.influence.models.GenericDialog +import io.github.chronosx88.influence.models.GenericMessage import io.github.chronosx88.influence.models.roomEntities.ChatEntity import io.github.chronosx88.influence.models.roomEntities.MessageEntity @@ -15,28 +18,23 @@ interface CoreContracts { // -----ChatList----- - interface IChatListLogicContract { + interface IDialogListLogicContract { fun loadAllChats(): List } - interface IChatListPresenterContract { - fun updateChatList() + interface IDialogListPresenterContract { fun openChat(chatID: String) - fun onContextItemSelected(item: MenuItem) } interface IChatListViewContract { - fun setRecycleAdapter(adapter: ChatListAdapter) + fun setDialogAdapter(adapter: DialogsListAdapter) fun startActivity(intent: Intent) - fun updateChatList(adapter: ChatListAdapter, chats: List) + fun getActivityContext(): Context? } // -----MainActivity----- interface IMainLogicContract { - fun initPeer() - fun sendStartChatMessage(username: String) - fun shutdownPeer() } interface IMainPresenterContract { @@ -53,19 +51,17 @@ interface CoreContracts { // -----ChatActivity----- interface IChatLogicContract { - fun sendMessage(message: MessageEntity) - fun stopTrackingForNewMsgs() + fun sendMessage(text: String): MessageEntity } interface IChatPresenterContract { - fun sendMessage(text: String) - fun updateAdapter() + fun sendMessage(text: String): Boolean + fun loadLocalMessages() fun onDestroy() } interface IChatViewContract { - fun updateMessageList(message: MessageEntity) - fun updateMessageList(messages: List) + fun setAdapter(adapter: MessagesListAdapter) } // -----SettingsFragment----- diff --git a/app/src/main/java/io/github/chronosx88/influence/contracts/observer/IObservable.java b/app/src/main/java/io/github/chronosx88/influence/contracts/observer/IObservable.java index 68a8126..d94166f 100644 --- a/app/src/main/java/io/github/chronosx88/influence/contracts/observer/IObservable.java +++ b/app/src/main/java/io/github/chronosx88/influence/contracts/observer/IObservable.java @@ -1,12 +1,9 @@ package io.github.chronosx88.influence.contracts.observer; -import com.google.gson.JsonObject; +import org.json.JSONObject; public interface IObservable { void register(IObserver observer); - void register(INetworkObserver networkObserver); void unregister(IObserver observer); - void unregister(INetworkObserver networkObserver); - void notifyUIObservers(JsonObject jsonObject); - void notifyNetworkObservers(Object object); + void notifyUIObservers(JSONObject jsonObject); } diff --git a/app/src/main/java/io/github/chronosx88/influence/contracts/observer/IObserver.java b/app/src/main/java/io/github/chronosx88/influence/contracts/observer/IObserver.java index 0ffa003..3eb83c5 100644 --- a/app/src/main/java/io/github/chronosx88/influence/contracts/observer/IObserver.java +++ b/app/src/main/java/io/github/chronosx88/influence/contracts/observer/IObserver.java @@ -1,7 +1,8 @@ package io.github.chronosx88.influence.contracts.observer; -import com.google.gson.JsonObject; +import org.json.JSONException; +import org.json.JSONObject; public interface IObserver { - void handleEvent(JsonObject object); + void handleEvent(JSONObject object) throws JSONException; } \ No newline at end of file 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 deleted file mode 100644 index 5ef7223..0000000 --- a/app/src/main/java/io/github/chronosx88/influence/helpers/ChatAdapter.java +++ /dev/null @@ -1,105 +0,0 @@ -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.text.DateFormat; -import java.util.ArrayList; -import java.util.Calendar; -import java.util.Collection; -import java.util.Collections; -import java.util.Comparator; -import java.util.Date; -import java.util.HashSet; -import java.util.List; -import java.util.Set; -import java.util.SortedSet; -import java.util.TimeZone; -import java.util.TreeSet; - -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 Context context = AppHelper.getContext(); - private ArrayList messages = new ArrayList<>(); - private static Comparator comparator = ((o1, o2) -> Long.compare(o1.timestamp, o2.timestamp)); - - @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) { - if(message != null) { - for (MessageEntity messageEntity : messages) { - if(messageEntity.messageID.equals(message.messageID)) { - return; - } - } - messages.add(message); - Collections.sort(messages, comparator); - } - } - - public void addMessages(List messages) { - this.messages.addAll(messages); - Collections.sort(messages, comparator); - } - - @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) - DateFormat dateFormat = DateFormat.getTimeInstance(DateFormat.SHORT); - dateFormat.setTimeZone(TimeZone.getDefault()); - String time = dateFormat.format(new Date(messages.get(position).timestamp)); - holder.messageTime.setText(time); - } - - @Override - public int getItemCount() { - return messages.size(); - } - - @Override - public int getItemViewType(int position) { - if(messages.get(position).senderID.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 deleted file mode 100644 index 4fe5f9f..0000000 --- a/app/src/main/java/io/github/chronosx88/influence/helpers/ChatListAdapter.java +++ /dev/null @@ -1,84 +0,0 @@ -package io.github.chronosx88.influence.helpers; - -import android.view.ContextMenu; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.TextView; - -import java.util.ArrayList; -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.IItemClickListener; -import io.github.chronosx88.influence.models.roomEntities.ChatEntity; - -public class ChatListAdapter extends RecyclerView.Adapter { - List chatList = new ArrayList<>(); - public int onClickPosition = -1; - private IItemClickListener itemClickListener; - - public ChatListAdapter(IItemClickListener itemClickListener) { - this.itemClickListener = itemClickListener; - } - - @NonNull - @Override - public ChatListViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { - View view = LayoutInflater.from(parent.getContext()) - .inflate(R.layout.chat_item, parent, false); - return new ChatListViewHolder(view); - } - - public void setChatList(List entities) { - chatList.clear(); - chatList.addAll(entities); - } - - public ChatEntity getItem(int position) { - return chatList.get(position); - } - - @Override - public void onBindViewHolder(@NonNull ChatListViewHolder holder, int position) { - holder.chatName.setText(chatList.get(position).name); - holder.onLongClick(position); - } - - public ChatEntity getChatEntity(int position) { - return chatList.get(position); - } - - @Override - public int getItemCount() { - return chatList.size(); - } - - class ChatListViewHolder extends RecyclerView.ViewHolder implements View.OnCreateContextMenuListener { - TextView chatName; - - public ChatListViewHolder(View itemView) { - super(itemView); - chatName = itemView.findViewById(R.id.chat_name); - itemView.setOnCreateContextMenuListener(this); - itemView.setOnClickListener((v) -> { - itemClickListener.onItemClick(v, getAdapterPosition()); - }); - } - - public void onLongClick(int position) { - itemView.setOnLongClickListener((v) -> { - onClickPosition = position; - itemView.showContextMenu(); - return true; - }); - } - - @Override - public void onCreateContextMenu(ContextMenu menu, View v, ContextMenu.ContextMenuInfo menuInfo) { - menu.add(0, 0, 0, "Remove chat"); - } - } -} diff --git a/app/src/main/java/io/github/chronosx88/influence/helpers/JavaSerializer.java b/app/src/main/java/io/github/chronosx88/influence/helpers/JavaSerializer.java new file mode 100644 index 0000000..d726152 --- /dev/null +++ b/app/src/main/java/io/github/chronosx88/influence/helpers/JavaSerializer.java @@ -0,0 +1,52 @@ +/* + * Copyright (C) 2019 ChronosX88 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package io.github.chronosx88.influence.helpers; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; + +public class JavaSerializer { + public byte[] serialize(T object) { + ByteArrayOutputStream byteArray = new ByteArrayOutputStream(); + try { + ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArray); + objectOutputStream.writeObject(object); + objectOutputStream.close(); + } catch (IOException e) { + e.printStackTrace(); + } + return byteArray.toByteArray(); + } + + public T deserialize(byte[] serializedObject) { + if(serializedObject == null) + return null; + ByteArrayInputStream inputStream = new ByteArrayInputStream(serializedObject); + Object object = null; + try { + ObjectInputStream objectInputStream = new ObjectInputStream(inputStream); + object = objectInputStream.readObject(); + } catch (ClassNotFoundException | IOException e) { + e.printStackTrace(); + } + return (T) object; + } +} diff --git a/app/src/main/java/io/github/chronosx88/influence/helpers/KeyPairManager.java b/app/src/main/java/io/github/chronosx88/influence/helpers/KeyPairManager.java index 2b7dc80..ff6e6c7 100644 --- a/app/src/main/java/io/github/chronosx88/influence/helpers/KeyPairManager.java +++ b/app/src/main/java/io/github/chronosx88/influence/helpers/KeyPairManager.java @@ -10,14 +10,14 @@ import java.security.NoSuchAlgorithmException; public class KeyPairManager { private File keyPairDir; - private Serializer serializer; + private JavaSerializer serializer; public KeyPairManager() { this.keyPairDir = new File(AppHelper.getContext().getFilesDir().getAbsoluteFile(), "keyPairs"); if(!this.keyPairDir.exists()) { this.keyPairDir.mkdir(); } - this.serializer = new Serializer<>(); + this.serializer = new JavaSerializer<>(); } public KeyPair openMainKeyPair() { 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 1347b63..4a6240b 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 @@ -13,7 +13,7 @@ public class LocalDBWrapper { private static RoomHelper dbInstance = AppHelper.getChatDB(); public static void createChatEntry(String jid, String chatName) { - dbInstance.chatDao().addChat(new ChatEntity(jid, chatName)); + dbInstance.chatDao().addChat(new ChatEntity(jid, chatName, new ArrayList<>(), 0)); } public static long createMessageEntry(String jid, String senderJid, long timestamp, String text, boolean isSent, boolean isRead) { diff --git a/app/src/main/java/io/github/chronosx88/influence/helpers/ObservableActions.java b/app/src/main/java/io/github/chronosx88/influence/helpers/ObservableActions.java new file mode 100644 index 0000000..fcc460b --- /dev/null +++ b/app/src/main/java/io/github/chronosx88/influence/helpers/ObservableActions.java @@ -0,0 +1,22 @@ +/* + * Copyright (C) 2019 ChronosX88 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package io.github.chronosx88.influence.helpers; + +public class ObservableActions { + public static final int NEW_CHAT_CREATED = 0x0; +} 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 c2ef9ab..60f698d 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 @@ -3,28 +3,48 @@ package io.github.chronosx88.influence.helpers; import com.google.gson.JsonArray; import com.google.gson.JsonObject; +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; + public class ObservableUtils { public static void notifyUI(int action) { - JsonObject jsonObject = new JsonObject(); - jsonObject.addProperty("action", action); + JSONObject jsonObject = new JSONObject(); + try { + jsonObject.put("action", action); + } catch (JSONException e) { + e.printStackTrace(); + } AppHelper.getObservable().notifyUIObservers(jsonObject); } public static void notifyUI(int action, String... additional) { - JsonObject jsonObject = new JsonObject(); - jsonObject.addProperty("action", action); - JsonArray jsonArray = new JsonArray(); - for(String info : additional) { - jsonArray.add(info); + JSONObject jsonObject = new JSONObject(); + try { + jsonObject.put("action", action); + } catch (JSONException e) { + e.printStackTrace(); + } + JSONArray jsonArray = new JSONArray(); + for(String info : additional) { + jsonArray.put(info); + } + try { + jsonObject.put("additional", jsonArray); + } catch (JSONException e) { + e.printStackTrace(); } - jsonObject.add("additional", jsonArray); AppHelper.getObservable().notifyUIObservers(jsonObject); } public static void notifyUI(int action, int additional) { - JsonObject jsonObject = new JsonObject(); - jsonObject.addProperty("action", action); - jsonObject.addProperty("additional", additional); + JSONObject jsonObject = new JSONObject(); + try { + jsonObject.put("action", action); + jsonObject.put("additional", additional); + } catch (JSONException e) { + e.printStackTrace(); + } AppHelper.getObservable().notifyUIObservers(jsonObject); } } diff --git a/app/src/main/java/io/github/chronosx88/influence/helpers/RoomTypeConverter.java b/app/src/main/java/io/github/chronosx88/influence/helpers/RoomTypeConverter.java index aa2dcdc..cd22de4 100644 --- a/app/src/main/java/io/github/chronosx88/influence/helpers/RoomTypeConverter.java +++ b/app/src/main/java/io/github/chronosx88/influence/helpers/RoomTypeConverter.java @@ -8,16 +8,18 @@ import java.util.ArrayList; import androidx.room.TypeConverter; +import io.github.chronosx88.influence.models.GenericUser; + public class RoomTypeConverter { @TypeConverter - public static ArrayList fromString(String value) { + public static ArrayList fromString(String value) { Type listType = new TypeToken>() {}.getType(); return new Gson().fromJson(value, listType); } @TypeConverter - public static String fromArrayList(ArrayList list) { + public static String fromArrayList(ArrayList list) { Gson gson = new Gson(); return gson.toJson(list); } diff --git a/app/src/main/java/io/github/chronosx88/influence/logic/ChatListLogic.java b/app/src/main/java/io/github/chronosx88/influence/logic/ChatListLogic.java index 12eda21..6e34198 100644 --- a/app/src/main/java/io/github/chronosx88/influence/logic/ChatListLogic.java +++ b/app/src/main/java/io/github/chronosx88/influence/logic/ChatListLogic.java @@ -6,7 +6,7 @@ import io.github.chronosx88.influence.contracts.CoreContracts; import io.github.chronosx88.influence.helpers.AppHelper; import io.github.chronosx88.influence.models.roomEntities.ChatEntity; -public class ChatListLogic implements CoreContracts.IChatListLogicContract { +public class ChatListLogic implements CoreContracts.IDialogListLogicContract { @Override public List loadAllChats() { 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 index 2d5897e..3639821 100644 --- a/app/src/main/java/io/github/chronosx88/influence/logic/ChatLogic.java +++ b/app/src/main/java/io/github/chronosx88/influence/logic/ChatLogic.java @@ -1,182 +1,39 @@ package io.github.chronosx88.influence.logic; -import com.google.gson.Gson; -import com.google.gson.JsonObject; -import com.google.gson.JsonParser; +import android.content.Intent; -import net.tomp2p.peers.Number640; -import net.tomp2p.storage.Data; - -import java.io.IOException; -import java.util.Map; -import java.util.Timer; -import java.util.TimerTask; -import java.util.UUID; +import com.instacart.library.truetime.TrueTime; +import io.github.chronosx88.influence.XMPPConnection; +import io.github.chronosx88.influence.XMPPConnectionService; import io.github.chronosx88.influence.contracts.CoreContracts; import io.github.chronosx88.influence.helpers.AppHelper; -import io.github.chronosx88.influence.helpers.KeyPairManager; import io.github.chronosx88.influence.helpers.LocalDBWrapper; -import io.github.chronosx88.influence.helpers.ObservableUtils; -import io.github.chronosx88.influence.helpers.actions.NetworkActions; -import io.github.chronosx88.influence.helpers.actions.UIActions; -import io.github.chronosx88.influence.models.JoinChatMessage; -import io.github.chronosx88.influence.models.NextChunkReference; -import io.github.chronosx88.influence.models.TextMessage; import io.github.chronosx88.influence.models.roomEntities.ChatEntity; import io.github.chronosx88.influence.models.roomEntities.MessageEntity; public class ChatLogic implements CoreContracts.IChatLogicContract { - private static Gson gson = new Gson(); private String chatID; - private volatile String newMessage = ""; private ChatEntity chatEntity; - private Thread checkNewMessagesThread = null; - private KeyPairManager keyPairManager; - private Timer timer; + //private KeyPairManager keyPairManager; public ChatLogic(ChatEntity chatEntity) { this.chatEntity = chatEntity; - this.chatID = chatEntity.chatID; - TimerTask timerTask = new TimerTask() { - @Override - public void run() { - checkForNewMessages(); - } - }; - this.timer = new Timer(); - if(AppHelper.getPeerDHT() != null) { - timer.schedule(timerTask, 1, 1000); - } - this.keyPairManager = new KeyPairManager(); + this.chatID = chatEntity.jid; + //this.keyPairManager = new KeyPairManager(); } @Override - public void sendMessage(MessageEntity message) { - if(AppHelper.getPeerDHT() == null) { - ObservableUtils.notifyUI(UIActions.NODE_IS_OFFLINE); - return; + public MessageEntity sendMessage(String text) { + if (XMPPConnectionService.CONNECTION_STATE.equals(XMPPConnection.ConnectionState.CONNECTED)) { + Intent intent = new Intent(XMPPConnectionService.INTENT_SEND_MESSAGE); + intent.putExtra(XMPPConnectionService.MESSAGE_BODY, text); + intent.putExtra(XMPPConnectionService.MESSAGE_RECIPIENT, chatEntity.jid); + AppHelper.getContext().sendBroadcast(intent); + long messageID = LocalDBWrapper.createMessageEntry(chatID, AppHelper.getJid(), TrueTime.now().getTime(), text, false, false); + return LocalDBWrapper.getMessageByID(messageID); + } else { + return null; } - new Thread(() -> { - Data data = null; - try { - data = new Data(gson.toJson(new TextMessage(message.senderID, message.messageID, message.chatID, message.username, message.timestamp, message.text, false))); - } catch (IOException e) { - e.printStackTrace(); - } - data.protectEntry(keyPairManager.getKeyPair("mainKeyPair")); - P2PUtils.put(chatID + "_messages" + chatEntity.chunkCursor, message.messageID, data); - try { - P2PUtils.put(chatID + "_newMessage", null, new Data(message.messageID)); - } catch (IOException e) { - e.printStackTrace(); - } - }).start(); - } - - private void checkForNewMessages() { - if(checkNewMessagesThread == null) { - checkNewMessagesThread = new Thread(() -> { - Map data = P2PUtils.get(chatID + "_newMessage"); - if(data != null) { - for(Map.Entry entry : data.entrySet()) { - String newMessage = null; - try { - newMessage = (String) entry.getValue().object(); - } catch (ClassNotFoundException e) { - e.printStackTrace(); - } catch (IOException e) { - e.printStackTrace(); - } - if(!newMessage.equals(this.newMessage)) { - handleNewMessages(chatEntity.chunkCursor); - this.newMessage = newMessage; - } - } - } - }); - } - - if(!checkNewMessagesThread.isAlive()) { - checkNewMessagesThread = new Thread(() -> { - Map data = P2PUtils.get(chatID + "_newMessage"); - if(data != null) { - for(Map.Entry entry : data.entrySet()) { - String newMessage = null; - try { - newMessage = (String) entry.getValue().object(); - } catch (ClassNotFoundException e) { - e.printStackTrace(); - } catch (IOException e) { - e.printStackTrace(); - } - if(!newMessage.equals(this.newMessage)) { - handleNewMessages(chatEntity.chunkCursor); - this.newMessage = newMessage; - } - } - } - }); - checkNewMessagesThread.start(); - } - } - - private void handleNewMessages(int chunkID) { - new Thread(() -> { - Map messages = P2PUtils.get(chatEntity.chatID + "_messages" + chunkID); - if (messages != null) { - for (Map.Entry message : messages.entrySet()) { - String json = null; - try { - json = (String) message.getValue().object(); - } catch (ClassNotFoundException e) { - e.printStackTrace(); - } catch (IOException e) { - e.printStackTrace(); - } - switch (getMessageAction(json)) { - case NetworkActions.TEXT_MESSAGE: { - TextMessage textMessage = gson.fromJson(json, TextMessage.class); - LocalDBWrapper.createMessageEntry(NetworkActions.TEXT_MESSAGE, textMessage.getMessageID(), textMessage.getChatID(), textMessage.getUsername(), textMessage.getSenderID(), textMessage.getTimestamp(), textMessage.getText(), true, false); - ObservableUtils.notifyUI(UIActions.MESSAGE_RECEIVED, chatID, textMessage.getMessageID()); - break; - } - case NetworkActions.JOIN_CHAT: { - JoinChatMessage joinChatMessage = gson.fromJson(json, JoinChatMessage.class); - LocalDBWrapper.createMessageEntry(NetworkActions.JOIN_CHAT, joinChatMessage.getMessageID(), joinChatMessage.getChatID(), joinChatMessage.getUsername(), joinChatMessage.getSenderID(), joinChatMessage.getTimestamp(), null, true, false); - ObservableUtils.notifyUI(UIActions.MESSAGE_RECEIVED, chatID, joinChatMessage.getMessageID()); - break; - } - case NetworkActions.NEXT_CHUNK_REF: { - NextChunkReference nextChunkReference = gson.fromJson(json, NextChunkReference.class); - chatEntity.chunkCursor = nextChunkReference.getNextChunkID(); - LocalDBWrapper.updateChatEntity(chatEntity); - break; - } - } - } - if(messages.size() > 10) { - String messageID = UUID.randomUUID().toString(); - try { - int nextChunkCursor = chatEntity.chunkCursor + 1; - P2PUtils.put(chatEntity.chatID + "_messages" + chunkID, messageID, new Data(gson.toJson(new NextChunkReference(messageID, AppHelper.getPeerID(), AppHelper.getPeerID(), System.currentTimeMillis(), nextChunkCursor)))); - P2PUtils.put(chatEntity.chatID + "_newMessage", null, new Data(messageID)); - } catch (IOException e) { - e.printStackTrace(); - } - } - } - }).start(); - } - - private int getMessageAction(String json) { - JsonObject jsonObject = new JsonParser().parse(json).getAsJsonObject(); - return jsonObject.get("action").getAsInt(); - } - - @Override - public void stopTrackingForNewMsgs() { - timer.cancel(); - timer.purge(); } } 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 219d7b3..f037fb8 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 @@ -1,83 +1,23 @@ package io.github.chronosx88.influence.logic; import android.content.Context; -import android.content.SharedPreferences; -import android.util.Log; import com.google.gson.Gson; -import com.google.gson.JsonObject; - -import net.tomp2p.connection.Bindings; -import net.tomp2p.connection.ChannelClientConfiguration; -import net.tomp2p.connection.ChannelServerConfiguration; -import net.tomp2p.connection.Ports; -import net.tomp2p.connection.RSASignatureFactory; -import net.tomp2p.dht.PeerBuilderDHT; -import net.tomp2p.dht.PeerDHT; -import net.tomp2p.dht.Storage; -import net.tomp2p.futures.FutureBootstrap; -import net.tomp2p.futures.FutureDiscover; -import net.tomp2p.nat.FutureRelayNAT; -import net.tomp2p.nat.PeerBuilderNAT; -import net.tomp2p.nat.PeerNAT; -import net.tomp2p.p2p.PeerBuilder; -import net.tomp2p.peers.Number160; -import net.tomp2p.peers.Number640; -import net.tomp2p.peers.PeerAddress; -import net.tomp2p.relay.tcp.TCPRelayClientConfig; -import net.tomp2p.replication.IndirectReplication; -import net.tomp2p.storage.Data; - -import org.jetbrains.annotations.NotNull; - -import java.io.File; -import java.io.IOException; -import java.net.Inet4Address; -import java.net.InetAddress; -import java.net.InetSocketAddress; -import java.net.UnknownHostException; -import java.security.KeyPair; -import java.util.ArrayList; -import java.util.Map; -import java.util.Timer; -import java.util.TimerTask; -import java.util.UUID; import io.github.chronosx88.influence.contracts.CoreContracts; import io.github.chronosx88.influence.helpers.AppHelper; import io.github.chronosx88.influence.helpers.KeyPairManager; -import io.github.chronosx88.influence.helpers.LocalDBWrapper; -import io.github.chronosx88.influence.helpers.NetworkHandler; -import io.github.chronosx88.influence.helpers.ObservableUtils; -import io.github.chronosx88.influence.helpers.StorageBerkeleyDB; -import io.github.chronosx88.influence.helpers.actions.UIActions; -import io.github.chronosx88.influence.models.ChatMetadata; -import io.github.chronosx88.influence.models.NewChatRequestMessage; -import io.github.chronosx88.influence.models.PublicUserProfile; public class MainLogic implements CoreContracts.IMainLogicContract { private static final String LOG_TAG = MainLogic.class.getName(); - private SharedPreferences preferences; - private Number160 peerID; - private PeerDHT peerDHT; private Context context; - private InetAddress bootstrapAddress = null; - private PeerAddress bootstrapPeerAddress = null; - private Gson gson; - private IndirectReplication replication; - private KeyPairManager keyPairManager; - private Thread checkNewChatsThread = null; - private Storage storage; public MainLogic() { this.context = AppHelper.getContext(); - this.preferences = context.getSharedPreferences("io.github.chronosx88.influence_preferences", context.MODE_PRIVATE); - gson = new Gson(); - keyPairManager = new KeyPairManager(); } - @Override + /*@Override public void initPeer() { org.apache.log4j.BasicConfigurator.configure(); @@ -366,5 +306,5 @@ public class MainLogic implements CoreContracts.IMainLogicContract { e.printStackTrace(); } return null; - } + }*/ } diff --git a/app/src/main/java/io/github/chronosx88/influence/logic/SettingsLogic.kt b/app/src/main/java/io/github/chronosx88/influence/logic/SettingsLogic.kt index e616a87..f24f618 100644 --- a/app/src/main/java/io/github/chronosx88/influence/logic/SettingsLogic.kt +++ b/app/src/main/java/io/github/chronosx88/influence/logic/SettingsLogic.kt @@ -1,24 +1,18 @@ package io.github.chronosx88.influence.logic -import android.util.Log import io.github.chronosx88.influence.contracts.CoreContracts -import io.github.chronosx88.influence.helpers.AppHelper import io.github.chronosx88.influence.helpers.KeyPairManager -import io.github.chronosx88.influence.helpers.ObservableUtils -import io.github.chronosx88.influence.helpers.actions.UIActions -import net.tomp2p.peers.Number640 -import net.tomp2p.storage.Data -import java.io.IOException class SettingsLogic : CoreContracts.ISettingsLogic { override fun checkUsernameExists(username: String) : Boolean { - if (AppHelper.getPeerDHT() == null) { + /*if (AppHelper.getPeerDHT() == null) { ObservableUtils.notifyUI(UIActions.NODE_IS_OFFLINE) return false } val usernameMap: MutableMap? = P2PUtils.get(username) usernameMap ?: return false + return true*/ return true } @@ -26,7 +20,7 @@ class SettingsLogic : CoreContracts.ISettingsLogic { private val LOG_TAG: String = "SettingsLogic" private val keyPairManager = KeyPairManager() - fun publishUsername(oldUsername: String?, username: String?) { + /*fun publishUsername(oldUsername: String?, username: String?) { if (AppHelper.getPeerDHT() == null) { ObservableUtils.notifyUI(UIActions.NODE_IS_OFFLINE) return @@ -53,6 +47,6 @@ class SettingsLogic : CoreContracts.ISettingsLogic { } ?: run { return } - } + }*/ } } \ No newline at end of file diff --git a/app/src/main/java/io/github/chronosx88/influence/models/GenericDialog.java b/app/src/main/java/io/github/chronosx88/influence/models/GenericDialog.java new file mode 100644 index 0000000..fd228b1 --- /dev/null +++ b/app/src/main/java/io/github/chronosx88/influence/models/GenericDialog.java @@ -0,0 +1,78 @@ +/* + * Copyright (C) 2019 ChronosX88 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package io.github.chronosx88.influence.models; + +import com.stfalcon.chatkit.commons.models.IDialog; +import com.stfalcon.chatkit.commons.models.IMessage; +import com.stfalcon.chatkit.commons.models.IUser; + +import java.util.ArrayList; +import java.util.List; + +import io.github.chronosx88.influence.models.roomEntities.ChatEntity; + +public class GenericDialog implements IDialog { + private String dialogID; + private String dialogPhoto = ""; + private String dialogName; + private List users; + private IMessage lastMessage; + private int unreadMessagesCount; + + public GenericDialog(ChatEntity chatEntity) { + dialogID = chatEntity.jid; + dialogName = chatEntity.chatName; + users = new ArrayList<>(); + unreadMessagesCount = chatEntity.unreadMessagesCount; + } + + @Override + public String getId() { + return dialogID; + } + + @Override + public String getDialogPhoto() { + return dialogPhoto; + } + + @Override + public String getDialogName() { + return dialogName; + } + + @Override + public List getUsers() { + return users; + } + + @Override + public IMessage getLastMessage() { + return lastMessage; + } + + @Override + public void setLastMessage(IMessage message) { + lastMessage = message; + } + + @Override + public int getUnreadCount() { + return unreadMessagesCount; + } +} diff --git a/app/src/main/java/io/github/chronosx88/influence/models/GenericMessage.java b/app/src/main/java/io/github/chronosx88/influence/models/GenericMessage.java new file mode 100644 index 0000000..be52fb5 --- /dev/null +++ b/app/src/main/java/io/github/chronosx88/influence/models/GenericMessage.java @@ -0,0 +1,59 @@ +/* + * Copyright (C) 2019 ChronosX88 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package io.github.chronosx88.influence.models; + +import com.stfalcon.chatkit.commons.models.IMessage; +import com.stfalcon.chatkit.commons.models.IUser; + +import java.util.Date; + +import io.github.chronosx88.influence.models.roomEntities.MessageEntity; + +public class GenericMessage implements IMessage { + private long messageID; + private IUser author; + private long timestamp; + private String text; + + public GenericMessage(MessageEntity messageEntity) { + this.messageID = messageEntity.messageID; + this.author = new GenericUser(messageEntity.senderJid, messageEntity.senderJid, ""); + this.timestamp = messageEntity.timestamp; + this.text = messageEntity.text; + } + + @Override + public String getId() { + return String.valueOf(messageID); + } + + @Override + public String getText() { + return text; + } + + @Override + public IUser getUser() { + return author; + } + + @Override + public Date getCreatedAt() { + return new Date(timestamp); + } +} diff --git a/app/src/main/java/io/github/chronosx88/influence/models/GenericUser.java b/app/src/main/java/io/github/chronosx88/influence/models/GenericUser.java new file mode 100644 index 0000000..245167e --- /dev/null +++ b/app/src/main/java/io/github/chronosx88/influence/models/GenericUser.java @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2019 ChronosX88 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package io.github.chronosx88.influence.models; + +import com.stfalcon.chatkit.commons.models.IUser; + +public class GenericUser implements IUser { + private String jid; + private String name; + private String avatar; + + public GenericUser(String jid, String name, String avatar) { + this.jid = jid; + this.name = name; + this.avatar = avatar; + } + + @Override + public String getId() { + return jid; + } + + @Override + public String getName() { + return name; + } + + @Override + public String getAvatar() { + return avatar; + } +} diff --git a/app/src/main/java/io/github/chronosx88/influence/models/roomEntities/ChatEntity.java b/app/src/main/java/io/github/chronosx88/influence/models/roomEntities/ChatEntity.java index 8bf0476..4477f85 100644 --- a/app/src/main/java/io/github/chronosx88/influence/models/roomEntities/ChatEntity.java +++ b/app/src/main/java/io/github/chronosx88/influence/models/roomEntities/ChatEntity.java @@ -5,13 +5,25 @@ import androidx.room.ColumnInfo; import androidx.room.Entity; import androidx.room.PrimaryKey; +import java.util.ArrayList; + +import io.github.chronosx88.influence.models.GenericUser; + @Entity(tableName = "chats") public class ChatEntity { @PrimaryKey @NonNull public String jid; @ColumnInfo public String chatName; + @ColumnInfo public ArrayList users; + @ColumnInfo public int unreadMessagesCount; - public ChatEntity(@NonNull String jid, String chatName) { + public ChatEntity(@NonNull String jid, String chatName, ArrayList users, int unreadMessagesCount) { this.jid = jid; this.chatName = chatName; + this.users = users; + this.unreadMessagesCount = unreadMessagesCount; + } + + public boolean isPrivateChat() { + return users.size() == 1; } } diff --git a/app/src/main/java/io/github/chronosx88/influence/observable/MainObservable.java b/app/src/main/java/io/github/chronosx88/influence/observable/MainObservable.java index 5d40190..e582c77 100644 --- a/app/src/main/java/io/github/chronosx88/influence/observable/MainObservable.java +++ b/app/src/main/java/io/github/chronosx88/influence/observable/MainObservable.java @@ -1,34 +1,21 @@ package io.github.chronosx88.influence.observable; -import com.google.gson.JsonObject; +import org.json.JSONException; +import org.json.JSONObject; import java.util.ArrayList; -import io.github.chronosx88.influence.contracts.observer.INetworkObserver; import io.github.chronosx88.influence.contracts.observer.IObservable; import io.github.chronosx88.influence.contracts.observer.IObserver; public class MainObservable implements IObservable { - public static final int UI_ACTIONS_CHANNEL = 0; - public static final int OTHER_ACTIONS_CHANNEL = 1; - - private ArrayList uiObservers; - private ArrayList networkObservers; - - public MainObservable() { - this.uiObservers = new ArrayList<>(); - this.networkObservers = new ArrayList<>(); - } + private ArrayList uiObservers = new ArrayList<>(); @Override public void register(IObserver observer) { uiObservers.add(observer); } - @Override - public void register(INetworkObserver observer) { - networkObservers.add(observer); - } @Override public void unregister(IObserver observer) { @@ -36,21 +23,13 @@ public class MainObservable implements IObservable { } @Override - public void unregister(INetworkObserver observer) { - networkObservers.remove(observer); - } - - @Override - public void notifyUIObservers(JsonObject jsonObject) { + public void notifyUIObservers(JSONObject jsonObject) { for (IObserver observer : uiObservers) { - observer.handleEvent(jsonObject); - } - } - - @Override - public void notifyNetworkObservers(Object object) { - for (INetworkObserver observer : networkObservers) { - observer.handleEvent(object); + try { + observer.handleEvent(jsonObject); + } catch (JSONException e) { + e.printStackTrace(); + } } } } 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 deleted file mode 100644 index 438cd47..0000000 --- a/app/src/main/java/io/github/chronosx88/influence/presenters/ChatListPresenter.java +++ /dev/null @@ -1,56 +0,0 @@ -package io.github.chronosx88.influence.presenters; - -import android.content.Intent; -import android.view.MenuItem; - -import io.github.chronosx88.influence.contracts.CoreContracts; -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 CoreContracts.IChatListPresenterContract { - private CoreContracts.IChatListViewContract view; - private CoreContracts.IChatListLogicContract logic; - private ChatListAdapter chatListAdapter; - - public ChatListPresenter(CoreContracts.IChatListViewContract view) { - this.view = view; - chatListAdapter = new ChatListAdapter((v, p)-> { - openChat(chatListAdapter.getChatEntity(p).chatID); - }); - this.logic = new ChatListLogic(); - this.view.setRecycleAdapter(chatListAdapter); - } - - @Override - public void updateChatList() { - view.updateChatList(chatListAdapter, logic.loadAllChats()); - } - - @Override - public void openChat(String chatID) { - Intent intent = new Intent(AppHelper.getContext(), ChatActivity.class); - intent.putExtra("chatID", chatID); - intent.putExtra("contactUsername", LocalDBWrapper.getChatByChatID(chatID).name); - view.startActivity(intent); - } - - @Override - public void onContextItemSelected(MenuItem item) { - switch(item.getItemId()) { - case 0: { - if(chatListAdapter.onClickPosition != -1) { - 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.kt b/app/src/main/java/io/github/chronosx88/influence/presenters/ChatPresenter.kt index a81e494..8794dad 100644 --- a/app/src/main/java/io/github/chronosx88/influence/presenters/ChatPresenter.kt +++ b/app/src/main/java/io/github/chronosx88/influence/presenters/ChatPresenter.kt @@ -1,70 +1,76 @@ package io.github.chronosx88.influence.presenters -import android.widget.Toast - +import android.content.BroadcastReceiver +import android.content.Context +import android.content.Intent +import android.content.IntentFilter import com.google.gson.Gson -import com.google.gson.JsonObject -import com.instacart.library.truetime.TrueTime - -import java.io.IOException -import java.util.ArrayList -import java.util.UUID - +import com.stfalcon.chatkit.commons.ImageLoader +import com.stfalcon.chatkit.messages.MessagesListAdapter +import io.github.chronosx88.influence.R +import io.github.chronosx88.influence.XMPPConnectionService import io.github.chronosx88.influence.contracts.CoreContracts -import io.github.chronosx88.influence.contracts.observer.IObserver import io.github.chronosx88.influence.helpers.AppHelper import io.github.chronosx88.influence.helpers.LocalDBWrapper -import io.github.chronosx88.influence.helpers.actions.NetworkActions -import io.github.chronosx88.influence.helpers.actions.UIActions import io.github.chronosx88.influence.logic.ChatLogic +import io.github.chronosx88.influence.models.GenericDialog +import io.github.chronosx88.influence.models.GenericMessage import io.github.chronosx88.influence.models.roomEntities.ChatEntity -import org.jetbrains.anko.doAsync -import org.jetbrains.anko.doAsyncResult +import io.github.chronosx88.influence.models.roomEntities.MessageEntity -class ChatPresenter(private val view: CoreContracts.IChatViewContract, private val chatID: String) : CoreContracts.IChatPresenterContract, IObserver { +class ChatPresenter(private val view: CoreContracts.IChatViewContract, private val chatID: String) : CoreContracts.IChatPresenterContract { private val logic: CoreContracts.IChatLogicContract private val chatEntity: ChatEntity? private val gson: Gson + private val chatAdapter: MessagesListAdapter + private val newMessageReceiver: BroadcastReceiver init { this.logic = ChatLogic(LocalDBWrapper.getChatByChatID(chatID)!!) this.chatEntity = LocalDBWrapper.getChatByChatID(chatID) - - AppHelper.getObservable().register(this) gson = Gson() - } + chatAdapter = MessagesListAdapter(AppHelper.getJid(), ImageLoader { imageView, _, _ -> imageView.setImageResource(R.mipmap.ic_launcher) }) + view.setAdapter(chatAdapter) - override fun sendMessage(text: String) { - doAsync { - val message = LocalDBWrapper.createMessageEntry(NetworkActions.TEXT_MESSAGE, UUID.randomUUID().toString(), chatID, AppHelper.getPeerID(), AppHelper.getPeerID(), TrueTime.now().time, text, false, false) - logic.sendMessage(message!!) - view.updateMessageList(message) - } - } - - override fun handleEvent(obj: JsonObject) { - when (obj.get("action").asInt) { - UIActions.MESSAGE_RECEIVED -> { - val jsonArray = obj.getAsJsonArray("additional") - if (jsonArray.get(0).asString != chatID) { - return + newMessageReceiver = object : BroadcastReceiver() { + override fun onReceive(context: Context, intent: Intent) { + if(intent.getStringExtra(XMPPConnectionService.MESSAGE_CHATID).equals(chatEntity.jid)) { + val messageID = intent.getLongExtra(XMPPConnectionService.MESSAGE_ID, -1) + chatAdapter.addToStart(GenericMessage(LocalDBWrapper.getMessageByID(messageID)), true) } - val messageEntity = LocalDBWrapper.getMessageByID(jsonArray.get(1).asString) - view.updateMessageList(messageEntity!!) - } - - UIActions.NODE_IS_OFFLINE -> { - Toast.makeText(AppHelper.getContext(), "Нода не запущена!", Toast.LENGTH_SHORT).show() } } + val filter = IntentFilter() + filter.addAction(XMPPConnectionService.INTENT_NEW_MESSAGE) + AppHelper.getContext().registerReceiver(newMessageReceiver, filter) } - override fun updateAdapter() { - val entities = LocalDBWrapper.getMessagesByChatID(chatID) - view.updateMessageList(entities ?: ArrayList()) + override fun sendMessage(text: String): Boolean { + val message: MessageEntity? = logic.sendMessage(text) + if(message != null) { + chatAdapter.addToStart(GenericMessage(message), true) + return true + } + return false + } + + override fun loadLocalMessages() { + val entities: List? = LocalDBWrapper.getMessagesByChatID(chatID) + val messages = ArrayList() + if(entities != null) { + entities.forEach { + messages.add(GenericMessage(it)) + } + } + chatAdapter.addToEnd(messages, true) } override fun onDestroy() { - logic.stopTrackingForNewMsgs() + // } + + private fun setupIncomingMessagesReceiver() { + + } + } diff --git a/app/src/main/java/io/github/chronosx88/influence/presenters/DialogListPresenter.java b/app/src/main/java/io/github/chronosx88/influence/presenters/DialogListPresenter.java new file mode 100644 index 0000000..e11883c --- /dev/null +++ b/app/src/main/java/io/github/chronosx88/influence/presenters/DialogListPresenter.java @@ -0,0 +1,96 @@ +package io.github.chronosx88.influence.presenters; + +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; + +import androidx.appcompat.app.AlertDialog; + +import com.stfalcon.chatkit.dialogs.DialogsListAdapter; + +import org.json.JSONException; +import org.json.JSONObject; + +import java.util.ArrayList; + +import io.github.chronosx88.influence.R; +import io.github.chronosx88.influence.XMPPConnectionService; +import io.github.chronosx88.influence.contracts.CoreContracts; +import io.github.chronosx88.influence.contracts.observer.IObserver; +import io.github.chronosx88.influence.helpers.AppHelper; +import io.github.chronosx88.influence.helpers.LocalDBWrapper; +import io.github.chronosx88.influence.helpers.ObservableActions; +import io.github.chronosx88.influence.logic.ChatListLogic; +import io.github.chronosx88.influence.models.GenericDialog; +import io.github.chronosx88.influence.views.ChatActivity; +import java8.util.stream.StreamSupport; + +public class DialogListPresenter implements CoreContracts.IDialogListPresenterContract, IObserver { + private CoreContracts.IChatListViewContract view; + private CoreContracts.IDialogListLogicContract logic; + private DialogsListAdapter dialogListAdapter = new DialogsListAdapter<>((imageView, url, payload) -> { + imageView.setImageResource(R.mipmap.ic_launcher); // FIXME + }); + private BroadcastReceiver incomingMessagesReceiver; + + public DialogListPresenter(CoreContracts.IChatListViewContract view) { + this.view = view; + dialogListAdapter.setOnDialogClickListener(dialog -> openChat(dialog.getId())); + dialogListAdapter.setOnDialogLongClickListener(dialog -> { + AlertDialog.Builder builder = new AlertDialog.Builder(view.getActivityContext()); + builder.setPositiveButton(R.string.ok, (dialog1, id) -> { + dialogListAdapter.deleteById(dialog.getId()); + AppHelper.getChatDB().chatDao().deleteChat(dialog.getId()); + AppHelper.getChatDB().messageDao().deleteMessagesByChatID(dialog.getId()); + }); + builder.setNegativeButton(R.string.cancel, (dialog2, which) -> { + // + }); + builder.setMessage("Remove chat?"); + builder.create().show(); + }); + this.logic = new ChatListLogic(); + this.view.setDialogAdapter(dialogListAdapter); + ArrayList dialogs = new ArrayList<>(); + StreamSupport.stream(logic.loadAllChats()) + .forEach(chatEntity -> dialogs.add(new GenericDialog(chatEntity))); + dialogListAdapter.setItems(dialogs); + setupIncomingMessagesReceiver(); + AppHelper.getObservable().register(this); + } + + @Override + public void openChat(String chatID) { + Intent intent = new Intent(AppHelper.getContext(), ChatActivity.class); + intent.putExtra("chatID", chatID); + intent.putExtra("chatName", LocalDBWrapper.getChatByChatID(chatID).chatName); + view.startActivity(intent); + } + + private void setupIncomingMessagesReceiver() { + incomingMessagesReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + String chatID = intent.getStringExtra(XMPPConnectionService.MESSAGE_CHATID); + GenericDialog dialog = dialogListAdapter.getItemById(chatID); + if(dialog == null) { + dialogListAdapter.addItem(new GenericDialog(LocalDBWrapper.getChatByChatID(chatID))); + } + } + }; + IntentFilter filter = new IntentFilter(); + filter.addAction(XMPPConnectionService.INTENT_NEW_MESSAGE); + AppHelper.getContext().registerReceiver(incomingMessagesReceiver, filter); + } + + @Override + public void handleEvent(JSONObject object) throws JSONException { + switch (object.getInt("action")) { + case ObservableActions.NEW_CHAT_CREATED: { + dialogListAdapter.addItem(new GenericDialog(LocalDBWrapper.getChatByChatID(object.getJSONArray("additional").optString(0)))); + break; + } + } + } +} diff --git a/app/src/main/java/io/github/chronosx88/influence/presenters/MainPresenter.kt b/app/src/main/java/io/github/chronosx88/influence/presenters/MainPresenter.kt index 6db7273..e42c694 100644 --- a/app/src/main/java/io/github/chronosx88/influence/presenters/MainPresenter.kt +++ b/app/src/main/java/io/github/chronosx88/influence/presenters/MainPresenter.kt @@ -1,56 +1,68 @@ package io.github.chronosx88.influence.presenters -import com.google.gson.JsonObject +import android.app.ActivityManager +import android.content.BroadcastReceiver +import android.content.Context +import android.content.Intent +import android.content.IntentFilter import io.github.chronosx88.influence.R +import io.github.chronosx88.influence.XMPPConnectionService import io.github.chronosx88.influence.contracts.CoreContracts -import io.github.chronosx88.influence.contracts.observer.IObserver import io.github.chronosx88.influence.helpers.AppHelper -import io.github.chronosx88.influence.helpers.actions.UIActions +import io.github.chronosx88.influence.helpers.LocalDBWrapper +import io.github.chronosx88.influence.helpers.ObservableActions +import io.github.chronosx88.influence.helpers.ObservableUtils import io.github.chronosx88.influence.logic.MainLogic +import io.github.chronosx88.influence.views.LoginActivity import org.jetbrains.anko.doAsync -class MainPresenter(private val view: CoreContracts.IMainViewContract) : CoreContracts.IMainPresenterContract, IObserver { +class MainPresenter(private val view: CoreContracts.IMainViewContract) : CoreContracts.IMainPresenterContract { private val logic: CoreContracts.IMainLogicContract = MainLogic() + private var broadcastReceiver: BroadcastReceiver? = null - init { - AppHelper.getObservable().register(this) - } override fun initPeer() { - if (AppHelper.getPeerDHT() == null) { - logic.initPeer() - } else { - view.showSnackbar(AppHelper.getContext().getString(R.string.node_already_running)) - view.showProgressBar(false) + broadcastReceiver = object : BroadcastReceiver() { + override fun onReceive(context: Context, intent: Intent) { + val action = intent.action + when (action) { + XMPPConnectionService.INTENT_AUTHENTICATED -> { + view.showProgressBar(false) + } + XMPPConnectionService.INTENT_AUTHENTICATION_FAILED -> { + view.showProgressBar(false) + val intent = Intent(AppHelper.getContext(), LoginActivity::class.java) + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) + AppHelper.getContext().startActivity(intent) + } + } + } } + val filter = IntentFilter() + filter.addAction(XMPPConnectionService.INTENT_AUTHENTICATED) + filter.addAction(XMPPConnectionService.INTENT_AUTHENTICATION_FAILED) + AppHelper.getContext().registerReceiver(broadcastReceiver, filter) + + AppHelper.getContext().startService(Intent(AppHelper.getContext(), XMPPConnectionService::class.java)) } override fun startChatWithPeer(username: String) { - doAsync { - logic.sendStartChatMessage(username) - } - } - - override fun handleEvent(obj: JsonObject) { - when(obj.get("action").asInt) { - UIActions.PEER_NOT_EXIST -> { - view.showProgressBar(false) - view.showSnackbar("Данный узел не существует!") - } - - UIActions.NEW_CHAT -> { - view.showProgressBar(false) - view.showSnackbar("Чат успешно создан!") - } - - UIActions.NODE_IS_OFFLINE -> { - view.showProgressBar(false) - view.showSnackbar("Нода не запущена!") - } - } + LocalDBWrapper.createChatEntry(username, username) + ObservableUtils.notifyUI(ObservableActions.NEW_CHAT_CREATED, username) } override fun onDestroy() { - logic.shutdownPeer() + // + } + + // TODO + private fun isServiceRunning(serviceClass: Class<*>): Boolean { + val manager = AppHelper.getContext().getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager + for (service in manager.getRunningServices(Integer.MAX_VALUE)) { + if (serviceClass.name == service.service.className) { + return true + } + } + return false } } diff --git a/app/src/main/java/io/github/chronosx88/influence/presenters/SettingsPresenter.kt b/app/src/main/java/io/github/chronosx88/influence/presenters/SettingsPresenter.kt index 8a538cf..fd9507f 100644 --- a/app/src/main/java/io/github/chronosx88/influence/presenters/SettingsPresenter.kt +++ b/app/src/main/java/io/github/chronosx88/influence/presenters/SettingsPresenter.kt @@ -1,28 +1,17 @@ package io.github.chronosx88.influence.presenters -import android.content.SharedPreferences import android.os.Handler -import com.google.gson.JsonObject -import io.github.chronosx88.influence.R import io.github.chronosx88.influence.contracts.CoreContracts -import io.github.chronosx88.influence.contracts.observer.IObserver import io.github.chronosx88.influence.helpers.AppHelper -import io.github.chronosx88.influence.helpers.ObservableUtils -import io.github.chronosx88.influence.helpers.actions.UIActions import io.github.chronosx88.influence.logic.SettingsLogic -import kotlinx.coroutines.GlobalScope -import kotlinx.coroutines.launch -class SettingsPresenter(private val view: CoreContracts.ISettingsView) : CoreContracts.ISettingsPresenter, IObserver { +class SettingsPresenter(private val view: CoreContracts.ISettingsView) : CoreContracts.ISettingsPresenter { private val mainThreadHandler: Handler = Handler(AppHelper.getContext().mainLooper) private val logic: SettingsLogic = SettingsLogic() - init { - AppHelper.getObservable().register(this) - } override fun updateUsername(username: String) { - view.loadingScreen(true) + /*view.loadingScreen(true) val editor: SharedPreferences.Editor = AppHelper.getPreferences().edit() GlobalScope.launch { @@ -52,23 +41,6 @@ class SettingsPresenter(private val view: CoreContracts.ISettingsView) : CoreCon } else { ObservableUtils.notifyUI(UIActions.USERNAME_ISNT_AVAILABLE) } - } - } - - override fun handleEvent(json: JsonObject) { - val post = { - when (json.get("action").asInt) { - UIActions.USERNAME_AVAILABLE -> { - view.loadingScreen(false) - view.showMessage(AppHelper.getContext().getString(R.string.username_saved)) - view.refreshScreen() - } - UIActions.USERNAME_ISNT_AVAILABLE -> { - view.loadingScreen(false) - view.showMessage(AppHelper.getContext().getString(R.string.username_isnt_saved)) - } - } - } - mainThreadHandler.post(post) + }*/ } } \ No newline at end of file diff --git a/app/src/main/java/io/github/chronosx88/influence/views/ChatActivity.kt b/app/src/main/java/io/github/chronosx88/influence/views/ChatActivity.kt index 5101141..9437564 100644 --- a/app/src/main/java/io/github/chronosx88/influence/views/ChatActivity.kt +++ b/app/src/main/java/io/github/chronosx88/influence/views/ChatActivity.kt @@ -2,25 +2,25 @@ package io.github.chronosx88.influence.views import android.os.Bundle import android.view.MenuItem -import android.widget.EditText -import android.widget.ImageButton import android.widget.TextView import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.widget.Toolbar import androidx.recyclerview.widget.LinearLayoutManager -import androidx.recyclerview.widget.RecyclerView +import com.stfalcon.chatkit.commons.ImageLoader +import com.stfalcon.chatkit.messages.MessageInput +import com.stfalcon.chatkit.messages.MessagesList +import com.stfalcon.chatkit.messages.MessagesListAdapter import io.github.chronosx88.influence.R import io.github.chronosx88.influence.contracts.CoreContracts -import io.github.chronosx88.influence.helpers.ChatAdapter +import io.github.chronosx88.influence.helpers.AppHelper +import io.github.chronosx88.influence.models.GenericMessage import io.github.chronosx88.influence.models.roomEntities.MessageEntity import io.github.chronosx88.influence.presenters.ChatPresenter class ChatActivity : AppCompatActivity(), CoreContracts.IChatViewContract { - private var chatAdapter: ChatAdapter? = null - private var messageList: RecyclerView? = null - private var sendMessageButton: ImageButton? = null - private var messageTextEdit: EditText? = null - private var contactUsernameTextView: TextView? = null + private var messageList: MessagesList? = null + private var messageInput: MessageInput? = null + private var chatNameTextView: TextView? = null private var presenter: ChatPresenter? = null override fun onCreate(savedInstanceState: Bundle?) { @@ -29,45 +29,21 @@ class ChatActivity : AppCompatActivity(), CoreContracts.IChatViewContract { val intent = intent - presenter = ChatPresenter(this, intent.getStringExtra("chatID")) val toolbar = findViewById(R.id.toolbar_chat_activity) setSupportActionBar(toolbar) supportActionBar!!.setTitle("") supportActionBar!!.setDisplayHomeAsUpEnabled(true) supportActionBar!!.setHomeButtonEnabled(true) - messageList = findViewById(R.id.message_list) - chatAdapter = ChatAdapter() - presenter!!.updateAdapter() - messageList!!.adapter = chatAdapter + messageList = findViewById(R.id.messages_list) messageList!!.layoutManager = LinearLayoutManager(this) - contactUsernameTextView = findViewById(R.id.appbar_username) - messageTextEdit = findViewById(R.id.message_input) - sendMessageButton = findViewById(R.id.send_button) - sendMessageButton!!.setOnClickListener sendMessageButton@{ - if (messageTextEdit!!.text.toString() == "") { - return@sendMessageButton - } - presenter!!.sendMessage(messageTextEdit!!.text.toString()) - messageTextEdit!!.setText("") - } - contactUsernameTextView!!.text = intent.getStringExtra("contactUsername") - messageList!!.scrollToPosition(chatAdapter!!.itemCount - 1) - } - - override fun updateMessageList(message: MessageEntity) { - runOnUiThread { - chatAdapter!!.addMessage(message) - messageList!!.scrollToPosition(chatAdapter!!.itemCount - 1) - chatAdapter!!.notifyDataSetChanged() - } - } - - override fun updateMessageList(messages: List) { - runOnUiThread { - chatAdapter!!.addMessages(messages) - messageList!!.scrollToPosition(chatAdapter!!.itemCount - 1) - chatAdapter!!.notifyDataSetChanged() + chatNameTextView = findViewById(R.id.appbar_username) + messageInput = findViewById(R.id.message_input) + messageInput!!.setInputListener { + presenter!!.sendMessage(it.toString()) } + chatNameTextView!!.text = intent.getStringExtra("chatName") + presenter = ChatPresenter(this, intent.getStringExtra("chatID")) + presenter!!.loadLocalMessages() } override fun onOptionsItemSelected(item: MenuItem): Boolean { @@ -84,4 +60,8 @@ class ChatActivity : AppCompatActivity(), CoreContracts.IChatViewContract { super.onDestroy() presenter!!.onDestroy() } + + override fun setAdapter(adapter: MessagesListAdapter) { + messageList!!.setAdapter(adapter) + } } diff --git a/app/src/main/java/io/github/chronosx88/influence/views/LoginActivity.java b/app/src/main/java/io/github/chronosx88/influence/views/LoginActivity.java index f07a5eb..f8e28e5 100644 --- a/app/src/main/java/io/github/chronosx88/influence/views/LoginActivity.java +++ b/app/src/main/java/io/github/chronosx88/influence/views/LoginActivity.java @@ -27,7 +27,6 @@ import android.text.TextUtils; import android.view.View; import android.widget.Button; import android.widget.EditText; -import android.widget.Toast; import androidx.appcompat.app.AppCompatActivity; @@ -51,6 +50,8 @@ public class LoginActivity extends AppCompatActivity implements CoreContracts.IL passwordEditText = findViewById(R.id.login_password); signInButton = findViewById(R.id.sign_in_button); progressDialog = new ProgressDialog(LoginActivity.this); + progressDialog.setCancelable(false); + progressDialog.setProgressStyle(android.R.style.Widget_ProgressBar_Small); signInButton.setOnClickListener((v) -> { if(checkLoginCredentials()) { saveLoginCredentials(); @@ -82,7 +83,7 @@ public class LoginActivity extends AppCompatActivity implements CoreContracts.IL } case XMPPConnectionService.INTENT_AUTHENTICATION_FAILED: { loadingScreen(false); - passwordEditText.setError("Invalid JID/Password"); + jidEditText.setError("Invalid JID/Password/Server"); break; } } 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 1d3f72f..60708b0 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 @@ -7,59 +7,51 @@ import android.view.MenuItem; import android.view.View; import android.view.ViewGroup; import android.widget.EditText; -import android.widget.Toast; -import com.google.android.material.bottomnavigation.BottomNavigationView; -import com.google.android.material.floatingactionbutton.FloatingActionButton; -import com.google.android.material.snackbar.Snackbar; -import com.google.gson.JsonObject; - -import androidx.annotation.NonNull; import androidx.appcompat.app.AlertDialog; import androidx.appcompat.app.AppCompatActivity; import androidx.fragment.app.Fragment; import androidx.fragment.app.FragmentTransaction; +import com.google.android.material.bottomnavigation.BottomNavigationView; +import com.google.android.material.floatingactionbutton.FloatingActionButton; +import com.google.android.material.snackbar.Snackbar; + import org.jetbrains.annotations.NotNull; +import org.json.JSONException; +import org.json.JSONObject; import io.github.chronosx88.influence.R; import io.github.chronosx88.influence.contracts.CoreContracts; import io.github.chronosx88.influence.contracts.observer.IObserver; import io.github.chronosx88.influence.helpers.AppHelper; -import io.github.chronosx88.influence.helpers.actions.UIActions; +import io.github.chronosx88.influence.helpers.ObservableActions; import io.github.chronosx88.influence.presenters.MainPresenter; -import io.github.chronosx88.influence.views.fragments.ChatListFragment; +import io.github.chronosx88.influence.views.fragments.DialogListFragment; import io.github.chronosx88.influence.views.fragments.SettingsFragment; import kotlin.Pair; -public class MainActivity extends AppCompatActivity implements CoreContracts.IMainViewContract { +public class MainActivity extends AppCompatActivity implements CoreContracts.IMainViewContract, IObserver { private CoreContracts.IMainPresenterContract presenter; private ProgressDialog progressDialog; - private BottomNavigationView.OnNavigationItemSelectedListener mOnNavigationItemSelectedListener - = new BottomNavigationView.OnNavigationItemSelectedListener() { + private BottomNavigationView.OnNavigationItemSelectedListener mOnNavigationItemSelectedListener = (item) -> { + Fragment selectedFragment = null; - @Override - public boolean onNavigationItemSelected(@NonNull MenuItem item) { - - Fragment selectedFragment = null; - - switch (item.getItemId()) { - case R.id.action_chats: - selectedFragment = new ChatListFragment(); - break; - case R.id.action_settings: - selectedFragment = new SettingsFragment(); - break; - } - - FragmentTransaction transaction = getSupportFragmentManager().beginTransaction(); - transaction.replace(R.id.main_fragment_container, selectedFragment); - transaction.commit(); - - return true; + switch (item.getItemId()) { + case R.id.action_chats: + selectedFragment = new DialogListFragment(); + break; + case R.id.action_settings: + selectedFragment = new SettingsFragment(); + break; } + FragmentTransaction transaction = getSupportFragmentManager().beginTransaction(); + transaction.replace(R.id.main_fragment_container, selectedFragment); + transaction.commit(); + + return true; }; @Override @@ -73,7 +65,7 @@ public class MainActivity extends AppCompatActivity implements CoreContracts.IMa fab.setOnClickListener((v) -> { Pair pair = ViewUtils.INSTANCE.setupEditTextDialog(MainActivity.this, getString(R.string.input_companion_username)); pair.getFirst().setPositiveButton(getString(R.string.ok), (dialog, which) -> { - progressDialog.show(); + showProgressBar(true); presenter.startChatWithPeer(pair.getSecond().getText().toString()); }); pair.getFirst().setNegativeButton(getString(R.string.cancel), (dialog, which) -> { @@ -84,17 +76,17 @@ public class MainActivity extends AppCompatActivity implements CoreContracts.IMa getSupportFragmentManager() .beginTransaction() - .replace(R.id.main_fragment_container, new ChatListFragment()) + .replace(R.id.main_fragment_container, new DialogListFragment()) .commit(); presenter = new MainPresenter(this); - progressDialog = new ProgressDialog(MainActivity.this, R.style.AlertDialogTheme); progressDialog.setCancelable(false); progressDialog.setProgressStyle(android.R.style.Widget_ProgressBar_Small); progressDialog.show(); presenter.initPeer(); + AppHelper.getObservable().register(this); } @Override @@ -120,10 +112,7 @@ public class MainActivity extends AppCompatActivity implements CoreContracts.IMa @Override public void showSnackbar(@NotNull String message) { - runOnUiThread(() -> { - Snackbar.make(getRootView(), message, Snackbar.LENGTH_LONG) - .show(); - }); + runOnUiThread(() -> Snackbar.make(getRootView(), message, Snackbar.LENGTH_LONG).show()); } @Override @@ -149,4 +138,14 @@ public class MainActivity extends AppCompatActivity implements CoreContracts.IMa return rootView; } + + @Override + public void handleEvent(JSONObject object) throws JSONException { + switch (object.getInt("action")) { + case ObservableActions.NEW_CHAT_CREATED: { + showProgressBar(false); + break; + } + } + } } diff --git a/app/src/main/java/io/github/chronosx88/influence/views/fragments/ChatListFragment.kt b/app/src/main/java/io/github/chronosx88/influence/views/fragments/ChatListFragment.kt deleted file mode 100644 index 433db06..0000000 --- a/app/src/main/java/io/github/chronosx88/influence/views/fragments/ChatListFragment.kt +++ /dev/null @@ -1,76 +0,0 @@ -package io.github.chronosx88.influence.views.fragments - -import android.os.Bundle -import android.os.Handler -import android.view.LayoutInflater -import android.view.MenuItem -import android.view.View -import android.view.ViewGroup - -import com.google.gson.JsonObject -import androidx.fragment.app.Fragment -import androidx.recyclerview.widget.LinearLayoutManager -import androidx.recyclerview.widget.RecyclerView -import io.github.chronosx88.influence.R -import io.github.chronosx88.influence.contracts.CoreContracts -import io.github.chronosx88.influence.contracts.observer.IObserver -import io.github.chronosx88.influence.helpers.AppHelper -import io.github.chronosx88.influence.helpers.ChatListAdapter -import io.github.chronosx88.influence.helpers.actions.UIActions -import io.github.chronosx88.influence.models.roomEntities.ChatEntity -import io.github.chronosx88.influence.presenters.ChatListPresenter - -class ChatListFragment : Fragment(), CoreContracts.IChatListViewContract, IObserver { - private var presenter: CoreContracts.IChatListPresenterContract? = null - private var chatList: RecyclerView? = null - private var mainThreadHandler: Handler? = null - - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - AppHelper.getObservable().register(this) - this.mainThreadHandler = Handler(context!!.mainLooper) - } - - override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, - savedInstanceState: Bundle?): View? { - return inflater.inflate(R.layout.chatlist_fragment, container, false) - } - - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) - chatList = view.findViewById(R.id.chatlist_container) - chatList!!.layoutManager = LinearLayoutManager(context) - presenter = ChatListPresenter(this) - presenter!!.updateChatList() - registerForContextMenu(chatList!!) - } - - override fun setRecycleAdapter(adapter: ChatListAdapter) { - chatList!!.adapter = adapter - } - - override fun onResume() { - super.onResume() - presenter!!.updateChatList() - } - - override fun handleEvent(`object`: JsonObject) { - when (`object`.get("action").asInt) { - UIActions.SUCCESSFUL_CREATE_CHAT, UIActions.NEW_CHAT -> { - presenter!!.updateChatList() - } - } - } - - override fun updateChatList(adapter: ChatListAdapter, chats: List) { - mainThreadHandler!!.post { - adapter.setChatList(chats) - adapter.notifyDataSetChanged() - } - } - - override fun onContextItemSelected(item: MenuItem): Boolean { - presenter!!.onContextItemSelected(item) - return super.onContextItemSelected(item) - } -} diff --git a/app/src/main/java/io/github/chronosx88/influence/views/fragments/DialogListFragment.kt b/app/src/main/java/io/github/chronosx88/influence/views/fragments/DialogListFragment.kt new file mode 100644 index 0000000..cc04adf --- /dev/null +++ b/app/src/main/java/io/github/chronosx88/influence/views/fragments/DialogListFragment.kt @@ -0,0 +1,39 @@ +package io.github.chronosx88.influence.views.fragments + +import android.content.Context +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.fragment.app.Fragment +import com.stfalcon.chatkit.dialogs.DialogsList +import com.stfalcon.chatkit.dialogs.DialogsListAdapter +import io.github.chronosx88.influence.R +import io.github.chronosx88.influence.contracts.CoreContracts +import io.github.chronosx88.influence.models.GenericDialog +import io.github.chronosx88.influence.presenters.DialogListPresenter + + +class DialogListFragment : Fragment(), CoreContracts.IChatListViewContract { + private var presenter: CoreContracts.IDialogListPresenterContract? = null + private var dialogList: DialogsList? = null + + override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, + savedInstanceState: Bundle?): View? { + return inflater.inflate(R.layout.chatlist_fragment, container, false) + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + dialogList = view.findViewById(R.id.dialogsList) + presenter = DialogListPresenter(this) + } + + override fun setDialogAdapter(adapter: DialogsListAdapter) { + dialogList!!.setAdapter(adapter) + } + + override fun getActivityContext(): Context? { + return context + } +} diff --git a/app/src/main/java/io/github/chronosx88/influence/views/fragments/SettingsFragment.java b/app/src/main/java/io/github/chronosx88/influence/views/fragments/SettingsFragment.java index e333c28..ee32987 100644 --- a/app/src/main/java/io/github/chronosx88/influence/views/fragments/SettingsFragment.java +++ b/app/src/main/java/io/github/chronosx88/influence/views/fragments/SettingsFragment.java @@ -33,12 +33,12 @@ public class SettingsFragment extends PreferenceFragmentCompat implements CoreCo presenter = new SettingsPresenter(this); // Load the Preferences from the XML file addPreferencesFromResource(R.xml.main_settings); - getPreferenceScreen().getPreference(0).setSummary(AppHelper.getPeerID()); + /*getPreferenceScreen().getPreference(0).setSummary(AppHelper.getPeerID()); getPreferenceScreen().getPreference(0).setOnPreferenceClickListener((preference -> { - ClipboardManager clipboard = (ClipboardManager) AppHelper.getContext().getSystemService(Context.CLIPBOARD_SERVICE); + ClipboardManager clipboard = (ClipboardManager) AppHelper.getActivityContext().getSystemService(Context.CLIPBOARD_SERVICE); ClipData clip = ClipData.newPlainText("", AppHelper.getPeerID()); clipboard.setPrimaryClip(clip); - Toast.makeText(AppHelper.getContext(), "Скопировано в буфер обмена!", Toast.LENGTH_SHORT).show(); + Toast.makeText(AppHelper.getActivityContext(), "Скопировано в буфер обмена!", Toast.LENGTH_SHORT).show(); return false; })); getPreferenceScreen().getPreference(1).setSummary(AppHelper.getUsername()); @@ -49,7 +49,7 @@ public class SettingsFragment extends PreferenceFragmentCompat implements CoreCo getPreferenceScreen().getPreference(1).setOnPreferenceChangeListener((p, nV) -> { getPreferenceScreen().getPreference(1).setSummary((String) nV); return true; - }); + });*/ } @Override diff --git a/app/src/main/res/layout/activity_chat.xml b/app/src/main/res/layout/activity_chat.xml index a8b9192..fe247cd 100644 --- a/app/src/main/res/layout/activity_chat.xml +++ b/app/src/main/res/layout/activity_chat.xml @@ -38,38 +38,26 @@ - + android:layout_above="@+id/message_input"/> - + + - - - - - + app:inputHint="@string/hint_enter_a_message" + app:showAttachmentButton="true"/> \ No newline at end of file diff --git a/app/src/main/res/layout/chatlist_fragment.xml b/app/src/main/res/layout/chatlist_fragment.xml index 3086578..3d00d18 100644 --- a/app/src/main/res/layout/chatlist_fragment.xml +++ b/app/src/main/res/layout/chatlist_fragment.xml @@ -3,8 +3,8 @@ android:orientation="vertical" android:layout_width="match_parent" android:layout_height="match_parent"> - \ No newline at end of file diff --git a/app/src/main/res/values-ru-rRU/strings.xml b/app/src/main/res/values-ru-rRU/strings.xml index 7ea5bdb..1c4d58d 100644 --- a/app/src/main/res/values-ru-rRU/strings.xml +++ b/app/src/main/res/values-ru-rRU/strings.xml @@ -10,4 +10,5 @@ Переподключиться к сети Узел уже запущен Введите имя пользователя собеседника + Введите сообщение... \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 89699f8..18bf544 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -9,4 +9,5 @@ Reconnect to the network Node already running Input interlocutor\'s username + Enter message...