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 in a country, would infringe one or more identifiable patents in that
country that you have reason to believe are valid. 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 arrangement, you convey, or propagate by procuring conveyance of, a
covered work, and grant a patent license to some of the parties covered work, and grant a patent license to some of the parties
receiving the covered work authorizing them to use, propagate, modify 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 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 the work, and under which the third party grants, to any of the
parties who would receive the covered work from you, a discriminatory 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 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, contain the covered work, unless you entered into that arrangement,
or that patent license was granted, prior to 28 March 2007. 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 If the disclaimer of warranty and limitation of liability provided
above cannot be given local legal effect according to their terms, above cannot be given local legal effect according to their terms,
reviewing courts shall apply local law that most closely approximates 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 Program, unless a warranty or assumption of liability accompanies a
copy of the Program in return for a fee. 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 'com.github.stfalcon:chatkit:0.3.3'
implementation 'net.sourceforge.streamsupport:streamsupport:1.7.0' 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 { repositories {
mavenCentral() mavenCentral()

View File

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

View File

@ -17,15 +17,13 @@
package io.github.chronosx88.influence; package io.github.chronosx88.influence;
import android.content.BroadcastReceiver;
import android.content.Context; import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.SharedPreferences; import android.content.SharedPreferences;
import android.util.Log; import android.util.Log;
import androidx.preference.PreferenceManager; import androidx.preference.PreferenceManager;
import org.greenrobot.eventbus.EventBus;
import org.jivesoftware.smack.ConnectionConfiguration; import org.jivesoftware.smack.ConnectionConfiguration;
import org.jivesoftware.smack.ConnectionListener; import org.jivesoftware.smack.ConnectionListener;
import org.jivesoftware.smack.ReconnectionManager; 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.Chat;
import org.jivesoftware.smack.chat2.ChatManager; import org.jivesoftware.smack.chat2.ChatManager;
import org.jivesoftware.smack.packet.Message; 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.XMPPTCPConnection;
import org.jivesoftware.smack.tcp.XMPPTCPConnectionConfiguration; import org.jivesoftware.smack.tcp.XMPPTCPConnectionConfiguration;
import org.jivesoftware.smackx.vcardtemp.VCardManager;
import org.jxmpp.jid.EntityBareJid; import org.jxmpp.jid.EntityBareJid;
import org.jxmpp.jid.impl.JidCreate;
import org.jxmpp.stringprep.XmppStringprepException;
import java.io.IOException; import java.io.IOException;
import java.util.Set;
import io.github.chronosx88.influence.helpers.AppHelper; import io.github.chronosx88.influence.helpers.AppHelper;
import io.github.chronosx88.influence.helpers.NetworkHandler; import io.github.chronosx88.influence.helpers.NetworkHandler;
import io.github.chronosx88.influence.models.appEvents.AuthenticationStatusEvent;
public class XMPPConnection implements ConnectionListener { public class XMPPConnection implements ConnectionListener {
private final static String LOG_TAG = "XMPPConnection"; private final static String LOG_TAG = "XMPPConnection";
@ -51,8 +52,8 @@ public class XMPPConnection implements ConnectionListener {
private XMPPTCPConnection connection = null; private XMPPTCPConnection connection = null;
private SharedPreferences prefs; private SharedPreferences prefs;
private NetworkHandler networkHandler; private NetworkHandler networkHandler;
private BroadcastReceiver sendMessageReceiver = null;
private Context context; private Context context;
private Roster roster;
public enum ConnectionState { public enum ConnectionState {
CONNECTED, CONNECTED,
@ -76,10 +77,10 @@ public class XMPPConnection implements ConnectionListener {
credentials.jabberHost = jabberHost; credentials.jabberHost = jabberHost;
credentials.password = password; 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) { if(connection == null) {
XMPPTCPConnectionConfiguration conf = XMPPTCPConnectionConfiguration.builder() XMPPTCPConnectionConfiguration conf = XMPPTCPConnectionConfiguration.builder()
.setXmppDomain(credentials.jabberHost) .setXmppDomain(credentials.jabberHost)
@ -90,8 +91,6 @@ public class XMPPConnection implements ConnectionListener {
.setCompressionEnabled(true) .setCompressionEnabled(true)
.build(); .build();
setupSendMessageReceiver();
connection = new XMPPTCPConnection(conf); connection = new XMPPTCPConnection(conf);
connection.addConnectionListener(this); connection.addConnectionListener(this);
if(credentials.jabberHost.equals("") && credentials.password.equals("") && credentials.username.equals("")){ 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 reconnectionManager = ReconnectionManager.getInstanceFor(connection);
ReconnectionManager.setEnabledPerDefault(true); ReconnectionManager.setEnabledPerDefault(true);
reconnectionManager.enableAutomaticReconnection(); 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) { public void authenticated(org.jivesoftware.smack.XMPPConnection connection, boolean resumed) {
XMPPConnectionService.SESSION_STATE = SessionState.LOGGED_IN; XMPPConnectionService.SESSION_STATE = SessionState.LOGGED_IN;
prefs.edit().putBoolean("logged_in", true).apply(); prefs.edit().putBoolean("logged_in", true).apply();
context.sendBroadcast(new Intent(XMPPConnectionService.INTENT_AUTHENTICATED)); EventBus.getDefault().post(new AuthenticationStatusEvent(AuthenticationStatusEvent.CONNECT_AND_LOGIN_SUCCESSFUL));
AppHelper.setJid(credentials.username + "@" + credentials.jabberHost);
} }
@Override @Override
@ -148,36 +147,10 @@ public class XMPPConnection implements ConnectionListener {
e.printStackTrace(); e.printStackTrace();
} }
private void setupSendMessageReceiver() { public void sendMessage(EntityBareJid recipientJid, String messageText) {
sendMessageReceiver = new BroadcastReceiver() { Chat chat = ChatManager.getInstanceFor(connection).chatWith(recipientJid);
@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;
try { try {
jid = JidCreate.entityBareFrom(recipientJid); Message message = new Message(recipientJid, Message.Type.chat);
} catch (XmppStringprepException e) {
e.printStackTrace();
}
Chat chat = ChatManager.getInstanceFor(connection).chatWith(jid);
try {
Message message = new Message(jid, Message.Type.chat);
message.setBody(messageText); message.setBody(messageText);
chat.send(message); chat.send(message);
} catch (SmackException.NotConnectedException e) { } catch (SmackException.NotConnectedException e) {
@ -190,4 +163,40 @@ public class XMPPConnection implements ConnectionListener {
public XMPPTCPConnection getConnection() { public XMPPTCPConnection getConnection() {
return connection; 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.app.Service;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.os.Binder;
import android.os.Handler; import android.os.Handler;
import android.os.IBinder; import android.os.IBinder;
import android.os.Looper; import android.os.Looper;
import org.greenrobot.eventbus.EventBus;
import org.jivesoftware.smack.SmackException; import org.jivesoftware.smack.SmackException;
import org.jivesoftware.smack.XMPPException; import org.jivesoftware.smack.XMPPException;
import java.io.IOException; import java.io.IOException;
import io.github.chronosx88.influence.helpers.AppHelper; import io.github.chronosx88.influence.helpers.AppHelper;
import io.github.chronosx88.influence.models.appEvents.AuthenticationStatusEvent;
public class XMPPConnectionService extends Service { public class XMPPConnectionService extends Service {
public static final String INTENT_NEW_MESSAGE = "io.github.chronosx88.intents.new_message";
public static final String INTENT_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.ConnectionState CONNECTION_STATE = XMPPConnection.ConnectionState.DISCONNECTED;
public static XMPPConnection.SessionState SESSION_STATE = XMPPConnection.SessionState.LOGGED_OUT; public static XMPPConnection.SessionState SESSION_STATE = XMPPConnection.SessionState.LOGGED_OUT;
@ -50,11 +43,14 @@ public class XMPPConnectionService extends Service {
private boolean isThreadAlive = false; private boolean isThreadAlive = false;
private XMPPConnection connection; private XMPPConnection connection;
private Context context = AppHelper.getContext(); private Context context = AppHelper.getContext();
private XMPPServiceBinder binder = new XMPPServiceBinder();
public XMPPConnectionService() { } public XMPPConnectionService() { }
@Override @Override
public IBinder onBind(Intent intent) { return null; } public IBinder onBind(Intent intent) {
return binder;
}
public void onServiceStart() { public void onServiceStart() {
if(!isThreadAlive) if(!isThreadAlive)
@ -77,6 +73,7 @@ public class XMPPConnectionService extends Service {
threadHandler.post(() -> { threadHandler.post(() -> {
if(connection != null) { if(connection != null) {
connection.disconnect(); connection.disconnect();
connection = null;
} }
}); });
} }
@ -87,9 +84,12 @@ public class XMPPConnectionService extends Service {
} }
try { try {
connection.connect(); connection.connect();
} catch (IOException | SmackException | XMPPException e) { } catch (IOException | SmackException e) {
Intent intent = new Intent(INTENT_AUTHENTICATION_FAILED); EventBus.getDefault().post(new AuthenticationStatusEvent(AuthenticationStatusEvent.NETWORK_ERROR));
context.sendBroadcast(intent); e.printStackTrace();
stopSelf();
} catch (XMPPException e) {
EventBus.getDefault().post(new AuthenticationStatusEvent(AuthenticationStatusEvent.INCORRECT_LOGIN_OR_PASSWORD));
e.printStackTrace(); e.printStackTrace();
stopSelf(); stopSelf();
} }
@ -106,4 +106,10 @@ public class XMPPConnectionService extends Service {
super.onDestroy(); super.onDestroy();
onServiceStop(); 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.GenericMessage
import io.github.chronosx88.influence.models.roomEntities.ChatEntity import io.github.chronosx88.influence.models.roomEntities.ChatEntity
import io.github.chronosx88.influence.models.roomEntities.MessageEntity import io.github.chronosx88.influence.models.roomEntities.MessageEntity
import org.jivesoftware.smack.roster.RosterEntry
interface CoreContracts { interface CoreContracts {
interface ViewWithLoadingScreen { interface IGenericView {
fun loadingScreen(state: Boolean); fun loadingScreen(state: Boolean)
} }
// -----ChatList----- // -----ChatList-----
interface IDialogListLogicContract { interface IDialogListLogicContract {
fun loadAllChats(): List<ChatEntity> fun loadLocalChats(): List<ChatEntity>
fun getRemoteContacts(): Set<RosterEntry>?
} }
interface IDialogListPresenterContract { interface IDialogListPresenterContract {
fun openChat(chatID: String) fun openChat(chatID: String)
fun onStart()
fun onStop()
fun loadRemoteContactList()
} }
interface IChatListViewContract { interface IChatListViewContract {
@ -35,12 +40,12 @@ interface CoreContracts {
// -----MainActivity----- // -----MainActivity-----
interface IMainLogicContract { interface IMainLogicContract {
fun startService()
} }
interface IMainPresenterContract { interface IMainPresenterContract {
fun initPeer() fun initConnection()
fun startChatWithPeer(username: String) fun startChatWithPeer(username: String)
fun onDestroy()
} }
interface IMainViewContract { interface IMainViewContract {
@ -51,7 +56,7 @@ interface CoreContracts {
// -----ChatActivity----- // -----ChatActivity-----
interface IChatLogicContract { interface IChatLogicContract {
fun sendMessage(text: String): MessageEntity fun sendMessage(text: String): MessageEntity?
} }
interface IChatPresenterContract { interface IChatPresenterContract {
@ -66,20 +71,15 @@ interface CoreContracts {
// -----SettingsFragment----- // -----SettingsFragment-----
interface ISettingsLogic { interface ISettingsLogic // TODO
fun checkUsernameExists(username: String) : Boolean
}
interface ISettingsPresenter { interface ISettingsPresenter // TODO
fun updateUsername(username: String)
}
interface ISettingsView { interface ISettingsView {
fun loadingScreen(state: Boolean) fun loadingScreen(state: Boolean)
fun showMessage(message: String) fun showMessage(message: String)
fun refreshScreen()
} }
// -----LoginActivity----- // -----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.app.Application;
import android.content.Context; import android.content.Context;
import android.content.SharedPreferences; import android.content.SharedPreferences;
import android.os.Handler;
import android.os.Looper;
import android.preference.PreferenceManager; import android.preference.PreferenceManager;
import androidx.multidex.MultiDexApplication; import androidx.multidex.MultiDexApplication;
@ -12,45 +14,39 @@ import com.instacart.library.truetime.TrueTime;
import java.io.IOException; 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. * Extended Application class which designed for centralized getting various objects from anywhere in the application.
*/ */
public class AppHelper extends MultiDexApplication { public class AppHelper extends MultiDexApplication {
private static Application instance; private static Application instance;
private static MainObservable observable;
public final static String APP_NAME = "Influence"; public final static String APP_NAME = "Influence";
private static String jid; private static String jid;
private static RoomHelper chatDB; private static RoomHelper chatDB;
private static SharedPreferences preferences; private static SharedPreferences preferences;
private static XMPPConnection xmppConnection;
private static LoginCredentials currentLoginCredentials;
private static Handler mainUIThreadHandler;
@Override @Override
public void onCreate() { public void onCreate() {
super.onCreate(); super.onCreate();
instance = this; instance = this;
observable = new MainObservable();
chatDB = Room.databaseBuilder(getApplicationContext(), RoomHelper.class, "chatDB") mainUIThreadHandler = new Handler(Looper.getMainLooper());
.allowMainThreadQueries() initChatDB();
.build();
preferences = PreferenceManager.getDefaultSharedPreferences(instance); preferences = PreferenceManager.getDefaultSharedPreferences(instance);
new Thread(() -> { initTrueTime();
try { loadLoginCredentials();
TrueTime.build().initialize();
} catch (IOException e) {
e.printStackTrace();
}
}).start();
} }
public static Context getContext() { public static Context getContext() {
return instance.getApplicationContext(); return instance.getApplicationContext();
} }
public static MainObservable getObservable() { return observable; }
public static String getJid() { return jid; } public static String getJid() { return jid; }
public static void setJid(String jid1) { jid = jid1; } public static void setJid(String jid1) { jid = jid1; }
@ -60,4 +56,50 @@ public class AppHelper extends MultiDexApplication {
public static SharedPreferences getPreferences() { public static SharedPreferences getPreferences() {
return preferences; 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; package io.github.chronosx88.influence.helpers;
import android.content.Context;
import android.content.Intent;
import com.instacart.library.truetime.TrueTime; import com.instacart.library.truetime.TrueTime;
import org.greenrobot.eventbus.EventBus;
import org.jivesoftware.smack.chat2.Chat; import org.jivesoftware.smack.chat2.Chat;
import org.jivesoftware.smack.chat2.IncomingChatMessageListener; import org.jivesoftware.smack.chat2.IncomingChatMessageListener;
import org.jivesoftware.smack.packet.Message; import org.jivesoftware.smack.packet.Message;
import org.jxmpp.jid.EntityBareJid; import org.jxmpp.jid.EntityBareJid;
import io.github.chronosx88.influence.XMPPConnectionService; import io.github.chronosx88.influence.models.appEvents.NewMessageEvent;
public class NetworkHandler implements IncomingChatMessageListener { public class NetworkHandler implements IncomingChatMessageListener {
private final static String LOG_TAG = "NetworkHandler"; private final static String LOG_TAG = "NetworkHandler";
private Context context;
public NetworkHandler(Context context) {
this.context = context;
}
@Override @Override
public void newIncomingMessage(EntityBareJid from, Message message, Chat chat) { 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()); 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); 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()); EventBus.getDefault().post(new NewMessageEvent(chat.getXmppAddressOfChatPartner().toString(), messageID));
intent.putExtra(XMPPConnectionService.MESSAGE_CHATID, chat.getXmppAddressOfChatPartner().toString());
intent.putExtra(XMPPConnectionService.MESSAGE_ID, messageID);
context.sendBroadcast(intent);
} }
} }

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; package io.github.chronosx88.influence.logic;
import android.content.Intent;
import com.instacart.library.truetime.TrueTime; import com.instacart.library.truetime.TrueTime;
import io.github.chronosx88.influence.XMPPConnection; import org.jxmpp.jid.EntityBareJid;
import io.github.chronosx88.influence.XMPPConnectionService; import org.jxmpp.jid.impl.JidCreate;
import org.jxmpp.stringprep.XmppStringprepException;
import io.github.chronosx88.influence.contracts.CoreContracts; import io.github.chronosx88.influence.contracts.CoreContracts;
import io.github.chronosx88.influence.helpers.AppHelper; import io.github.chronosx88.influence.helpers.AppHelper;
import io.github.chronosx88.influence.helpers.LocalDBWrapper; import io.github.chronosx88.influence.helpers.LocalDBWrapper;
@ -25,11 +25,14 @@ public class ChatLogic implements CoreContracts.IChatLogicContract {
@Override @Override
public MessageEntity sendMessage(String text) { public MessageEntity sendMessage(String text) {
if (XMPPConnectionService.CONNECTION_STATE.equals(XMPPConnection.ConnectionState.CONNECTED)) { if (AppHelper.getXmppConnection().isConnectionAlive()) {
Intent intent = new Intent(XMPPConnectionService.INTENT_SEND_MESSAGE); EntityBareJid jid;
intent.putExtra(XMPPConnectionService.MESSAGE_BODY, text); try {
intent.putExtra(XMPPConnectionService.MESSAGE_RECIPIENT, chatEntity.jid); jid = JidCreate.entityBareFrom(chatEntity.jid);
AppHelper.getContext().sendBroadcast(intent); } catch (XmppStringprepException e) {
return null;
}
AppHelper.getXmppConnection().sendMessage(jid, text);
long messageID = LocalDBWrapper.createMessageEntry(chatID, AppHelper.getJid(), TrueTime.now().getTime(), text, false, false); long messageID = LocalDBWrapper.createMessageEntry(chatID, AppHelper.getJid(), TrueTime.now().getTime(), text, false, false);
return LocalDBWrapper.getMessageByID(messageID); return LocalDBWrapper.getMessageByID(messageID);
} else { } 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; package io.github.chronosx88.influence.logic;
import android.content.ComponentName;
import android.content.Context; 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.contracts.CoreContracts;
import io.github.chronosx88.influence.helpers.AppHelper; import io.github.chronosx88.influence.helpers.AppHelper;
import io.github.chronosx88.influence.helpers.KeyPairManager;
public class MainLogic implements CoreContracts.IMainLogicContract { public class MainLogic implements CoreContracts.IMainLogicContract {
private static final String LOG_TAG = MainLogic.class.getName(); private static final String LOG_TAG = MainLogic.class.getName();
@ -17,294 +19,21 @@ public class MainLogic implements CoreContracts.IMainLogicContract {
this.context = AppHelper.getContext(); 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 @Override
public void run() { public void startService() {
if(checkNewChatsThread == null) { context.startService(new Intent(context, XMPPConnectionService.class));
checkNewChatsThread = new Thread(NetworkHandler::handlePendingChatRequests); ServiceConnection connection = new ServiceConnection() {
checkNewChatsThread.start(); @Override
} public void onServiceConnected(ComponentName name, IBinder service) {
if(!checkNewChatsThread.isAlive()) { XMPPConnectionService.XMPPServiceBinder binder = (XMPPConnectionService.XMPPServiceBinder) service;
checkNewChatsThread = new Thread(NetworkHandler::handlePendingChatRequests); AppHelper.setXmppConnection(binder.getConnection());
checkNewChatsThread.start();
} }
@Override
public void onServiceDisconnected(ComponentName name) {
AppHelper.setXmppConnection(null);
} }
}; };
Timer timer = new Timer(); context.bindService(new Intent(context, XMPPConnectionService.class), connection,Context.BIND_AUTO_CREATE);
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();
}
if(peerDHT != null) {
peerDHT.peer().announceShutdown().start().awaitUninterruptibly();
peerDHT.peer().shutdown().awaitUninterruptibly();
}
storage.close();
System.exit(0);
}).start();
}
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 import io.github.chronosx88.influence.helpers.KeyPairManager
class SettingsLogic : CoreContracts.ISettingsLogic { 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 { companion object {
private val LOG_TAG: String = "SettingsLogic" private val LOG_TAG: String = "SettingsLogic"
private val keyPairManager = KeyPairManager() private val keyPairManager = KeyPairManager()
/*fun publishUsername(oldUsername: String?, username: String?) {
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 { public class GenericDialog implements IDialog {
private String dialogID; private String dialogID;
private String dialogPhoto = ""; private String dialogPhoto;
private String dialogName; private String dialogName;
private List<GenericUser> users; private List<GenericUser> users;
private IMessage lastMessage; private IMessage lastMessage;
@ -36,6 +36,7 @@ public class GenericDialog implements IDialog {
public GenericDialog(ChatEntity chatEntity) { public GenericDialog(ChatEntity chatEntity) {
dialogID = chatEntity.jid; dialogID = chatEntity.jid;
dialogPhoto = chatEntity.jid;
dialogName = chatEntity.chatName; dialogName = chatEntity.chatName;
users = new ArrayList<>(); users = new ArrayList<>();
unreadMessagesCount = chatEntity.unreadMessagesCount; 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 package io.github.chronosx88.influence.presenters
import android.content.BroadcastReceiver import android.graphics.BitmapFactory
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import com.google.gson.Gson import com.google.gson.Gson
import com.stfalcon.chatkit.commons.ImageLoader import com.stfalcon.chatkit.commons.ImageLoader
import com.stfalcon.chatkit.messages.MessagesListAdapter import com.stfalcon.chatkit.messages.MessagesListAdapter
import io.github.chronosx88.influence.R import io.github.chronosx88.influence.R
import io.github.chronosx88.influence.XMPPConnectionService
import io.github.chronosx88.influence.contracts.CoreContracts import io.github.chronosx88.influence.contracts.CoreContracts
import io.github.chronosx88.influence.helpers.AppHelper import io.github.chronosx88.influence.helpers.AppHelper
import io.github.chronosx88.influence.helpers.LocalDBWrapper import io.github.chronosx88.influence.helpers.LocalDBWrapper
import io.github.chronosx88.influence.logic.ChatLogic import io.github.chronosx88.influence.logic.ChatLogic
import io.github.chronosx88.influence.models.GenericMessage 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.ChatEntity
import io.github.chronosx88.influence.models.roomEntities.MessageEntity 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 { class ChatPresenter(private val view: CoreContracts.IChatViewContract, private val chatID: String) : CoreContracts.IChatPresenterContract {
private val logic: CoreContracts.IChatLogicContract private val logic: CoreContracts.IChatLogicContract
private val chatEntity: ChatEntity? private val chatEntity: ChatEntity?
private val gson: Gson private val gson: Gson
private val chatAdapter: MessagesListAdapter<GenericMessage> private val chatAdapter: MessagesListAdapter<GenericMessage>
private lateinit var newMessageReceiver: BroadcastReceiver
init { init {
this.logic = ChatLogic(LocalDBWrapper.getChatByChatID(chatID)!!) this.logic = ChatLogic(LocalDBWrapper.getChatByChatID(chatID)!!)
this.chatEntity = LocalDBWrapper.getChatByChatID(chatID) this.chatEntity = LocalDBWrapper.getChatByChatID(chatID)
gson = Gson() gson = Gson()
chatAdapter = MessagesListAdapter(AppHelper.getJid(), ImageLoader { imageView, _, _ -> imageView.setImageResource(R.mipmap.ic_launcher) }) chatAdapter = MessagesListAdapter(AppHelper.getJid(), ImageLoader { imageView, url, _ ->
view.setAdapter(chatAdapter) 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 { override fun sendMessage(text: String): Boolean {
@ -58,18 +77,11 @@ class ChatPresenter(private val view: CoreContracts.IChatViewContract, private v
// //
} }
private fun setupIncomingMessagesReceiver() { @Subscribe
newMessageReceiver = object : BroadcastReceiver() { public fun onNewMessage(event: NewMessageEvent) {
override fun onReceive(context: Context, intent: Intent) { if(event.chatID.equals(chatEntity!!.jid)) {
if(intent.getStringExtra(XMPPConnectionService.MESSAGE_CHATID).equals(chatEntity!!.jid)) { val messageID = event.messageID
val messageID = intent.getLongExtra(XMPPConnectionService.MESSAGE_ID, -1)
chatAdapter.addToStart(GenericMessage(LocalDBWrapper.getMessageByID(messageID)), true) 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; package io.github.chronosx88.influence.presenters;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.content.IntentFilter; import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import androidx.appcompat.app.AlertDialog; import androidx.appcompat.app.AlertDialog;
import com.stfalcon.chatkit.dialogs.DialogsListAdapter; import com.stfalcon.chatkit.dialogs.DialogsListAdapter;
import org.json.JSONException; import org.greenrobot.eventbus.EventBus;
import org.json.JSONObject; 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.ArrayList;
import java.util.Set;
import io.github.chronosx88.influence.R; import io.github.chronosx88.influence.R;
import io.github.chronosx88.influence.XMPPConnectionService;
import io.github.chronosx88.influence.contracts.CoreContracts; import io.github.chronosx88.influence.contracts.CoreContracts;
import io.github.chronosx88.influence.contracts.observer.IObserver;
import io.github.chronosx88.influence.helpers.AppHelper; import io.github.chronosx88.influence.helpers.AppHelper;
import io.github.chronosx88.influence.helpers.LocalDBWrapper; import io.github.chronosx88.influence.helpers.LocalDBWrapper;
import io.github.chronosx88.influence.helpers.ObservableActions; import io.github.chronosx88.influence.logic.DialogListLogic;
import io.github.chronosx88.influence.logic.ChatListLogic;
import io.github.chronosx88.influence.models.GenericDialog; 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 io.github.chronosx88.influence.views.ChatActivity;
import java8.util.stream.StreamSupport; 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.IChatListViewContract view;
private CoreContracts.IDialogListLogicContract logic; private CoreContracts.IDialogListLogicContract logic;
private DialogsListAdapter<GenericDialog> dialogListAdapter = new DialogsListAdapter<>((imageView, url, payload) -> { 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) { public DialogListPresenter(CoreContracts.IChatListViewContract view) {
this.view = view; this.view = view;
@ -50,14 +72,23 @@ public class DialogListPresenter implements CoreContracts.IDialogListPresenterCo
builder.setMessage("Remove chat?"); builder.setMessage("Remove chat?");
builder.create().show(); builder.create().show();
}); });
this.logic = new ChatListLogic(); this.logic = new DialogListLogic();
this.view.setDialogAdapter(dialogListAdapter); this.view.setDialogAdapter(dialogListAdapter);
ArrayList<GenericDialog> dialogs = new ArrayList<>(); ArrayList<GenericDialog> dialogs = new ArrayList<>();
StreamSupport.stream(logic.loadAllChats()) StreamSupport.stream(logic.loadLocalChats())
.forEach(chatEntity -> dialogs.add(new GenericDialog(chatEntity))); .forEach(chatEntity -> dialogs.add(new GenericDialog(chatEntity)));
dialogListAdapter.setItems(dialogs); dialogListAdapter.setItems(dialogs);
setupIncomingMessagesReceiver(); loadRemoteContactList();
AppHelper.getObservable().register(this); }
@Override
public void onStart() {
EventBus.getDefault().register(this);
}
@Override
public void onStop() {
EventBus.getDefault().unregister(this);
} }
@Override @Override
@ -68,29 +99,36 @@ public class DialogListPresenter implements CoreContracts.IDialogListPresenterCo
view.startActivity(intent); view.startActivity(intent);
} }
private void setupIncomingMessagesReceiver() { @Subscribe
incomingMessagesReceiver = new BroadcastReceiver() { public void onNewChatCreated(NewChatEvent event) {
@Override dialogListAdapter.upsertItem(new GenericDialog(LocalDBWrapper.getChatByChatID(event.chatID)));
public void onReceive(Context context, Intent intent) { }
String chatID = intent.getStringExtra(XMPPConnectionService.MESSAGE_CHATID);
@Subscribe
public void onNewMessage(NewMessageEvent event) {
String chatID = event.chatID;
GenericDialog dialog = dialogListAdapter.getItemById(chatID); GenericDialog dialog = dialogListAdapter.getItemById(chatID);
if(dialog == null) { if(dialog == null) {
dialogListAdapter.addItem(new GenericDialog(LocalDBWrapper.getChatByChatID(chatID))); dialogListAdapter.addItem(new GenericDialog(LocalDBWrapper.getChatByChatID(chatID)));
} }
} }
};
IntentFilter filter = new IntentFilter(); @Subscribe
filter.addAction(XMPPConnectionService.INTENT_NEW_MESSAGE); public void onAuthenticate(AuthenticationStatusEvent event) {
AppHelper.getContext().registerReceiver(incomingMessagesReceiver, filter); if(event.authenticationStatus == AuthenticationStatusEvent.CONNECT_AND_LOGIN_SUCCESSFUL) {
loadRemoteContactList();
}
} }
@Override @Override
public void handleEvent(JSONObject object) throws JSONException { public void loadRemoteContactList() {
switch (object.getInt("action")) { CompletableFuture.supplyAsync(() -> logic.getRemoteContacts()).thenAccept((contacts) -> {
case ObservableActions.NEW_CHAT_CREATED: { if(contacts != null) {
dialogListAdapter.addItem(new GenericDialog(LocalDBWrapper.getChatByChatID(object.getJSONArray("additional").optString(0)))); StreamSupport.stream(contacts).forEach(contact -> {
break; 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,36 +1,37 @@
package io.github.chronosx88.influence.presenters 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.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.CoreContracts
import io.github.chronosx88.influence.helpers.AppHelper import io.github.chronosx88.influence.helpers.AppHelper
import io.github.chronosx88.influence.helpers.LocalDBWrapper import io.github.chronosx88.influence.helpers.LocalDBWrapper
import io.github.chronosx88.influence.helpers.ObservableActions
import io.github.chronosx88.influence.helpers.ObservableUtils
import io.github.chronosx88.influence.logic.MainLogic import io.github.chronosx88.influence.logic.MainLogic
import io.github.chronosx88.influence.models.appEvents.AuthenticationStatusEvent
import io.github.chronosx88.influence.models.appEvents.NewChatEvent
import io.github.chronosx88.influence.views.LoginActivity 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 { class MainPresenter(private val view: CoreContracts.IMainViewContract) : CoreContracts.IMainPresenterContract {
private val logic: CoreContracts.IMainLogicContract = MainLogic() private val logic: CoreContracts.IMainLogicContract = MainLogic()
private var broadcastReceiver: BroadcastReceiver? = null
init {
override fun initPeer() { EventBus.getDefault().register(this)
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) override fun initConnection() {
logic.startService()
}
override fun startChatWithPeer(username: String) {
LocalDBWrapper.createChatEntry(username, username)
EventBus.getDefault().post(NewChatEvent(username))
}
@Subscribe(threadMode = ThreadMode.MAIN)
fun onAuthenticate(event: AuthenticationStatusEvent) {
when(event.authenticationStatus) {
AuthenticationStatusEvent.INCORRECT_LOGIN_OR_PASSWORD -> {
val intent = Intent(AppHelper.getContext(), LoginActivity::class.java) val intent = Intent(AppHelper.getContext(), LoginActivity::class.java)
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
AppHelper.getContext().startActivity(intent) AppHelper.getContext().startActivity(intent)
@ -38,31 +39,3 @@ class MainPresenter(private val view: CoreContracts.IMainViewContract) : CoreCon
} }
} }
} }
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) {
LocalDBWrapper.createChatEntry(username, username)
ObservableUtils.notifyUI(ObservableActions.NEW_CHAT_CREATED, 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
}
}
return false
}
}

View File

@ -8,39 +8,4 @@ import io.github.chronosx88.influence.logic.SettingsLogic
class SettingsPresenter(private val view: CoreContracts.ISettingsView) : CoreContracts.ISettingsPresenter { class SettingsPresenter(private val view: CoreContracts.ISettingsView) : CoreContracts.ISettingsPresenter {
private val mainThreadHandler: Handler = Handler(AppHelper.getContext().mainLooper) private val mainThreadHandler: Handler = Handler(AppHelper.getContext().mainLooper)
private val logic: SettingsLogic = SettingsLogic() private val logic: SettingsLogic = SettingsLogic()
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; package io.github.chronosx88.influence.views;
import android.app.ProgressDialog; import android.app.ProgressDialog;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.content.IntentFilter;
import android.os.Bundle; import android.os.Bundle;
import android.text.TextUtils; import android.text.TextUtils;
import android.view.View; import android.view.View;
@ -30,24 +27,40 @@ import android.widget.EditText;
import androidx.appcompat.app.AppCompatActivity; 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.R;
import io.github.chronosx88.influence.XMPPConnectionService; import io.github.chronosx88.influence.XMPPConnectionService;
import io.github.chronosx88.influence.contracts.CoreContracts; import io.github.chronosx88.influence.contracts.CoreContracts;
import io.github.chronosx88.influence.helpers.AppHelper; import io.github.chronosx88.influence.helpers.AppHelper;
import io.github.chronosx88.influence.helpers.HashUtils;
import io.github.chronosx88.influence.models.appEvents.AuthenticationStatusEvent;
public class LoginActivity extends AppCompatActivity implements CoreContracts.ILoginViewContract { public class LoginActivity extends AppCompatActivity implements CoreContracts.ILoginViewContract {
private EditText jidEditText; private EditText jidEditText;
private EditText passwordEditText; private EditText passwordEditText;
private TextInputLayout jidInputLayout;
private TextInputLayout passwordInputLayout;
private Button signInButton; private Button signInButton;
private BroadcastReceiver broadcastReceiver;
private ProgressDialog progressDialog; private ProgressDialog progressDialog;
@Override @Override
protected void onCreate(Bundle savedInstanceState) { protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
setContentView(R.layout.activity_login); setContentView(R.layout.activity_login);
jidEditText = findViewById(R.id.login_jid); jidEditText = findViewById(R.id.login_jid);
passwordEditText = findViewById(R.id.login_password); 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); signInButton = findViewById(R.id.sign_in_button);
progressDialog = new ProgressDialog(LoginActivity.this); progressDialog = new ProgressDialog(LoginActivity.this);
progressDialog.setCancelable(false); progressDialog.setCancelable(false);
@ -68,39 +81,6 @@ public class LoginActivity extends AppCompatActivity implements CoreContracts.IL
progressDialog.dismiss(); 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() { private boolean checkLoginCredentials() {
jidEditText.setError(null); jidEditText.setError(null);
passwordEditText.setError(null); passwordEditText.setError(null);
@ -121,7 +101,7 @@ public class LoginActivity extends AppCompatActivity implements CoreContracts.IL
jidEditText.setError("Field is required!"); jidEditText.setError("Field is required!");
focusView = jidEditText; focusView = jidEditText;
cancel = true; cancel = true;
} else if (!isEmailValid(jid)) { } else if (!isJidValid(jid)) {
jidEditText.setError("Invalid JID"); jidEditText.setError("Invalid JID");
focusView = jidEditText; focusView = jidEditText;
cancel = true; cancel = true;
@ -135,8 +115,8 @@ public class LoginActivity extends AppCompatActivity implements CoreContracts.IL
} }
} }
private boolean isEmailValid(String email) { private boolean isJidValid(String jid) {
return email.contains("@"); return jid.contains("@");
} }
private boolean isPasswordValid(String password) { private boolean isPasswordValid(String password) {
@ -146,7 +126,7 @@ public class LoginActivity extends AppCompatActivity implements CoreContracts.IL
private void saveLoginCredentials() { private void saveLoginCredentials() {
AppHelper.getPreferences().edit() AppHelper.getPreferences().edit()
.putString("jid", jidEditText.getText().toString()) .putString("jid", jidEditText.getText().toString())
.putString("pass", passwordEditText.getText().toString()) .putString("pass", HashUtils.sha1(passwordEditText.getText().toString()))
.putBoolean("logged_in", true) .putBoolean("logged_in", true)
.apply(); .apply();
} }
@ -155,4 +135,37 @@ public class LoginActivity extends AppCompatActivity implements CoreContracts.IL
loadingScreen(true); loadingScreen(true);
startService(new Intent(this, XMPPConnectionService.class)); 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 com.google.android.material.snackbar.Snackbar;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import org.json.JSONException;
import org.json.JSONObject;
import io.github.chronosx88.influence.R; import io.github.chronosx88.influence.R;
import io.github.chronosx88.influence.contracts.CoreContracts; import io.github.chronosx88.influence.contracts.CoreContracts;
import io.github.chronosx88.influence.contracts.observer.IObserver;
import io.github.chronosx88.influence.helpers.AppHelper;
import io.github.chronosx88.influence.helpers.ObservableActions;
import io.github.chronosx88.influence.presenters.MainPresenter; import io.github.chronosx88.influence.presenters.MainPresenter;
import io.github.chronosx88.influence.views.fragments.DialogListFragment; import io.github.chronosx88.influence.views.fragments.DialogListFragment;
import io.github.chronosx88.influence.views.fragments.SettingsFragment; import io.github.chronosx88.influence.views.fragments.SettingsFragment;
import kotlin.Pair; import kotlin.Pair;
public class MainActivity extends AppCompatActivity implements CoreContracts.IMainViewContract, IObserver { public class MainActivity extends AppCompatActivity implements CoreContracts.IMainViewContract {
private CoreContracts.IMainPresenterContract presenter; private CoreContracts.IMainPresenterContract presenter;
private ProgressDialog progressDialog; private ProgressDialog progressDialog;
@ -84,15 +79,7 @@ public class MainActivity extends AppCompatActivity implements CoreContracts.IMa
progressDialog = new ProgressDialog(MainActivity.this, R.style.AlertDialogTheme); progressDialog = new ProgressDialog(MainActivity.this, R.style.AlertDialogTheme);
progressDialog.setCancelable(false); progressDialog.setCancelable(false);
progressDialog.setProgressStyle(android.R.style.Widget_ProgressBar_Small); progressDialog.setProgressStyle(android.R.style.Widget_ProgressBar_Small);
progressDialog.show(); presenter.initConnection();
presenter.initPeer();
AppHelper.getObservable().register(this);
}
@Override
protected void onDestroy() {
super.onDestroy();
presenter.onDestroy();
} }
@Override @Override
@ -105,7 +92,7 @@ public class MainActivity extends AppCompatActivity implements CoreContracts.IMa
public boolean onOptionsItemSelected(MenuItem item) { public boolean onOptionsItemSelected(MenuItem item) {
if(item.getItemId() == R.id.action_reconnect_network) { if(item.getItemId() == R.id.action_reconnect_network) {
progressDialog.show(); progressDialog.show();
presenter.initPeer(); presenter.initConnection();
} }
return true; return true;
} }
@ -138,14 +125,4 @@ public class MainActivity extends AppCompatActivity implements CoreContracts.IMa
return rootView; return rootView;
} }
@Override
public void handleEvent(JSONObject object) throws JSONException {
switch (object.getInt("action")) {
case ObservableActions.NEW_CHAT_CREATED: {
showProgressBar(false);
break;
}
}
}
} }

View File

@ -15,8 +15,8 @@ import io.github.chronosx88.influence.presenters.DialogListPresenter
class DialogListFragment : Fragment(), CoreContracts.IChatListViewContract { class DialogListFragment : Fragment(), CoreContracts.IChatListViewContract {
private var presenter: CoreContracts.IDialogListPresenterContract? = null private lateinit var presenter: CoreContracts.IDialogListPresenterContract
private var dialogList: DialogsList? = null private lateinit var dialogList: DialogsList
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?): View? { savedInstanceState: Bundle?): View? {
@ -30,10 +30,20 @@ class DialogListFragment : Fragment(), CoreContracts.IChatListViewContract {
} }
override fun setDialogAdapter(adapter: DialogsListAdapter<GenericDialog>) { override fun setDialogAdapter(adapter: DialogsListAdapter<GenericDialog>) {
dialogList!!.setAdapter(adapter) dialogList.setAdapter(adapter)
} }
override fun getActivityContext(): Context? { override fun getActivityContext(): Context? {
return 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; package io.github.chronosx88.influence.views.fragments;
import android.app.AlertDialog;
import android.app.ProgressDialog; import android.app.ProgressDialog;
import android.content.ClipData;
import android.content.ClipboardManager;
import android.content.Context;
import android.os.Bundle; import android.os.Bundle;
import android.widget.EditText;
import android.widget.LinearLayout;
import android.widget.Toast; import android.widget.Toast;
import org.jetbrains.annotations.NotNull;
import androidx.preference.PreferenceFragmentCompat; 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.R;
import io.github.chronosx88.influence.contracts.CoreContracts; import io.github.chronosx88.influence.contracts.CoreContracts;
@ -31,25 +23,7 @@ public class SettingsFragment extends PreferenceFragmentCompat implements CoreCo
progressDialog.setCancelable(false); progressDialog.setCancelable(false);
progressDialog.setProgressStyle(android.R.style.Widget_ProgressBar_Small); progressDialog.setProgressStyle(android.R.style.Widget_ProgressBar_Small);
presenter = new SettingsPresenter(this); presenter = new SettingsPresenter(this);
// Load the Preferences from the XML file
addPreferencesFromResource(R.xml.main_settings); addPreferencesFromResource(R.xml.main_settings);
/*getPreferenceScreen().getPreference(0).setSummary(AppHelper.getPeerID());
getPreferenceScreen().getPreference(0).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 @Override
@ -64,29 +38,4 @@ public class SettingsFragment extends PreferenceFragmentCompat implements CoreCo
public void showMessage(@NotNull String message) { public void showMessage(@NotNull String message) {
Toast.makeText(AppHelper.getContext(), message, Toast.LENGTH_LONG).show(); 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" <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" xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
@ -25,6 +24,7 @@
tools:context=".views.LoginActivity"> tools:context=".views.LoginActivity">
<com.google.android.material.textfield.TextInputLayout <com.google.android.material.textfield.TextInputLayout
android:id="@+id/jid_input_layout"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content"> android:layout_height="wrap_content">
@ -40,6 +40,7 @@
</com.google.android.material.textfield.TextInputLayout> </com.google.android.material.textfield.TextInputLayout>
<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_width="match_parent"
android:layout_height="wrap_content"> android:layout_height="wrap_content">

View File

@ -1,22 +1,3 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"> <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> </PreferenceScreen>