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 a66a83c0fd
commit be1b342e8a
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-extensions:4.3.3'
implementation 'com.github.stfalcon:chatkit:0.3.3'
implementation 'net.sourceforge.streamsupport:streamsupport:1.7.0'
}
repositories {
mavenCentral()

View File

@ -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\")"
]
}
}

View File

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

View File

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

View File

@ -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<ChatEntity>
}
interface IChatListPresenterContract {
fun updateChatList()
interface IDialogListPresenterContract {
fun openChat(chatID: String)
fun onContextItemSelected(item: MenuItem)
}
interface IChatListViewContract {
fun setRecycleAdapter(adapter: ChatListAdapter)
fun setDialogAdapter(adapter: DialogsListAdapter<GenericDialog>)
fun startActivity(intent: Intent)
fun updateChatList(adapter: ChatListAdapter, chats: List<ChatEntity>)
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<MessageEntity>)
fun setAdapter(adapter: MessagesListAdapter<GenericMessage>)
}
// -----SettingsFragment-----

View File

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

View File

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

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 {
private File keyPairDir;
private Serializer<KeyPair> serializer;
private JavaSerializer<KeyPair> 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() {

View File

@ -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) {

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.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);
}
}

View File

@ -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<String> fromString(String value) {
public static ArrayList<GenericUser> fromString(String value) {
Type listType = new TypeToken<ArrayList<String>>() {}.getType();
return new Gson().fromJson(value, listType);
}
@TypeConverter
public static String fromArrayList(ArrayList<String> list) {
public static <T> String fromArrayList(ArrayList<GenericUser> list) {
Gson gson = new Gson();
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.models.roomEntities.ChatEntity;
public class ChatListLogic implements CoreContracts.IChatListLogicContract {
public class ChatListLogic implements CoreContracts.IDialogListLogicContract {
@Override
public List<ChatEntity> loadAllChats() {

View File

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

View File

@ -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<Number640, Data>? = 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
}
}
}*/
}
}

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.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<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.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;
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<IObserver> uiObservers;
private ArrayList<INetworkObserver> networkObservers;
public MainObservable() {
this.uiObservers = new ArrayList<>();
this.networkObservers = new ArrayList<>();
}
private ArrayList<IObserver> 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) {
try {
observer.handleEvent(jsonObject);
}
}
@Override
public void notifyNetworkObservers(Object object) {
for (INetworkObserver observer : networkObservers) {
observer.handleEvent(object);
} catch (JSONException e) {
e.printStackTrace();
}
}
}
}

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
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<GenericMessage>
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)
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 filter = IntentFilter()
filter.addAction(XMPPConnectionService.INTENT_NEW_MESSAGE)
AppHelper.getContext().registerReceiver(newMessageReceiver, filter)
}
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 sendMessage(text: String): Boolean {
val message: MessageEntity? = logic.sendMessage(text)
if(message != null) {
chatAdapter.addToStart(GenericMessage(message), true)
return true
}
return false
}
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()
override fun loadLocalMessages() {
val entities: List<MessageEntity>? = LocalDBWrapper.getMessagesByChatID(chatID)
val messages = ArrayList<GenericMessage>()
if(entities != null) {
entities.forEach {
messages.add(GenericMessage(it))
}
}
}
override fun updateAdapter() {
val entities = LocalDBWrapper.getMessagesByChatID(chatID)
view.updateMessageList(entities ?: ArrayList())
chatAdapter.addToEnd(messages, true)
}
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
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))
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
}
}

View File

@ -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)
}*/
}
}

View File

@ -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<Toolbar>(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<MessageEntity>) {
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<GenericMessage>) {
messageList!!.setAdapter(adapter)
}
}

View File

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

View File

@ -7,46 +7,40 @@ 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() {
@Override
public boolean onNavigationItemSelected(@NonNull MenuItem item) {
private BottomNavigationView.OnNavigationItemSelectedListener mOnNavigationItemSelectedListener = (item) -> {
Fragment selectedFragment = null;
switch (item.getItemId()) {
case R.id.action_chats:
selectedFragment = new ChatListFragment();
selectedFragment = new DialogListFragment();
break;
case R.id.action_settings:
selectedFragment = new SettingsFragment();
@ -58,8 +52,6 @@ public class MainActivity extends AppCompatActivity implements CoreContracts.IMa
transaction.commit();
return true;
}
};
@Override
@ -73,7 +65,7 @@ public class MainActivity extends AppCompatActivity implements CoreContracts.IMa
fab.setOnClickListener((v) -> {
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) -> {
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;
}
}
}
}

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);
// 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

View File

@ -38,38 +38,26 @@
</com.google.android.material.appbar.AppBarLayout>
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/message_list"
<com.stfalcon.chatkit.messages.MessagesList
android:id="@+id/messages_list"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_below="@id/appbar_chat_activity"
android:layout_above="@+id/chat_activity_bottom_container"/>
android:layout_above="@+id/message_input"/>
<RelativeLayout
android:id="@+id/chat_activity_bottom_container"
<View
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:background="#FFFFFF"
android:orientation="horizontal"
android:padding="5dp">
android:layout_height="1dp"
android:layout_above="@+id/message_input"
android:layout_marginLeft="16dp"
android:layout_marginRight="16dp"
android:background="@android:color/darker_gray"/>
<EditText
<com.stfalcon.chatkit.messages.MessageInput
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>
android:layout_alignParentBottom="true"
app:inputHint="@string/hint_enter_a_message"
app:showAttachmentButton="true"/>
</RelativeLayout>

View File

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

View File

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

View File

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