[BIG COMMIT] Made a username system and slightly fixed the signature system. Refactor.

This commit is contained in:
ChronosX88 2019-04-16 14:52:41 +04:00
parent 1eb1f0c0bc
commit 99409e1a13
44 changed files with 565 additions and 381 deletions

View File

@ -29,6 +29,7 @@ android {
exclude 'META-INF/io.netty.versions.properties' exclude 'META-INF/io.netty.versions.properties'
exclude 'LICENSE-EPL-1.0.txt' exclude 'LICENSE-EPL-1.0.txt'
exclude 'LICENSE-EDL-1.0.txt' exclude 'LICENSE-EDL-1.0.txt'
exclude 'META-INF/atomicfu.kotlin_module'
} }
compileOptions { compileOptions {
@ -40,20 +41,19 @@ android {
dependencies { 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.constraintlayout:constraintlayout:2.0.0-alpha3'
implementation "androidx.room:room-runtime:2.1.0-alpha04" implementation "androidx.room:room-runtime:2.1.0-alpha04"
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 group: 'com.h2database', name: 'h2-mvstore', version: '1.4.197'
implementation 'net.tomp2p:tomp2p-all:5.0-Beta8' implementation 'net.tomp2p:tomp2p-all:5.0-Beta8'
implementation 'com.google.android.material:material:1.1.0-alpha04' implementation 'com.google.android.material:material:1.1.0-alpha04'
implementation 'androidx.preference:preference:1.1.0-alpha03' implementation 'androidx.preference:preference:1.1.0-alpha03'
implementation 'com.google.code.gson:gson:2.8.5' implementation 'com.google.code.gson:gson:2.8.5'
implementation group: 'org.springframework.security', name: 'spring-security-crypto', version: '3.1.0.RELEASE'
implementation 'de.hdodenhof:circleimageview:3.0.0' implementation 'de.hdodenhof:circleimageview:3.0.0'
implementation group: 'org.objenesis', name: 'objenesis', version: '2.6'
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version" implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
implementation 'com.android.support:multidex:1.0.3' implementation 'com.android.support:multidex:1.0.3'
implementation "org.jetbrains.anko:anko:0.10.8"
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.2.0'
implementation 'com.esotericsoftware:kryo:5.0.0-RC1'
} }
repositories { repositories {
mavenCentral() mavenCentral()

View File

@ -3,14 +3,16 @@
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
package="io.github.chronosx88.influence"> package="io.github.chronosx88.influence">
<uses-permission android:name="android.permission.INTERNET" />
<application <application
android:name=".helpers.AppHelper"
android:allowBackup="true" android:allowBackup="true"
android:icon="@mipmap/ic_launcher" android:icon="@mipmap/ic_launcher"
android:label="@string/app_name" android:label="@string/app_name"
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">
android:name=".helpers.AppHelper">
<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" />
@ -20,7 +22,7 @@
</activity> </activity>
<activity <activity
android:name=".views.ChatActivity" android:name=".views.ChatActivity"
android:theme="@style/NoWindowActionBar"/> android:theme="@style/NoWindowActionBar" />
</application> </application>
<uses-permission android:name="android.permission.INTERNET"/>
</manifest> </manifest>

View File

@ -0,0 +1,91 @@
package io.github.chronosx88.influence.contracts
import android.content.Intent
import android.view.MenuItem
import io.github.chronosx88.influence.helpers.ChatListAdapter
import io.github.chronosx88.influence.models.roomEntities.ChatEntity
import io.github.chronosx88.influence.models.roomEntities.MessageEntity
interface CoreContracts {
// -----ChatList-----
interface IChatListLogicContract {
fun loadAllChats(): List<ChatEntity>
}
interface IChatListPresenterContract {
fun updateChatList()
fun openChat(chatID: String)
fun onContextItemSelected(item: MenuItem)
}
interface IChatListViewContract {
fun setRecycleAdapter(adapter: ChatListAdapter)
fun startActivity(intent: Intent)
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-----
interface IMainLogicContract {
fun initPeer()
fun shutdownPeer()
}
interface IMainPresenterContract {
fun initPeer()
fun onDestroy()
}
interface IMainViewContract//
// -----ChatActivity-----
interface IChatLogicContract {
fun sendMessage(message: MessageEntity)
fun stopTrackingForNewMsgs()
}
interface IChatPresenterContract {
fun sendMessage(text: String)
fun updateAdapter()
fun onDestroy()
}
interface IChatViewContract {
fun updateMessageList(message: MessageEntity)
fun updateMessageList(messages: List<MessageEntity>)
}
// -----SettingsFragment-----
interface ISettingsLogic {
fun checkUsernameExists(username: String) : Boolean
}
interface ISettingsPresenter {
fun updateUsername(username: String)
}
interface ISettingsView {
fun loadingScreen(state: Boolean)
fun showMessage(message: String)
}
}

View File

@ -1,8 +0,0 @@
package io.github.chronosx88.influence.contracts.chatactivity;
import io.github.chronosx88.influence.models.roomEntities.MessageEntity;
public interface IChatLogicContract {
void sendMessage(MessageEntity message);
void stopTrackingForNewMsgs();
}

View File

@ -1,7 +0,0 @@
package io.github.chronosx88.influence.contracts.chatactivity;
public interface IChatPresenterContract {
void sendMessage(String text);
void updateAdapter();
void onDestroy();
}

View File

@ -1,10 +0,0 @@
package io.github.chronosx88.influence.contracts.chatactivity;
import java.util.List;
import io.github.chronosx88.influence.models.roomEntities.MessageEntity;
public interface IChatViewContract {
void updateMessageList(MessageEntity message);
void updateMessageList(List<MessageEntity> messages);
}

View File

@ -1,9 +0,0 @@
package io.github.chronosx88.influence.contracts.chatlist;
import java.util.List;
import io.github.chronosx88.influence.models.roomEntities.ChatEntity;
public interface IChatListLogicContract {
List<ChatEntity> loadAllChats();
}

View File

@ -1,9 +0,0 @@
package io.github.chronosx88.influence.contracts.chatlist;
import android.view.MenuItem;
public interface IChatListPresenterContract {
void updateChatList();
void openChat(String chatID);
void onContextItemSelected(MenuItem item);
}

View File

@ -1,14 +0,0 @@
package io.github.chronosx88.influence.contracts.chatlist;
import android.content.Intent;
import java.util.List;
import io.github.chronosx88.influence.helpers.ChatListAdapter;
import io.github.chronosx88.influence.models.roomEntities.ChatEntity;
public interface IChatListViewContract {
void setRecycleAdapter(ChatListAdapter adapter);
void startActivity(Intent intent);
void updateChatList(ChatListAdapter adapter, List<ChatEntity> chats);
}

View File

@ -1,6 +0,0 @@
package io.github.chronosx88.influence.contracts.mainactivity;
public interface IMainLogicContract {
void initPeer();
void shutdownPeer();
}

View File

@ -1,6 +0,0 @@
package io.github.chronosx88.influence.contracts.mainactivity;
public interface IMainPresenterContract {
void initPeer();
void onDestroy();
}

View File

@ -1,5 +0,0 @@
package io.github.chronosx88.influence.contracts.mainactivity;
public interface IMainViewContract {
//
}

View File

@ -1,5 +0,0 @@
package io.github.chronosx88.influence.contracts.startchat;
public interface IStartChatLogicContract {
void sendStartChatMessage(String peerID);
}

View File

@ -1,5 +0,0 @@
package io.github.chronosx88.influence.contracts.startchat;
public interface IStartChatPresenterContract {
void startChatWithPeer(String peerID);
}

View File

@ -1,6 +0,0 @@
package io.github.chronosx88.influence.contracts.startchat;
public interface IStartChatViewContract {
void showMessage(String message);
void showProgressDialog(boolean enabled);
}

View File

@ -2,6 +2,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 net.tomp2p.dht.PeerDHT; import net.tomp2p.dht.PeerDHT;
@ -10,7 +11,7 @@ import androidx.room.Room;
import io.github.chronosx88.influence.observable.MainObservable; import io.github.chronosx88.influence.observable.MainObservable;
/** /**
* Extended Application class which designed for 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 {
@ -20,6 +21,8 @@ public class AppHelper extends MultiDexApplication {
private static PeerDHT peerDHT; private static PeerDHT peerDHT;
private static RoomHelper chatDB; private static RoomHelper chatDB;
private static NetworkHandler networkHandler; private static NetworkHandler networkHandler;
private static String username = "";
private static SharedPreferences preferences;
@Override @Override
public void onCreate() { public void onCreate() {
@ -29,10 +32,13 @@ 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);
} }
public static void storePeerID(String peerID1) { peerID = peerID1; } 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 void storePeerDHT(PeerDHT peerDHT1) { peerDHT = peerDHT1; }
public static Context getContext() { public static Context getContext() {
@ -43,9 +49,15 @@ public class AppHelper extends MultiDexApplication {
public static String getPeerID() { return peerID; } public static String getPeerID() { return peerID; }
public static String getUsername() { return username; }
public static PeerDHT getPeerDHT() { return peerDHT; } 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 void initNetworkHandler() { networkHandler = new NetworkHandler(); }
public static SharedPreferences getPreferences() {
return preferences;
}
} }

View File

@ -7,6 +7,7 @@ import io.netty.buffer.Unpooled
import net.tomp2p.connection.SignatureFactory import net.tomp2p.connection.SignatureFactory
import net.tomp2p.storage.AlternativeCompositeByteBuf import net.tomp2p.storage.AlternativeCompositeByteBuf
import net.tomp2p.storage.Data import net.tomp2p.storage.Data
import org.mapdb.DataInput2
import java.io.* import java.io.*
import java.nio.ByteBuffer import java.nio.ByteBuffer
import java.security.InvalidKeyException import java.security.InvalidKeyException
@ -27,7 +28,7 @@ class DataSerializerEx(private val signatureFactory: SignatureFactory) : EntryBi
data = Data.decodeHeader(buf, signatureFactory) data = Data.decodeHeader(buf, signatureFactory)
} }
val len = data.length() val len = data.length()
val me = ByteArray(len) var me = ByteArray(len)
try { try {
inputStream.read(me) inputStream.read(me)
} catch (e: IOException) { } catch (e: IOException) {
@ -39,6 +40,14 @@ class DataSerializerEx(private val signatureFactory: SignatureFactory) : EntryBi
if (!retVal) { if (!retVal) {
Log.e(LOG_TAG, "# ERROR: Data could not be deserialized!") Log.e(LOG_TAG, "# ERROR: Data could not be deserialized!")
} }
val dataInput = DataInputStream(inputStream)
me = ByteArray(signatureFactory.signatureSize())
dataInput.readFully(me)
buf = Unpooled.wrappedBuffer(me)
retVal = data.decodeDone(buf, signatureFactory);
if(!retVal) {
throw IOException("signature could not be read")
}
return data return data
} }

View File

@ -25,7 +25,6 @@ public class KeyPairManager {
} }
public KeyPair getKeyPair(String keyPairName) { public KeyPair getKeyPair(String keyPairName) {
KeyPair keyPair = null;
keyPairName = keyPairName + ".kp"; keyPairName = keyPairName + ".kp";
File keyPairFile = new File(keyPairDir, keyPairName); File keyPairFile = new File(keyPairDir, keyPairName);
if (!keyPairFile.exists()) { if (!keyPairFile.exists()) {
@ -52,7 +51,9 @@ public class KeyPairManager {
KeyPair keyPair = null; KeyPair keyPair = null;
try { try {
keyPairFile.createNewFile(); keyPairFile.createNewFile();
keyPair = KeyPairGenerator.getInstance("RSA").generateKeyPair(); KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");
keyPairGenerator.initialize(1024);
keyPair = keyPairGenerator.generateKeyPair();
FileOutputStream outputStream = new FileOutputStream(keyPairFile); FileOutputStream outputStream = new FileOutputStream(keyPairFile);
outputStream.write(serializer.serialize(keyPair)); outputStream.write(serializer.serialize(keyPair));
outputStream.close(); outputStream.close();

View File

@ -58,7 +58,7 @@ public class NetworkHandler implements INetworkObserver {
LocalDBWrapper.createChatEntry( LocalDBWrapper.createChatEntry(
newChatRequestMessage.getChatID(), newChatRequestMessage.getChatID(),
newChatRequestMessage.getSenderID(), newChatRequestMessage.getUsername(),
newChatRequestMessage.getChatID() + "_metadata", newChatRequestMessage.getChatID() + "_metadata",
newChatRequestMessage.getChatID() + "_members", newChatRequestMessage.getChatID() + "_members",
newChatRequestMessage.getChunkID() newChatRequestMessage.getChunkID()
@ -67,7 +67,7 @@ public class NetworkHandler implements INetworkObserver {
P2PUtils.remove(AppHelper.getPeerID() + "_pendingChats", newChatRequestMessage.getChatID()); P2PUtils.remove(AppHelper.getPeerID() + "_pendingChats", newChatRequestMessage.getChatID());
String messageID = UUID.randomUUID().toString(); String messageID = UUID.randomUUID().toString();
try { try {
P2PUtils.put(newChatRequestMessage.getChatID() + "_messages", messageID, new Data(gson.toJson(new JoinChatMessage(AppHelper.getPeerID(), AppHelper.getPeerID(), newChatRequestMessage.getChatID(), System.currentTimeMillis()))).protectEntry(keyPairManager.openMainKeyPair())); 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) { } catch (IOException e) {
e.printStackTrace(); e.printStackTrace();
} }
@ -75,6 +75,4 @@ public class NetworkHandler implements INetworkObserver {
} }
} }
} }
} }

View File

@ -13,34 +13,14 @@ import net.tomp2p.peers.Number640;
import net.tomp2p.peers.PeerAddress; import net.tomp2p.peers.PeerAddress;
import net.tomp2p.storage.Data; import net.tomp2p.storage.Data;
import java.security.KeyPair;
import java.util.Map; import java.util.Map;
public class P2PUtils { public class P2PUtils {
private static Gson gson = new Gson(); private static Gson gson = new Gson();
private static PeerDHT peerDHT = AppHelper.getPeerDHT(); private static PeerDHT peerDHT = AppHelper.getPeerDHT();
public static boolean ping(PeerAddress recipientPeerAddress) {
// For connection opening
for (int i = 0; i < 5; i++) {
peerDHT
.peer()
.ping()
.peerAddress(recipientPeerAddress)
.start()
.awaitUninterruptibly();
}
FuturePing ping = peerDHT
.peer()
.ping()
.peerAddress(recipientPeerAddress)
.start()
.awaitUninterruptibly();
return ping.isSuccess();
}
public static boolean put(String locationKey, String contentKey, Data data) { public static boolean put(String locationKey, String contentKey, Data data) {
data.signed(false);
FuturePut futurePut = peerDHT FuturePut futurePut = peerDHT
.put(Number160.createHash(locationKey)) .put(Number160.createHash(locationKey))
.data(contentKey == null ? Number160.ZERO : Number160.createHash(contentKey), data) .data(contentKey == null ? Number160.ZERO : Number160.createHash(contentKey), data)
@ -49,6 +29,16 @@ public class P2PUtils {
return futurePut.isSuccess(); 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) { public static Map<Number640, Data> get(String locationKey) {
FutureGet futureGet = peerDHT FutureGet futureGet = peerDHT
.get(Number160.createHash(locationKey)) .get(Number160.createHash(locationKey))
@ -61,16 +51,6 @@ public class P2PUtils {
return null; return null;
} }
public static boolean send(PeerAddress address, String data) {
FutureDirect futureDirect = peerDHT
.peer()
.sendDirect(address)
.object(data)
.start()
.awaitUninterruptibly();
return futureDirect.isSuccess();
}
public static boolean remove(String locationKey, String contentKey) { public static boolean remove(String locationKey, String contentKey) {
FutureRemove futureRemove = peerDHT FutureRemove futureRemove = peerDHT
.remove(Number160.createHash(locationKey)) .remove(Number160.createHash(locationKey))
@ -79,4 +59,14 @@ public class P2PUtils {
.awaitUninterruptibly(); .awaitUninterruptibly();
return futureRemove.isRemoved(); 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();
}
} }

View File

@ -72,7 +72,7 @@ class StorageBerkeleyDB(peerId: Number160, path : File, signatureFactory: Signat
storageCheckIntervalMillis = 60 * 1000 storageCheckIntervalMillis = 60 * 1000
dataMap = StoredSortedMap(dataMapDB, Serializer<Number640>(), DataSerializerEx(signatureFactory), true) dataMap = StoredSortedMap(dataMapDB, Serializer<Number640>(), DataSerializer(signatureFactory), true)
timeoutMap = StoredSortedMap(timeoutMapDB, Serializer<Number640>(), Serializer<Long>(), true) timeoutMap = StoredSortedMap(timeoutMapDB, Serializer<Number640>(), Serializer<Long>(), true)
timeoutMapRev = StoredSortedMap(timeoutMapRevDB, Serializer<Long>(), Serializer<Set<Number640>>(), true) timeoutMapRev = StoredSortedMap(timeoutMapRevDB, Serializer<Long>(), Serializer<Set<Number640>>(), true)
protectedDomainMap = StoredSortedMap(protectedDomainMapDB, Serializer<Number320>(), Serializer<PublicKey>(), true) protectedDomainMap = StoredSortedMap(protectedDomainMapDB, Serializer<Number320>(), Serializer<PublicKey>(), true)
@ -89,8 +89,8 @@ class StorageBerkeleyDB(peerId: Number160, path : File, signatureFactory: Signat
return dataMap.subMap(from, true, to, true).size return dataMap.subMap(from, true, to, true).size
} }
override fun findContentForResponsiblePeerID(peerID: Number160?): MutableSet<Number160> { override fun findContentForResponsiblePeerID(peerID: Number160?): MutableSet<Number160>? {
return responsibilityMapRev[peerID] as MutableSet<Number160> return responsibilityMapRev[peerID] as MutableSet<Number160>?
} }
override fun findPeerIDsForResponsibleContent(locationKey: Number160?): Number160? { override fun findPeerIDsForResponsibleContent(locationKey: Number160?): Number160? {

View File

@ -12,4 +12,6 @@ public class UIActions {
public static final int SUCCESSFUL_CREATE_CHAT = 0x8; public static final int SUCCESSFUL_CREATE_CHAT = 0x8;
public static final int MESSAGE_RECEIVED = 0x9; public static final int MESSAGE_RECEIVED = 0x9;
public static final int NODE_IS_OFFLINE = 0x10; public static final int NODE_IS_OFFLINE = 0x10;
public static final int USERNAME_ISNT_AVAILABLE = 0x11;
public static final int USERNAME_AVAILABLE = 0x12;
} }

View File

@ -2,11 +2,11 @@ package io.github.chronosx88.influence.logic;
import java.util.List; import java.util.List;
import io.github.chronosx88.influence.contracts.chatlist.IChatListLogicContract; import io.github.chronosx88.influence.contracts.CoreContracts;
import io.github.chronosx88.influence.helpers.AppHelper; import io.github.chronosx88.influence.helpers.AppHelper;
import io.github.chronosx88.influence.models.roomEntities.ChatEntity; import io.github.chronosx88.influence.models.roomEntities.ChatEntity;
public class ChatListLogic implements IChatListLogicContract { public class ChatListLogic implements CoreContracts.IChatListLogicContract {
@Override @Override
public List<ChatEntity> loadAllChats() { public List<ChatEntity> loadAllChats() {

View File

@ -13,7 +13,7 @@ import java.util.Timer;
import java.util.TimerTask; import java.util.TimerTask;
import java.util.UUID; import java.util.UUID;
import io.github.chronosx88.influence.contracts.chatactivity.IChatLogicContract; import io.github.chronosx88.influence.contracts.CoreContracts;
import io.github.chronosx88.influence.helpers.AppHelper; import io.github.chronosx88.influence.helpers.AppHelper;
import io.github.chronosx88.influence.helpers.KeyPairManager; import io.github.chronosx88.influence.helpers.KeyPairManager;
import io.github.chronosx88.influence.helpers.LocalDBWrapper; import io.github.chronosx88.influence.helpers.LocalDBWrapper;
@ -27,7 +27,7 @@ import io.github.chronosx88.influence.models.TextMessage;
import io.github.chronosx88.influence.models.roomEntities.ChatEntity; import io.github.chronosx88.influence.models.roomEntities.ChatEntity;
import io.github.chronosx88.influence.models.roomEntities.MessageEntity; import io.github.chronosx88.influence.models.roomEntities.MessageEntity;
public class ChatLogic implements IChatLogicContract { public class ChatLogic implements CoreContracts.IChatLogicContract {
private static Gson gson = new Gson(); private static Gson gson = new Gson();
private String chatID; private String chatID;
private String newMessage = ""; private String newMessage = "";

View File

@ -22,6 +22,8 @@ import net.tomp2p.peers.Number160;
import net.tomp2p.peers.PeerAddress; import net.tomp2p.peers.PeerAddress;
import net.tomp2p.relay.tcp.TCPRelayClientConfig; import net.tomp2p.relay.tcp.TCPRelayClientConfig;
import net.tomp2p.replication.AutoReplication; import net.tomp2p.replication.AutoReplication;
import net.tomp2p.replication.IndirectReplication;
import net.tomp2p.replication.Replication;
import net.tomp2p.storage.Data; import net.tomp2p.storage.Data;
import java.io.IOException; import java.io.IOException;
@ -34,7 +36,7 @@ import java.util.Timer;
import java.util.TimerTask; import java.util.TimerTask;
import java.util.UUID; import java.util.UUID;
import io.github.chronosx88.influence.contracts.mainactivity.IMainLogicContract; 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.JVMShutdownHook;
import io.github.chronosx88.influence.helpers.KeyPairManager; import io.github.chronosx88.influence.helpers.KeyPairManager;
@ -44,8 +46,8 @@ 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.PublicUserProfile; import io.github.chronosx88.influence.models.PublicUserProfile;
public class MainLogic implements IMainLogicContract { public class MainLogic implements CoreContracts.IMainLogicContract {
private static final String LOG_TAG = "MainLogic"; private static final String LOG_TAG = MainLogic.class.getName();
private SharedPreferences preferences; private SharedPreferences preferences;
private Number160 peerID; private Number160 peerID;
@ -54,7 +56,7 @@ public class MainLogic implements IMainLogicContract {
private InetAddress bootstrapAddress = null; private InetAddress bootstrapAddress = null;
private PeerAddress bootstrapPeerAddress = null; private PeerAddress bootstrapPeerAddress = null;
private Gson gson; private Gson gson;
private AutoReplication replication; private IndirectReplication replication;
private KeyPairManager keyPairManager; private KeyPairManager keyPairManager;
private Thread checkNewChatsThread = null; private Thread checkNewChatsThread = null;
private StorageBerkeleyDB storage; private StorageBerkeleyDB storage;
@ -142,11 +144,13 @@ public class MainLogic implements IMainLogicContract {
jsonObject.addProperty("action", UIActions.BOOTSTRAP_SUCCESS); jsonObject.addProperty("action", UIActions.BOOTSTRAP_SUCCESS);
AppHelper.getObservable().notifyUIObservers(jsonObject); AppHelper.getObservable().notifyUIObservers(jsonObject);
AppHelper.storePeerID(preferences.getString("peerID", null)); AppHelper.storePeerID(preferences.getString("peerID", null));
AppHelper.updateUsername(preferences.getString("username", null));
AppHelper.storePeerDHT(peerDHT); AppHelper.storePeerDHT(peerDHT);
AppHelper.initNetworkHandler(); AppHelper.initNetworkHandler();
setReceiveHandler(); setReceiveHandler();
gson = new Gson(); gson = new Gson();
publicProfileToDHT(); publicProfileToDHT();
SettingsLogic.Companion.publishUsername(AppHelper.getUsername(), AppHelper.getUsername());
NetworkHandler.handlePendingChatRequests(); NetworkHandler.handlePendingChatRequests();
TimerTask timerTask = new TimerTask() { TimerTask timerTask = new TimerTask() {
@Override @Override
@ -163,7 +167,7 @@ public class MainLogic implements IMainLogicContract {
}; };
Timer timer = new Timer(); Timer timer = new Timer();
timer.schedule(timerTask, 1, 5000); timer.schedule(timerTask, 1, 5000);
replication = new AutoReplication(peerDHT.peer()).start(); replication = new IndirectReplication(peerDHT).start();
} catch (IOException e) { } catch (IOException e) {
e.printStackTrace(); e.printStackTrace();
} }
@ -224,7 +228,7 @@ public class MainLogic implements IMainLogicContract {
public void shutdownPeer() { public void shutdownPeer() {
new Thread(() -> { new Thread(() -> {
if(replication != null) { if(replication != null) {
replication.shutdown().start(); replication.shutdown();
} }
peerDHT.peer().announceShutdown().start().awaitUninterruptibly(); peerDHT.peer().announceShutdown().start().awaitUninterruptibly();
peerDHT.peer().shutdown().awaitUninterruptibly(); peerDHT.peer().shutdown().awaitUninterruptibly();
@ -245,7 +249,7 @@ public class MainLogic implements IMainLogicContract {
private void publicProfileToDHT() { private void publicProfileToDHT() {
KeyPair mainKeyPair = keyPairManager.openMainKeyPair(); KeyPair mainKeyPair = keyPairManager.openMainKeyPair();
PublicUserProfile userProfile = new PublicUserProfile(AppHelper.getPeerID(), peerDHT.peerAddress()); PublicUserProfile userProfile = new PublicUserProfile(AppHelper.getUsername(), peerDHT.peerAddress());
Data serializedUserProfile = null; Data serializedUserProfile = null;
try { try {
serializedUserProfile = new Data(gson.toJson(userProfile)) serializedUserProfile = new Data(gson.toJson(userProfile))

View File

@ -0,0 +1,46 @@
package io.github.chronosx88.influence.logic
import android.util.Log
import io.github.chronosx88.influence.contracts.CoreContracts
import io.github.chronosx88.influence.helpers.AppHelper
import io.github.chronosx88.influence.helpers.KeyPairManager
import io.github.chronosx88.influence.helpers.P2PUtils
import net.tomp2p.peers.Number640
import net.tomp2p.storage.Data
import java.io.IOException
class SettingsLogic : CoreContracts.ISettingsLogic {
override fun checkUsernameExists(username: String) : Boolean {
val usernameMap: MutableMap<Number640, Data>? = P2PUtils.get(username)
usernameMap ?: return false
return true
}
companion object {
private val LOG_TAG: String = "SettingsLogic"
private val keyPairManager = KeyPairManager()
fun publishUsername(oldUsername: String?, username: String?) {
val mainKeyPair = keyPairManager.openMainKeyPair()
if(oldUsername != null || !oldUsername.equals("")) {
P2PUtils.remove(oldUsername, null, mainKeyPair)
}
if (username.equals("") || username == null) {
return
}
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!")
}
}
}

View File

@ -13,7 +13,7 @@ import java.util.ArrayList;
import java.util.Map; import java.util.Map;
import java.util.UUID; import java.util.UUID;
import io.github.chronosx88.influence.contracts.startchat.IStartChatLogicContract; import io.github.chronosx88.influence.contracts.CoreContracts;
import io.github.chronosx88.influence.helpers.AppHelper; import io.github.chronosx88.influence.helpers.AppHelper;
import io.github.chronosx88.influence.helpers.KeyPairManager; import io.github.chronosx88.influence.helpers.KeyPairManager;
import io.github.chronosx88.influence.helpers.LocalDBWrapper; import io.github.chronosx88.influence.helpers.LocalDBWrapper;
@ -24,7 +24,7 @@ import io.github.chronosx88.influence.models.ChatMetadata;
import io.github.chronosx88.influence.models.NewChatRequestMessage; import io.github.chronosx88.influence.models.NewChatRequestMessage;
import io.github.chronosx88.influence.models.PublicUserProfile; import io.github.chronosx88.influence.models.PublicUserProfile;
public class StartChatLogic implements IStartChatLogicContract { public class StartChatLogic implements CoreContracts.IStartChatLogicContract {
private PeerDHT peerDHT; private PeerDHT peerDHT;
private Gson gson; private Gson gson;
private KeyPairManager keyPairManager; private KeyPairManager keyPairManager;
@ -37,20 +37,25 @@ public class StartChatLogic implements IStartChatLogicContract {
} }
@Override @Override
public void sendStartChatMessage(String peerID) { public void sendStartChatMessage(String username) {
if(peerDHT == null) { if(peerDHT == null) {
ObservableUtils.notifyUI(UIActions.NODE_IS_OFFLINE); ObservableUtils.notifyUI(UIActions.NODE_IS_OFFLINE);
return; return;
} }
new Thread(() -> { new Thread(() -> {
String peerID = getPeerIDByUsername(username);
if(peerID == null) {
ObservableUtils.notifyUI(UIActions.PEER_NOT_EXIST);
return;
}
PublicUserProfile recipientPublicProfile = getPublicProfile(peerID); PublicUserProfile recipientPublicProfile = getPublicProfile(peerID);
if(recipientPublicProfile == null) { if(recipientPublicProfile == null) {
ObservableUtils.notifyUI(UIActions.PEER_NOT_EXIST); ObservableUtils.notifyUI(UIActions.PEER_NOT_EXIST);
return; return;
} }
NewChatRequestMessage newChatRequestMessage = new NewChatRequestMessage(UUID.randomUUID().toString(), UUID.randomUUID().toString(), AppHelper.getPeerID(), AppHelper.getPeerID(), System.currentTimeMillis(), 0); NewChatRequestMessage newChatRequestMessage = new NewChatRequestMessage(UUID.randomUUID().toString(), UUID.randomUUID().toString(), AppHelper.getPeerID(), AppHelper.getUsername(), System.currentTimeMillis(), 0);
try { try {
if(P2PUtils.put(peerID + "_pendingChats", newChatRequestMessage.getChatID(), new Data(gson.toJson(newChatRequestMessage)))) { if(P2PUtils.put(peerID + "_pendingChats", newChatRequestMessage.getChatID(), new Data(gson.toJson(newChatRequestMessage)))) {
Log.i(LOG_TAG, "# Create new offline chat request is successful! ChatID: " + newChatRequestMessage.getChatID()); Log.i(LOG_TAG, "# Create new offline chat request is successful! ChatID: " + newChatRequestMessage.getChatID());
@ -65,13 +70,13 @@ public class StartChatLogic implements IStartChatLogicContract {
admins.add(AppHelper.getPeerID()); admins.add(AppHelper.getPeerID());
Data data = null; Data data = null;
try { try {
data = new Data(gson.toJson(new ChatMetadata(peerID, admins, new ArrayList<>()))); data = new Data(gson.toJson(new ChatMetadata(username, admins, new ArrayList<>())));
} catch (IOException e) { } catch (IOException e) {
e.printStackTrace(); e.printStackTrace();
} }
data.protectEntry(keyPairManager.openMainKeyPair()); data.protectEntry(keyPairManager.openMainKeyPair());
P2PUtils.put(newChatRequestMessage.getChatID() + "_metadata", null, data); P2PUtils.put(newChatRequestMessage.getChatID() + "_metadata", null, data);
LocalDBWrapper.createChatEntry(newChatRequestMessage.getChatID(), peerID, newChatRequestMessage.getChatID() + "_metadata", newChatRequestMessage.getChatID() + "_members", 0); LocalDBWrapper.createChatEntry(newChatRequestMessage.getChatID(), username, newChatRequestMessage.getChatID() + "_metadata", newChatRequestMessage.getChatID() + "_members", 0);
ObservableUtils.notifyUI(UIActions.NEW_CHAT); ObservableUtils.notifyUI(UIActions.NEW_CHAT);
}).start(); }).start();
} }
@ -89,4 +94,17 @@ public class StartChatLogic implements IStartChatLogicContract {
} }
return null; 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

@ -3,9 +3,7 @@ package io.github.chronosx88.influence.presenters;
import android.content.Intent; import android.content.Intent;
import android.view.MenuItem; import android.view.MenuItem;
import io.github.chronosx88.influence.contracts.chatlist.IChatListLogicContract; import io.github.chronosx88.influence.contracts.CoreContracts;
import io.github.chronosx88.influence.contracts.chatlist.IChatListPresenterContract;
import io.github.chronosx88.influence.contracts.chatlist.IChatListViewContract;
import io.github.chronosx88.influence.helpers.AppHelper; import io.github.chronosx88.influence.helpers.AppHelper;
import io.github.chronosx88.influence.helpers.ChatListAdapter; import io.github.chronosx88.influence.helpers.ChatListAdapter;
import io.github.chronosx88.influence.helpers.LocalDBWrapper; import io.github.chronosx88.influence.helpers.LocalDBWrapper;
@ -13,12 +11,12 @@ import io.github.chronosx88.influence.logic.ChatListLogic;
import io.github.chronosx88.influence.models.roomEntities.ChatEntity; import io.github.chronosx88.influence.models.roomEntities.ChatEntity;
import io.github.chronosx88.influence.views.ChatActivity; import io.github.chronosx88.influence.views.ChatActivity;
public class ChatListPresenter implements IChatListPresenterContract { public class ChatListPresenter implements CoreContracts.IChatListPresenterContract {
private IChatListViewContract view; private CoreContracts.IChatListViewContract view;
private IChatListLogicContract logic; private CoreContracts.IChatListLogicContract logic;
private ChatListAdapter chatListAdapter; private ChatListAdapter chatListAdapter;
public ChatListPresenter(IChatListViewContract view) { public ChatListPresenter(CoreContracts.IChatListViewContract view) {
this.view = view; this.view = view;
chatListAdapter = new ChatListAdapter((v, p)-> { chatListAdapter = new ChatListAdapter((v, p)-> {
openChat(chatListAdapter.getChatEntity(p).chatID); openChat(chatListAdapter.getChatEntity(p).chatID);

View File

@ -10,9 +10,7 @@ import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.UUID; import java.util.UUID;
import io.github.chronosx88.influence.contracts.chatactivity.IChatLogicContract; import io.github.chronosx88.influence.contracts.CoreContracts;
import io.github.chronosx88.influence.contracts.chatactivity.IChatPresenterContract;
import io.github.chronosx88.influence.contracts.chatactivity.IChatViewContract;
import io.github.chronosx88.influence.contracts.observer.IObserver; import io.github.chronosx88.influence.contracts.observer.IObserver;
import io.github.chronosx88.influence.helpers.AppHelper; import io.github.chronosx88.influence.helpers.AppHelper;
import io.github.chronosx88.influence.helpers.LocalDBWrapper; import io.github.chronosx88.influence.helpers.LocalDBWrapper;
@ -22,14 +20,14 @@ import io.github.chronosx88.influence.logic.ChatLogic;
import io.github.chronosx88.influence.models.roomEntities.ChatEntity; import io.github.chronosx88.influence.models.roomEntities.ChatEntity;
import io.github.chronosx88.influence.models.roomEntities.MessageEntity; import io.github.chronosx88.influence.models.roomEntities.MessageEntity;
public class ChatPresenter implements IChatPresenterContract, IObserver { public class ChatPresenter implements CoreContracts.IChatPresenterContract, IObserver {
private IChatLogicContract logic; private CoreContracts.IChatLogicContract logic;
private IChatViewContract view; private CoreContracts.IChatViewContract view;
private ChatEntity chatEntity; private ChatEntity chatEntity;
private String chatID; private String chatID;
private Gson gson; private Gson gson;
public ChatPresenter(IChatViewContract view, String chatID) { public ChatPresenter(CoreContracts.IChatViewContract view, String chatID) {
this.logic = new ChatLogic(LocalDBWrapper.getChatByChatID(chatID)); this.logic = new ChatLogic(LocalDBWrapper.getChatByChatID(chatID));
this.view = view; this.view = view;
this.chatID = chatID; this.chatID = chatID;

View File

@ -1,15 +1,13 @@
package io.github.chronosx88.influence.presenters; package io.github.chronosx88.influence.presenters;
import io.github.chronosx88.influence.contracts.mainactivity.IMainLogicContract; import io.github.chronosx88.influence.contracts.CoreContracts;
import io.github.chronosx88.influence.contracts.mainactivity.IMainPresenterContract;
import io.github.chronosx88.influence.contracts.mainactivity.IMainViewContract;
import io.github.chronosx88.influence.logic.MainLogic; import io.github.chronosx88.influence.logic.MainLogic;
public class MainPresenter implements IMainPresenterContract { public class MainPresenter implements CoreContracts.IMainPresenterContract {
private IMainLogicContract logic; private CoreContracts.IMainLogicContract logic;
private IMainViewContract view; private CoreContracts.IMainViewContract view;
public MainPresenter(IMainViewContract view) { public MainPresenter(CoreContracts.IMainViewContract view) {
this.view = view; this.view = view;
logic = new MainLogic(); logic = new MainLogic();
} }

View File

@ -0,0 +1,64 @@
package io.github.chronosx88.influence.presenters
import android.content.SharedPreferences
import android.os.Handler
import com.google.gson.JsonObject
import io.github.chronosx88.influence.R
import io.github.chronosx88.influence.contracts.CoreContracts
import io.github.chronosx88.influence.contracts.observer.IObserver
import io.github.chronosx88.influence.helpers.AppHelper
import io.github.chronosx88.influence.helpers.ObservableUtils
import io.github.chronosx88.influence.helpers.actions.UIActions
import io.github.chronosx88.influence.logic.SettingsLogic
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
class SettingsPresenter(private val view: CoreContracts.ISettingsView) : CoreContracts.ISettingsPresenter, IObserver {
private val mainThreadHandler: Handler = Handler(AppHelper.getContext().mainLooper)
private val logic: SettingsLogic = SettingsLogic()
init {
AppHelper.getObservable().register(this)
}
override fun updateUsername(username: String) {
view.loadingScreen(true)
GlobalScope.launch {
if(!logic.checkUsernameExists(username)) {
// Save username in SharedPreferences
val editor: SharedPreferences.Editor = AppHelper.getPreferences().edit()
val oldUsername = AppHelper.getPreferences().getString("username", null)
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)
}
}
}
override fun handleEvent(json: JsonObject) {
val post = {
when (json.get("action").asInt) {
UIActions.USERNAME_AVAILABLE -> {
view.loadingScreen(false)
view.showMessage(AppHelper.getContext().getString(R.string.username_saved))
}
UIActions.USERNAME_ISNT_AVAILABLE -> {
view.loadingScreen(false)
view.showMessage(AppHelper.getContext().getString(R.string.username_isnt_saved))
}
}
}
mainThreadHandler.post(post)
}
}

View File

@ -2,19 +2,17 @@ package io.github.chronosx88.influence.presenters;
import com.google.gson.JsonObject; import com.google.gson.JsonObject;
import io.github.chronosx88.influence.contracts.CoreContracts;
import io.github.chronosx88.influence.contracts.observer.IObserver; import io.github.chronosx88.influence.contracts.observer.IObserver;
import io.github.chronosx88.influence.contracts.startchat.IStartChatLogicContract;
import io.github.chronosx88.influence.contracts.startchat.IStartChatPresenterContract;
import io.github.chronosx88.influence.contracts.startchat.IStartChatViewContract;
import io.github.chronosx88.influence.helpers.AppHelper; import io.github.chronosx88.influence.helpers.AppHelper;
import io.github.chronosx88.influence.helpers.actions.UIActions; import io.github.chronosx88.influence.helpers.actions.UIActions;
import io.github.chronosx88.influence.logic.StartChatLogic; import io.github.chronosx88.influence.logic.StartChatLogic;
public class StartChatPresenter implements IStartChatPresenterContract, IObserver { public class StartChatPresenter implements CoreContracts.IStartChatPresenterContract, IObserver {
private IStartChatViewContract view; private CoreContracts.IStartChatViewContract view;
private IStartChatLogicContract logic; private CoreContracts.IStartChatLogicContract logic;
public StartChatPresenter(IStartChatViewContract view) { public StartChatPresenter(CoreContracts.IStartChatViewContract view) {
this.view = view; this.view = view;
this.logic = new StartChatLogic(); this.logic = new StartChatLogic();
AppHelper.getObservable().register(this); AppHelper.getObservable().register(this);

View File

@ -1,95 +0,0 @@
package io.github.chronosx88.influence.views;
import android.content.Intent;
import android.os.Bundle;
import android.view.MenuItem;
import android.widget.EditText;
import android.widget.ImageButton;
import android.widget.TextView;
import java.util.List;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.widget.Toolbar;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import io.github.chronosx88.influence.R;
import io.github.chronosx88.influence.contracts.chatactivity.IChatViewContract;
import io.github.chronosx88.influence.helpers.ChatAdapter;
import io.github.chronosx88.influence.models.roomEntities.MessageEntity;
import io.github.chronosx88.influence.presenters.ChatPresenter;
public class ChatActivity extends AppCompatActivity implements IChatViewContract {
private ChatAdapter chatAdapter;
private RecyclerView messageList;
private ImageButton sendMessageButton;
private EditText messageTextEdit;
private TextView contactUsernameTextView;
private ChatPresenter presenter;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_chat);
Intent intent = getIntent();
presenter = new ChatPresenter(this, intent.getStringExtra("chatID"));
Toolbar toolbar = findViewById(R.id.toolbar_chat_activity);
setSupportActionBar(toolbar);
getSupportActionBar().setTitle("");
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
getSupportActionBar().setHomeButtonEnabled(true);
messageList = findViewById(R.id.message_list);
chatAdapter = new ChatAdapter();
presenter.updateAdapter();
messageList.setAdapter(chatAdapter);
messageList.setLayoutManager(new LinearLayoutManager(this));
contactUsernameTextView = findViewById(R.id.appbar_username);
messageTextEdit = findViewById(R.id.message_input);
sendMessageButton = findViewById(R.id.send_button);
sendMessageButton.setOnClickListener((v) -> {
if(messageTextEdit.getText().toString().equals("")) {
return;
}
presenter.sendMessage(messageTextEdit.getText().toString());
messageTextEdit.setText("");
messageList.scrollToPosition(chatAdapter.getItemCount()-1);
});
contactUsernameTextView.setText(intent.getStringExtra("contactUsername"));
messageList.scrollToPosition(chatAdapter.getItemCount()-1);
}
@Override
public void updateMessageList(MessageEntity message) {
runOnUiThread(() -> {
chatAdapter.addMessage(message);
chatAdapter.notifyDataSetChanged();
});
}
@Override
public void updateMessageList(List<MessageEntity> messages) {
runOnUiThread(() -> {
chatAdapter.addMessages(messages);
chatAdapter.notifyDataSetChanged();
});
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch(item.getItemId()) {
case android.R.id.home:
finish();
return true;
}
return super.onOptionsItemSelected(item);
}
@Override
protected void onDestroy() {
super.onDestroy();
presenter.onDestroy();
}
}

View File

@ -0,0 +1,86 @@
package io.github.chronosx88.influence.views
import android.os.Bundle
import android.view.MenuItem
import android.widget.EditText
import android.widget.ImageButton
import android.widget.TextView
import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.widget.Toolbar
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import io.github.chronosx88.influence.R
import io.github.chronosx88.influence.contracts.CoreContracts
import io.github.chronosx88.influence.helpers.ChatAdapter
import io.github.chronosx88.influence.models.roomEntities.MessageEntity
import io.github.chronosx88.influence.presenters.ChatPresenter
class ChatActivity : AppCompatActivity(), CoreContracts.IChatViewContract {
private var chatAdapter: ChatAdapter? = null
private var messageList: RecyclerView? = null
private var sendMessageButton: ImageButton? = null
private var messageTextEdit: EditText? = null
private var contactUsernameTextView: TextView? = null
private var presenter: ChatPresenter? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_chat)
val intent = intent
presenter = ChatPresenter(this, intent.getStringExtra("chatID"))
val toolbar = findViewById<Toolbar>(R.id.toolbar_chat_activity)
setSupportActionBar(toolbar)
supportActionBar!!.setTitle("")
supportActionBar!!.setDisplayHomeAsUpEnabled(true)
supportActionBar!!.setHomeButtonEnabled(true)
messageList = findViewById(R.id.message_list)
chatAdapter = ChatAdapter()
presenter!!.updateAdapter()
messageList!!.adapter = chatAdapter
messageList!!.layoutManager = LinearLayoutManager(this)
contactUsernameTextView = findViewById(R.id.appbar_username)
messageTextEdit = findViewById(R.id.message_input)
sendMessageButton = findViewById(R.id.send_button)
sendMessageButton!!.setOnClickListener sendMessageButton@{
if (messageTextEdit!!.text.toString() == "") {
return@sendMessageButton
}
presenter!!.sendMessage(messageTextEdit!!.text.toString())
messageTextEdit!!.setText("")
messageList!!.scrollToPosition(chatAdapter!!.itemCount - 1)
}
contactUsernameTextView!!.text = intent.getStringExtra("contactUsername")
messageList!!.scrollToPosition(chatAdapter!!.itemCount - 1)
}
override fun updateMessageList(message: MessageEntity) {
runOnUiThread {
chatAdapter!!.addMessage(message)
chatAdapter!!.notifyDataSetChanged()
}
}
override fun updateMessageList(messages: List<MessageEntity>) {
runOnUiThread {
chatAdapter!!.addMessages(messages)
chatAdapter!!.notifyDataSetChanged()
}
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
when (item.itemId) {
android.R.id.home -> {
finish()
return true
}
}
return super.onOptionsItemSelected(item)
}
override fun onDestroy() {
super.onDestroy()
presenter!!.onDestroy()
}
}

View File

@ -13,8 +13,7 @@ import androidx.appcompat.app.AppCompatActivity;
import androidx.fragment.app.Fragment; import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentTransaction; import androidx.fragment.app.FragmentTransaction;
import io.github.chronosx88.influence.R; import io.github.chronosx88.influence.R;
import io.github.chronosx88.influence.contracts.mainactivity.IMainPresenterContract; import io.github.chronosx88.influence.contracts.CoreContracts;
import io.github.chronosx88.influence.contracts.mainactivity.IMainViewContract;
import io.github.chronosx88.influence.contracts.observer.IObserver; import io.github.chronosx88.influence.contracts.observer.IObserver;
import io.github.chronosx88.influence.helpers.AppHelper; import io.github.chronosx88.influence.helpers.AppHelper;
import io.github.chronosx88.influence.helpers.actions.UIActions; import io.github.chronosx88.influence.helpers.actions.UIActions;
@ -23,9 +22,9 @@ 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 io.github.chronosx88.influence.views.fragments.StartChatFragment; import io.github.chronosx88.influence.views.fragments.StartChatFragment;
public class MainActivity extends AppCompatActivity implements IObserver, IMainViewContract { public class MainActivity extends AppCompatActivity implements IObserver, CoreContracts.IMainViewContract {
private IMainPresenterContract presenter; private CoreContracts.IMainPresenterContract presenter;
private ProgressDialog progressDialog; private ProgressDialog progressDialog;
private BottomNavigationView.OnNavigationItemSelectedListener mOnNavigationItemSelectedListener private BottomNavigationView.OnNavigationItemSelectedListener mOnNavigationItemSelectedListener
= new BottomNavigationView.OnNavigationItemSelectedListener() { = new BottomNavigationView.OnNavigationItemSelectedListener() {

View File

@ -1,92 +0,0 @@
package io.github.chronosx88.influence.views.fragments;
import android.os.Bundle;
import android.os.Handler;
import android.view.LayoutInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import com.google.gson.JsonObject;
import java.util.List;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import io.github.chronosx88.influence.R;
import io.github.chronosx88.influence.contracts.chatlist.IChatListPresenterContract;
import io.github.chronosx88.influence.contracts.chatlist.IChatListViewContract;
import io.github.chronosx88.influence.contracts.observer.IObserver;
import io.github.chronosx88.influence.helpers.AppHelper;
import io.github.chronosx88.influence.helpers.ChatListAdapter;
import io.github.chronosx88.influence.helpers.actions.UIActions;
import io.github.chronosx88.influence.models.roomEntities.ChatEntity;
import io.github.chronosx88.influence.presenters.ChatListPresenter;
public class ChatListFragment extends Fragment implements IChatListViewContract, IObserver {
private IChatListPresenterContract presenter;
private RecyclerView chatList;
private Handler mainThreadHandler;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
AppHelper.getObservable().register(this);
this.mainThreadHandler = new Handler(getContext().getMainLooper());
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
return inflater.inflate(R.layout.chatlist_fragment, container, false);
}
@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
chatList = view.findViewById(R.id.chatlist_container);
chatList.setLayoutManager(new LinearLayoutManager(getContext()));
presenter = new ChatListPresenter(this);
presenter.updateChatList();
registerForContextMenu(chatList);
}
@Override
public void setRecycleAdapter(ChatListAdapter adapter) {
chatList.setAdapter(adapter);
}
@Override
public void onResume() {
super.onResume();
presenter.updateChatList();
}
@Override
public void handleEvent(JsonObject object) {
switch (object.get("action").getAsInt()) {
case UIActions.SUCCESSFUL_CREATE_CHAT:
case UIActions.NEW_CHAT: {
presenter.updateChatList();
break;
}
}
}
@Override
public void updateChatList(ChatListAdapter adapter, List<ChatEntity> chats) {
mainThreadHandler.post(() -> {
adapter.setChatList(chats);
adapter.notifyDataSetChanged();
});
}
@Override
public boolean onContextItemSelected(@NonNull MenuItem item) {
presenter.onContextItemSelected(item);
return super.onContextItemSelected(item);
}
}

View File

@ -0,0 +1,76 @@
package io.github.chronosx88.influence.views.fragments
import android.os.Bundle
import android.os.Handler
import android.view.LayoutInflater
import android.view.MenuItem
import android.view.View
import android.view.ViewGroup
import com.google.gson.JsonObject
import androidx.fragment.app.Fragment
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import io.github.chronosx88.influence.R
import io.github.chronosx88.influence.contracts.CoreContracts
import io.github.chronosx88.influence.contracts.observer.IObserver
import io.github.chronosx88.influence.helpers.AppHelper
import io.github.chronosx88.influence.helpers.ChatListAdapter
import io.github.chronosx88.influence.helpers.actions.UIActions
import io.github.chronosx88.influence.models.roomEntities.ChatEntity
import io.github.chronosx88.influence.presenters.ChatListPresenter
class ChatListFragment : Fragment(), CoreContracts.IChatListViewContract, IObserver {
private var presenter: CoreContracts.IChatListPresenterContract? = null
private var chatList: RecyclerView? = null
private var mainThreadHandler: Handler? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
AppHelper.getObservable().register(this)
this.mainThreadHandler = Handler(context!!.mainLooper)
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?): View? {
return inflater.inflate(R.layout.chatlist_fragment, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
chatList = view.findViewById(R.id.chatlist_container)
chatList!!.layoutManager = LinearLayoutManager(context)
presenter = ChatListPresenter(this)
presenter!!.updateChatList()
registerForContextMenu(chatList!!)
}
override fun setRecycleAdapter(adapter: ChatListAdapter) {
chatList!!.adapter = adapter
}
override fun onResume() {
super.onResume()
presenter!!.updateChatList()
}
override fun handleEvent(`object`: JsonObject) {
when (`object`.get("action").asInt) {
UIActions.SUCCESSFUL_CREATE_CHAT, UIActions.NEW_CHAT -> {
presenter!!.updateChatList()
}
}
}
override fun updateChatList(adapter: ChatListAdapter, chats: List<ChatEntity>) {
mainThreadHandler!!.post {
adapter.setChatList(chats)
adapter.notifyDataSetChanged()
}
}
override fun onContextItemSelected(item: MenuItem): Boolean {
presenter!!.onContextItemSelected(item)
return super.onContextItemSelected(item)
}
}

View File

@ -1,19 +1,33 @@
package io.github.chronosx88.influence.views.fragments; package io.github.chronosx88.influence.views.fragments;
import android.app.AlertDialog;
import android.app.ProgressDialog;
import android.content.ClipData; import android.content.ClipData;
import android.content.ClipboardManager; import android.content.ClipboardManager;
import android.content.Context; 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 io.github.chronosx88.influence.R; import io.github.chronosx88.influence.R;
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.presenters.SettingsPresenter;
public class SettingsFragment extends PreferenceFragmentCompat { public class SettingsFragment extends PreferenceFragmentCompat implements CoreContracts.ISettingsView {
private ProgressDialog progressDialog;
private SettingsPresenter presenter;
@Override @Override
public void onCreatePreferences(Bundle bundle, String s) { public void onCreatePreferences(Bundle bundle, String s) {
progressDialog = new ProgressDialog(getContext(), R.style.AlertDialogTheme);
progressDialog.setCancelable(false);
progressDialog.setProgressStyle(android.R.style.Widget_ProgressBar_Small);
presenter = new SettingsPresenter(this);
// Load the Preferences from the XML file // Load the Preferences from the XML file
addPreferencesFromResource(R.xml.main_settings); addPreferencesFromResource(R.xml.main_settings);
getPreferenceScreen().getPreference(0).setSummary(AppHelper.getPeerID()); getPreferenceScreen().getPreference(0).setSummary(AppHelper.getPeerID());
@ -24,5 +38,42 @@ public class SettingsFragment extends PreferenceFragmentCompat {
Toast.makeText(AppHelper.getContext(), "Скопировано в буфер обмена!", Toast.LENGTH_SHORT).show(); Toast.makeText(AppHelper.getContext(), "Скопировано в буфер обмена!", Toast.LENGTH_SHORT).show();
return false; return false;
})); }));
getPreferenceScreen().getPreference(1).setSummary(AppHelper.getUsername());
getPreferenceScreen().getPreference(1).setOnPreferenceClickListener((v) -> {
setupUsernameEditDialog().show();
return true;
});
}
@Override
public void loadingScreen(boolean state) {
if(state)
progressDialog.show();
else
progressDialog.dismiss();
}
@Override
public void showMessage(@NotNull String message) {
Toast.makeText(AppHelper.getContext(), message, Toast.LENGTH_LONG).show();
}
private AlertDialog.Builder setupUsernameEditDialog() {
AlertDialog.Builder alertDialog = new AlertDialog.Builder(getContext());
alertDialog.setTitle(getContext().getString(R.string.username_settings));
final EditText input = new EditText(getContext());
LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams(
LinearLayout.LayoutParams.MATCH_PARENT,
LinearLayout.LayoutParams.MATCH_PARENT);
input.setSingleLine();
input.setLayoutParams(lp);
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;
} }
} }

View File

@ -16,10 +16,10 @@ import androidx.annotation.Nullable;
import androidx.coordinatorlayout.widget.CoordinatorLayout; import androidx.coordinatorlayout.widget.CoordinatorLayout;
import androidx.fragment.app.Fragment; import androidx.fragment.app.Fragment;
import io.github.chronosx88.influence.R; import io.github.chronosx88.influence.R;
import io.github.chronosx88.influence.contracts.startchat.IStartChatViewContract; import io.github.chronosx88.influence.contracts.CoreContracts;
import io.github.chronosx88.influence.presenters.StartChatPresenter; import io.github.chronosx88.influence.presenters.StartChatPresenter;
public class StartChatFragment extends Fragment implements IStartChatViewContract { public class StartChatFragment extends Fragment implements CoreContracts.IStartChatViewContract {
private TextInputLayout textInputPeerID; private TextInputLayout textInputPeerID;
private ProgressDialog progressDialog; private ProgressDialog progressDialog;
private Button createChatButton; private Button createChatButton;

View File

@ -20,7 +20,7 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_gravity="center" android:layout_gravity="center"
android:hint="Peer ID"/> android:hint="@string/username_hint"/>
</com.google.android.material.textfield.TextInputLayout> </com.google.android.material.textfield.TextInputLayout>
<Button <Button

View File

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="app_name">Influence</string>
<string name="username_saved">Ваше имя пользователя успешно сохранено!</string>
<string name="username_isnt_saved">К сожалению, это имя пользователя занято.</string>
<string name="ok">OK</string>
<string name="cancel">Отмена</string>
<string name="username_settings">Ваше имя пользователя</string>
<string name="username_hint">Имя пользователя</string>
</resources>

View File

@ -1,3 +1,9 @@
<resources> <resources>
<string name="app_name">Influence</string> <string name="app_name">Influence</string>
<string name="username_saved">Your username saved successfully!</string>
<string name="username_isnt_saved">Sorry, this username is busy.</string>
<string name="ok">OK</string>
<string name="cancel">Cancel</string>
<string name="username_settings">Your username</string>
<string name="username_hint">Username</string>
</resources> </resources>

View File

@ -4,6 +4,10 @@
android:key="peerID" android:key="peerID"
android:title="Мой Peer ID" android:title="Мой Peer ID"
android:summary=""/> android:summary=""/>
<Preference
android:key="username"
android:title="Мой username"
android:summary=""/>
<EditTextPreference <EditTextPreference
android:key="bootstrapAddress" android:key="bootstrapAddress"
android:title="Адрес Bootstrap-ноды" android:title="Адрес Bootstrap-ноды"

View File

@ -1,7 +1,7 @@
// Top-level build file where you can add configuration options common to all sub-projects/modules. // Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript { buildscript {
ext.kotlin_version = '1.3.21' ext.kotlin_version = '1.3.30'
repositories { repositories {
google() google()
jcenter() jcenter()