Now basic chat functional are working! Fully replaced with XMPP.

This commit is contained in:
ChronosX88 2019-05-20 23:16:06 +04:00
parent 1f0416fda4
commit 482d0be0c1
39 changed files with 718 additions and 908 deletions

View File

@ -64,6 +64,9 @@ dependencies {
implementation 'org.igniterealtime.smack:smack-android:4.3.3' implementation 'org.igniterealtime.smack:smack-android:4.3.3'
implementation 'org.igniterealtime.smack:smack-extensions: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 { repositories {
mavenCentral() mavenCentral()

View File

@ -2,39 +2,27 @@
"formatVersion": 1, "formatVersion": 1,
"database": { "database": {
"version": 2, "version": 2,
"identityHash": "81501115d10a6dc46002667323359631", "identityHash": "2409c873b47ccd635ed7d10e4d8604f8",
"entities": [ "entities": [
{ {
"tableName": "messages", "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": [ "fields": [
{ {
"fieldPath": "messageID", "fieldPath": "messageID",
"columnName": "messageID", "columnName": "messageID",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "type",
"columnName": "type",
"affinity": "INTEGER", "affinity": "INTEGER",
"notNull": true "notNull": true
}, },
{ {
"fieldPath": "chatID", "fieldPath": "jid",
"columnName": "chatID", "columnName": "jid",
"affinity": "TEXT", "affinity": "TEXT",
"notNull": false "notNull": false
}, },
{ {
"fieldPath": "senderID", "fieldPath": "senderJid",
"columnName": "senderID", "columnName": "senderJid",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "username",
"columnName": "username",
"affinity": "TEXT", "affinity": "TEXT",
"notNull": false "notNull": false
}, },
@ -67,55 +55,43 @@
"columnNames": [ "columnNames": [
"messageID" "messageID"
], ],
"autoGenerate": false "autoGenerate": true
}, },
"indices": [], "indices": [],
"foreignKeys": [] "foreignKeys": []
}, },
{ {
"tableName": "chats", "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": [ "fields": [
{ {
"fieldPath": "chatID", "fieldPath": "jid",
"columnName": "chatID", "columnName": "jid",
"affinity": "TEXT", "affinity": "TEXT",
"notNull": true "notNull": true
}, },
{ {
"fieldPath": "name", "fieldPath": "chatName",
"columnName": "name", "columnName": "chatName",
"affinity": "TEXT", "affinity": "TEXT",
"notNull": false "notNull": false
}, },
{ {
"fieldPath": "metadataRef", "fieldPath": "users",
"columnName": "metadataRef", "columnName": "users",
"affinity": "TEXT", "affinity": "TEXT",
"notNull": false "notNull": false
}, },
{ {
"fieldPath": "membersRef", "fieldPath": "unreadMessagesCount",
"columnName": "membersRef", "columnName": "unreadMessagesCount",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "bannedUsers",
"columnName": "bannedUsers",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "chunkCursor",
"columnName": "chunkCursor",
"affinity": "INTEGER", "affinity": "INTEGER",
"notNull": true "notNull": true
} }
], ],
"primaryKey": { "primaryKey": {
"columnNames": [ "columnNames": [
"chatID" "jid"
], ],
"autoGenerate": false "autoGenerate": false
}, },
@ -126,7 +102,7 @@
"views": [], "views": [],
"setupQueries": [ "setupQueries": [
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)", "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, \"81501115d10a6dc46002667323359631\")" "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, \"2409c873b47ccd635ed7d10e4d8604f8\")"
] ]
} }
} }

View File

@ -56,9 +56,6 @@ public class XMPPConnection implements ConnectionListener {
public enum ConnectionState { public enum ConnectionState {
CONNECTED, CONNECTED,
AUTHENTICATED,
CONNECTING,
DISCONNECTING,
DISCONNECTED DISCONNECTED
} }
@ -68,6 +65,8 @@ public class XMPPConnection implements ConnectionListener {
} }
public XMPPConnection(Context context) { public XMPPConnection(Context context) {
this.prefs = PreferenceManager.getDefaultSharedPreferences(context);
this.context = context;
String jid = prefs.getString("jid", null); String jid = prefs.getString("jid", null);
String password = prefs.getString("pass", null); String password = prefs.getString("pass", null);
if(jid != null && password != null) { if(jid != null && password != null) {
@ -78,8 +77,6 @@ public class XMPPConnection implements ConnectionListener {
credentials.password = password; credentials.password = password;
} }
networkHandler = new NetworkHandler(context); networkHandler = new NetworkHandler(context);
prefs = PreferenceManager.getDefaultSharedPreferences(context);
this.context = context;
} }
public void connect() throws XMPPException, SmackException, IOException { public void connect() throws XMPPException, SmackException, IOException {
@ -97,6 +94,9 @@ public class XMPPConnection implements ConnectionListener {
connection = new XMPPTCPConnection(conf); connection = new XMPPTCPConnection(conf);
connection.addConnectionListener(this); connection.addConnectionListener(this);
if(credentials.jabberHost.equals("") && credentials.password.equals("") && credentials.username.equals("")){
throw new IOException();
}
try { try {
connection.connect(); connection.connect();
connection.login(credentials.username, credentials.password); connection.login(credentials.username, credentials.password);
@ -121,26 +121,28 @@ public class XMPPConnection implements ConnectionListener {
@Override @Override
public void connected(org.jivesoftware.smack.XMPPConnection connection) { public void connected(org.jivesoftware.smack.XMPPConnection connection) {
XMPPConnectionService.connectionState = ConnectionState.CONNECTED; XMPPConnectionService.CONNECTION_STATE = ConnectionState.CONNECTED;
} }
@Override @Override
public void authenticated(org.jivesoftware.smack.XMPPConnection connection, boolean resumed) { 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(); prefs.edit().putBoolean("logged_in", true).apply();
context.sendBroadcast(new Intent(XMPPConnectionService.INTENT_AUTHENTICATED));
AppHelper.setJid(credentials.username + "@" + credentials.jabberHost);
} }
@Override @Override
public void connectionClosed() { public void connectionClosed() {
XMPPConnectionService.connectionState = ConnectionState.DISCONNECTED; XMPPConnectionService.CONNECTION_STATE = ConnectionState.DISCONNECTED;
XMPPConnectionService.sessionState = SessionState.LOGGED_OUT; XMPPConnectionService.SESSION_STATE = SessionState.LOGGED_OUT;
prefs.edit().putBoolean("logged_in", false).apply(); prefs.edit().putBoolean("logged_in", false).apply();
} }
@Override @Override
public void connectionClosedOnError(Exception e) { public void connectionClosedOnError(Exception e) {
XMPPConnectionService.connectionState = ConnectionState.DISCONNECTED; XMPPConnectionService.CONNECTION_STATE = ConnectionState.DISCONNECTED;
XMPPConnectionService.sessionState = SessionState.LOGGED_OUT; XMPPConnectionService.SESSION_STATE = SessionState.LOGGED_OUT;
prefs.edit().putBoolean("logged_in", false).apply(); prefs.edit().putBoolean("logged_in", false).apply();
Log.e(LOG_TAG, "Connection closed, exception occurred"); Log.e(LOG_TAG, "Connection closed, exception occurred");
e.printStackTrace(); e.printStackTrace();

View File

@ -29,6 +29,8 @@ import org.jivesoftware.smack.XMPPException;
import java.io.IOException; import java.io.IOException;
import io.github.chronosx88.influence.helpers.AppHelper;
public class XMPPConnectionService extends Service { public class XMPPConnectionService extends Service {
public static final String INTENT_NEW_MESSAGE = "io.github.chronosx88.intents.new_message"; 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"; 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_BODY = "message_body";
public static final String MESSAGE_RECIPIENT = "message_recipient"; public static final String MESSAGE_RECIPIENT = "message_recipient";
public static XMPPConnection.ConnectionState connectionState = XMPPConnection.ConnectionState.DISCONNECTED; public static XMPPConnection.ConnectionState CONNECTION_STATE = XMPPConnection.ConnectionState.DISCONNECTED;
public static XMPPConnection.SessionState sessionState = XMPPConnection.SessionState.LOGGED_OUT; public static XMPPConnection.SessionState SESSION_STATE = XMPPConnection.SessionState.LOGGED_OUT;
private Thread thread; private Thread thread;
private Handler threadHandler; private Handler threadHandler;
private boolean isThreadAlive = false; private boolean isThreadAlive = false;
private XMPPConnection connection; private XMPPConnection connection;
private Context context; private Context context = AppHelper.getContext();
public XMPPConnectionService(Context context) { public XMPPConnectionService() { }
this.context = context;
}
@Override @Override
public IBinder onBind(Intent intent) { return null; } public IBinder onBind(Intent intent) { return null; }
@ -89,9 +89,8 @@ public class XMPPConnectionService extends Service {
connection.connect(); connection.connect();
} catch (IOException | SmackException | XMPPException e) { } catch (IOException | SmackException | XMPPException e) {
Intent intent = new Intent(INTENT_AUTHENTICATION_FAILED); Intent intent = new Intent(INTENT_AUTHENTICATION_FAILED);
context.sendBroadcast(intent);
e.printStackTrace(); e.printStackTrace();
//Stop the service all together.
stopSelf(); stopSelf();
} }
} }

View File

@ -1,9 +1,12 @@
package io.github.chronosx88.influence.contracts package io.github.chronosx88.influence.contracts
import android.content.Context
import android.content.Intent 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.ChatEntity
import io.github.chronosx88.influence.models.roomEntities.MessageEntity import io.github.chronosx88.influence.models.roomEntities.MessageEntity
@ -15,28 +18,23 @@ interface CoreContracts {
// -----ChatList----- // -----ChatList-----
interface IChatListLogicContract { interface IDialogListLogicContract {
fun loadAllChats(): List<ChatEntity> fun loadAllChats(): List<ChatEntity>
} }
interface IChatListPresenterContract { interface IDialogListPresenterContract {
fun updateChatList()
fun openChat(chatID: String) fun openChat(chatID: String)
fun onContextItemSelected(item: MenuItem)
} }
interface IChatListViewContract { interface IChatListViewContract {
fun setRecycleAdapter(adapter: ChatListAdapter) fun setDialogAdapter(adapter: DialogsListAdapter<GenericDialog>)
fun startActivity(intent: Intent) fun startActivity(intent: Intent)
fun updateChatList(adapter: ChatListAdapter, chats: List<ChatEntity>) fun getActivityContext(): Context?
} }
// -----MainActivity----- // -----MainActivity-----
interface IMainLogicContract { interface IMainLogicContract {
fun initPeer()
fun sendStartChatMessage(username: String)
fun shutdownPeer()
} }
interface IMainPresenterContract { interface IMainPresenterContract {
@ -53,19 +51,17 @@ interface CoreContracts {
// -----ChatActivity----- // -----ChatActivity-----
interface IChatLogicContract { interface IChatLogicContract {
fun sendMessage(message: MessageEntity) fun sendMessage(text: String): MessageEntity
fun stopTrackingForNewMsgs()
} }
interface IChatPresenterContract { interface IChatPresenterContract {
fun sendMessage(text: String) fun sendMessage(text: String): Boolean
fun updateAdapter() fun loadLocalMessages()
fun onDestroy() fun onDestroy()
} }
interface IChatViewContract { interface IChatViewContract {
fun updateMessageList(message: MessageEntity) fun setAdapter(adapter: MessagesListAdapter<GenericMessage>)
fun updateMessageList(messages: List<MessageEntity>)
} }
// -----SettingsFragment----- // -----SettingsFragment-----

View File

@ -1,12 +1,9 @@
package io.github.chronosx88.influence.contracts.observer; package io.github.chronosx88.influence.contracts.observer;
import com.google.gson.JsonObject; import org.json.JSONObject;
public interface IObservable { public interface IObservable {
void register(IObserver observer); void register(IObserver observer);
void register(INetworkObserver networkObserver);
void unregister(IObserver observer); void unregister(IObserver observer);
void unregister(INetworkObserver networkObserver); void notifyUIObservers(JSONObject jsonObject);
void notifyUIObservers(JsonObject jsonObject);
void notifyNetworkObservers(Object object);
} }

View File

@ -1,7 +1,8 @@
package io.github.chronosx88.influence.contracts.observer; package io.github.chronosx88.influence.contracts.observer;
import com.google.gson.JsonObject; import org.json.JSONException;
import org.json.JSONObject;
public interface IObserver { public interface IObserver {
void handleEvent(JsonObject object); void handleEvent(JSONObject object) throws JSONException;
} }

View File

@ -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<ChatAdapter.ViewHolder> {
private final static int RIGHT_ITEM = 0;
private final static int LEFT_ITEM = 1;
private final static int TECHNICAL_MESSAGE = 2; // TODO
private final Context context = AppHelper.getContext();
private ArrayList<MessageEntity> messages = new ArrayList<>();
private static Comparator<MessageEntity> 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<MessageEntity> 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);
}
}
}

View File

@ -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<ChatListAdapter.ChatListViewHolder> {
List<ChatEntity> 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<ChatEntity> 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");
}
}
}

View File

@ -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 <https://www.gnu.org/licenses/>.
*/
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<T> {
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;
}
}

View File

@ -10,14 +10,14 @@ import java.security.NoSuchAlgorithmException;
public class KeyPairManager { public class KeyPairManager {
private File keyPairDir; private File keyPairDir;
private Serializer<KeyPair> serializer; private JavaSerializer<KeyPair> serializer;
public KeyPairManager() { public KeyPairManager() {
this.keyPairDir = new File(AppHelper.getContext().getFilesDir().getAbsoluteFile(), "keyPairs"); this.keyPairDir = new File(AppHelper.getContext().getFilesDir().getAbsoluteFile(), "keyPairs");
if(!this.keyPairDir.exists()) { if(!this.keyPairDir.exists()) {
this.keyPairDir.mkdir(); this.keyPairDir.mkdir();
} }
this.serializer = new Serializer<>(); this.serializer = new JavaSerializer<>();
} }
public KeyPair openMainKeyPair() { public KeyPair openMainKeyPair() {

View File

@ -13,7 +13,7 @@ public class LocalDBWrapper {
private static RoomHelper dbInstance = AppHelper.getChatDB(); private static RoomHelper dbInstance = AppHelper.getChatDB();
public static void createChatEntry(String jid, String chatName) { 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) { public static long createMessageEntry(String jid, String senderJid, long timestamp, String text, boolean isSent, boolean isRead) {

View File

@ -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 <https://www.gnu.org/licenses/>.
*/
package io.github.chronosx88.influence.helpers;
public class ObservableActions {
public static final int NEW_CHAT_CREATED = 0x0;
}

View File

@ -3,28 +3,48 @@ package io.github.chronosx88.influence.helpers;
import com.google.gson.JsonArray; import com.google.gson.JsonArray;
import com.google.gson.JsonObject; import com.google.gson.JsonObject;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
public class ObservableUtils { public class ObservableUtils {
public static void notifyUI(int action) { public static void notifyUI(int action) {
JsonObject jsonObject = new JsonObject(); JSONObject jsonObject = new JSONObject();
jsonObject.addProperty("action", action); try {
jsonObject.put("action", action);
} catch (JSONException e) {
e.printStackTrace();
}
AppHelper.getObservable().notifyUIObservers(jsonObject); AppHelper.getObservable().notifyUIObservers(jsonObject);
} }
public static void notifyUI(int action, String... additional) { public static void notifyUI(int action, String... additional) {
JsonObject jsonObject = new JsonObject(); JSONObject jsonObject = new JSONObject();
jsonObject.addProperty("action", action); try {
JsonArray jsonArray = new JsonArray(); jsonObject.put("action", action);
for(String info : additional) { } catch (JSONException e) {
jsonArray.add(info); 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); AppHelper.getObservable().notifyUIObservers(jsonObject);
} }
public static void notifyUI(int action, int additional) { public static void notifyUI(int action, int additional) {
JsonObject jsonObject = new JsonObject(); JSONObject jsonObject = new JSONObject();
jsonObject.addProperty("action", action); try {
jsonObject.addProperty("additional", additional); jsonObject.put("action", action);
jsonObject.put("additional", additional);
} catch (JSONException e) {
e.printStackTrace();
}
AppHelper.getObservable().notifyUIObservers(jsonObject); AppHelper.getObservable().notifyUIObservers(jsonObject);
} }
} }

View File

@ -8,16 +8,18 @@ import java.util.ArrayList;
import androidx.room.TypeConverter; import androidx.room.TypeConverter;
import io.github.chronosx88.influence.models.GenericUser;
public class RoomTypeConverter { public class RoomTypeConverter {
@TypeConverter @TypeConverter
public static ArrayList<String> fromString(String value) { public static ArrayList<GenericUser> fromString(String value) {
Type listType = new TypeToken<ArrayList<String>>() {}.getType(); Type listType = new TypeToken<ArrayList<String>>() {}.getType();
return new Gson().fromJson(value, listType); return new Gson().fromJson(value, listType);
} }
@TypeConverter @TypeConverter
public static String fromArrayList(ArrayList<String> list) { public static <T> String fromArrayList(ArrayList<GenericUser> list) {
Gson gson = new Gson(); Gson gson = new Gson();
return gson.toJson(list); return gson.toJson(list);
} }

View File

@ -6,7 +6,7 @@ import io.github.chronosx88.influence.contracts.CoreContracts;
import io.github.chronosx88.influence.helpers.AppHelper; import io.github.chronosx88.influence.helpers.AppHelper;
import io.github.chronosx88.influence.models.roomEntities.ChatEntity; import io.github.chronosx88.influence.models.roomEntities.ChatEntity;
public class ChatListLogic implements CoreContracts.IChatListLogicContract { public class ChatListLogic implements CoreContracts.IDialogListLogicContract {
@Override @Override
public List<ChatEntity> loadAllChats() { public List<ChatEntity> loadAllChats() {

View File

@ -1,182 +1,39 @@
package io.github.chronosx88.influence.logic; package io.github.chronosx88.influence.logic;
import com.google.gson.Gson; import android.content.Intent;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import net.tomp2p.peers.Number640; import com.instacart.library.truetime.TrueTime;
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 io.github.chronosx88.influence.XMPPConnection;
import io.github.chronosx88.influence.XMPPConnectionService;
import io.github.chronosx88.influence.contracts.CoreContracts; import io.github.chronosx88.influence.contracts.CoreContracts;
import io.github.chronosx88.influence.helpers.AppHelper; 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.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.ChatEntity;
import io.github.chronosx88.influence.models.roomEntities.MessageEntity; import io.github.chronosx88.influence.models.roomEntities.MessageEntity;
public class ChatLogic implements CoreContracts.IChatLogicContract { public class ChatLogic implements CoreContracts.IChatLogicContract {
private static Gson gson = new Gson();
private String chatID; private String chatID;
private volatile String newMessage = "";
private ChatEntity chatEntity; private ChatEntity chatEntity;
private Thread checkNewMessagesThread = null; //private KeyPairManager keyPairManager;
private KeyPairManager keyPairManager;
private Timer timer;
public ChatLogic(ChatEntity chatEntity) { public ChatLogic(ChatEntity chatEntity) {
this.chatEntity = chatEntity; this.chatEntity = chatEntity;
this.chatID = chatEntity.chatID; this.chatID = chatEntity.jid;
TimerTask timerTask = new TimerTask() { //this.keyPairManager = new KeyPairManager();
@Override
public void run() {
checkForNewMessages();
}
};
this.timer = new Timer();
if(AppHelper.getPeerDHT() != null) {
timer.schedule(timerTask, 1, 1000);
}
this.keyPairManager = new KeyPairManager();
} }
@Override @Override
public void sendMessage(MessageEntity message) { public MessageEntity sendMessage(String text) {
if(AppHelper.getPeerDHT() == null) { if (XMPPConnectionService.CONNECTION_STATE.equals(XMPPConnection.ConnectionState.CONNECTED)) {
ObservableUtils.notifyUI(UIActions.NODE_IS_OFFLINE); Intent intent = new Intent(XMPPConnectionService.INTENT_SEND_MESSAGE);
return; 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<Number640, Data> data = P2PUtils.get(chatID + "_newMessage");
if(data != null) {
for(Map.Entry<Number640, Data> 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<Number640, Data> data = P2PUtils.get(chatID + "_newMessage");
if(data != null) {
for(Map.Entry<Number640, Data> 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<Number640, Data> messages = P2PUtils.get(chatEntity.chatID + "_messages" + chunkID);
if (messages != null) {
for (Map.Entry<Number640, Data> 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();
} }
} }

View File

@ -1,83 +1,23 @@
package io.github.chronosx88.influence.logic; package io.github.chronosx88.influence.logic;
import android.content.Context; import android.content.Context;
import android.content.SharedPreferences;
import android.util.Log;
import com.google.gson.Gson; 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.contracts.CoreContracts;
import io.github.chronosx88.influence.helpers.AppHelper; import io.github.chronosx88.influence.helpers.AppHelper;
import io.github.chronosx88.influence.helpers.KeyPairManager; 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 { public class MainLogic implements CoreContracts.IMainLogicContract {
private static final String LOG_TAG = MainLogic.class.getName(); private static final String LOG_TAG = MainLogic.class.getName();
private SharedPreferences preferences;
private Number160 peerID;
private PeerDHT peerDHT;
private Context context; 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() { public MainLogic() {
this.context = AppHelper.getContext(); 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() { public void initPeer() {
org.apache.log4j.BasicConfigurator.configure(); org.apache.log4j.BasicConfigurator.configure();
@ -366,5 +306,5 @@ public class MainLogic implements CoreContracts.IMainLogicContract {
e.printStackTrace(); e.printStackTrace();
} }
return null; return null;
} }*/
} }

View File

@ -1,24 +1,18 @@
package io.github.chronosx88.influence.logic package io.github.chronosx88.influence.logic
import android.util.Log
import io.github.chronosx88.influence.contracts.CoreContracts 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.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 { class SettingsLogic : CoreContracts.ISettingsLogic {
override fun checkUsernameExists(username: String) : Boolean { override fun checkUsernameExists(username: String) : Boolean {
if (AppHelper.getPeerDHT() == null) { /*if (AppHelper.getPeerDHT() == null) {
ObservableUtils.notifyUI(UIActions.NODE_IS_OFFLINE) ObservableUtils.notifyUI(UIActions.NODE_IS_OFFLINE)
return false return false
} }
val usernameMap: MutableMap<Number640, Data>? = P2PUtils.get(username) val usernameMap: MutableMap<Number640, Data>? = P2PUtils.get(username)
usernameMap ?: return false usernameMap ?: return false
return true*/
return true return true
} }
@ -26,7 +20,7 @@ class SettingsLogic : CoreContracts.ISettingsLogic {
private val LOG_TAG: String = "SettingsLogic" private val LOG_TAG: String = "SettingsLogic"
private val keyPairManager = KeyPairManager() private val keyPairManager = KeyPairManager()
fun publishUsername(oldUsername: String?, username: String?) { /*fun publishUsername(oldUsername: String?, username: String?) {
if (AppHelper.getPeerDHT() == null) { if (AppHelper.getPeerDHT() == null) {
ObservableUtils.notifyUI(UIActions.NODE_IS_OFFLINE) ObservableUtils.notifyUI(UIActions.NODE_IS_OFFLINE)
return return
@ -53,6 +47,6 @@ class SettingsLogic : CoreContracts.ISettingsLogic {
} ?: run { } ?: run {
return return
} }
} }*/
} }
} }

View File

@ -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 <https://www.gnu.org/licenses/>.
*/
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<GenericUser> 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<? extends IUser> getUsers() {
return users;
}
@Override
public IMessage getLastMessage() {
return lastMessage;
}
@Override
public void setLastMessage(IMessage message) {
lastMessage = message;
}
@Override
public int getUnreadCount() {
return unreadMessagesCount;
}
}

View File

@ -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 <https://www.gnu.org/licenses/>.
*/
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);
}
}

View File

@ -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 <https://www.gnu.org/licenses/>.
*/
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;
}
}

View File

@ -5,13 +5,25 @@ import androidx.room.ColumnInfo;
import androidx.room.Entity; import androidx.room.Entity;
import androidx.room.PrimaryKey; import androidx.room.PrimaryKey;
import java.util.ArrayList;
import io.github.chronosx88.influence.models.GenericUser;
@Entity(tableName = "chats") @Entity(tableName = "chats")
public class ChatEntity { public class ChatEntity {
@PrimaryKey @NonNull public String jid; @PrimaryKey @NonNull public String jid;
@ColumnInfo public String chatName; @ColumnInfo public String chatName;
@ColumnInfo public ArrayList<GenericUser> users;
@ColumnInfo public int unreadMessagesCount;
public ChatEntity(@NonNull String jid, String chatName) { public ChatEntity(@NonNull String jid, String chatName, ArrayList<GenericUser> users, int unreadMessagesCount) {
this.jid = jid; this.jid = jid;
this.chatName = chatName; this.chatName = chatName;
this.users = users;
this.unreadMessagesCount = unreadMessagesCount;
}
public boolean isPrivateChat() {
return users.size() == 1;
} }
} }

View File

@ -1,34 +1,21 @@
package io.github.chronosx88.influence.observable; package io.github.chronosx88.influence.observable;
import com.google.gson.JsonObject; import org.json.JSONException;
import org.json.JSONObject;
import java.util.ArrayList; 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.IObservable;
import io.github.chronosx88.influence.contracts.observer.IObserver; import io.github.chronosx88.influence.contracts.observer.IObserver;
public class MainObservable implements IObservable { public class MainObservable implements IObservable {
public static final int UI_ACTIONS_CHANNEL = 0; private ArrayList<IObserver> uiObservers = new ArrayList<>();
public static final int OTHER_ACTIONS_CHANNEL = 1;
private ArrayList<IObserver> uiObservers;
private ArrayList<INetworkObserver> networkObservers;
public MainObservable() {
this.uiObservers = new ArrayList<>();
this.networkObservers = new ArrayList<>();
}
@Override @Override
public void register(IObserver observer) { public void register(IObserver observer) {
uiObservers.add(observer); uiObservers.add(observer);
} }
@Override
public void register(INetworkObserver observer) {
networkObservers.add(observer);
}
@Override @Override
public void unregister(IObserver observer) { public void unregister(IObserver observer) {
@ -36,21 +23,13 @@ public class MainObservable implements IObservable {
} }
@Override @Override
public void unregister(INetworkObserver observer) { public void notifyUIObservers(JSONObject jsonObject) {
networkObservers.remove(observer);
}
@Override
public void notifyUIObservers(JsonObject jsonObject) {
for (IObserver observer : uiObservers) { for (IObserver observer : uiObservers) {
observer.handleEvent(jsonObject); try {
} observer.handleEvent(jsonObject);
} } catch (JSONException e) {
e.printStackTrace();
@Override }
public void notifyNetworkObservers(Object object) {
for (INetworkObserver observer : networkObservers) {
observer.handleEvent(object);
} }
} }
} }

View File

@ -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();
}
}
}
}
}

View File

@ -1,70 +1,76 @@
package io.github.chronosx88.influence.presenters 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.Gson
import com.google.gson.JsonObject import com.stfalcon.chatkit.commons.ImageLoader
import com.instacart.library.truetime.TrueTime import com.stfalcon.chatkit.messages.MessagesListAdapter
import io.github.chronosx88.influence.R
import java.io.IOException import io.github.chronosx88.influence.XMPPConnectionService
import java.util.ArrayList
import java.util.UUID
import io.github.chronosx88.influence.contracts.CoreContracts 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.AppHelper
import io.github.chronosx88.influence.helpers.LocalDBWrapper 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.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 io.github.chronosx88.influence.models.roomEntities.ChatEntity
import org.jetbrains.anko.doAsync import io.github.chronosx88.influence.models.roomEntities.MessageEntity
import org.jetbrains.anko.doAsyncResult
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 logic: CoreContracts.IChatLogicContract
private val chatEntity: ChatEntity? private val chatEntity: ChatEntity?
private val gson: Gson private val gson: Gson
private val chatAdapter: MessagesListAdapter<GenericMessage>
private val newMessageReceiver: BroadcastReceiver
init { init {
this.logic = ChatLogic(LocalDBWrapper.getChatByChatID(chatID)!!) this.logic = ChatLogic(LocalDBWrapper.getChatByChatID(chatID)!!)
this.chatEntity = LocalDBWrapper.getChatByChatID(chatID) this.chatEntity = LocalDBWrapper.getChatByChatID(chatID)
AppHelper.getObservable().register(this)
gson = Gson() gson = Gson()
} chatAdapter = MessagesListAdapter(AppHelper.getJid(), ImageLoader { imageView, _, _ -> imageView.setImageResource(R.mipmap.ic_launcher) })
view.setAdapter(chatAdapter)
override fun sendMessage(text: String) { newMessageReceiver = object : BroadcastReceiver() {
doAsync { override fun onReceive(context: Context, intent: Intent) {
val message = LocalDBWrapper.createMessageEntry(NetworkActions.TEXT_MESSAGE, UUID.randomUUID().toString(), chatID, AppHelper.getPeerID(), AppHelper.getPeerID(), TrueTime.now().time, text, false, false) if(intent.getStringExtra(XMPPConnectionService.MESSAGE_CHATID).equals(chatEntity.jid)) {
logic.sendMessage(message!!) val messageID = intent.getLongExtra(XMPPConnectionService.MESSAGE_ID, -1)
view.updateMessageList(message) chatAdapter.addToStart(GenericMessage(LocalDBWrapper.getMessageByID(messageID)), true)
}
}
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
} }
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() { override fun sendMessage(text: String): Boolean {
val entities = LocalDBWrapper.getMessagesByChatID(chatID) val message: MessageEntity? = logic.sendMessage(text)
view.updateMessageList(entities ?: ArrayList()) if(message != null) {
chatAdapter.addToStart(GenericMessage(message), true)
return true
}
return false
}
override fun loadLocalMessages() {
val entities: List<MessageEntity>? = LocalDBWrapper.getMessagesByChatID(chatID)
val messages = ArrayList<GenericMessage>()
if(entities != null) {
entities.forEach {
messages.add(GenericMessage(it))
}
}
chatAdapter.addToEnd(messages, true)
} }
override fun onDestroy() { override fun onDestroy() {
logic.stopTrackingForNewMsgs() //
} }
private fun setupIncomingMessagesReceiver() {
}
} }

View File

@ -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<GenericDialog> 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<GenericDialog> 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;
}
}
}
}

View File

@ -1,56 +1,68 @@
package io.github.chronosx88.influence.presenters 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.R
import io.github.chronosx88.influence.XMPPConnectionService
import io.github.chronosx88.influence.contracts.CoreContracts 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.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.logic.MainLogic
import io.github.chronosx88.influence.views.LoginActivity
import org.jetbrains.anko.doAsync 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 val logic: CoreContracts.IMainLogicContract = MainLogic()
private var broadcastReceiver: BroadcastReceiver? = null
init {
AppHelper.getObservable().register(this)
}
override fun initPeer() { override fun initPeer() {
if (AppHelper.getPeerDHT() == null) { broadcastReceiver = object : BroadcastReceiver() {
logic.initPeer() override fun onReceive(context: Context, intent: Intent) {
} else { val action = intent.action
view.showSnackbar(AppHelper.getContext().getString(R.string.node_already_running)) when (action) {
view.showProgressBar(false) 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) { override fun startChatWithPeer(username: String) {
doAsync { LocalDBWrapper.createChatEntry(username, username)
logic.sendStartChatMessage(username) ObservableUtils.notifyUI(ObservableActions.NEW_CHAT_CREATED, 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("Нода не запущена!")
}
}
} }
override fun onDestroy() { 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
} }
} }

View File

@ -1,28 +1,17 @@
package io.github.chronosx88.influence.presenters package io.github.chronosx88.influence.presenters
import android.content.SharedPreferences
import android.os.Handler 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.CoreContracts
import io.github.chronosx88.influence.contracts.observer.IObserver
import io.github.chronosx88.influence.helpers.AppHelper 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 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 mainThreadHandler: Handler = Handler(AppHelper.getContext().mainLooper)
private val logic: SettingsLogic = SettingsLogic() private val logic: SettingsLogic = SettingsLogic()
init {
AppHelper.getObservable().register(this)
}
override fun updateUsername(username: String) { override fun updateUsername(username: String) {
view.loadingScreen(true) /*view.loadingScreen(true)
val editor: SharedPreferences.Editor = AppHelper.getPreferences().edit() val editor: SharedPreferences.Editor = AppHelper.getPreferences().edit()
GlobalScope.launch { GlobalScope.launch {
@ -52,23 +41,6 @@ class SettingsPresenter(private val view: CoreContracts.ISettingsView) : CoreCon
} else { } else {
ObservableUtils.notifyUI(UIActions.USERNAME_ISNT_AVAILABLE) 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)
} }
} }

View File

@ -2,25 +2,25 @@ package io.github.chronosx88.influence.views
import android.os.Bundle import android.os.Bundle
import android.view.MenuItem import android.view.MenuItem
import android.widget.EditText
import android.widget.ImageButton
import android.widget.TextView import android.widget.TextView
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.widget.Toolbar import androidx.appcompat.widget.Toolbar
import androidx.recyclerview.widget.LinearLayoutManager 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.R
import io.github.chronosx88.influence.contracts.CoreContracts 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.models.roomEntities.MessageEntity
import io.github.chronosx88.influence.presenters.ChatPresenter import io.github.chronosx88.influence.presenters.ChatPresenter
class ChatActivity : AppCompatActivity(), CoreContracts.IChatViewContract { class ChatActivity : AppCompatActivity(), CoreContracts.IChatViewContract {
private var chatAdapter: ChatAdapter? = null private var messageList: MessagesList? = null
private var messageList: RecyclerView? = null private var messageInput: MessageInput? = null
private var sendMessageButton: ImageButton? = null private var chatNameTextView: TextView? = null
private var messageTextEdit: EditText? = null
private var contactUsernameTextView: TextView? = null
private var presenter: ChatPresenter? = null private var presenter: ChatPresenter? = null
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
@ -29,45 +29,21 @@ class ChatActivity : AppCompatActivity(), CoreContracts.IChatViewContract {
val intent = intent val intent = intent
presenter = ChatPresenter(this, intent.getStringExtra("chatID"))
val toolbar = findViewById<Toolbar>(R.id.toolbar_chat_activity) val toolbar = findViewById<Toolbar>(R.id.toolbar_chat_activity)
setSupportActionBar(toolbar) setSupportActionBar(toolbar)
supportActionBar!!.setTitle("") supportActionBar!!.setTitle("")
supportActionBar!!.setDisplayHomeAsUpEnabled(true) supportActionBar!!.setDisplayHomeAsUpEnabled(true)
supportActionBar!!.setHomeButtonEnabled(true) supportActionBar!!.setHomeButtonEnabled(true)
messageList = findViewById(R.id.message_list) messageList = findViewById(R.id.messages_list)
chatAdapter = ChatAdapter()
presenter!!.updateAdapter()
messageList!!.adapter = chatAdapter
messageList!!.layoutManager = LinearLayoutManager(this) messageList!!.layoutManager = LinearLayoutManager(this)
contactUsernameTextView = findViewById(R.id.appbar_username) chatNameTextView = findViewById(R.id.appbar_username)
messageTextEdit = findViewById(R.id.message_input) messageInput = findViewById(R.id.message_input)
sendMessageButton = findViewById(R.id.send_button) messageInput!!.setInputListener {
sendMessageButton!!.setOnClickListener sendMessageButton@{ presenter!!.sendMessage(it.toString())
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<MessageEntity>) {
runOnUiThread {
chatAdapter!!.addMessages(messages)
messageList!!.scrollToPosition(chatAdapter!!.itemCount - 1)
chatAdapter!!.notifyDataSetChanged()
} }
chatNameTextView!!.text = intent.getStringExtra("chatName")
presenter = ChatPresenter(this, intent.getStringExtra("chatID"))
presenter!!.loadLocalMessages()
} }
override fun onOptionsItemSelected(item: MenuItem): Boolean { override fun onOptionsItemSelected(item: MenuItem): Boolean {
@ -84,4 +60,8 @@ class ChatActivity : AppCompatActivity(), CoreContracts.IChatViewContract {
super.onDestroy() super.onDestroy()
presenter!!.onDestroy() presenter!!.onDestroy()
} }
override fun setAdapter(adapter: MessagesListAdapter<GenericMessage>) {
messageList!!.setAdapter(adapter)
}
} }

View File

@ -27,7 +27,6 @@ import android.text.TextUtils;
import android.view.View; import android.view.View;
import android.widget.Button; import android.widget.Button;
import android.widget.EditText; import android.widget.EditText;
import android.widget.Toast;
import androidx.appcompat.app.AppCompatActivity; import androidx.appcompat.app.AppCompatActivity;
@ -51,6 +50,8 @@ public class LoginActivity extends AppCompatActivity implements CoreContracts.IL
passwordEditText = findViewById(R.id.login_password); passwordEditText = findViewById(R.id.login_password);
signInButton = findViewById(R.id.sign_in_button); signInButton = findViewById(R.id.sign_in_button);
progressDialog = new ProgressDialog(LoginActivity.this); progressDialog = new ProgressDialog(LoginActivity.this);
progressDialog.setCancelable(false);
progressDialog.setProgressStyle(android.R.style.Widget_ProgressBar_Small);
signInButton.setOnClickListener((v) -> { signInButton.setOnClickListener((v) -> {
if(checkLoginCredentials()) { if(checkLoginCredentials()) {
saveLoginCredentials(); saveLoginCredentials();
@ -82,7 +83,7 @@ public class LoginActivity extends AppCompatActivity implements CoreContracts.IL
} }
case XMPPConnectionService.INTENT_AUTHENTICATION_FAILED: { case XMPPConnectionService.INTENT_AUTHENTICATION_FAILED: {
loadingScreen(false); loadingScreen(false);
passwordEditText.setError("Invalid JID/Password"); jidEditText.setError("Invalid JID/Password/Server");
break; break;
} }
} }

View File

@ -7,59 +7,51 @@ import android.view.MenuItem;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.widget.EditText; 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.AlertDialog;
import androidx.appcompat.app.AppCompatActivity; import androidx.appcompat.app.AppCompatActivity;
import androidx.fragment.app.Fragment; import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentTransaction; import androidx.fragment.app.FragmentTransaction;
import 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.jetbrains.annotations.NotNull;
import org.json.JSONException;
import org.json.JSONObject;
import io.github.chronosx88.influence.R; import io.github.chronosx88.influence.R;
import io.github.chronosx88.influence.contracts.CoreContracts; import io.github.chronosx88.influence.contracts.CoreContracts;
import io.github.chronosx88.influence.contracts.observer.IObserver; import io.github.chronosx88.influence.contracts.observer.IObserver;
import io.github.chronosx88.influence.helpers.AppHelper; import io.github.chronosx88.influence.helpers.AppHelper;
import io.github.chronosx88.influence.helpers.actions.UIActions; import io.github.chronosx88.influence.helpers.ObservableActions;
import io.github.chronosx88.influence.presenters.MainPresenter; 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 io.github.chronosx88.influence.views.fragments.SettingsFragment;
import kotlin.Pair; 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 CoreContracts.IMainPresenterContract presenter;
private ProgressDialog progressDialog; private ProgressDialog progressDialog;
private BottomNavigationView.OnNavigationItemSelectedListener mOnNavigationItemSelectedListener private BottomNavigationView.OnNavigationItemSelectedListener mOnNavigationItemSelectedListener = (item) -> {
= new BottomNavigationView.OnNavigationItemSelectedListener() { Fragment selectedFragment = null;
@Override switch (item.getItemId()) {
public boolean onNavigationItemSelected(@NonNull MenuItem item) { case R.id.action_chats:
selectedFragment = new DialogListFragment();
Fragment selectedFragment = null; break;
case R.id.action_settings:
switch (item.getItemId()) { selectedFragment = new SettingsFragment();
case R.id.action_chats: break;
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;
} }
FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
transaction.replace(R.id.main_fragment_container, selectedFragment);
transaction.commit();
return true;
}; };
@Override @Override
@ -73,7 +65,7 @@ public class MainActivity extends AppCompatActivity implements CoreContracts.IMa
fab.setOnClickListener((v) -> { fab.setOnClickListener((v) -> {
Pair<AlertDialog.Builder, EditText> pair = ViewUtils.INSTANCE.setupEditTextDialog(MainActivity.this, getString(R.string.input_companion_username)); Pair<AlertDialog.Builder, EditText> pair = ViewUtils.INSTANCE.setupEditTextDialog(MainActivity.this, getString(R.string.input_companion_username));
pair.getFirst().setPositiveButton(getString(R.string.ok), (dialog, which) -> { pair.getFirst().setPositiveButton(getString(R.string.ok), (dialog, which) -> {
progressDialog.show(); showProgressBar(true);
presenter.startChatWithPeer(pair.getSecond().getText().toString()); presenter.startChatWithPeer(pair.getSecond().getText().toString());
}); });
pair.getFirst().setNegativeButton(getString(R.string.cancel), (dialog, which) -> { pair.getFirst().setNegativeButton(getString(R.string.cancel), (dialog, which) -> {
@ -84,17 +76,17 @@ public class MainActivity extends AppCompatActivity implements CoreContracts.IMa
getSupportFragmentManager() getSupportFragmentManager()
.beginTransaction() .beginTransaction()
.replace(R.id.main_fragment_container, new ChatListFragment()) .replace(R.id.main_fragment_container, new DialogListFragment())
.commit(); .commit();
presenter = new MainPresenter(this); presenter = new MainPresenter(this);
progressDialog = new ProgressDialog(MainActivity.this, R.style.AlertDialogTheme); progressDialog = new ProgressDialog(MainActivity.this, R.style.AlertDialogTheme);
progressDialog.setCancelable(false); progressDialog.setCancelable(false);
progressDialog.setProgressStyle(android.R.style.Widget_ProgressBar_Small); progressDialog.setProgressStyle(android.R.style.Widget_ProgressBar_Small);
progressDialog.show(); progressDialog.show();
presenter.initPeer(); presenter.initPeer();
AppHelper.getObservable().register(this);
} }
@Override @Override
@ -120,10 +112,7 @@ public class MainActivity extends AppCompatActivity implements CoreContracts.IMa
@Override @Override
public void showSnackbar(@NotNull String message) { public void showSnackbar(@NotNull String message) {
runOnUiThread(() -> { runOnUiThread(() -> Snackbar.make(getRootView(), message, Snackbar.LENGTH_LONG).show());
Snackbar.make(getRootView(), message, Snackbar.LENGTH_LONG)
.show();
});
} }
@Override @Override
@ -149,4 +138,14 @@ public class MainActivity extends AppCompatActivity implements CoreContracts.IMa
return rootView; return rootView;
} }
@Override
public void handleEvent(JSONObject object) throws JSONException {
switch (object.getInt("action")) {
case ObservableActions.NEW_CHAT_CREATED: {
showProgressBar(false);
break;
}
}
}
} }

View File

@ -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<ChatEntity>) {
mainThreadHandler!!.post {
adapter.setChatList(chats)
adapter.notifyDataSetChanged()
}
}
override fun onContextItemSelected(item: MenuItem): Boolean {
presenter!!.onContextItemSelected(item)
return super.onContextItemSelected(item)
}
}

View File

@ -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<GenericDialog>) {
dialogList!!.setAdapter(adapter)
}
override fun getActivityContext(): Context? {
return context
}
}

View File

@ -33,12 +33,12 @@ public class SettingsFragment extends PreferenceFragmentCompat implements CoreCo
presenter = new SettingsPresenter(this); presenter = new SettingsPresenter(this);
// Load the Preferences from the XML file // Load the Preferences from the XML file
addPreferencesFromResource(R.xml.main_settings); addPreferencesFromResource(R.xml.main_settings);
getPreferenceScreen().getPreference(0).setSummary(AppHelper.getPeerID()); /*getPreferenceScreen().getPreference(0).setSummary(AppHelper.getPeerID());
getPreferenceScreen().getPreference(0).setOnPreferenceClickListener((preference -> { 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()); ClipData clip = ClipData.newPlainText("", AppHelper.getPeerID());
clipboard.setPrimaryClip(clip); clipboard.setPrimaryClip(clip);
Toast.makeText(AppHelper.getContext(), "Скопировано в буфер обмена!", Toast.LENGTH_SHORT).show(); Toast.makeText(AppHelper.getActivityContext(), "Скопировано в буфер обмена!", Toast.LENGTH_SHORT).show();
return false; return false;
})); }));
getPreferenceScreen().getPreference(1).setSummary(AppHelper.getUsername()); 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).setOnPreferenceChangeListener((p, nV) -> {
getPreferenceScreen().getPreference(1).setSummary((String) nV); getPreferenceScreen().getPreference(1).setSummary((String) nV);
return true; return true;
}); });*/
} }
@Override @Override

View File

@ -38,38 +38,26 @@
</com.google.android.material.appbar.AppBarLayout> </com.google.android.material.appbar.AppBarLayout>
<androidx.recyclerview.widget.RecyclerView <com.stfalcon.chatkit.messages.MessagesList
android:id="@+id/message_list" android:id="@+id/messages_list"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:layout_below="@id/appbar_chat_activity" android:layout_above="@+id/message_input"/>
android:layout_above="@+id/chat_activity_bottom_container"/>
<RelativeLayout <View
android:id="@+id/chat_activity_bottom_container" android:layout_width="match_parent"
android:layout_height="1dp"
android:layout_above="@+id/message_input"
android:layout_marginLeft="16dp"
android:layout_marginRight="16dp"
android:background="@android:color/darker_gray"/>
<com.stfalcon.chatkit.messages.MessageInput
android:id="@+id/message_input"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_alignParentBottom="true" android:layout_alignParentBottom="true"
android:background="#FFFFFF" app:inputHint="@string/hint_enter_a_message"
android:orientation="horizontal" app:showAttachmentButton="true"/>
android:padding="5dp">
<EditText
android:id="@+id/message_input"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:layout_toLeftOf="@+id/send_button"
android:background="@android:color/transparent"
android:hint="Type a message..." />
<ImageButton
android:id="@+id/send_button"
android:layout_width="40dp"
android:layout_height="40dp"
android:layout_alignParentEnd="true"
android:layout_alignParentRight="true"
android:background="@drawable/ic_send_green_24dp" />
</RelativeLayout>
</RelativeLayout> </RelativeLayout>

View File

@ -3,8 +3,8 @@
android:orientation="vertical" android:orientation="vertical"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent"> android:layout_height="match_parent">
<androidx.recyclerview.widget.RecyclerView <com.stfalcon.chatkit.dialogs.DialogsList
android:id="@+id/chatlist_container" android:id="@+id/dialogsList"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent"/> android:layout_height="match_parent"/>
</LinearLayout> </LinearLayout>

View File

@ -10,4 +10,5 @@
<string name="reconnect_network">Переподключиться к сети</string> <string name="reconnect_network">Переподключиться к сети</string>
<string name="node_already_running">Узел уже запущен</string> <string name="node_already_running">Узел уже запущен</string>
<string name="input_companion_username">Введите имя пользователя собеседника</string> <string name="input_companion_username">Введите имя пользователя собеседника</string>
<string name="hint_enter_a_message">Введите сообщение...</string>
</resources> </resources>

View File

@ -9,4 +9,5 @@
<string name="reconnect_network">Reconnect to the network</string> <string name="reconnect_network">Reconnect to the network</string>
<string name="node_already_running">Node already running</string> <string name="node_already_running">Node already running</string>
<string name="input_companion_username">Input interlocutor\'s username</string> <string name="input_companion_username">Input interlocutor\'s username</string>
<string name="hint_enter_a_message">Enter message...</string>
</resources> </resources>