mirror of
https://github.com/ChronosX88/Influence-P2P.git
synced 2024-11-22 23:32:18 +00:00
[WIP] Migrating to XMPP, added XMPPConnectionService, LoginActivity
This commit is contained in:
parent
ff8aa48b3e
commit
1f0416fda4
@ -42,6 +42,7 @@ dependencies {
|
|||||||
implementation fileTree(include: ['*.jar'], dir: 'libs')
|
implementation fileTree(include: ['*.jar'], dir: 'libs')
|
||||||
implementation 'androidx.appcompat:appcompat:1.1.0-alpha02'
|
implementation 'androidx.appcompat:appcompat:1.1.0-alpha02'
|
||||||
implementation "androidx.room:room-runtime:2.1.0-alpha04"
|
implementation "androidx.room:room-runtime:2.1.0-alpha04"
|
||||||
|
implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
|
||||||
annotationProcessor "androidx.room:room-compiler:2.1.0-alpha04"
|
annotationProcessor "androidx.room:room-compiler:2.1.0-alpha04"
|
||||||
implementation 'org.slf4j:slf4j-log4j12:1.7.26'
|
implementation 'org.slf4j:slf4j-log4j12:1.7.26'
|
||||||
implementation('net.tomp2p:tomp2p-all:5.0-Beta8') {
|
implementation('net.tomp2p:tomp2p-all:5.0-Beta8') {
|
||||||
@ -58,6 +59,11 @@ dependencies {
|
|||||||
implementation 'com.esotericsoftware:kryo:5.0.0-RC1'
|
implementation 'com.esotericsoftware:kryo:5.0.0-RC1'
|
||||||
|
|
||||||
implementation 'com.github.instacart.truetime-android:library:3.4'
|
implementation 'com.github.instacart.truetime-android:library:3.4'
|
||||||
|
implementation 'org.igniterealtime.smack:smack-core:4.3.3'
|
||||||
|
implementation 'org.igniterealtime.smack:smack-tcp:4.3.3'
|
||||||
|
implementation 'org.igniterealtime.smack:smack-android:4.3.3'
|
||||||
|
implementation 'org.igniterealtime.smack:smack-extensions:4.3.3'
|
||||||
|
|
||||||
}
|
}
|
||||||
repositories {
|
repositories {
|
||||||
mavenCentral()
|
mavenCentral()
|
||||||
|
@ -13,6 +13,13 @@
|
|||||||
android:roundIcon="@mipmap/ic_launcher_round"
|
android:roundIcon="@mipmap/ic_launcher_round"
|
||||||
android:supportsRtl="true"
|
android:supportsRtl="true"
|
||||||
android:theme="@style/AppTheme">
|
android:theme="@style/AppTheme">
|
||||||
|
<activity android:name=".views.LoginActivity"></activity>
|
||||||
|
|
||||||
|
<service
|
||||||
|
android:name=".XMPPConnectionService"
|
||||||
|
android:enabled="true"
|
||||||
|
android:exported="true" />
|
||||||
|
|
||||||
<activity android:name=".views.MainActivity">
|
<activity android:name=".views.MainActivity">
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="android.intent.action.MAIN" />
|
<action android:name="android.intent.action.MAIN" />
|
||||||
|
@ -0,0 +1,24 @@
|
|||||||
|
/*
|
||||||
|
* 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;
|
||||||
|
|
||||||
|
public class LoginCredentials {
|
||||||
|
String username = "";
|
||||||
|
String password = "";
|
||||||
|
String jabberHost = "";
|
||||||
|
}
|
@ -0,0 +1,191 @@
|
|||||||
|
/*
|
||||||
|
* 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;
|
||||||
|
|
||||||
|
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.jivesoftware.smack.ConnectionConfiguration;
|
||||||
|
import org.jivesoftware.smack.ConnectionListener;
|
||||||
|
import org.jivesoftware.smack.ReconnectionManager;
|
||||||
|
import org.jivesoftware.smack.SmackException;
|
||||||
|
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.tcp.XMPPTCPConnection;
|
||||||
|
import org.jivesoftware.smack.tcp.XMPPTCPConnectionConfiguration;
|
||||||
|
import org.jxmpp.jid.EntityBareJid;
|
||||||
|
import org.jxmpp.jid.impl.JidCreate;
|
||||||
|
import org.jxmpp.stringprep.XmppStringprepException;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
import io.github.chronosx88.influence.helpers.AppHelper;
|
||||||
|
import io.github.chronosx88.influence.helpers.NetworkHandler;
|
||||||
|
|
||||||
|
public class XMPPConnection implements ConnectionListener {
|
||||||
|
private final static String LOG_TAG = "XMPPConnection";
|
||||||
|
private LoginCredentials credentials = new LoginCredentials();
|
||||||
|
private XMPPTCPConnection connection = null;
|
||||||
|
private SharedPreferences prefs;
|
||||||
|
private NetworkHandler networkHandler;
|
||||||
|
private BroadcastReceiver sendMessageReceiver = null;
|
||||||
|
private Context context;
|
||||||
|
|
||||||
|
public enum ConnectionState {
|
||||||
|
CONNECTED,
|
||||||
|
AUTHENTICATED,
|
||||||
|
CONNECTING,
|
||||||
|
DISCONNECTING,
|
||||||
|
DISCONNECTED
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum SessionState {
|
||||||
|
LOGGED_IN,
|
||||||
|
LOGGED_OUT
|
||||||
|
}
|
||||||
|
|
||||||
|
public XMPPConnection(Context context) {
|
||||||
|
String jid = prefs.getString("jid", null);
|
||||||
|
String password = prefs.getString("pass", null);
|
||||||
|
if(jid != null && password != null) {
|
||||||
|
String username = jid.split("@")[0];
|
||||||
|
String jabberHost = jid.split("@")[1];
|
||||||
|
credentials.username = username;
|
||||||
|
credentials.jabberHost = jabberHost;
|
||||||
|
credentials.password = password;
|
||||||
|
}
|
||||||
|
networkHandler = new NetworkHandler(context);
|
||||||
|
prefs = PreferenceManager.getDefaultSharedPreferences(context);
|
||||||
|
this.context = context;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void connect() throws XMPPException, SmackException, IOException {
|
||||||
|
if(connection == null) {
|
||||||
|
XMPPTCPConnectionConfiguration conf = XMPPTCPConnectionConfiguration.builder()
|
||||||
|
.setXmppDomain(credentials.jabberHost)
|
||||||
|
.setHost(credentials.jabberHost)
|
||||||
|
.setResource(AppHelper.APP_NAME)
|
||||||
|
.setKeystoreType(null)
|
||||||
|
.setSecurityMode(ConnectionConfiguration.SecurityMode.required)
|
||||||
|
.setCompressionEnabled(true)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
setupSendMessageReceiver();
|
||||||
|
|
||||||
|
connection = new XMPPTCPConnection(conf);
|
||||||
|
connection.addConnectionListener(this);
|
||||||
|
try {
|
||||||
|
connection.connect();
|
||||||
|
connection.login(credentials.username, credentials.password);
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
|
||||||
|
ChatManager.getInstanceFor(connection).addIncomingListener(networkHandler);
|
||||||
|
ReconnectionManager reconnectionManager = ReconnectionManager.getInstanceFor(connection);
|
||||||
|
ReconnectionManager.setEnabledPerDefault(true);
|
||||||
|
reconnectionManager.enableAutomaticReconnection();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void disconnect() {
|
||||||
|
prefs.edit().putBoolean("logged_in", false).apply();
|
||||||
|
if(connection != null) {
|
||||||
|
connection.disconnect();
|
||||||
|
connection = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void connected(org.jivesoftware.smack.XMPPConnection connection) {
|
||||||
|
XMPPConnectionService.connectionState = ConnectionState.CONNECTED;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void authenticated(org.jivesoftware.smack.XMPPConnection connection, boolean resumed) {
|
||||||
|
XMPPConnectionService.sessionState = SessionState.LOGGED_IN;
|
||||||
|
prefs.edit().putBoolean("logged_in", true).apply();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void connectionClosed() {
|
||||||
|
XMPPConnectionService.connectionState = ConnectionState.DISCONNECTED;
|
||||||
|
XMPPConnectionService.sessionState = SessionState.LOGGED_OUT;
|
||||||
|
prefs.edit().putBoolean("logged_in", false).apply();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void connectionClosedOnError(Exception e) {
|
||||||
|
XMPPConnectionService.connectionState = ConnectionState.DISCONNECTED;
|
||||||
|
XMPPConnectionService.sessionState = SessionState.LOGGED_OUT;
|
||||||
|
prefs.edit().putBoolean("logged_in", false).apply();
|
||||||
|
Log.e(LOG_TAG, "Connection closed, exception occurred");
|
||||||
|
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;
|
||||||
|
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.setBody(messageText);
|
||||||
|
chat.send(message);
|
||||||
|
} catch (SmackException.NotConnectedException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public XMPPTCPConnection getConnection() {
|
||||||
|
return connection;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,110 @@
|
|||||||
|
/*
|
||||||
|
* 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;
|
||||||
|
|
||||||
|
import android.app.Service;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.os.Handler;
|
||||||
|
import android.os.IBinder;
|
||||||
|
import android.os.Looper;
|
||||||
|
|
||||||
|
import org.jivesoftware.smack.SmackException;
|
||||||
|
import org.jivesoftware.smack.XMPPException;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
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 connectionState = XMPPConnection.ConnectionState.DISCONNECTED;
|
||||||
|
public static XMPPConnection.SessionState sessionState = XMPPConnection.SessionState.LOGGED_OUT;
|
||||||
|
|
||||||
|
private Thread thread;
|
||||||
|
private Handler threadHandler;
|
||||||
|
private boolean isThreadAlive = false;
|
||||||
|
private XMPPConnection connection;
|
||||||
|
private Context context;
|
||||||
|
|
||||||
|
public XMPPConnectionService(Context context) {
|
||||||
|
this.context = context;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public IBinder onBind(Intent intent) { return null; }
|
||||||
|
|
||||||
|
public void onServiceStart() {
|
||||||
|
if(!isThreadAlive)
|
||||||
|
{
|
||||||
|
isThreadAlive = true;
|
||||||
|
if(thread == null || !thread.isAlive()) {
|
||||||
|
thread = new Thread(() -> {
|
||||||
|
Looper.prepare();
|
||||||
|
threadHandler = new Handler();
|
||||||
|
createConnection();
|
||||||
|
Looper.loop();
|
||||||
|
});
|
||||||
|
thread.start();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void onServiceStop() {
|
||||||
|
isThreadAlive = false;
|
||||||
|
threadHandler.post(() -> {
|
||||||
|
if(connection != null) {
|
||||||
|
connection.disconnect();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void createConnection() {
|
||||||
|
if(connection == null) {
|
||||||
|
connection = new XMPPConnection(this);
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
connection.connect();
|
||||||
|
} catch (IOException | SmackException | XMPPException e) {
|
||||||
|
Intent intent = new Intent(INTENT_AUTHENTICATION_FAILED);
|
||||||
|
|
||||||
|
e.printStackTrace();
|
||||||
|
//Stop the service all together.
|
||||||
|
stopSelf();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int onStartCommand(Intent intent, int flags, int startId) {
|
||||||
|
onServiceStart();
|
||||||
|
return Service.START_STICKY;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onDestroy() {
|
||||||
|
super.onDestroy();
|
||||||
|
onServiceStop();
|
||||||
|
}
|
||||||
|
}
|
@ -9,6 +9,10 @@ import io.github.chronosx88.influence.models.roomEntities.MessageEntity
|
|||||||
|
|
||||||
interface CoreContracts {
|
interface CoreContracts {
|
||||||
|
|
||||||
|
interface ViewWithLoadingScreen {
|
||||||
|
fun loadingScreen(state: Boolean);
|
||||||
|
}
|
||||||
|
|
||||||
// -----ChatList-----
|
// -----ChatList-----
|
||||||
|
|
||||||
interface IChatListLogicContract {
|
interface IChatListLogicContract {
|
||||||
@ -27,21 +31,6 @@ interface CoreContracts {
|
|||||||
fun updateChatList(adapter: ChatListAdapter, chats: List<ChatEntity>)
|
fun updateChatList(adapter: ChatListAdapter, chats: List<ChatEntity>)
|
||||||
}
|
}
|
||||||
|
|
||||||
// -----StartChat-----
|
|
||||||
|
|
||||||
interface IStartChatLogicContract {
|
|
||||||
fun sendStartChatMessage(peerID: String)
|
|
||||||
}
|
|
||||||
|
|
||||||
interface IStartChatPresenterContract {
|
|
||||||
fun startChatWithPeer(peerID: String)
|
|
||||||
}
|
|
||||||
|
|
||||||
interface IStartChatViewContract {
|
|
||||||
fun showMessage(message: String)
|
|
||||||
fun showProgressDialog(enabled: Boolean)
|
|
||||||
}
|
|
||||||
|
|
||||||
// -----MainActivity-----
|
// -----MainActivity-----
|
||||||
|
|
||||||
interface IMainLogicContract {
|
interface IMainLogicContract {
|
||||||
@ -94,4 +83,7 @@ interface CoreContracts {
|
|||||||
fun showMessage(message: String)
|
fun showMessage(message: String)
|
||||||
fun refreshScreen()
|
fun refreshScreen()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// -----LoginActivity-----
|
||||||
|
interface ILoginViewContract : ViewWithLoadingScreen
|
||||||
}
|
}
|
||||||
|
@ -3,6 +3,7 @@ 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.preference.PreferenceManager;
|
||||||
|
|
||||||
import net.tomp2p.dht.PeerDHT;
|
import net.tomp2p.dht.PeerDHT;
|
||||||
|
|
||||||
@ -22,11 +23,10 @@ import io.github.chronosx88.influence.observable.MainObservable;
|
|||||||
public class AppHelper extends MultiDexApplication {
|
public class AppHelper extends MultiDexApplication {
|
||||||
private static Application instance;
|
private static Application instance;
|
||||||
private static MainObservable observable;
|
private static MainObservable observable;
|
||||||
private static String peerID;
|
public final static String APP_NAME = "Influence";
|
||||||
private static PeerDHT peerDHT;
|
|
||||||
|
private static String jid;
|
||||||
private static RoomHelper chatDB;
|
private static RoomHelper chatDB;
|
||||||
private static NetworkHandler networkHandler;
|
|
||||||
private static String username = "";
|
|
||||||
private static SharedPreferences preferences;
|
private static SharedPreferences preferences;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -37,7 +37,7 @@ public class AppHelper extends MultiDexApplication {
|
|||||||
chatDB = Room.databaseBuilder(getApplicationContext(), RoomHelper.class, "chatDB")
|
chatDB = Room.databaseBuilder(getApplicationContext(), RoomHelper.class, "chatDB")
|
||||||
.allowMainThreadQueries()
|
.allowMainThreadQueries()
|
||||||
.build();
|
.build();
|
||||||
preferences = getApplicationContext().getSharedPreferences("io.github.chronosx88.influence_preferences", MODE_PRIVATE);
|
preferences = PreferenceManager.getDefaultSharedPreferences(instance);
|
||||||
new Thread(() -> {
|
new Thread(() -> {
|
||||||
try {
|
try {
|
||||||
TrueTime.build().initialize();
|
TrueTime.build().initialize();
|
||||||
@ -47,28 +47,18 @@ public class AppHelper extends MultiDexApplication {
|
|||||||
}).start();
|
}).start();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void storePeerID(String peerID1) { peerID = peerID1; }
|
|
||||||
|
|
||||||
public static void updateUsername(String username1) { username = username1; }
|
|
||||||
|
|
||||||
public static void storePeerDHT(PeerDHT peerDHT1) { peerDHT = peerDHT1; }
|
|
||||||
|
|
||||||
public static Context getContext() {
|
public static Context getContext() {
|
||||||
return instance.getApplicationContext();
|
return instance.getApplicationContext();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static MainObservable getObservable() { return observable; }
|
public static MainObservable getObservable() { return observable; }
|
||||||
|
|
||||||
public static String getPeerID() { return peerID; }
|
public static String getJid() { return jid; }
|
||||||
|
|
||||||
public static String getUsername() { return username; }
|
public static void setJid(String jid1) { jid = jid1; }
|
||||||
|
|
||||||
public static PeerDHT getPeerDHT() { return peerDHT; }
|
|
||||||
|
|
||||||
public static RoomHelper getChatDB() { return chatDB; }
|
public static RoomHelper getChatDB() { return chatDB; }
|
||||||
|
|
||||||
public static void initNetworkHandler() { networkHandler = new NetworkHandler(); }
|
|
||||||
|
|
||||||
public static SharedPreferences getPreferences() {
|
public static SharedPreferences getPreferences() {
|
||||||
return preferences;
|
return preferences;
|
||||||
}
|
}
|
||||||
|
@ -1,98 +0,0 @@
|
|||||||
package io.github.chronosx88.influence.helpers
|
|
||||||
|
|
||||||
import android.util.Log
|
|
||||||
import com.sleepycat.bind.EntryBinding
|
|
||||||
import com.sleepycat.je.DatabaseEntry
|
|
||||||
import io.netty.buffer.Unpooled
|
|
||||||
import net.tomp2p.connection.SignatureFactory
|
|
||||||
import net.tomp2p.storage.Data
|
|
||||||
import java.io.*
|
|
||||||
import java.nio.ByteBuffer
|
|
||||||
import java.security.InvalidKeyException
|
|
||||||
import java.security.SignatureException
|
|
||||||
|
|
||||||
|
|
||||||
class DataSerializer(private val signatureFactory: SignatureFactory) : EntryBinding<Data>, Serializable {
|
|
||||||
private val LOG_TAG = "DataSerializer"
|
|
||||||
|
|
||||||
override fun entryToObject(databaseEntry: DatabaseEntry): Data? {
|
|
||||||
if (databaseEntry.data == null) {
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
val dataInput = ByteArrayInputStream(databaseEntry.data)
|
|
||||||
var buf = Unpooled.buffer()
|
|
||||||
var data: Data? = null
|
|
||||||
while (data == null) {
|
|
||||||
buf.writeByte(dataInput.read())
|
|
||||||
data = Data.decodeHeader(buf, signatureFactory)
|
|
||||||
}
|
|
||||||
val len = data.length()
|
|
||||||
var me = ByteArray(len)
|
|
||||||
try {
|
|
||||||
dataInput.read(me)
|
|
||||||
} catch (e: IOException) {
|
|
||||||
e.printStackTrace()
|
|
||||||
}
|
|
||||||
|
|
||||||
buf = Unpooled.wrappedBuffer(me)
|
|
||||||
var retVal = data.decodeBuffer(buf)
|
|
||||||
if (!retVal) {
|
|
||||||
Log.e(LOG_TAG, "# ERROR: Data could not be deserialized!")
|
|
||||||
}
|
|
||||||
if (data.isSigned) {
|
|
||||||
me = ByteArray(signatureFactory.signatureSize())
|
|
||||||
dataInput.read(me)
|
|
||||||
buf = Unpooled.wrappedBuffer(me)
|
|
||||||
}
|
|
||||||
retVal = data.decodeDone(buf, signatureFactory);
|
|
||||||
if(!retVal) {
|
|
||||||
throw IOException("Signature could not be read!")
|
|
||||||
}
|
|
||||||
return data
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun objectToEntry(data: Data, databaseEntry: DatabaseEntry) {
|
|
||||||
val out = ByteArrayOutputStream()
|
|
||||||
val acb = Unpooled.buffer()
|
|
||||||
// store data to disk
|
|
||||||
// header first
|
|
||||||
data.encodeHeader(acb, signatureFactory)
|
|
||||||
writeData(out, acb.nioBuffers())
|
|
||||||
acb.skipBytes(acb.writerIndex())
|
|
||||||
// next data - no need to copy to another buffer, just take the data
|
|
||||||
// from memory
|
|
||||||
writeData(out, data.toByteBuffers())
|
|
||||||
// rest
|
|
||||||
try {
|
|
||||||
data.encodeDone(acb, signatureFactory)
|
|
||||||
writeData(out, acb.nioBuffers())
|
|
||||||
} catch (e: InvalidKeyException) {
|
|
||||||
throw IOException(e)
|
|
||||||
} catch (e: SignatureException) {
|
|
||||||
throw IOException(e)
|
|
||||||
}
|
|
||||||
out.flush()
|
|
||||||
databaseEntry.data = out.toByteArray()
|
|
||||||
out.close()
|
|
||||||
}
|
|
||||||
|
|
||||||
@Throws(IOException::class)
|
|
||||||
private fun writeData(out: OutputStream, nioBuffers: Array<ByteBuffer>) {
|
|
||||||
val length = nioBuffers.size
|
|
||||||
for (i in 0 until length) {
|
|
||||||
val remaining = nioBuffers[i].remaining()
|
|
||||||
if (nioBuffers[i].hasArray()) {
|
|
||||||
out.write(nioBuffers[i].array(), nioBuffers[i].arrayOffset(), remaining)
|
|
||||||
} else {
|
|
||||||
val me = ByteArray(remaining)
|
|
||||||
nioBuffers[i].get(me)
|
|
||||||
out.write(me)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
private const val serialVersionUID = 1428836065493792295L
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,23 +0,0 @@
|
|||||||
package io.github.chronosx88.influence.helpers;
|
|
||||||
|
|
||||||
import android.util.Log;
|
|
||||||
|
|
||||||
import net.tomp2p.dht.Storage;
|
|
||||||
|
|
||||||
public class JVMShutdownHook extends Thread {
|
|
||||||
|
|
||||||
Storage storage;
|
|
||||||
|
|
||||||
public JVMShutdownHook(Storage storage) {
|
|
||||||
this.storage = storage;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
super.run();
|
|
||||||
Log.d("JVMShutdownHook", "# Closing storage...");
|
|
||||||
storage.close();
|
|
||||||
Log.d("JVMShutdownHook", "# Storage is closed");
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -12,39 +12,22 @@ public class LocalDBWrapper {
|
|||||||
private static final String LOG_TAG = "LocalDBWrapper";
|
private static final String LOG_TAG = "LocalDBWrapper";
|
||||||
private static RoomHelper dbInstance = AppHelper.getChatDB();
|
private static RoomHelper dbInstance = AppHelper.getChatDB();
|
||||||
|
|
||||||
/**
|
public static void createChatEntry(String jid, String chatName) {
|
||||||
* Create a chat entry in the local database.
|
dbInstance.chatDao().addChat(new ChatEntity(jid, chatName));
|
||||||
* @param chatID Chat ID
|
|
||||||
* @param name Chat name
|
|
||||||
* @param metadataRef Reference to general chat metadata (key in DHT)
|
|
||||||
* @param membersRef Reference to member list
|
|
||||||
*/
|
|
||||||
public static void createChatEntry(String chatID, String name, String metadataRef, String membersRef, int chunkID) {
|
|
||||||
dbInstance.chatDao().addChat(new ChatEntity(chatID, name, metadataRef, membersRef, new ArrayList<>(), chunkID));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
public static long createMessageEntry(String jid, String senderJid, long timestamp, String text, boolean isSent, boolean isRead) {
|
||||||
* Creating a message entry in the local database
|
List<ChatEntity> chatEntities = AppHelper.getChatDB().chatDao().getChatByChatID(jid);
|
||||||
* @param type Message type
|
|
||||||
* @param chatID ID of the chat in which need to create a message
|
|
||||||
* @param username Sender username
|
|
||||||
* @param senderID Sender peer ID
|
|
||||||
* @param timestamp Message timestamp
|
|
||||||
* @param text Message text
|
|
||||||
* @return New message
|
|
||||||
*/
|
|
||||||
public static MessageEntity createMessageEntry(int type, String messageID, String chatID, String username, String senderID, long timestamp, String text, boolean isSent, boolean isRead) {
|
|
||||||
List<ChatEntity> chatEntities = AppHelper.getChatDB().chatDao().getChatByChatID(chatID);
|
|
||||||
if(chatEntities.size() < 1) {
|
if(chatEntities.size() < 1) {
|
||||||
Log.e(LOG_TAG, "Failed to create message entry because chat " + chatID + " doesn't exists!");
|
Log.e(LOG_TAG, "Failed to create message entry because chat " + jid + " doesn't exists!");
|
||||||
return null;
|
return -1;
|
||||||
}
|
}
|
||||||
MessageEntity message = new MessageEntity(type, messageID, chatID, senderID, username, timestamp, text, isSent, isRead);
|
MessageEntity message = new MessageEntity(jid, senderJid, timestamp, text, isSent, isRead);
|
||||||
dbInstance.messageDao().insertMessage(message);
|
long index = dbInstance.messageDao().insertMessage(message);
|
||||||
return message;
|
return index;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static MessageEntity getMessageByID(String messageID) {
|
public static MessageEntity getMessageByID(long messageID) {
|
||||||
List<MessageEntity> messages = dbInstance.messageDao().getMessageByID(messageID);
|
List<MessageEntity> messages = dbInstance.messageDao().getMessageByID(messageID);
|
||||||
if(messages.isEmpty()) {
|
if(messages.isEmpty()) {
|
||||||
return null;
|
return null;
|
||||||
|
@ -1,78 +1,35 @@
|
|||||||
package io.github.chronosx88.influence.helpers;
|
package io.github.chronosx88.influence.helpers;
|
||||||
|
|
||||||
import com.google.gson.Gson;
|
import android.content.Context;
|
||||||
|
import android.content.Intent;
|
||||||
|
|
||||||
import net.tomp2p.dht.PeerDHT;
|
import com.instacart.library.truetime.TrueTime;
|
||||||
import net.tomp2p.peers.Number640;
|
|
||||||
import net.tomp2p.storage.Data;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
import org.jivesoftware.smack.chat2.Chat;
|
||||||
import java.util.Map;
|
import org.jivesoftware.smack.chat2.IncomingChatMessageListener;
|
||||||
import java.util.UUID;
|
import org.jivesoftware.smack.packet.Message;
|
||||||
|
import org.jxmpp.jid.EntityBareJid;
|
||||||
|
|
||||||
import io.github.chronosx88.influence.contracts.observer.INetworkObserver;
|
import io.github.chronosx88.influence.XMPPConnectionService;
|
||||||
import io.github.chronosx88.influence.helpers.actions.UIActions;
|
|
||||||
import io.github.chronosx88.influence.models.ChatMember;
|
|
||||||
import io.github.chronosx88.influence.models.JoinChatMessage;
|
|
||||||
import io.github.chronosx88.influence.models.NewChatRequestMessage;
|
|
||||||
|
|
||||||
public class NetworkHandler implements INetworkObserver {
|
public class NetworkHandler implements IncomingChatMessageListener {
|
||||||
private final static String LOG_TAG = "NetworkHandler";
|
private final static String LOG_TAG = "NetworkHandler";
|
||||||
private static Gson gson = new Gson();
|
private Context context;
|
||||||
private static PeerDHT peerDHT = AppHelper.getPeerDHT();
|
|
||||||
private static KeyPairManager keyPairManager = new KeyPairManager();
|
|
||||||
|
|
||||||
public NetworkHandler() {
|
public NetworkHandler(Context context) {
|
||||||
AppHelper.getObservable().register(this);
|
this.context = context;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void handleEvent(Object object) {
|
public void newIncomingMessage(EntityBareJid from, Message message, Chat chat) {
|
||||||
// Empty
|
if(LocalDBWrapper.getChatByChatID(from.asEntityBareJidString()) == null) {
|
||||||
}
|
LocalDBWrapper.createChatEntry(chat.getXmppAddressOfChatPartner().asUnescapedString(), chat.getXmppAddressOfChatPartner().asBareJid().asUnescapedString());
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
public static void handlePendingChatRequests() {
|
|
||||||
Map<Number640, Data> pendingChats = P2PUtils.get(AppHelper.getPeerID() + "_pendingChats");
|
|
||||||
if (pendingChats != null) {
|
|
||||||
for (Map.Entry<Number640, Data> entry : pendingChats.entrySet()) {
|
|
||||||
NewChatRequestMessage newChatRequestMessage = null;
|
|
||||||
try {
|
|
||||||
newChatRequestMessage = gson.fromJson((String) entry.getValue().object(), NewChatRequestMessage.class);
|
|
||||||
} catch (ClassNotFoundException e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
} catch (IOException e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
|
|
||||||
ChatMember chatMember = new ChatMember(AppHelper.getPeerID(), AppHelper.getPeerID());
|
|
||||||
Data putData = null;
|
|
||||||
try {
|
|
||||||
putData = new Data(gson.toJson(chatMember)).protectEntry(keyPairManager.openMainKeyPair());
|
|
||||||
} catch (IOException e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
|
|
||||||
P2PUtils.put(newChatRequestMessage.getChatID() + "_members", AppHelper.getPeerID(), putData);
|
|
||||||
|
|
||||||
LocalDBWrapper.createChatEntry(
|
|
||||||
newChatRequestMessage.getChatID(),
|
|
||||||
newChatRequestMessage.getUsername(),
|
|
||||||
newChatRequestMessage.getChatID() + "_metadata",
|
|
||||||
newChatRequestMessage.getChatID() + "_members",
|
|
||||||
newChatRequestMessage.getChunkID()
|
|
||||||
);
|
|
||||||
|
|
||||||
P2PUtils.remove(AppHelper.getPeerID() + "_pendingChats", newChatRequestMessage.getChatID());
|
|
||||||
String messageID = UUID.randomUUID().toString();
|
|
||||||
try {
|
|
||||||
P2PUtils.put(newChatRequestMessage.getChatID() + "_messages", messageID, new Data(gson.toJson(new JoinChatMessage(AppHelper.getPeerID(), AppHelper.getUsername() == null ? AppHelper.getPeerID() : AppHelper.getUsername(), newChatRequestMessage.getChatID(), System.currentTimeMillis()))).protectEntry(keyPairManager.openMainKeyPair()));
|
|
||||||
} catch (IOException e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
ObservableUtils.notifyUI(UIActions.SUCCESSFUL_CREATE_CHAT);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
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);
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -1,74 +0,0 @@
|
|||||||
package io.github.chronosx88.influence.helpers;
|
|
||||||
|
|
||||||
import com.google.gson.Gson;
|
|
||||||
|
|
||||||
import net.tomp2p.dht.FutureGet;
|
|
||||||
import net.tomp2p.dht.FuturePut;
|
|
||||||
import net.tomp2p.dht.FutureRemove;
|
|
||||||
import net.tomp2p.dht.PeerDHT;
|
|
||||||
import net.tomp2p.futures.FutureDirect;
|
|
||||||
import net.tomp2p.futures.FuturePing;
|
|
||||||
import net.tomp2p.peers.Number160;
|
|
||||||
import net.tomp2p.peers.Number640;
|
|
||||||
import net.tomp2p.peers.PeerAddress;
|
|
||||||
import net.tomp2p.storage.Data;
|
|
||||||
|
|
||||||
import java.security.KeyPair;
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
public class P2PUtils {
|
|
||||||
private static Gson gson = new Gson();
|
|
||||||
private static PeerDHT peerDHT = AppHelper.getPeerDHT();
|
|
||||||
|
|
||||||
public static boolean put(String locationKey, String contentKey, Data data) {
|
|
||||||
FuturePut futurePut = peerDHT
|
|
||||||
.put(Number160.createHash(locationKey))
|
|
||||||
.data(contentKey == null ? Number160.ZERO : Number160.createHash(contentKey), data)
|
|
||||||
.start()
|
|
||||||
.awaitUninterruptibly();
|
|
||||||
return futurePut.isSuccess();
|
|
||||||
}
|
|
||||||
|
|
||||||
public static boolean put(String locationKey, String contentKey, Data data, KeyPair keyPair) {
|
|
||||||
FuturePut futurePut = peerDHT
|
|
||||||
.put(Number160.createHash(locationKey))
|
|
||||||
.data(contentKey == null ? Number160.ZERO : Number160.createHash(contentKey), data)
|
|
||||||
.keyPair(keyPair)
|
|
||||||
.start()
|
|
||||||
.awaitUninterruptibly();
|
|
||||||
return futurePut.isSuccess();
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Map<Number640, Data> get(String locationKey) {
|
|
||||||
FutureGet futureGet = peerDHT
|
|
||||||
.get(Number160.createHash(locationKey))
|
|
||||||
.all()
|
|
||||||
.start()
|
|
||||||
.awaitUninterruptibly();
|
|
||||||
if(futureGet != null) {
|
|
||||||
if(!futureGet.isEmpty()) {
|
|
||||||
return futureGet.dataMap();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static boolean remove(String locationKey, String contentKey) {
|
|
||||||
FutureRemove futureRemove = peerDHT
|
|
||||||
.remove(Number160.createHash(locationKey))
|
|
||||||
.contentKey(contentKey == null ? null : Number160.createHash(contentKey))
|
|
||||||
.start()
|
|
||||||
.awaitUninterruptibly();
|
|
||||||
return futureRemove.isRemoved();
|
|
||||||
}
|
|
||||||
|
|
||||||
public static boolean remove(String locationKey, String contentKey, KeyPair keyPair) {
|
|
||||||
FutureRemove futureRemove = peerDHT
|
|
||||||
.remove(Number160.createHash(locationKey))
|
|
||||||
.keyPair(keyPair)
|
|
||||||
.contentKey(contentKey == null ? null : Number160.createHash(contentKey))
|
|
||||||
.start()
|
|
||||||
.awaitUninterruptibly();
|
|
||||||
return futureRemove.isRemoved();
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,48 +0,0 @@
|
|||||||
package io.github.chronosx88.influence.helpers;
|
|
||||||
|
|
||||||
import com.sleepycat.bind.EntryBinding;
|
|
||||||
import com.sleepycat.je.DatabaseEntry;
|
|
||||||
|
|
||||||
import java.io.ByteArrayInputStream;
|
|
||||||
import java.io.ByteArrayOutputStream;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.ObjectInputStream;
|
|
||||||
import java.io.ObjectOutputStream;
|
|
||||||
|
|
||||||
public class Serializer<T> implements EntryBinding<T> {
|
|
||||||
public byte[] serialize(T object) {
|
|
||||||
ByteArrayOutputStream byteArray = new ByteArrayOutputStream();
|
|
||||||
try {
|
|
||||||
ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArray);
|
|
||||||
objectOutputStream.writeObject(object);
|
|
||||||
objectOutputStream.close();
|
|
||||||
} catch (IOException e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
return byteArray.toByteArray();
|
|
||||||
}
|
|
||||||
|
|
||||||
public T deserialize(byte[] serializedObject) {
|
|
||||||
if(serializedObject == null)
|
|
||||||
return null;
|
|
||||||
ByteArrayInputStream inputStream = new ByteArrayInputStream(serializedObject);
|
|
||||||
Object object = null;
|
|
||||||
try {
|
|
||||||
ObjectInputStream objectInputStream = new ObjectInputStream(inputStream);
|
|
||||||
object = objectInputStream.readObject();
|
|
||||||
} catch (ClassNotFoundException | IOException e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
return (T) object;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public T entryToObject(DatabaseEntry databaseEntry) {
|
|
||||||
return deserialize(databaseEntry.getData());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void objectToEntry(T object, DatabaseEntry databaseEntry) {
|
|
||||||
databaseEntry.setData(serialize(object));
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,290 +0,0 @@
|
|||||||
package io.github.chronosx88.influence.helpers
|
|
||||||
|
|
||||||
import com.sleepycat.collections.StoredSortedMap
|
|
||||||
import com.sleepycat.je.Database
|
|
||||||
import com.sleepycat.je.DatabaseConfig
|
|
||||||
import com.sleepycat.je.Environment
|
|
||||||
import com.sleepycat.je.EnvironmentConfig
|
|
||||||
import io.github.chronosx88.influence.helpers.comparators.CompareLong
|
|
||||||
import io.github.chronosx88.influence.helpers.comparators.CompareNumber640
|
|
||||||
import net.tomp2p.connection.SignatureFactory
|
|
||||||
import net.tomp2p.dht.Storage
|
|
||||||
import net.tomp2p.peers.Number160
|
|
||||||
import net.tomp2p.peers.Number320
|
|
||||||
import net.tomp2p.peers.Number480
|
|
||||||
import net.tomp2p.peers.Number640
|
|
||||||
import net.tomp2p.storage.Data
|
|
||||||
import java.io.File
|
|
||||||
import java.security.PublicKey
|
|
||||||
import java.util.*
|
|
||||||
import java.util.concurrent.ConcurrentHashMap
|
|
||||||
import kotlin.collections.ArrayList
|
|
||||||
import kotlin.collections.HashMap
|
|
||||||
|
|
||||||
class StorageBerkeleyDB(peerId: Number160, path : File, signatureFactory: SignatureFactory) : Storage {
|
|
||||||
// Core
|
|
||||||
private val dataMap: StoredSortedMap<Number640, Data>
|
|
||||||
// Maintenance
|
|
||||||
private val timeoutMap: StoredSortedMap<Number640, Long>
|
|
||||||
private val timeoutMapRev: StoredSortedMap<Long, Set<Number640>>
|
|
||||||
// Protection
|
|
||||||
private val protectedDomainMap: StoredSortedMap<Number320, PublicKey>
|
|
||||||
private val protectedEntryMap: StoredSortedMap<Number480, PublicKey>
|
|
||||||
// Responsibility
|
|
||||||
private val responsibilityMap: StoredSortedMap<Number160, Number160>
|
|
||||||
private val responsibilityMapRev: StoredSortedMap<Number160, Set<Number160>>
|
|
||||||
|
|
||||||
private val dataMapDB: Database
|
|
||||||
private val timeoutMapDB: Database
|
|
||||||
private val timeoutMapRevDB: Database
|
|
||||||
private val protectedDomainMapDB: Database
|
|
||||||
private val protectedEntryMapDB: Database
|
|
||||||
private val responsibilityMapDB: Database
|
|
||||||
private val responsibilityMapRevDB: Database
|
|
||||||
|
|
||||||
|
|
||||||
private val storageCheckIntervalMillis: Int
|
|
||||||
private val dbEnvironment: Environment
|
|
||||||
|
|
||||||
init {
|
|
||||||
val envConfig = EnvironmentConfig()
|
|
||||||
envConfig.allowCreate = true
|
|
||||||
dbEnvironment = Environment(path, envConfig)
|
|
||||||
|
|
||||||
val configMap : HashMap<String, DatabaseConfig> = HashMap()
|
|
||||||
|
|
||||||
val compareNumber640 = CompareNumber640()
|
|
||||||
val compareLong = CompareLong()
|
|
||||||
configMap["dataMapConfig"] = DatabaseConfig().setBtreeComparator(compareNumber640)
|
|
||||||
configMap["dataMapConfig"]!!.allowCreate = true
|
|
||||||
configMap["timeoutMapRevConfig"] = DatabaseConfig().setBtreeComparator(compareLong)
|
|
||||||
configMap["timeoutMapRevConfig"]!!.allowCreate = true
|
|
||||||
configMap["other"] = DatabaseConfig()
|
|
||||||
configMap["other"]!!.allowCreate = true
|
|
||||||
|
|
||||||
dataMapDB = dbEnvironment.openDatabase(null, "dataMap_$peerId", configMap["dataMapConfig"])
|
|
||||||
timeoutMapDB = dbEnvironment.openDatabase(null, "timeoutMap_$peerId", configMap["other"])
|
|
||||||
timeoutMapRevDB = dbEnvironment.openDatabase(null, "timeoutMapRev_$peerId", configMap["timeoutMapRevConfig"])
|
|
||||||
protectedDomainMapDB = dbEnvironment.openDatabase(null, "protectedDomainMap_$peerId", configMap["other"])
|
|
||||||
protectedEntryMapDB = dbEnvironment.openDatabase(null, "protectedEntryMap_$peerId", configMap["other"])
|
|
||||||
responsibilityMapDB = dbEnvironment.openDatabase(null, "responsibilityMap_$peerId", configMap["other"])
|
|
||||||
responsibilityMapRevDB = dbEnvironment.openDatabase(null, "responsibilityMapRev_$peerId", configMap["other"])
|
|
||||||
|
|
||||||
storageCheckIntervalMillis = 60 * 1000
|
|
||||||
|
|
||||||
dataMap = StoredSortedMap(dataMapDB, Serializer<Number640>(), DataSerializer(signatureFactory), true)
|
|
||||||
timeoutMap = StoredSortedMap(timeoutMapDB, Serializer<Number640>(), Serializer<Long>(), true)
|
|
||||||
timeoutMapRev = StoredSortedMap(timeoutMapRevDB, Serializer<Long>(), Serializer<Set<Number640>>(), true)
|
|
||||||
protectedDomainMap = StoredSortedMap(protectedDomainMapDB, Serializer<Number320>(), Serializer<PublicKey>(), true)
|
|
||||||
protectedEntryMap = StoredSortedMap(protectedEntryMapDB, Serializer<Number480>(), Serializer<PublicKey>(), true)
|
|
||||||
responsibilityMap = StoredSortedMap(responsibilityMapDB, Serializer<Number160>(), Serializer<Number160>(), true)
|
|
||||||
responsibilityMapRev = StoredSortedMap(responsibilityMapRevDB, Serializer<Number160>(), Serializer<Set<Number160>>(), true)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun contains(key: Number640?): Boolean {
|
|
||||||
return dataMap.containsKey(key)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun contains(from: Number640?, to: Number640?): Int {
|
|
||||||
return dataMap.subMap(from, true, to, true).size
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun findContentForResponsiblePeerID(peerID: Number160?): MutableSet<Number160>? {
|
|
||||||
return responsibilityMapRev[peerID] as MutableSet<Number160>?
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun findPeerIDsForResponsibleContent(locationKey: Number160?): Number160? {
|
|
||||||
return responsibilityMap[locationKey]
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun put(key: Number640?, value: Data?): Data? {
|
|
||||||
val oldData = dataMap.put(key, value)
|
|
||||||
dbEnvironment.sync()
|
|
||||||
return oldData
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun get(key: Number640?): Data? {
|
|
||||||
return dataMap[key]
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun remove(key: Number640?, returnData: Boolean): Data? {
|
|
||||||
val oldData = dataMap.remove(key)
|
|
||||||
dbEnvironment.sync()
|
|
||||||
return oldData
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun remove(from: Number640?, to: Number640?): NavigableMap<Number640, Data> {
|
|
||||||
val tmp = dataMap.subMap(from, true, to, true)
|
|
||||||
val retVal = TreeMap<Number640, Data>()
|
|
||||||
for(entry : Map.Entry<Number640, Data> in tmp.entries) {
|
|
||||||
retVal[entry.key] = entry.value
|
|
||||||
}
|
|
||||||
tmp.clear()
|
|
||||||
dbEnvironment.sync()
|
|
||||||
return retVal
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun addTimeout(key: Number640, expiration: Long) {
|
|
||||||
val oldExpiration = timeoutMap.put(key, expiration)
|
|
||||||
putIfAbsent2(expiration, key)
|
|
||||||
if (oldExpiration == null) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
removeRevTimeout(key, oldExpiration)
|
|
||||||
dbEnvironment.sync()
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun putIfAbsent2(expiration: Long, key: Number640) {
|
|
||||||
var timeouts = timeoutMapRev[expiration]
|
|
||||||
//var timeouts : MutableSet<Number640> = timeoutMapRev[expiration] as MutableSet<Number640>
|
|
||||||
if (timeouts == null) {
|
|
||||||
timeouts = Collections.newSetFromMap(ConcurrentHashMap())
|
|
||||||
}
|
|
||||||
(timeouts as MutableSet).add(key)
|
|
||||||
timeoutMapRev[expiration] = timeouts
|
|
||||||
dbEnvironment.sync()
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun removeRevTimeout(key: Number640, expiration: Long?) {
|
|
||||||
val tmp = timeoutMapRev[expiration] as MutableSet<Number640>?
|
|
||||||
if (tmp != null) {
|
|
||||||
tmp.remove(key)
|
|
||||||
if (tmp.isEmpty()) {
|
|
||||||
timeoutMapRev.remove(expiration)
|
|
||||||
} else {
|
|
||||||
timeoutMapRev[expiration!!] = tmp
|
|
||||||
}
|
|
||||||
}
|
|
||||||
dbEnvironment.sync()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun updateResponsibilities(locationKey: Number160, peerId: Number160?): Boolean {
|
|
||||||
val oldPeerID = responsibilityMap.put(locationKey, peerId)
|
|
||||||
val hasChanged: Boolean
|
|
||||||
if (oldPeerID != null) {
|
|
||||||
if (oldPeerID == peerId) {
|
|
||||||
hasChanged = false
|
|
||||||
} else {
|
|
||||||
removeRevResponsibility(oldPeerID, locationKey)
|
|
||||||
hasChanged = true
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
hasChanged = true
|
|
||||||
}
|
|
||||||
var contentIDs: MutableSet<Number160>? = responsibilityMapRev[peerId] as MutableSet?
|
|
||||||
if (contentIDs == null) {
|
|
||||||
contentIDs = HashSet()
|
|
||||||
}
|
|
||||||
contentIDs.add(locationKey)
|
|
||||||
responsibilityMapRev[peerId] = contentIDs
|
|
||||||
dbEnvironment.sync()
|
|
||||||
return hasChanged
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun removeRevResponsibility(peerId: Number160, locationKey: Number160) {
|
|
||||||
val contentIDs = responsibilityMapRev[peerId] as MutableSet
|
|
||||||
if (contentIDs != null) {
|
|
||||||
contentIDs.remove(locationKey)
|
|
||||||
if (contentIDs.isEmpty()) {
|
|
||||||
responsibilityMapRev.remove(peerId)
|
|
||||||
} else {
|
|
||||||
responsibilityMapRev[peerId] = contentIDs
|
|
||||||
}
|
|
||||||
}
|
|
||||||
dbEnvironment.sync()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun protectDomain(key: Number320?, publicKey: PublicKey?): Boolean {
|
|
||||||
protectedDomainMap[key] = publicKey
|
|
||||||
dbEnvironment.sync()
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun storageCheckIntervalMillis(): Int {
|
|
||||||
return storageCheckIntervalMillis
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun isDomainProtectedByOthers(key: Number320?, publicKey: PublicKey?): Boolean {
|
|
||||||
val other = protectedDomainMap[key] ?: return false
|
|
||||||
return other != publicKey
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun removeTimeout(key: Number640) {
|
|
||||||
val expiration = timeoutMap.remove(key) ?: return
|
|
||||||
removeRevTimeout(key, expiration)
|
|
||||||
timeoutMapDB.sync()
|
|
||||||
dbEnvironment.sync()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun removeResponsibility(locationKey: Number160) {
|
|
||||||
val peerId = responsibilityMap.remove(locationKey)
|
|
||||||
if (peerId != null) {
|
|
||||||
removeRevResponsibility(peerId, locationKey)
|
|
||||||
}
|
|
||||||
dbEnvironment.sync()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun protectEntry(key: Number480?, publicKey: PublicKey?): Boolean {
|
|
||||||
protectedEntryMap[key] = publicKey
|
|
||||||
dbEnvironment.sync()
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun map(): NavigableMap<Number640, Data> {
|
|
||||||
val retVal = TreeMap<Number640, Data>()
|
|
||||||
for ((key, value) in dataMap) {
|
|
||||||
retVal[key] = value
|
|
||||||
}
|
|
||||||
|
|
||||||
return retVal
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun isEntryProtectedByOthers(key: Number480?, publicKey: PublicKey?): Boolean {
|
|
||||||
val other = protectedEntryMap[key] ?: return false
|
|
||||||
return other != publicKey
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun subMap(from: Number640?, to: Number640?, limit: Int, ascending: Boolean): NavigableMap<Number640, Data> {
|
|
||||||
val tmp = dataMap.subMap(from, true, to, true)
|
|
||||||
val descendingMap = TreeMap<Number640, Data>(tmp).descendingMap()
|
|
||||||
val retVal = TreeMap<Number640, Data>()
|
|
||||||
if (limit < 0) {
|
|
||||||
for ((key, value) in if (ascending) tmp else descendingMap) {
|
|
||||||
retVal[key] = value
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
val limit1 = Math.min(limit, tmp.size)
|
|
||||||
val iterator = if (ascending)
|
|
||||||
tmp.entries.iterator()
|
|
||||||
else
|
|
||||||
descendingMap.entries.iterator()
|
|
||||||
var i = 0
|
|
||||||
while (iterator.hasNext() && i < limit1) {
|
|
||||||
val entry = iterator.next()
|
|
||||||
retVal[entry.key] = entry.value
|
|
||||||
i++
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return retVal
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun subMapTimeout(to: Long): MutableCollection<Number640> {
|
|
||||||
val tmp = timeoutMapRev.subMap(0L, to)
|
|
||||||
val toRemove = ArrayList<Number640>()
|
|
||||||
for (set in tmp.values) {
|
|
||||||
toRemove.addAll(set)
|
|
||||||
}
|
|
||||||
return toRemove
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun close() {
|
|
||||||
dataMapDB.close()
|
|
||||||
timeoutMapDB.close()
|
|
||||||
timeoutMapRevDB.close()
|
|
||||||
protectedDomainMapDB.close()
|
|
||||||
protectedEntryMapDB.close()
|
|
||||||
responsibilityMapDB.close()
|
|
||||||
responsibilityMapRevDB.close()
|
|
||||||
dbEnvironment.close()
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,8 +0,0 @@
|
|||||||
package io.github.chronosx88.influence.helpers.actions;
|
|
||||||
|
|
||||||
public class NetworkActions {
|
|
||||||
public static final int CREATE_CHAT = 0x0;
|
|
||||||
public static final int TEXT_MESSAGE = 0x1;
|
|
||||||
public static final int JOIN_CHAT = 0x2;
|
|
||||||
public static final int NEXT_CHUNK_REF = 0x3;
|
|
||||||
}
|
|
@ -1,17 +0,0 @@
|
|||||||
package io.github.chronosx88.influence.helpers.actions;
|
|
||||||
|
|
||||||
public class UIActions {
|
|
||||||
public static final int BOOTSTRAP_NOT_SPECIFIED = 0x0;
|
|
||||||
public static final int NETWORK_ERROR = 0x1;
|
|
||||||
public static final int BOOTSTRAP_SUCCESS = 0x2;
|
|
||||||
public static final int PORT_FORWARDING_ERROR = 0x3;
|
|
||||||
public static final int RELAY_CONNECTION_ERROR = 0x4;
|
|
||||||
public static final int BOOTSTRAP_ERROR = 0x5;
|
|
||||||
public static final int NEW_CHAT = 0x6;
|
|
||||||
public static final int PEER_NOT_EXIST = 0x7;
|
|
||||||
public static final int SUCCESSFUL_CREATE_CHAT = 0x8;
|
|
||||||
public static final int MESSAGE_RECEIVED = 0x9;
|
|
||||||
public static final int NODE_IS_OFFLINE = 0x10;
|
|
||||||
public static final int USERNAME_ISNT_AVAILABLE = 0x11;
|
|
||||||
public static final int USERNAME_AVAILABLE = 0x12;
|
|
||||||
}
|
|
@ -1,16 +0,0 @@
|
|||||||
package io.github.chronosx88.influence.helpers.comparators;
|
|
||||||
|
|
||||||
import java.io.Serializable;
|
|
||||||
import java.util.Comparator;
|
|
||||||
|
|
||||||
import io.github.chronosx88.influence.helpers.Serializer;
|
|
||||||
|
|
||||||
public class CompareLong implements Comparator<byte[]>, Serializable {
|
|
||||||
@Override
|
|
||||||
public int compare(byte[] o1, byte[] o2) {
|
|
||||||
Serializer<Long> serializer = new Serializer<>();
|
|
||||||
Long num1 = serializer.deserialize(o1);
|
|
||||||
Long num2 = serializer.deserialize(o2);
|
|
||||||
return num1.compareTo(num2);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,20 +0,0 @@
|
|||||||
package io.github.chronosx88.influence.helpers.comparators;
|
|
||||||
|
|
||||||
import net.tomp2p.peers.Number640;
|
|
||||||
|
|
||||||
import java.io.Serializable;
|
|
||||||
import java.util.Comparator;
|
|
||||||
|
|
||||||
import io.github.chronosx88.influence.helpers.Serializer;
|
|
||||||
|
|
||||||
public class CompareNumber640 implements Comparator<byte[]>, Serializable {
|
|
||||||
@Override
|
|
||||||
public int compare(byte[] o1, byte[] o2) {
|
|
||||||
Serializer<Number640> serializer = new Serializer<>();
|
|
||||||
Number640 num1 = serializer.deserialize(o1);
|
|
||||||
Number640 num2 = serializer.deserialize(o2);
|
|
||||||
return num1.compareTo(num2);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
@ -18,7 +18,6 @@ import io.github.chronosx88.influence.helpers.AppHelper;
|
|||||||
import io.github.chronosx88.influence.helpers.KeyPairManager;
|
import io.github.chronosx88.influence.helpers.KeyPairManager;
|
||||||
import io.github.chronosx88.influence.helpers.LocalDBWrapper;
|
import io.github.chronosx88.influence.helpers.LocalDBWrapper;
|
||||||
import io.github.chronosx88.influence.helpers.ObservableUtils;
|
import io.github.chronosx88.influence.helpers.ObservableUtils;
|
||||||
import io.github.chronosx88.influence.helpers.P2PUtils;
|
|
||||||
import io.github.chronosx88.influence.helpers.actions.NetworkActions;
|
import io.github.chronosx88.influence.helpers.actions.NetworkActions;
|
||||||
import io.github.chronosx88.influence.helpers.actions.UIActions;
|
import io.github.chronosx88.influence.helpers.actions.UIActions;
|
||||||
import io.github.chronosx88.influence.models.JoinChatMessage;
|
import io.github.chronosx88.influence.models.JoinChatMessage;
|
||||||
@ -159,9 +158,9 @@ public class ChatLogic implements CoreContracts.IChatLogicContract {
|
|||||||
if(messages.size() > 10) {
|
if(messages.size() > 10) {
|
||||||
String messageID = UUID.randomUUID().toString();
|
String messageID = UUID.randomUUID().toString();
|
||||||
try {
|
try {
|
||||||
P2PUtils.put(chatEntity.chatID + "_messages" + chunkID, messageID, new Data(gson.toJson(new NextChunkReference(messageID, AppHelper.getPeerID(), AppHelper.getPeerID(), System.currentTimeMillis(), chatEntity.chunkCursor+1))));
|
int nextChunkCursor = chatEntity.chunkCursor + 1;
|
||||||
|
P2PUtils.put(chatEntity.chatID + "_messages" + chunkID, messageID, new Data(gson.toJson(new NextChunkReference(messageID, AppHelper.getPeerID(), AppHelper.getPeerID(), System.currentTimeMillis(), nextChunkCursor))));
|
||||||
P2PUtils.put(chatEntity.chatID + "_newMessage", null, new Data(messageID));
|
P2PUtils.put(chatEntity.chatID + "_newMessage", null, new Data(messageID));
|
||||||
LocalDBWrapper.updateChatEntity(chatEntity);
|
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
}
|
}
|
||||||
|
@ -45,12 +45,10 @@ import java.util.UUID;
|
|||||||
|
|
||||||
import io.github.chronosx88.influence.contracts.CoreContracts;
|
import io.github.chronosx88.influence.contracts.CoreContracts;
|
||||||
import io.github.chronosx88.influence.helpers.AppHelper;
|
import io.github.chronosx88.influence.helpers.AppHelper;
|
||||||
import io.github.chronosx88.influence.helpers.JVMShutdownHook;
|
|
||||||
import io.github.chronosx88.influence.helpers.KeyPairManager;
|
import io.github.chronosx88.influence.helpers.KeyPairManager;
|
||||||
import io.github.chronosx88.influence.helpers.LocalDBWrapper;
|
import io.github.chronosx88.influence.helpers.LocalDBWrapper;
|
||||||
import io.github.chronosx88.influence.helpers.NetworkHandler;
|
import io.github.chronosx88.influence.helpers.NetworkHandler;
|
||||||
import io.github.chronosx88.influence.helpers.ObservableUtils;
|
import io.github.chronosx88.influence.helpers.ObservableUtils;
|
||||||
import io.github.chronosx88.influence.helpers.P2PUtils;
|
|
||||||
import io.github.chronosx88.influence.helpers.StorageBerkeleyDB;
|
import io.github.chronosx88.influence.helpers.StorageBerkeleyDB;
|
||||||
import io.github.chronosx88.influence.helpers.actions.UIActions;
|
import io.github.chronosx88.influence.helpers.actions.UIActions;
|
||||||
import io.github.chronosx88.influence.models.ChatMetadata;
|
import io.github.chronosx88.influence.models.ChatMetadata;
|
||||||
|
@ -5,7 +5,6 @@ import io.github.chronosx88.influence.contracts.CoreContracts
|
|||||||
import io.github.chronosx88.influence.helpers.AppHelper
|
import io.github.chronosx88.influence.helpers.AppHelper
|
||||||
import io.github.chronosx88.influence.helpers.KeyPairManager
|
import io.github.chronosx88.influence.helpers.KeyPairManager
|
||||||
import io.github.chronosx88.influence.helpers.ObservableUtils
|
import io.github.chronosx88.influence.helpers.ObservableUtils
|
||||||
import io.github.chronosx88.influence.helpers.P2PUtils
|
|
||||||
import io.github.chronosx88.influence.helpers.actions.UIActions
|
import io.github.chronosx88.influence.helpers.actions.UIActions
|
||||||
import net.tomp2p.peers.Number640
|
import net.tomp2p.peers.Number640
|
||||||
import net.tomp2p.storage.Data
|
import net.tomp2p.storage.Data
|
||||||
|
@ -1,46 +0,0 @@
|
|||||||
package io.github.chronosx88.influence.models;
|
|
||||||
|
|
||||||
import java.io.Serializable;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Абстрактный класс-модель для любых сообщений, которые передаются по DHT-сети
|
|
||||||
*/
|
|
||||||
public class BasicNetworkMessage implements Serializable {
|
|
||||||
private int action;
|
|
||||||
private String messageID;
|
|
||||||
private String senderID;
|
|
||||||
private String username;
|
|
||||||
private long timestamp;
|
|
||||||
|
|
||||||
public BasicNetworkMessage() {
|
|
||||||
//
|
|
||||||
}
|
|
||||||
|
|
||||||
public BasicNetworkMessage(int action, String messageID, String senderID, String username, long timestamp) {
|
|
||||||
this.action = action;
|
|
||||||
this.senderID = senderID;
|
|
||||||
this.username = username;
|
|
||||||
this.messageID = messageID;
|
|
||||||
this.timestamp = timestamp;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getAction() {
|
|
||||||
return action;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getSenderID() {
|
|
||||||
return senderID;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getUsername() {
|
|
||||||
return username;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getMessageID() {
|
|
||||||
return messageID;
|
|
||||||
}
|
|
||||||
|
|
||||||
public long getTimestamp() {
|
|
||||||
return timestamp;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,21 +0,0 @@
|
|||||||
package io.github.chronosx88.influence.models;
|
|
||||||
|
|
||||||
import java.io.Serializable;
|
|
||||||
|
|
||||||
public class ChatMember implements Serializable {
|
|
||||||
private String username;
|
|
||||||
private String peerID;
|
|
||||||
|
|
||||||
public ChatMember(String username, String peerID) {
|
|
||||||
this.username = username;
|
|
||||||
this.peerID = peerID;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getUsername() {
|
|
||||||
return username;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getPeerID() {
|
|
||||||
return peerID;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,40 +0,0 @@
|
|||||||
package io.github.chronosx88.influence.models;
|
|
||||||
|
|
||||||
import java.io.Serializable;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
|
|
||||||
public class ChatMetadata implements Serializable {
|
|
||||||
private String name;
|
|
||||||
private ArrayList<String> admins;
|
|
||||||
private ArrayList<String> banned;
|
|
||||||
|
|
||||||
public ChatMetadata(String name, ArrayList<String> admins, ArrayList<String> banned) {
|
|
||||||
this.name = name;
|
|
||||||
this.admins = admins;
|
|
||||||
this.banned = banned;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getName() {
|
|
||||||
return name;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ArrayList<String> getAdmins() {
|
|
||||||
return admins;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ArrayList<String> getBanned() {
|
|
||||||
return banned;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setName(String name) {
|
|
||||||
this.name = name;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setAdmins(ArrayList<String> admins) {
|
|
||||||
this.admins = admins;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setBanned(ArrayList<String> banned) {
|
|
||||||
this.banned = banned;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,19 +0,0 @@
|
|||||||
package io.github.chronosx88.influence.models;
|
|
||||||
|
|
||||||
import java.io.Serializable;
|
|
||||||
import java.util.UUID;
|
|
||||||
|
|
||||||
import io.github.chronosx88.influence.helpers.actions.NetworkActions;
|
|
||||||
|
|
||||||
public class JoinChatMessage extends BasicNetworkMessage implements Serializable {
|
|
||||||
private String chatID;
|
|
||||||
|
|
||||||
public JoinChatMessage(String senderID, String username, String chatID, long timestamp) {
|
|
||||||
super(NetworkActions.JOIN_CHAT, UUID.randomUUID().toString(), senderID, username, timestamp);
|
|
||||||
this.chatID = chatID;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getChatID() {
|
|
||||||
return chatID;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,24 +0,0 @@
|
|||||||
package io.github.chronosx88.influence.models;
|
|
||||||
|
|
||||||
import java.io.Serializable;
|
|
||||||
|
|
||||||
import io.github.chronosx88.influence.helpers.actions.NetworkActions;
|
|
||||||
|
|
||||||
public class NewChatRequestMessage extends BasicNetworkMessage implements Serializable {
|
|
||||||
private String chatID;
|
|
||||||
private int chunkID;
|
|
||||||
|
|
||||||
public NewChatRequestMessage(String messageID, String chatID, String senderID, String username, long timestamp, int chunkID) {
|
|
||||||
super(NetworkActions.CREATE_CHAT, messageID, senderID, username, timestamp);
|
|
||||||
this.chatID = chatID;
|
|
||||||
this.chunkID = chunkID;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getChatID() {
|
|
||||||
return chatID;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getChunkID() {
|
|
||||||
return chunkID;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,18 +0,0 @@
|
|||||||
package io.github.chronosx88.influence.models;
|
|
||||||
|
|
||||||
import java.io.Serializable;
|
|
||||||
|
|
||||||
import io.github.chronosx88.influence.helpers.actions.NetworkActions;
|
|
||||||
|
|
||||||
public class NextChunkReference extends BasicNetworkMessage implements Serializable {
|
|
||||||
private int nextChunkID;
|
|
||||||
|
|
||||||
public NextChunkReference(String messageID, String senderID, String username, long timestamp, int nextChunkID) {
|
|
||||||
super(NetworkActions.NEXT_CHUNK_REF, messageID, senderID, username, timestamp);
|
|
||||||
this.nextChunkID = nextChunkID;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getNextChunkID() {
|
|
||||||
return nextChunkID;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,10 +0,0 @@
|
|||||||
package io.github.chronosx88.influence.models
|
|
||||||
|
|
||||||
import net.tomp2p.peers.PeerAddress
|
|
||||||
|
|
||||||
import java.io.Serializable
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Класс-модель публичного профиля для размещения в DHT-сети
|
|
||||||
*/
|
|
||||||
data class PublicUserProfile(var userName: String?, var peerAddress: PeerAddress?) : Serializable
|
|
@ -1,30 +0,0 @@
|
|||||||
package io.github.chronosx88.influence.models;
|
|
||||||
|
|
||||||
import java.io.Serializable;
|
|
||||||
|
|
||||||
import io.github.chronosx88.influence.helpers.actions.NetworkActions;
|
|
||||||
|
|
||||||
public class TextMessage extends BasicNetworkMessage implements Serializable {
|
|
||||||
private String chatID; // Chat ID
|
|
||||||
private String text; // Message text
|
|
||||||
private boolean isRead; // Message Read Indicator
|
|
||||||
|
|
||||||
public TextMessage(String senderID, String messageID, String chatID, String username, long timestamp, String text, boolean isRead) {
|
|
||||||
super(NetworkActions.TEXT_MESSAGE, messageID, senderID, username, timestamp);
|
|
||||||
this.chatID = chatID;
|
|
||||||
this.text = text;
|
|
||||||
this.isRead = isRead;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getChatID() {
|
|
||||||
return chatID;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getText() {
|
|
||||||
return text;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isRead() {
|
|
||||||
return isRead;
|
|
||||||
}
|
|
||||||
}
|
|
@ -14,14 +14,14 @@ public interface ChatDao {
|
|||||||
@Insert(onConflict = OnConflictStrategy.IGNORE)
|
@Insert(onConflict = OnConflictStrategy.IGNORE)
|
||||||
void addChat(ChatEntity chatEntity);
|
void addChat(ChatEntity chatEntity);
|
||||||
|
|
||||||
@Query("DELETE FROM chats WHERE chatID = :chatID")
|
@Query("DELETE FROM chats WHERE jid = :jid")
|
||||||
void deleteChat(String chatID);
|
void deleteChat(String jid);
|
||||||
|
|
||||||
@Query("SELECT * FROM chats")
|
@Query("SELECT * FROM chats")
|
||||||
List<ChatEntity> getAllChats();
|
List<ChatEntity> getAllChats();
|
||||||
|
|
||||||
@Query("SELECT * FROM chats WHERE chatID = :chatID")
|
@Query("SELECT * FROM chats WHERE jid = :jid")
|
||||||
List<ChatEntity> getChatByChatID(String chatID);
|
List<ChatEntity> getChatByChatID(String jid);
|
||||||
|
|
||||||
@Update
|
@Update
|
||||||
void updateChat(ChatEntity chat);
|
void updateChat(ChatEntity chat);
|
||||||
|
@ -12,19 +12,19 @@ import io.github.chronosx88.influence.models.roomEntities.MessageEntity;
|
|||||||
@Dao
|
@Dao
|
||||||
public interface MessageDao {
|
public interface MessageDao {
|
||||||
@Insert(onConflict = OnConflictStrategy.IGNORE)
|
@Insert(onConflict = OnConflictStrategy.IGNORE)
|
||||||
void insertMessage(MessageEntity chatModel);
|
long insertMessage(MessageEntity chatModel);
|
||||||
|
|
||||||
@Query("DELETE FROM messages WHERE messageID = :messageID")
|
@Query("DELETE FROM messages WHERE messageID = :messageID")
|
||||||
void deleteMessage(String messageID);
|
void deleteMessage(String messageID);
|
||||||
|
|
||||||
@Query("DELETE FROM messages WHERE chatID = :chatID")
|
@Query("DELETE FROM messages WHERE jid = :jid")
|
||||||
void deleteMessagesByChatID(String chatID);
|
void deleteMessagesByChatID(String jid);
|
||||||
|
|
||||||
@Query("SELECT * FROM messages WHERE chatID = :chatID")
|
@Query("SELECT * FROM messages WHERE jid = :jid")
|
||||||
List<MessageEntity> getMessagesByChatID(String chatID);
|
List<MessageEntity> getMessagesByChatID(String jid);
|
||||||
|
|
||||||
@Query("SELECT * FROM messages WHERE messageID = :messageID")
|
@Query("SELECT * FROM messages WHERE messageID = :messageID")
|
||||||
List<MessageEntity> getMessageByID(String messageID);
|
List<MessageEntity> getMessageByID(long messageID);
|
||||||
|
|
||||||
@Update
|
@Update
|
||||||
void updateMessage(MessageEntity message);
|
void updateMessage(MessageEntity message);
|
||||||
|
@ -1,7 +1,5 @@
|
|||||||
package io.github.chronosx88.influence.models.roomEntities;
|
package io.github.chronosx88.influence.models.roomEntities;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.room.ColumnInfo;
|
import androidx.room.ColumnInfo;
|
||||||
import androidx.room.Entity;
|
import androidx.room.Entity;
|
||||||
@ -9,19 +7,11 @@ import androidx.room.PrimaryKey;
|
|||||||
|
|
||||||
@Entity(tableName = "chats")
|
@Entity(tableName = "chats")
|
||||||
public class ChatEntity {
|
public class ChatEntity {
|
||||||
@PrimaryKey @NonNull public String chatID;
|
@PrimaryKey @NonNull public String jid;
|
||||||
@ColumnInfo public String name;
|
@ColumnInfo public String chatName;
|
||||||
@ColumnInfo public String metadataRef;
|
|
||||||
@ColumnInfo public String membersRef;
|
|
||||||
@ColumnInfo public ArrayList<String> bannedUsers;
|
|
||||||
@ColumnInfo public int chunkCursor;
|
|
||||||
|
|
||||||
public ChatEntity(@NonNull String chatID, String name, String metadataRef, String membersRef, ArrayList<String> bannedUsers, int chunkCursor) {
|
public ChatEntity(@NonNull String jid, String chatName) {
|
||||||
this.chatID = chatID;
|
this.jid = jid;
|
||||||
this.name = name;
|
this.chatName = chatName;
|
||||||
this.metadataRef = metadataRef;
|
|
||||||
this.membersRef = membersRef;
|
|
||||||
this.bannedUsers = bannedUsers;
|
|
||||||
this.chunkCursor = chunkCursor;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -7,22 +7,17 @@ import androidx.room.PrimaryKey;
|
|||||||
|
|
||||||
@Entity(tableName = "messages")
|
@Entity(tableName = "messages")
|
||||||
public class MessageEntity {
|
public class MessageEntity {
|
||||||
@PrimaryKey @NonNull public String messageID; // Global message ID
|
@PrimaryKey(autoGenerate = true) public long messageID; // Global message ID
|
||||||
@ColumnInfo public int type; // Message type
|
@ColumnInfo public String jid; // Chat ID
|
||||||
@ColumnInfo public String chatID; // Chat ID
|
@ColumnInfo public String senderJid;
|
||||||
@ColumnInfo public String senderID; // PeerID
|
|
||||||
@ColumnInfo public String username; // Username
|
|
||||||
@ColumnInfo public long timestamp; // Timestamp
|
@ColumnInfo public long timestamp; // Timestamp
|
||||||
@ColumnInfo public String text; // Message text
|
@ColumnInfo public String text; // Message text
|
||||||
@ColumnInfo public boolean isSent; // Send status indicator
|
@ColumnInfo public boolean isSent; // Send status indicator
|
||||||
@ColumnInfo public boolean isRead; // Message Read Indicator
|
@ColumnInfo public boolean isRead; // Message Read Indicator
|
||||||
|
|
||||||
public MessageEntity(int type, String messageID, String chatID, String senderID, String username, long timestamp, String text, boolean isSent, boolean isRead) {
|
public MessageEntity(String jid, String senderJid, long timestamp, String text, boolean isSent, boolean isRead) {
|
||||||
this.type = type;
|
this.jid = jid;
|
||||||
this.messageID = messageID;
|
this.senderJid = senderJid;
|
||||||
this.chatID = chatID;
|
|
||||||
this.senderID = senderID;
|
|
||||||
this.username = username;
|
|
||||||
this.timestamp = timestamp;
|
this.timestamp = timestamp;
|
||||||
this.text = text;
|
this.text = text;
|
||||||
this.isSent = isSent;
|
this.isSent = isSent;
|
||||||
|
@ -0,0 +1,157 @@
|
|||||||
|
/*
|
||||||
|
* 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.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;
|
||||||
|
import android.widget.Button;
|
||||||
|
import android.widget.EditText;
|
||||||
|
import android.widget.Toast;
|
||||||
|
|
||||||
|
import androidx.appcompat.app.AppCompatActivity;
|
||||||
|
|
||||||
|
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;
|
||||||
|
|
||||||
|
public class LoginActivity extends AppCompatActivity implements CoreContracts.ILoginViewContract {
|
||||||
|
private EditText jidEditText;
|
||||||
|
private EditText passwordEditText;
|
||||||
|
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);
|
||||||
|
signInButton = findViewById(R.id.sign_in_button);
|
||||||
|
progressDialog = new ProgressDialog(LoginActivity.this);
|
||||||
|
signInButton.setOnClickListener((v) -> {
|
||||||
|
if(checkLoginCredentials()) {
|
||||||
|
saveLoginCredentials();
|
||||||
|
doLogin();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void loadingScreen(boolean state) {
|
||||||
|
if(state)
|
||||||
|
progressDialog.show();
|
||||||
|
else
|
||||||
|
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);
|
||||||
|
passwordEditText.setError("Invalid JID/Password");
|
||||||
|
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);
|
||||||
|
|
||||||
|
String jid = jidEditText.getText().toString();
|
||||||
|
String password = passwordEditText.getText().toString();
|
||||||
|
|
||||||
|
boolean cancel = false;
|
||||||
|
View focusView = null;
|
||||||
|
|
||||||
|
if (!TextUtils.isEmpty(password) && !isPasswordValid(password)) {
|
||||||
|
passwordEditText.setError("Invalid password");
|
||||||
|
focusView = passwordEditText;
|
||||||
|
cancel = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (TextUtils.isEmpty(jid)) {
|
||||||
|
jidEditText.setError("Field is required!");
|
||||||
|
focusView = jidEditText;
|
||||||
|
cancel = true;
|
||||||
|
} else if (!isEmailValid(jid)) {
|
||||||
|
jidEditText.setError("Invalid JID");
|
||||||
|
focusView = jidEditText;
|
||||||
|
cancel = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cancel) {
|
||||||
|
focusView.requestFocus();
|
||||||
|
return false;
|
||||||
|
} else {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isEmailValid(String email) {
|
||||||
|
return email.contains("@");
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isPasswordValid(String password) {
|
||||||
|
return password.length() > 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void saveLoginCredentials() {
|
||||||
|
AppHelper.getPreferences().edit()
|
||||||
|
.putString("jid", jidEditText.getText().toString())
|
||||||
|
.putString("pass", passwordEditText.getText().toString())
|
||||||
|
.putBoolean("logged_in", true)
|
||||||
|
.apply();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void doLogin() {
|
||||||
|
loadingScreen(true);
|
||||||
|
startService(new Intent(this, XMPPConnectionService.class));
|
||||||
|
}
|
||||||
|
}
|
@ -32,7 +32,7 @@ import io.github.chronosx88.influence.views.fragments.ChatListFragment;
|
|||||||
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 IObserver, CoreContracts.IMainViewContract {
|
public class MainActivity extends AppCompatActivity implements CoreContracts.IMainViewContract {
|
||||||
|
|
||||||
private CoreContracts.IMainPresenterContract presenter;
|
private CoreContracts.IMainPresenterContract presenter;
|
||||||
private ProgressDialog progressDialog;
|
private ProgressDialog progressDialog;
|
||||||
@ -88,7 +88,7 @@ public class MainActivity extends AppCompatActivity implements IObserver, CoreCo
|
|||||||
.commit();
|
.commit();
|
||||||
|
|
||||||
presenter = new MainPresenter(this);
|
presenter = new MainPresenter(this);
|
||||||
AppHelper.getObservable().register(this);
|
|
||||||
|
|
||||||
progressDialog = new ProgressDialog(MainActivity.this, R.style.AlertDialogTheme);
|
progressDialog = new ProgressDialog(MainActivity.this, R.style.AlertDialogTheme);
|
||||||
progressDialog.setCancelable(false);
|
progressDialog.setCancelable(false);
|
||||||
@ -101,54 +101,6 @@ public class MainActivity extends AppCompatActivity implements IObserver, CoreCo
|
|||||||
protected void onDestroy() {
|
protected void onDestroy() {
|
||||||
super.onDestroy();
|
super.onDestroy();
|
||||||
presenter.onDestroy();
|
presenter.onDestroy();
|
||||||
AppHelper.getObservable().unregister(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void handleEvent(JsonObject object) {
|
|
||||||
switch (object.get("action").getAsInt()) {
|
|
||||||
case UIActions.BOOTSTRAP_NOT_SPECIFIED: {
|
|
||||||
runOnUiThread(() -> {
|
|
||||||
progressDialog.dismiss();
|
|
||||||
Toast.makeText(this, "Bootstrap-нода не указана. Прерываю подключение к сети...", Toast.LENGTH_LONG).show();
|
|
||||||
});
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case UIActions.NETWORK_ERROR: {
|
|
||||||
runOnUiThread(() -> {
|
|
||||||
progressDialog.dismiss();
|
|
||||||
Toast.makeText(this, "Ошибка сети. Возможно, нода недоступна, или у вас отсутствует Интернет.", Toast.LENGTH_LONG).show();
|
|
||||||
});
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case UIActions.BOOTSTRAP_SUCCESS: {
|
|
||||||
runOnUiThread(() -> {
|
|
||||||
progressDialog.dismiss();
|
|
||||||
Toast.makeText(this, "Нода успешно запущена!", Toast.LENGTH_LONG).show();
|
|
||||||
});
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case UIActions.PORT_FORWARDING_ERROR: {
|
|
||||||
runOnUiThread(() -> {
|
|
||||||
Toast.makeText(this, "Проблемы с пробросом портов. Возможно, у вас не настроен uPnP.", Toast.LENGTH_LONG).show();
|
|
||||||
});
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case UIActions.BOOTSTRAP_ERROR: {
|
|
||||||
runOnUiThread(() -> {
|
|
||||||
progressDialog.dismiss();
|
|
||||||
Toast.makeText(this, "Не удалось подключиться к бутстрап-ноде.", Toast.LENGTH_LONG).show();
|
|
||||||
});
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case UIActions.RELAY_CONNECTION_ERROR: {
|
|
||||||
runOnUiThread(() -> {
|
|
||||||
progressDialog.dismiss();
|
|
||||||
Toast.makeText(this, "Не удалось подключиться к relay-ноде.", Toast.LENGTH_LONG).show();
|
|
||||||
});
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
68
app/src/main/res/layout/activity_login.xml
Normal file
68
app/src/main/res/layout/activity_login.xml
Normal file
@ -0,0 +1,68 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!--
|
||||||
|
~ 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/>.
|
||||||
|
-->
|
||||||
|
|
||||||
|
<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"
|
||||||
|
android:orientation="vertical"
|
||||||
|
tools:context=".views.LoginActivity">
|
||||||
|
|
||||||
|
<com.google.android.material.textfield.TextInputLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content">
|
||||||
|
|
||||||
|
<EditText
|
||||||
|
android:id="@+id/login_jid"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:hint="JabberID"
|
||||||
|
android:inputType="textEmailAddress"
|
||||||
|
android:maxLines="1"
|
||||||
|
android:singleLine="true" />
|
||||||
|
|
||||||
|
</com.google.android.material.textfield.TextInputLayout>
|
||||||
|
|
||||||
|
<com.google.android.material.textfield.TextInputLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content">
|
||||||
|
|
||||||
|
<EditText
|
||||||
|
android:id="@+id/login_password"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:hint="Password"
|
||||||
|
android:imeActionLabel="SIGN IN"
|
||||||
|
android:imeOptions="actionUnspecified"
|
||||||
|
android:inputType="textPassword"
|
||||||
|
android:maxLines="1"
|
||||||
|
android:singleLine="true" />
|
||||||
|
|
||||||
|
</com.google.android.material.textfield.TextInputLayout>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:id="@+id/sign_in_button"
|
||||||
|
style="?android:textAppearanceSmall"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="16dp"
|
||||||
|
android:text="SIGN IN"
|
||||||
|
android:textStyle="bold" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
Loading…
Reference in New Issue
Block a user