Moved interclass communication to EventBus library from greenrobot, implemented loading roster entries and user avatars. Refactored service system

This commit is contained in:
ChronosX88 2019-05-22 23:42:01 +04:00
parent aea3ca1542
commit 2a78d7bd81
33 changed files with 552 additions and 849 deletions

View File

@ -510,7 +510,7 @@ covered work in a country, or your recipient's use of the covered work
in a country, would infringe one or more identifiable patents in that
country that you have reason to believe are valid.
If, pursuant to or in connection with a single transaction or
If, pursuant to or in xmppConnection with a single transaction or
arrangement, you convey, or propagate by procuring conveyance of, a
covered work, and grant a patent license to some of the parties
receiving the covered work authorizing them to use, propagate, modify
@ -527,9 +527,9 @@ in the business of distributing software, under which you make payment
to the third party based on the extent of your activity of conveying
the work, and under which the third party grants, to any of the
parties who would receive the covered work from you, a discriminatory
patent license (a) in connection with copies of the covered work
patent license (a) in xmppConnection with copies of the covered work
conveyed by you (or copies made from those copies), or (b) primarily
for and in connection with specific products or compilations that
for and in xmppConnection with specific products or compilations that
contain the covered work, unless you entered into that arrangement,
or that patent license was granted, prior to 28 March 2007.
@ -614,7 +614,7 @@ SUCH DAMAGES.
If the disclaimer of warranty and limitation of liability provided
above cannot be given local legal effect according to their terms,
reviewing courts shall apply local law that most closely approximates
an absolute waiver of all civil liability in connection with the
an absolute waiver of all civil liability in xmppConnection with the
Program, unless a warranty or assumption of liability accompanies a
copy of the Program in return for a fee.

View File

@ -63,7 +63,8 @@ dependencies {
implementation 'com.github.stfalcon:chatkit:0.3.3'
implementation 'net.sourceforge.streamsupport:streamsupport:1.7.0'
implementation 'org.greenrobot:eventbus:3.1.1'
implementation 'net.sourceforge.streamsupport:android-retrofuture:1.7.0'
}
repositories {
mavenCentral()

View File

@ -18,7 +18,7 @@
package io.github.chronosx88.influence;
public class LoginCredentials {
String username = "";
String password = "";
String jabberHost = "";
public String username = "";
public String password = "";
public String jabberHost = "";
}

View File

@ -17,15 +17,13 @@
package io.github.chronosx88.influence;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.SharedPreferences;
import android.util.Log;
import androidx.preference.PreferenceManager;
import org.greenrobot.eventbus.EventBus;
import org.jivesoftware.smack.ConnectionConfiguration;
import org.jivesoftware.smack.ConnectionListener;
import org.jivesoftware.smack.ReconnectionManager;
@ -34,16 +32,19 @@ import org.jivesoftware.smack.XMPPException;
import org.jivesoftware.smack.chat2.Chat;
import org.jivesoftware.smack.chat2.ChatManager;
import org.jivesoftware.smack.packet.Message;
import org.jivesoftware.smack.roster.Roster;
import org.jivesoftware.smack.roster.RosterEntry;
import org.jivesoftware.smack.tcp.XMPPTCPConnection;
import org.jivesoftware.smack.tcp.XMPPTCPConnectionConfiguration;
import org.jivesoftware.smackx.vcardtemp.VCardManager;
import org.jxmpp.jid.EntityBareJid;
import org.jxmpp.jid.impl.JidCreate;
import org.jxmpp.stringprep.XmppStringprepException;
import java.io.IOException;
import java.util.Set;
import io.github.chronosx88.influence.helpers.AppHelper;
import io.github.chronosx88.influence.helpers.NetworkHandler;
import io.github.chronosx88.influence.models.appEvents.AuthenticationStatusEvent;
public class XMPPConnection implements ConnectionListener {
private final static String LOG_TAG = "XMPPConnection";
@ -51,8 +52,8 @@ public class XMPPConnection implements ConnectionListener {
private XMPPTCPConnection connection = null;
private SharedPreferences prefs;
private NetworkHandler networkHandler;
private BroadcastReceiver sendMessageReceiver = null;
private Context context;
private Roster roster;
public enum ConnectionState {
CONNECTED,
@ -76,10 +77,10 @@ public class XMPPConnection implements ConnectionListener {
credentials.jabberHost = jabberHost;
credentials.password = password;
}
networkHandler = new NetworkHandler(context);
networkHandler = new NetworkHandler();
}
public void connect() throws XMPPException, SmackException, IOException {
public void connect() throws XMPPException, IOException, SmackException {
if(connection == null) {
XMPPTCPConnectionConfiguration conf = XMPPTCPConnectionConfiguration.builder()
.setXmppDomain(credentials.jabberHost)
@ -90,8 +91,6 @@ public class XMPPConnection implements ConnectionListener {
.setCompressionEnabled(true)
.build();
setupSendMessageReceiver();
connection = new XMPPTCPConnection(conf);
connection.addConnectionListener(this);
if(credentials.jabberHost.equals("") && credentials.password.equals("") && credentials.username.equals("")){
@ -108,6 +107,7 @@ public class XMPPConnection implements ConnectionListener {
ReconnectionManager reconnectionManager = ReconnectionManager.getInstanceFor(connection);
ReconnectionManager.setEnabledPerDefault(true);
reconnectionManager.enableAutomaticReconnection();
roster = roster.getInstanceFor(connection);
}
}
@ -128,8 +128,7 @@ public class XMPPConnection implements ConnectionListener {
public void authenticated(org.jivesoftware.smack.XMPPConnection connection, boolean resumed) {
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);
EventBus.getDefault().post(new AuthenticationStatusEvent(AuthenticationStatusEvent.CONNECT_AND_LOGIN_SUCCESSFUL));
}
@Override
@ -148,36 +147,10 @@ public class XMPPConnection implements ConnectionListener {
e.printStackTrace();
}
private void setupSendMessageReceiver() {
sendMessageReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
switch (action) {
case XMPPConnectionService.INTENT_SEND_MESSAGE: {
String recipientJid = intent.getStringExtra(XMPPConnectionService.MESSAGE_RECIPIENT);
String messageText = intent.getStringExtra(XMPPConnectionService.MESSAGE_BODY);
sendMessage(recipientJid, messageText);
break;
}
}
}
};
IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction(XMPPConnectionService.INTENT_SEND_MESSAGE);
context.registerReceiver(sendMessageReceiver, intentFilter);
}
private void sendMessage(String recipientJid, String messageText) {
EntityBareJid jid = null;
public void sendMessage(EntityBareJid recipientJid, String messageText) {
Chat chat = ChatManager.getInstanceFor(connection).chatWith(recipientJid);
try {
jid = JidCreate.entityBareFrom(recipientJid);
} catch (XmppStringprepException e) {
e.printStackTrace();
}
Chat chat = ChatManager.getInstanceFor(connection).chatWith(jid);
try {
Message message = new Message(jid, Message.Type.chat);
Message message = new Message(recipientJid, Message.Type.chat);
message.setBody(messageText);
chat.send(message);
} catch (SmackException.NotConnectedException e) {
@ -190,4 +163,40 @@ public class XMPPConnection implements ConnectionListener {
public XMPPTCPConnection getConnection() {
return connection;
}
public byte[] getAvatar(EntityBareJid jid) {
if(isConnectionAlive()) {
VCardManager manager = VCardManager.getInstanceFor(connection);
byte[] avatar = null;
try {
avatar = manager.loadVCard(jid).getAvatar();
} catch (SmackException.NoResponseException e) {
e.printStackTrace();
} catch (XMPPException.XMPPErrorException e) {
e.printStackTrace();
} catch (SmackException.NotConnectedException e) {
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
}
return avatar;
}
return null;
}
public Set<RosterEntry> getContactList() {
if(isConnectionAlive()) {
while (roster == null);
return roster.getEntries();
}
return null;
}
public boolean isConnectionAlive() {
if(XMPPConnectionService.CONNECTION_STATE.equals(ConnectionState.CONNECTED) && XMPPConnectionService.SESSION_STATE.equals(SessionState.LOGGED_IN)) {
return true;
} else {
return false;
}
}
}

View File

@ -20,28 +20,21 @@ package io.github.chronosx88.influence;
import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.os.Binder;
import android.os.Handler;
import android.os.IBinder;
import android.os.Looper;
import org.greenrobot.eventbus.EventBus;
import org.jivesoftware.smack.SmackException;
import org.jivesoftware.smack.XMPPException;
import java.io.IOException;
import io.github.chronosx88.influence.helpers.AppHelper;
import io.github.chronosx88.influence.models.appEvents.AuthenticationStatusEvent;
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";
public static final String INTENT_AUTHENTICATED = "io.github.chronosx88.intents.authenticated";
public static final String INTENT_AUTHENTICATION_FAILED = "io.github.chronosx88.intents.authentication_failed";
public static final String MESSAGE_CHATID = "chat_jid";
public static final String MESSAGE_ID = "message_id";
public static final String MESSAGE_BODY = "message_body";
public static final String MESSAGE_RECIPIENT = "message_recipient";
public static XMPPConnection.ConnectionState CONNECTION_STATE = XMPPConnection.ConnectionState.DISCONNECTED;
public static XMPPConnection.SessionState SESSION_STATE = XMPPConnection.SessionState.LOGGED_OUT;
@ -50,11 +43,14 @@ public class XMPPConnectionService extends Service {
private boolean isThreadAlive = false;
private XMPPConnection connection;
private Context context = AppHelper.getContext();
private XMPPServiceBinder binder = new XMPPServiceBinder();
public XMPPConnectionService() { }
@Override
public IBinder onBind(Intent intent) { return null; }
public IBinder onBind(Intent intent) {
return binder;
}
public void onServiceStart() {
if(!isThreadAlive)
@ -77,6 +73,7 @@ public class XMPPConnectionService extends Service {
threadHandler.post(() -> {
if(connection != null) {
connection.disconnect();
connection = null;
}
});
}
@ -87,9 +84,12 @@ public class XMPPConnectionService extends Service {
}
try {
connection.connect();
} catch (IOException | SmackException | XMPPException e) {
Intent intent = new Intent(INTENT_AUTHENTICATION_FAILED);
context.sendBroadcast(intent);
} catch (IOException | SmackException e) {
EventBus.getDefault().post(new AuthenticationStatusEvent(AuthenticationStatusEvent.NETWORK_ERROR));
e.printStackTrace();
stopSelf();
} catch (XMPPException e) {
EventBus.getDefault().post(new AuthenticationStatusEvent(AuthenticationStatusEvent.INCORRECT_LOGIN_OR_PASSWORD));
e.printStackTrace();
stopSelf();
}
@ -106,4 +106,10 @@ public class XMPPConnectionService extends Service {
super.onDestroy();
onServiceStop();
}
public class XMPPServiceBinder extends Binder {
public XMPPConnection getConnection() {
return connection;
}
}
}

View File

@ -9,21 +9,26 @@ 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
import org.jivesoftware.smack.roster.RosterEntry
interface CoreContracts {
interface ViewWithLoadingScreen {
fun loadingScreen(state: Boolean);
interface IGenericView {
fun loadingScreen(state: Boolean)
}
// -----ChatList-----
interface IDialogListLogicContract {
fun loadAllChats(): List<ChatEntity>
fun loadLocalChats(): List<ChatEntity>
fun getRemoteContacts(): Set<RosterEntry>?
}
interface IDialogListPresenterContract {
fun openChat(chatID: String)
fun onStart()
fun onStop()
fun loadRemoteContactList()
}
interface IChatListViewContract {
@ -35,12 +40,12 @@ interface CoreContracts {
// -----MainActivity-----
interface IMainLogicContract {
fun startService()
}
interface IMainPresenterContract {
fun initPeer()
fun initConnection()
fun startChatWithPeer(username: String)
fun onDestroy()
}
interface IMainViewContract {
@ -51,7 +56,7 @@ interface CoreContracts {
// -----ChatActivity-----
interface IChatLogicContract {
fun sendMessage(text: String): MessageEntity
fun sendMessage(text: String): MessageEntity?
}
interface IChatPresenterContract {
@ -66,20 +71,15 @@ interface CoreContracts {
// -----SettingsFragment-----
interface ISettingsLogic {
fun checkUsernameExists(username: String) : Boolean
}
interface ISettingsLogic // TODO
interface ISettingsPresenter {
fun updateUsername(username: String)
}
interface ISettingsPresenter // TODO
interface ISettingsView {
fun loadingScreen(state: Boolean)
fun showMessage(message: String)
fun refreshScreen()
}
// -----LoginActivity-----
interface ILoginViewContract : ViewWithLoadingScreen
interface ILoginViewContract : IGenericView
}

View File

@ -1,5 +0,0 @@
package io.github.chronosx88.influence.contracts.observer;
public interface INetworkObserver {
void handleEvent(Object object);
}

View File

@ -1,9 +0,0 @@
package io.github.chronosx88.influence.contracts.observer;
import org.json.JSONObject;
public interface IObservable {
void register(IObserver observer);
void unregister(IObserver observer);
void notifyUIObservers(JSONObject jsonObject);
}

View File

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

View File

@ -3,6 +3,8 @@ package io.github.chronosx88.influence.helpers;
import android.app.Application;
import android.content.Context;
import android.content.SharedPreferences;
import android.os.Handler;
import android.os.Looper;
import android.preference.PreferenceManager;
import androidx.multidex.MultiDexApplication;
@ -12,45 +14,39 @@ import com.instacart.library.truetime.TrueTime;
import java.io.IOException;
import io.github.chronosx88.influence.observable.MainObservable;
import io.github.chronosx88.influence.LoginCredentials;
import io.github.chronosx88.influence.XMPPConnection;
/**
* Extended Application class which designed for centralized getting various objects from anywhere in the application.
*/
public class AppHelper extends MultiDexApplication {
private static Application instance;
private static MainObservable observable;
public final static String APP_NAME = "Influence";
private static String jid;
private static RoomHelper chatDB;
private static SharedPreferences preferences;
private static XMPPConnection xmppConnection;
private static LoginCredentials currentLoginCredentials;
private static Handler mainUIThreadHandler;
@Override
public void onCreate() {
super.onCreate();
instance = this;
observable = new MainObservable();
chatDB = Room.databaseBuilder(getApplicationContext(), RoomHelper.class, "chatDB")
.allowMainThreadQueries()
.build();
mainUIThreadHandler = new Handler(Looper.getMainLooper());
initChatDB();
preferences = PreferenceManager.getDefaultSharedPreferences(instance);
new Thread(() -> {
try {
TrueTime.build().initialize();
} catch (IOException e) {
e.printStackTrace();
}
}).start();
initTrueTime();
loadLoginCredentials();
}
public static Context getContext() {
return instance.getApplicationContext();
}
public static MainObservable getObservable() { return observable; }
public static String getJid() { return jid; }
public static void setJid(String jid1) { jid = jid1; }
@ -60,4 +56,50 @@ public class AppHelper extends MultiDexApplication {
public static SharedPreferences getPreferences() {
return preferences;
}
public static XMPPConnection getXmppConnection() {
return xmppConnection;
}
public static void setXmppConnection(XMPPConnection xmppConnection) {
AppHelper.xmppConnection = xmppConnection;
}
private static void loadLoginCredentials() {
currentLoginCredentials = new LoginCredentials();
String jid = preferences.getString("jid", null);
String password = preferences.getString("pass", null);
if(jid != null && password != null) {
String username = jid.split("@")[0];
String jabberHost = jid.split("@")[1];
currentLoginCredentials.username = username;
currentLoginCredentials.jabberHost = jabberHost;
currentLoginCredentials.password = password;
}
AppHelper.setJid(currentLoginCredentials.username + "@" + currentLoginCredentials.jabberHost);
}
private static void initTrueTime() {
new Thread(() -> {
boolean isTrueTimeIsOn = false;
while(!isTrueTimeIsOn) {
try {
TrueTime.build().initialize();
isTrueTimeIsOn = true;
} catch (IOException e) {
e.printStackTrace();
}
}
}).start();
}
private void initChatDB() {
chatDB = Room.databaseBuilder(getApplicationContext(), RoomHelper.class, "chatDB")
.allowMainThreadQueries()
.build();
}
public static Handler getMainUIThread() {
return mainUIThreadHandler;
}
}

View File

@ -0,0 +1,56 @@
/*
* 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.UnsupportedEncodingException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
public class HashUtils {
public static String sha1(final String text) {
try {
MessageDigest md = MessageDigest.getInstance("SHA-1");
md.update(text.getBytes("UTF-8"), 0, text.length());
byte[] sha1hash = md.digest();
return hashToString(sha1hash);
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
return null;
}
private static String hashToString(final byte[] buf) {
if (buf == null) return "";
int l = buf.length;
StringBuffer result = new StringBuffer(2 * l);
for (int i = 0; i < buf.length; i++) {
appendByte(result, buf[i]);
}
return result.toString();
}
private final static String HEX_PACK = "0123456789ABCDEF";
private static void appendByte(final StringBuffer sb, final byte b) {
sb
.append(HEX_PACK.charAt((b >> 4) & 0x0f))
.append(HEX_PACK.charAt(b & 0x0f));
}
}

View File

@ -1,24 +1,17 @@
package io.github.chronosx88.influence.helpers;
import android.content.Context;
import android.content.Intent;
import com.instacart.library.truetime.TrueTime;
import org.greenrobot.eventbus.EventBus;
import org.jivesoftware.smack.chat2.Chat;
import org.jivesoftware.smack.chat2.IncomingChatMessageListener;
import org.jivesoftware.smack.packet.Message;
import org.jxmpp.jid.EntityBareJid;
import io.github.chronosx88.influence.XMPPConnectionService;
import io.github.chronosx88.influence.models.appEvents.NewMessageEvent;
public class NetworkHandler implements IncomingChatMessageListener {
private final static String LOG_TAG = "NetworkHandler";
private Context context;
public NetworkHandler(Context context) {
this.context = context;
}
@Override
public void newIncomingMessage(EntityBareJid from, Message message, Chat chat) {
@ -26,10 +19,7 @@ public class NetworkHandler implements IncomingChatMessageListener {
LocalDBWrapper.createChatEntry(chat.getXmppAddressOfChatPartner().asUnescapedString(), chat.getXmppAddressOfChatPartner().asBareJid().asUnescapedString());
}
long messageID = LocalDBWrapper.createMessageEntry(chat.getXmppAddressOfChatPartner().asUnescapedString(), from.asUnescapedString(), TrueTime.now().getTime(), message.getBody(), true, false);
Intent intent = new Intent(XMPPConnectionService.INTENT_NEW_MESSAGE);
intent.setPackage(context.getPackageName());
intent.putExtra(XMPPConnectionService.MESSAGE_CHATID, chat.getXmppAddressOfChatPartner().toString());
intent.putExtra(XMPPConnectionService.MESSAGE_ID, messageID);
context.sendBroadcast(intent);
EventBus.getDefault().post(new NewMessageEvent(chat.getXmppAddressOfChatPartner().toString(), messageID));
}
}

View File

@ -1,50 +0,0 @@
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();
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();
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();
}
AppHelper.getObservable().notifyUIObservers(jsonObject);
}
public static void notifyUI(int action, int 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

@ -1,15 +0,0 @@
package io.github.chronosx88.influence.logic;
import java.util.List;
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.IDialogListLogicContract {
@Override
public List<ChatEntity> loadAllChats() {
return AppHelper.getChatDB().chatDao().getAllChats();
}
}

View File

@ -1,11 +1,11 @@
package io.github.chronosx88.influence.logic;
import android.content.Intent;
import com.instacart.library.truetime.TrueTime;
import io.github.chronosx88.influence.XMPPConnection;
import io.github.chronosx88.influence.XMPPConnectionService;
import org.jxmpp.jid.EntityBareJid;
import org.jxmpp.jid.impl.JidCreate;
import org.jxmpp.stringprep.XmppStringprepException;
import io.github.chronosx88.influence.contracts.CoreContracts;
import io.github.chronosx88.influence.helpers.AppHelper;
import io.github.chronosx88.influence.helpers.LocalDBWrapper;
@ -25,11 +25,14 @@ public class ChatLogic implements CoreContracts.IChatLogicContract {
@Override
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);
if (AppHelper.getXmppConnection().isConnectionAlive()) {
EntityBareJid jid;
try {
jid = JidCreate.entityBareFrom(chatEntity.jid);
} catch (XmppStringprepException e) {
return null;
}
AppHelper.getXmppConnection().sendMessage(jid, text);
long messageID = LocalDBWrapper.createMessageEntry(chatID, AppHelper.getJid(), TrueTime.now().getTime(), text, false, false);
return LocalDBWrapper.getMessageByID(messageID);
} else {

View File

@ -0,0 +1,26 @@
package io.github.chronosx88.influence.logic;
import org.jivesoftware.smack.roster.RosterEntry;
import java.util.List;
import java.util.Set;
import io.github.chronosx88.influence.contracts.CoreContracts;
import io.github.chronosx88.influence.helpers.AppHelper;
import io.github.chronosx88.influence.models.roomEntities.ChatEntity;
public class DialogListLogic implements CoreContracts.IDialogListLogicContract {
@Override
public List<ChatEntity> loadLocalChats() {
return AppHelper.getChatDB().chatDao().getAllChats();
}
@Override
public Set<RosterEntry> getRemoteContacts() {
if(AppHelper.getXmppConnection() != null) {
return AppHelper.getXmppConnection().getContactList();
}
return null;
}
}

View File

@ -1,12 +1,14 @@
package io.github.chronosx88.influence.logic;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.IBinder;
import com.google.gson.Gson;
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;
public class MainLogic implements CoreContracts.IMainLogicContract {
private static final String LOG_TAG = MainLogic.class.getName();
@ -17,294 +19,21 @@ public class MainLogic implements CoreContracts.IMainLogicContract {
this.context = AppHelper.getContext();
}
/*@Override
public void initPeer() {
org.apache.log4j.BasicConfigurator.configure();
if(checkFirstRun()) {
SharedPreferences.Editor editor = preferences.edit();
String uuid = UUID.randomUUID().toString();
editor.putString("peerID", uuid);
editor.apply();
}
peerID = Number160.createHash(preferences.getString("peerID", null));
new Thread(() -> {
try {
File dhtDBEnv = new File(context.getFilesDir(), "dhtDBEnv");
if(!dhtDBEnv.exists())
dhtDBEnv.mkdirs();
Storage storage = new StorageBerkeleyDB(peerID, dhtDBEnv, new RSASignatureFactory());
this.storage = storage;
peerDHT = new PeerBuilderDHT(
new PeerBuilder(peerID)
.ports(7243)
.channelClientConfiguration(createChannelClientConfig())
.channelServerConfiguration(createChannelServerConfig())
.start()
)
.storage(storage)
.start();
Runtime.getRuntime().addShutdownHook(new JVMShutdownHook(storage));
try {
String bootstrapIP = this.preferences.getString("bootstrapAddress", null);
if(bootstrapIP == null) {
throw new NullPointerException();
}
bootstrapAddress = Inet4Address.getByName(bootstrapIP);
} catch (NullPointerException e) {
JsonObject jsonObject = new JsonObject();
jsonObject.addProperty("action", UIActions.BOOTSTRAP_NOT_SPECIFIED);
AppHelper.getObservable().notifyUIObservers(jsonObject);
peerDHT.shutdown();
return;
} catch (UnknownHostException e) {
JsonObject jsonObject = new JsonObject();
jsonObject.addProperty("action", UIActions.NETWORK_ERROR);
AppHelper.getObservable().notifyUIObservers(jsonObject);
peerDHT.shutdown();
return;
}
boolean discoveredExternalAddress = false;
if(discoverExternalAddress()) {
discoveredExternalAddress = true;
}
if(!discoveredExternalAddress) {
if(!setupConnectionToRelay()) {
JsonObject jsonObject = new JsonObject();
jsonObject.addProperty("action", UIActions.RELAY_CONNECTION_ERROR);
AppHelper.getObservable().notifyUIObservers(jsonObject);
return;
}
}
if(!bootstrapPeer()) {
JsonObject jsonObject = new JsonObject();
jsonObject.addProperty("action", UIActions.BOOTSTRAP_ERROR);
AppHelper.getObservable().notifyUIObservers(jsonObject);
return;
}
JsonObject jsonObject = new JsonObject();
jsonObject.addProperty("action", UIActions.BOOTSTRAP_SUCCESS);
AppHelper.getObservable().notifyUIObservers(jsonObject);
AppHelper.storePeerID(preferences.getString("peerID", null));
AppHelper.updateUsername(preferences.getString("username", null));
AppHelper.storePeerDHT(peerDHT);
AppHelper.initNetworkHandler();
//setReceiveHandler();
gson = new Gson();
publicProfileToDHT();
SettingsLogic.Companion.publishUsername(AppHelper.getUsername(), AppHelper.getUsername());
NetworkHandler.handlePendingChatRequests();
TimerTask timerTask = new TimerTask() {
@Override
public void run() {
if(checkNewChatsThread == null) {
checkNewChatsThread = new Thread(NetworkHandler::handlePendingChatRequests);
checkNewChatsThread.start();
}
if(!checkNewChatsThread.isAlive()) {
checkNewChatsThread = new Thread(NetworkHandler::handlePendingChatRequests);
checkNewChatsThread.start();
}
}
};
Timer timer = new Timer();
timer.schedule(timerTask, 1, 5000);
replication = new IndirectReplication(peerDHT).start();
} catch (IOException e) {
e.printStackTrace();
}
}).start();
}
private boolean bootstrapPeer() {
FutureBootstrap futureBootstrap = peerDHT.peer().bootstrap().inetAddress(bootstrapAddress).ports(7243).start();
futureBootstrap.awaitUninterruptibly();
if(futureBootstrap.isSuccess()) {
Log.i("MainLogic", "# Successfully bootstrapped to " + bootstrapAddress.toString());
return true;
} else {
Log.e("MainLogic", "# Cannot bootstrap to " + bootstrapAddress.toString() + ". Reason: " + futureBootstrap.failedReason());
return false;
}
}
private boolean discoverExternalAddress() {
FutureDiscover futureDiscover = peerDHT
.peer()
.discover()
.inetAddress(bootstrapAddress)
.ports(7243)
.start();
futureDiscover.awaitUninterruptibly();
bootstrapPeerAddress = futureDiscover.reporter();
if(futureDiscover.isSuccess()) {
Log.i(LOG_TAG, "# Success discover! Your external IP: " + futureDiscover.peerAddress().toString());
return true;
} else {
Log.e(LOG_TAG, "# Failed to discover my external IP. Reason: " + futureDiscover.failedReason());
return false;
}
}
private boolean setupConnectionToRelay() {
PeerNAT peerNat = new PeerBuilderNAT(peerDHT.peer()).start();
FutureRelayNAT futureRelayNAT = peerNat.startRelay(new TCPRelayClientConfig(), bootstrapPeerAddress).awaitUninterruptibly();
if (futureRelayNAT.isSuccess()) {
Log.i(LOG_TAG, "# Successfully connected to relay node.");
return true;
} else {
Log.e(LOG_TAG, "# Cannot connect to relay node. Reason: " + futureRelayNAT.failedReason());
return false;
}
}
private void setReceiveHandler() {
AppHelper.getPeerDHT().peer().objectDataReply((s, r) -> {
Log.i(LOG_TAG, "# Incoming message: " + r);
AppHelper.getObservable().notifyNetworkObservers(r);
return null;
});
}
@Override
public void shutdownPeer() {
new Thread(() -> {
if(replication != null) {
replication.shutdown();
public void startService() {
context.startService(new Intent(context, XMPPConnectionService.class));
ServiceConnection connection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
XMPPConnectionService.XMPPServiceBinder binder = (XMPPConnectionService.XMPPServiceBinder) service;
AppHelper.setXmppConnection(binder.getConnection());
}
if(peerDHT != null) {
peerDHT.peer().announceShutdown().start().awaitUninterruptibly();
peerDHT.peer().shutdown().awaitUninterruptibly();
@Override
public void onServiceDisconnected(ComponentName name) {
AppHelper.setXmppConnection(null);
}
storage.close();
System.exit(0);
}).start();
};
context.bindService(new Intent(context, XMPPConnectionService.class), connection,Context.BIND_AUTO_CREATE);
}
private boolean checkFirstRun() {
if (preferences.getBoolean("firstRun", true)) {
SharedPreferences.Editor editor = preferences.edit();
editor.putBoolean("firstRun", false);
editor.apply();
return true;
}
return false;
}
private void publicProfileToDHT() {
KeyPair mainKeyPair = keyPairManager.openMainKeyPair();
PublicUserProfile userProfile = new PublicUserProfile(AppHelper.getUsername(), peerDHT.peerAddress());
Data serializedUserProfile = null;
try {
serializedUserProfile = new Data(gson.toJson(userProfile))
.protectEntry(mainKeyPair.getPrivate());
} catch (IOException e) {
e.printStackTrace();
}
Log.i(LOG_TAG, P2PUtils.put(AppHelper.getPeerID() + "_profile", null, serializedUserProfile, mainKeyPair) ? "# Profile successfully published!" : "# Profile publishing failed!");
}
private ChannelClientConfiguration createChannelClientConfig() {
ChannelClientConfiguration channelClientConfiguration = new ChannelClientConfiguration();
channelClientConfiguration.bindings(new Bindings());
channelClientConfiguration.maxPermitsPermanentTCP(250);
channelClientConfiguration.maxPermitsTCP(250);
channelClientConfiguration.maxPermitsUDP(250);
channelClientConfiguration.pipelineFilter(new PeerBuilder.DefaultPipelineFilter());
channelClientConfiguration.signatureFactory(new RSASignatureFactory());
channelClientConfiguration.senderTCP((new InetSocketAddress(0)).getAddress());
channelClientConfiguration.senderUDP((new InetSocketAddress(0)).getAddress());
channelClientConfiguration.byteBufPool(false);
return channelClientConfiguration;
}
private ChannelServerConfiguration createChannelServerConfig() {
ChannelServerConfiguration channelServerConfiguration = new ChannelServerConfiguration();
channelServerConfiguration.bindings(new Bindings());
//these two values may be overwritten in the peer builder
channelServerConfiguration.ports(new Ports(Ports.DEFAULT_PORT, Ports.DEFAULT_PORT));
channelServerConfiguration.portsForwarding(new Ports(Ports.DEFAULT_PORT, Ports.DEFAULT_PORT));
channelServerConfiguration.behindFirewall(false);
channelServerConfiguration.pipelineFilter(new PeerBuilder.DefaultPipelineFilter());
channelServerConfiguration.signatureFactory(new RSASignatureFactory());
channelServerConfiguration.byteBufPool(false);
return channelServerConfiguration;
}
@Override
public void sendStartChatMessage(@NotNull String username) {
if(AppHelper.getPeerDHT() == null) {
ObservableUtils.notifyUI(UIActions.NODE_IS_OFFLINE);
return;
}
String companionPeerID = getPeerIDByUsername(username);
if(companionPeerID == null) {
ObservableUtils.notifyUI(UIActions.PEER_NOT_EXIST);
return;
}
PublicUserProfile recipientPublicProfile = getPublicProfile(companionPeerID);
if(recipientPublicProfile == null) {
ObservableUtils.notifyUI(UIActions.PEER_NOT_EXIST);
return;
}
NewChatRequestMessage newChatRequestMessage = new NewChatRequestMessage(UUID.randomUUID().toString(), UUID.randomUUID().toString(), AppHelper.getPeerID(), AppHelper.getUsername(), System.currentTimeMillis(), 0);
try {
if(P2PUtils.put(companionPeerID + "_pendingChats", newChatRequestMessage.getChatID(), new Data(gson.toJson(newChatRequestMessage)))) {
Log.i(LOG_TAG, "# Create new offline chat request is successful! ChatID: " + newChatRequestMessage.getChatID());
} else {
Log.e(LOG_TAG, "# Failed to create offline chat request. ChatID: " + newChatRequestMessage.getChatID());
}
} catch (IOException e) {
e.printStackTrace();
}
ArrayList<String> admins = new ArrayList<>();
admins.add(AppHelper.getPeerID());
Data data = null;
try {
data = new Data(gson.toJson(new ChatMetadata(username, admins, new ArrayList<>())));
} catch (IOException e) {
e.printStackTrace();
}
data.protectEntry(keyPairManager.openMainKeyPair());
P2PUtils.put(newChatRequestMessage.getChatID() + "_metadata", null, data);
LocalDBWrapper.createChatEntry(newChatRequestMessage.getChatID(), username, newChatRequestMessage.getChatID() + "_metadata", newChatRequestMessage.getChatID() + "_members", 0);
ObservableUtils.notifyUI(UIActions.NEW_CHAT);
}
private PublicUserProfile getPublicProfile(String peerID) {
PublicUserProfile publicProfile = null;
Map<Number640, Data> data = P2PUtils.get(peerID + "_profile");
if (data != null && data.size() == 1) {
try {
publicProfile = gson.fromJson((String) data.values().iterator().next().object(), PublicUserProfile.class);
} catch (ClassNotFoundException | IOException e) {
e.printStackTrace();
}
return publicProfile;
}
return null;
}
private String getPeerIDByUsername(String username) {
Map<Number640, Data> usernameMap = P2PUtils.get(username);
if(usernameMap == null) {
return null;
}
try {
return (String) usernameMap.values().iterator().next().object();
} catch (ClassNotFoundException | IOException e) {
e.printStackTrace();
}
return null;
}*/
}

View File

@ -4,49 +4,8 @@ import io.github.chronosx88.influence.contracts.CoreContracts
import io.github.chronosx88.influence.helpers.KeyPairManager
class SettingsLogic : CoreContracts.ISettingsLogic {
override fun checkUsernameExists(username: String) : Boolean {
/*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
}
companion object {
private val LOG_TAG: String = "SettingsLogic"
private val keyPairManager = KeyPairManager()
/*fun publishUsername(oldUsername: String?, username: String?) {
if (AppHelper.getPeerDHT() == null) {
ObservableUtils.notifyUI(UIActions.NODE_IS_OFFLINE)
return
}
val mainKeyPair = keyPairManager.openMainKeyPair()
oldUsername?.let {
if(!oldUsername.equals("")) {
P2PUtils.remove(oldUsername, null, mainKeyPair)
}
}
username?.let {
var data: Data? = null
try {
data = Data(AppHelper.getPeerID())
} catch (e: IOException) {
e.printStackTrace()
}
data!!.protectEntry(mainKeyPair)
val isSuccess = P2PUtils.put(username, null, data, mainKeyPair)
Log.i(LOG_TAG, if (isSuccess) "# Username $username is published!" else "# Username $username isn't published!")
} ?: run {
return
}
}*/
}
}

View File

@ -28,7 +28,7 @@ import io.github.chronosx88.influence.models.roomEntities.ChatEntity;
public class GenericDialog implements IDialog {
private String dialogID;
private String dialogPhoto = "";
private String dialogPhoto;
private String dialogName;
private List<GenericUser> users;
private IMessage lastMessage;
@ -36,6 +36,7 @@ public class GenericDialog implements IDialog {
public GenericDialog(ChatEntity chatEntity) {
dialogID = chatEntity.jid;
dialogPhoto = chatEntity.jid;
dialogName = chatEntity.chatName;
users = new ArrayList<>();
unreadMessagesCount = chatEntity.unreadMessagesCount;

View File

@ -0,0 +1,30 @@
/*
* 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.appEvents;
public class AuthenticationStatusEvent {
public static final int NETWORK_ERROR = 0x0;
public static final int INCORRECT_LOGIN_OR_PASSWORD = 0x1;
public static final int CONNECT_AND_LOGIN_SUCCESSFUL = 0x2;
public final int authenticationStatus;
public AuthenticationStatusEvent(int authenticationStatus) {
this.authenticationStatus = authenticationStatus;
}
}

View File

@ -0,0 +1,26 @@
/*
* 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.appEvents;
public class NewChatEvent {
public final String chatID;
public NewChatEvent(String chatID) {
this.chatID = chatID;
}
}

View File

@ -0,0 +1,28 @@
/*
* 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.appEvents;
public class NewMessageEvent {
public final String chatID;
public final long messageID;
public NewMessageEvent(String chatID, long messageID) {
this.chatID = chatID;
this.messageID = messageID;
}
}

View File

@ -1,35 +0,0 @@
package io.github.chronosx88.influence.observable;
import org.json.JSONException;
import org.json.JSONObject;
import java.util.ArrayList;
import io.github.chronosx88.influence.contracts.observer.IObservable;
import io.github.chronosx88.influence.contracts.observer.IObserver;
public class MainObservable implements IObservable {
private ArrayList<IObserver> uiObservers = new ArrayList<>();
@Override
public void register(IObserver observer) {
uiObservers.add(observer);
}
@Override
public void unregister(IObserver observer) {
uiObservers.remove(observer);
}
@Override
public void notifyUIObservers(JSONObject jsonObject) {
for (IObserver observer : uiObservers) {
try {
observer.handleEvent(jsonObject);
} catch (JSONException e) {
e.printStackTrace();
}
}
}
}

View File

@ -1,37 +1,56 @@
package io.github.chronosx88.influence.presenters
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import android.graphics.BitmapFactory
import com.google.gson.Gson
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.helpers.AppHelper
import io.github.chronosx88.influence.helpers.LocalDBWrapper
import io.github.chronosx88.influence.logic.ChatLogic
import io.github.chronosx88.influence.models.GenericMessage
import io.github.chronosx88.influence.models.appEvents.NewMessageEvent
import io.github.chronosx88.influence.models.roomEntities.ChatEntity
import io.github.chronosx88.influence.models.roomEntities.MessageEntity
import java9.util.concurrent.CompletableFuture
import org.greenrobot.eventbus.EventBus
import org.greenrobot.eventbus.Subscribe
import org.jxmpp.jid.EntityBareJid
import org.jxmpp.jid.impl.JidCreate
import org.jxmpp.stringprep.XmppStringprepException
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 lateinit var newMessageReceiver: BroadcastReceiver
init {
this.logic = ChatLogic(LocalDBWrapper.getChatByChatID(chatID)!!)
this.chatEntity = LocalDBWrapper.getChatByChatID(chatID)
gson = Gson()
chatAdapter = MessagesListAdapter(AppHelper.getJid(), ImageLoader { imageView, _, _ -> imageView.setImageResource(R.mipmap.ic_launcher) })
view.setAdapter(chatAdapter)
chatAdapter = MessagesListAdapter(AppHelper.getJid(), ImageLoader { imageView, url, _ ->
imageView.setImageResource(R.mipmap.ic_launcher)
CompletableFuture.supplyAsync { while (AppHelper.getXmppConnection() == null) ;
while (!AppHelper.getXmppConnection().isConnectionAlive) ;
var jid: EntityBareJid? = null
try {
jid = JidCreate.entityBareFrom(url)
} catch (e: XmppStringprepException) {
e.printStackTrace()
}
setupIncomingMessagesReceiver()
AppHelper.getXmppConnection().getAvatar(jid) }.thenAccept { avatarBytes -> AppHelper.getMainUIThread().post {
if (avatarBytes != null) {
val avatar = BitmapFactory.decodeByteArray(avatarBytes, 0, avatarBytes.size)
imageView.setImageBitmap(avatar)
}
}
}
})
view.setAdapter(chatAdapter)
EventBus.getDefault().register(this)
}
override fun sendMessage(text: String): Boolean {
@ -58,18 +77,11 @@ class ChatPresenter(private val view: CoreContracts.IChatViewContract, private v
//
}
private fun setupIncomingMessagesReceiver() {
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)
}
}
@Subscribe
public fun onNewMessage(event: NewMessageEvent) {
if(event.chatID.equals(chatEntity!!.jid)) {
val messageID = event.messageID
chatAdapter.addToStart(GenericMessage(LocalDBWrapper.getMessageByID(messageID)), true)
}
val filter = IntentFilter()
filter.addAction(XMPPConnectionService.INTENT_NEW_MESSAGE)
AppHelper.getContext().registerReceiver(newMessageReceiver, filter)
}
}

View File

@ -1,38 +1,60 @@
package io.github.chronosx88.influence.presenters;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import androidx.appcompat.app.AlertDialog;
import com.stfalcon.chatkit.dialogs.DialogsListAdapter;
import org.json.JSONException;
import org.json.JSONObject;
import org.greenrobot.eventbus.EventBus;
import org.greenrobot.eventbus.Subscribe;
import org.jivesoftware.smack.roster.RosterEntry;
import org.jxmpp.jid.EntityBareJid;
import org.jxmpp.jid.impl.JidCreate;
import org.jxmpp.stringprep.XmppStringprepException;
import java.util.ArrayList;
import java.util.Set;
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.logic.DialogListLogic;
import io.github.chronosx88.influence.models.GenericDialog;
import io.github.chronosx88.influence.models.appEvents.AuthenticationStatusEvent;
import io.github.chronosx88.influence.models.appEvents.NewChatEvent;
import io.github.chronosx88.influence.models.appEvents.NewMessageEvent;
import io.github.chronosx88.influence.views.ChatActivity;
import java8.util.stream.StreamSupport;
import java9.util.concurrent.CompletableFuture;
public class DialogListPresenter implements CoreContracts.IDialogListPresenterContract, IObserver {
public class DialogListPresenter implements CoreContracts.IDialogListPresenterContract {
private CoreContracts.IChatListViewContract view;
private CoreContracts.IDialogListLogicContract logic;
private DialogsListAdapter<GenericDialog> dialogListAdapter = new DialogsListAdapter<>((imageView, url, payload) -> {
imageView.setImageResource(R.mipmap.ic_launcher); // FIXME
imageView.setImageResource(R.mipmap.ic_launcher);
CompletableFuture.supplyAsync(() -> {
while (AppHelper.getXmppConnection() == null);
while (AppHelper.getXmppConnection().isConnectionAlive() != true);
EntityBareJid jid = null;
try {
jid = JidCreate.entityBareFrom(url);
} catch (XmppStringprepException e) {
e.printStackTrace();
}
return AppHelper.getXmppConnection().getAvatar(jid);
}).thenAccept((avatarBytes) -> {
AppHelper.getMainUIThread().post(() -> {
if(avatarBytes != null) {
Bitmap avatar = BitmapFactory.decodeByteArray(avatarBytes, 0, avatarBytes.length);
imageView.setImageBitmap(avatar);
}
});
});
});
private BroadcastReceiver incomingMessagesReceiver;
public DialogListPresenter(CoreContracts.IChatListViewContract view) {
this.view = view;
@ -50,14 +72,23 @@ public class DialogListPresenter implements CoreContracts.IDialogListPresenterCo
builder.setMessage("Remove chat?");
builder.create().show();
});
this.logic = new ChatListLogic();
this.logic = new DialogListLogic();
this.view.setDialogAdapter(dialogListAdapter);
ArrayList<GenericDialog> dialogs = new ArrayList<>();
StreamSupport.stream(logic.loadAllChats())
StreamSupport.stream(logic.loadLocalChats())
.forEach(chatEntity -> dialogs.add(new GenericDialog(chatEntity)));
dialogListAdapter.setItems(dialogs);
setupIncomingMessagesReceiver();
AppHelper.getObservable().register(this);
loadRemoteContactList();
}
@Override
public void onStart() {
EventBus.getDefault().register(this);
}
@Override
public void onStop() {
EventBus.getDefault().unregister(this);
}
@Override
@ -68,29 +99,36 @@ public class DialogListPresenter implements CoreContracts.IDialogListPresenterCo
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);
@Subscribe
public void onNewChatCreated(NewChatEvent event) {
dialogListAdapter.upsertItem(new GenericDialog(LocalDBWrapper.getChatByChatID(event.chatID)));
}
@Subscribe
public void onNewMessage(NewMessageEvent event) {
String chatID = event.chatID;
GenericDialog dialog = dialogListAdapter.getItemById(chatID);
if(dialog == null) {
dialogListAdapter.addItem(new GenericDialog(LocalDBWrapper.getChatByChatID(chatID)));
}
}
@Subscribe
public void onAuthenticate(AuthenticationStatusEvent event) {
if(event.authenticationStatus == AuthenticationStatusEvent.CONNECT_AND_LOGIN_SUCCESSFUL) {
loadRemoteContactList();
}
}
@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;
public void loadRemoteContactList() {
CompletableFuture.supplyAsync(() -> logic.getRemoteContacts()).thenAccept((contacts) -> {
if(contacts != null) {
StreamSupport.stream(contacts).forEach(contact -> {
LocalDBWrapper.createChatEntry(contact.getJid().asUnescapedString(), contact.getName() == null ? contact.getJid().asUnescapedString() : contact.getName());
dialogListAdapter.upsertItem(new GenericDialog(LocalDBWrapper.getChatByChatID(contact.getJid().asUnescapedString())));
});
}
}
});
}
}

View File

@ -1,68 +1,41 @@
package io.github.chronosx88.influence.presenters
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.helpers.AppHelper
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.models.appEvents.AuthenticationStatusEvent
import io.github.chronosx88.influence.models.appEvents.NewChatEvent
import io.github.chronosx88.influence.views.LoginActivity
import org.jetbrains.anko.doAsync
import org.greenrobot.eventbus.EventBus
import org.greenrobot.eventbus.Subscribe
import org.greenrobot.eventbus.ThreadMode
class MainPresenter(private val view: CoreContracts.IMainViewContract) : CoreContracts.IMainPresenterContract {
private val logic: CoreContracts.IMainLogicContract = MainLogic()
private var broadcastReceiver: BroadcastReceiver? = null
init {
EventBus.getDefault().register(this)
}
override fun initPeer() {
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 initConnection() {
logic.startService()
}
override fun startChatWithPeer(username: String) {
LocalDBWrapper.createChatEntry(username, username)
ObservableUtils.notifyUI(ObservableActions.NEW_CHAT_CREATED, username)
EventBus.getDefault().post(NewChatEvent(username))
}
override fun onDestroy() {
//
}
// 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
@Subscribe(threadMode = ThreadMode.MAIN)
fun onAuthenticate(event: AuthenticationStatusEvent) {
when(event.authenticationStatus) {
AuthenticationStatusEvent.INCORRECT_LOGIN_OR_PASSWORD -> {
val intent = Intent(AppHelper.getContext(), LoginActivity::class.java)
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
AppHelper.getContext().startActivity(intent)
}
}
return false
}
}

View File

@ -8,39 +8,4 @@ import io.github.chronosx88.influence.logic.SettingsLogic
class SettingsPresenter(private val view: CoreContracts.ISettingsView) : CoreContracts.ISettingsPresenter {
private val mainThreadHandler: Handler = Handler(AppHelper.getContext().mainLooper)
private val logic: SettingsLogic = SettingsLogic()
override fun updateUsername(username: String) {
/*view.loadingScreen(true)
val editor: SharedPreferences.Editor = AppHelper.getPreferences().edit()
GlobalScope.launch {
val oldUsername = AppHelper.getPreferences().getString("username", null)
if(username.equals("")) {
SettingsLogic.publishUsername(oldUsername, null)
editor.remove("username")
editor.apply()
AppHelper.updateUsername(null)
ObservableUtils.notifyUI(UIActions.USERNAME_AVAILABLE)
return@launch
}
if(!logic.checkUsernameExists(username)) {
// Save username in SharedPreferences
if(username.equals("")) {
editor.remove("username")
} else {
editor.putString("username", username)
}
editor.apply()
AppHelper.updateUsername(if (username.equals("")) null else username)
// Publish username on DHT network
SettingsLogic.publishUsername(oldUsername, username)
ObservableUtils.notifyUI(UIActions.USERNAME_AVAILABLE)
} else {
ObservableUtils.notifyUI(UIActions.USERNAME_ISNT_AVAILABLE)
}
}*/
}
}

View File

@ -18,10 +18,7 @@
package io.github.chronosx88.influence.views;
import android.app.ProgressDialog;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.Bundle;
import android.text.TextUtils;
import android.view.View;
@ -30,24 +27,40 @@ import android.widget.EditText;
import androidx.appcompat.app.AppCompatActivity;
import com.google.android.material.textfield.TextInputLayout;
import org.greenrobot.eventbus.EventBus;
import org.greenrobot.eventbus.Subscribe;
import org.greenrobot.eventbus.ThreadMode;
import io.github.chronosx88.influence.R;
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.HashUtils;
import io.github.chronosx88.influence.models.appEvents.AuthenticationStatusEvent;
public class LoginActivity extends AppCompatActivity implements CoreContracts.ILoginViewContract {
private EditText jidEditText;
private EditText passwordEditText;
private TextInputLayout jidInputLayout;
private TextInputLayout passwordInputLayout;
private Button signInButton;
private BroadcastReceiver broadcastReceiver;
private ProgressDialog progressDialog;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_login);
jidEditText = findViewById(R.id.login_jid);
passwordEditText = findViewById(R.id.login_password);
jidInputLayout = findViewById(R.id.jid_input_layout);
passwordInputLayout = findViewById(R.id.password_input_layout);
jidInputLayout.setErrorEnabled(true);
passwordInputLayout.setErrorEnabled(true);
signInButton = findViewById(R.id.sign_in_button);
progressDialog = new ProgressDialog(LoginActivity.this);
progressDialog.setCancelable(false);
@ -68,39 +81,6 @@ public class LoginActivity extends AppCompatActivity implements CoreContracts.IL
progressDialog.dismiss();
}
@Override
protected void onResume() {
super.onResume();
broadcastReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
switch (action) {
case XMPPConnectionService.INTENT_AUTHENTICATED: {
loadingScreen(false);
finish();
break;
}
case XMPPConnectionService.INTENT_AUTHENTICATION_FAILED: {
loadingScreen(false);
jidEditText.setError("Invalid JID/Password/Server");
break;
}
}
}
};
IntentFilter filter = new IntentFilter();
filter.addAction(XMPPConnectionService.INTENT_AUTHENTICATED);
filter.addAction(XMPPConnectionService.INTENT_AUTHENTICATION_FAILED);
this.registerReceiver(broadcastReceiver, filter);
}
@Override
protected void onPause() {
super.onPause();
this.unregisterReceiver(broadcastReceiver);
}
private boolean checkLoginCredentials() {
jidEditText.setError(null);
passwordEditText.setError(null);
@ -121,7 +101,7 @@ public class LoginActivity extends AppCompatActivity implements CoreContracts.IL
jidEditText.setError("Field is required!");
focusView = jidEditText;
cancel = true;
} else if (!isEmailValid(jid)) {
} else if (!isJidValid(jid)) {
jidEditText.setError("Invalid JID");
focusView = jidEditText;
cancel = true;
@ -135,8 +115,8 @@ public class LoginActivity extends AppCompatActivity implements CoreContracts.IL
}
}
private boolean isEmailValid(String email) {
return email.contains("@");
private boolean isJidValid(String jid) {
return jid.contains("@");
}
private boolean isPasswordValid(String password) {
@ -146,7 +126,7 @@ public class LoginActivity extends AppCompatActivity implements CoreContracts.IL
private void saveLoginCredentials() {
AppHelper.getPreferences().edit()
.putString("jid", jidEditText.getText().toString())
.putString("pass", passwordEditText.getText().toString())
.putString("pass", HashUtils.sha1(passwordEditText.getText().toString()))
.putBoolean("logged_in", true)
.apply();
}
@ -155,4 +135,37 @@ public class LoginActivity extends AppCompatActivity implements CoreContracts.IL
loadingScreen(true);
startService(new Intent(this, XMPPConnectionService.class));
}
@Subscribe(threadMode = ThreadMode.MAIN)
public void onAuthenticate(AuthenticationStatusEvent event) {
switch (event.authenticationStatus) {
case AuthenticationStatusEvent.CONNECT_AND_LOGIN_SUCCESSFUL: {
loadingScreen(false);
finish();
break;
}
case AuthenticationStatusEvent.INCORRECT_LOGIN_OR_PASSWORD: {
loadingScreen(false);
passwordInputLayout.setError("Invalid JID/Password");
break;
}
case AuthenticationStatusEvent.NETWORK_ERROR: {
loadingScreen(false);
jidInputLayout.setError("Network error");
break;
}
}
}
@Override
protected void onStart() {
super.onStart();
EventBus.getDefault().register(this);
}
@Override
protected void onStop() {
EventBus.getDefault().unregister(this);
super.onStop();
}
}

View File

@ -18,20 +18,15 @@ 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.ObservableActions;
import io.github.chronosx88.influence.presenters.MainPresenter;
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, IObserver {
public class MainActivity extends AppCompatActivity implements CoreContracts.IMainViewContract {
private CoreContracts.IMainPresenterContract presenter;
private ProgressDialog progressDialog;
@ -84,15 +79,7 @@ public class MainActivity extends AppCompatActivity implements CoreContracts.IMa
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
protected void onDestroy() {
super.onDestroy();
presenter.onDestroy();
presenter.initConnection();
}
@Override
@ -105,7 +92,7 @@ public class MainActivity extends AppCompatActivity implements CoreContracts.IMa
public boolean onOptionsItemSelected(MenuItem item) {
if(item.getItemId() == R.id.action_reconnect_network) {
progressDialog.show();
presenter.initPeer();
presenter.initConnection();
}
return true;
}
@ -138,14 +125,4 @@ 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

@ -15,8 +15,8 @@ import io.github.chronosx88.influence.presenters.DialogListPresenter
class DialogListFragment : Fragment(), CoreContracts.IChatListViewContract {
private var presenter: CoreContracts.IDialogListPresenterContract? = null
private var dialogList: DialogsList? = null
private lateinit var presenter: CoreContracts.IDialogListPresenterContract
private lateinit var dialogList: DialogsList
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?): View? {
@ -30,10 +30,20 @@ class DialogListFragment : Fragment(), CoreContracts.IChatListViewContract {
}
override fun setDialogAdapter(adapter: DialogsListAdapter<GenericDialog>) {
dialogList!!.setAdapter(adapter)
dialogList.setAdapter(adapter)
}
override fun getActivityContext(): Context? {
return context
}
override fun onStart() {
super.onStart()
presenter.onStart()
}
override fun onStop() {
presenter.onStop()
super.onStop()
}
}

View File

@ -1,20 +1,12 @@
package io.github.chronosx88.influence.views.fragments;
import android.app.AlertDialog;
import android.app.ProgressDialog;
import android.content.ClipData;
import android.content.ClipboardManager;
import android.content.Context;
import android.os.Bundle;
import android.widget.EditText;
import android.widget.LinearLayout;
import android.widget.Toast;
import org.jetbrains.annotations.NotNull;
import androidx.preference.PreferenceFragmentCompat;
import androidx.preference.PreferenceScreen;
import androidx.recyclerview.widget.RecyclerView;
import org.jetbrains.annotations.NotNull;
import io.github.chronosx88.influence.R;
import io.github.chronosx88.influence.contracts.CoreContracts;
@ -31,25 +23,7 @@ public class SettingsFragment extends PreferenceFragmentCompat implements CoreCo
progressDialog.setCancelable(false);
progressDialog.setProgressStyle(android.R.style.Widget_ProgressBar_Small);
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).setOnPreferenceClickListener((preference -> {
ClipboardManager clipboard = (ClipboardManager) AppHelper.getActivityContext().getSystemService(Context.CLIPBOARD_SERVICE);
ClipData clip = ClipData.newPlainText("", AppHelper.getPeerID());
clipboard.setPrimaryClip(clip);
Toast.makeText(AppHelper.getActivityContext(), "Скопировано в буфер обмена!", Toast.LENGTH_SHORT).show();
return false;
}));
getPreferenceScreen().getPreference(1).setSummary(AppHelper.getUsername());
getPreferenceScreen().getPreference(1).setOnPreferenceClickListener((v) -> {
setupUsernameEditDialog().show();
return true;
});
getPreferenceScreen().getPreference(1).setOnPreferenceChangeListener((p, nV) -> {
getPreferenceScreen().getPreference(1).setSummary((String) nV);
return true;
});*/
}
@Override
@ -64,29 +38,4 @@ public class SettingsFragment extends PreferenceFragmentCompat implements CoreCo
public void showMessage(@NotNull String message) {
Toast.makeText(AppHelper.getContext(), message, Toast.LENGTH_LONG).show();
}
private AlertDialog.Builder setupUsernameEditDialog() {
AlertDialog.Builder alertDialog = new AlertDialog.Builder(getContext());
alertDialog.setTitle(getContext().getString(R.string.username_settings));
final EditText input = new EditText(getContext());
LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams(
LinearLayout.LayoutParams.MATCH_PARENT,
LinearLayout.LayoutParams.MATCH_PARENT);
input.setSingleLine();
input.setLayoutParams(lp);
input.setText(AppHelper.getPreferences().getString("username", null));
alertDialog.setView(input);
alertDialog.setPositiveButton(getContext().getString(R.string.ok), (dialog, which) -> presenter.updateUsername(input.getText().toString()));
alertDialog.setNegativeButton(getContext().getString(R.string.cancel), (dialog, which) -> dialog.cancel());
return alertDialog;
}
@Override
public void refreshScreen() {
getPreferenceScreen().getPreference(1).callChangeListener(AppHelper.getPreferences().getString("username", null));
}
}

View File

@ -17,7 +17,6 @@
-->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
@ -25,6 +24,7 @@
tools:context=".views.LoginActivity">
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/jid_input_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content">
@ -40,6 +40,7 @@
</com.google.android.material.textfield.TextInputLayout>
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/password_input_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content">

View File

@ -1,22 +1,3 @@
<?xml version="1.0" encoding="utf-8"?>
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
<Preference
android:key="peerID"
android:title="Мой Peer ID"
android:summary=""/>
<Preference
android:key="username"
android:title="Мой username"
android:summary=""/>
<EditTextPreference
android:key="bootstrapAddress"
android:title="Адрес Bootstrap-ноды"
android:summary="Bootstrap-нода необходима для подключения к сети" />
<CheckBoxPreference
android:key="allowWorkBackground"
android:title="Работать в фоне (not implemented)"
android:summary="Позволять узлу работать в фоне (может повысить потребление трафика и батареи)"/>
<Preference
android:key="exportEncryptionKeys"
android:title="Экспортировать ключи шифрования (not implemented)" />
</PreferenceScreen>