Merge CleanArchitecture
This commit is contained in:
commit
576b300732
|
@ -1,4 +0,0 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="Encoding" addBOMForNewFiles="with NO BOM" />
|
||||
</project>
|
|
@ -1,91 +0,0 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="GradleSettings">
|
||||
<option name="linkedExternalProjectsSettings">
|
||||
<GradleProjectSettings>
|
||||
<compositeConfiguration>
|
||||
<compositeBuild compositeDefinitionSource="SCRIPT">
|
||||
<builds>
|
||||
<build path="$PROJECT_DIR$/libs/Smack" name="Smack">
|
||||
<projects>
|
||||
<project path="$PROJECT_DIR$/libs/Smack" />
|
||||
<project path="$PROJECT_DIR$/libs/Smack/smack-android" />
|
||||
<project path="$PROJECT_DIR$/libs/Smack/smack-android-extensions" />
|
||||
<project path="$PROJECT_DIR$/libs/Smack/smack-bosh" />
|
||||
<project path="$PROJECT_DIR$/libs/Smack/smack-compression-jzlib" />
|
||||
<project path="$PROJECT_DIR$/libs/Smack/smack-core" />
|
||||
<project path="$PROJECT_DIR$/libs/Smack/smack-debug" />
|
||||
<project path="$PROJECT_DIR$/libs/Smack/smack-debug-slf4j" />
|
||||
<project path="$PROJECT_DIR$/libs/Smack/smack-experimental" />
|
||||
<project path="$PROJECT_DIR$/libs/Smack/smack-extensions" />
|
||||
<project path="$PROJECT_DIR$/libs/Smack/smack-im" />
|
||||
<project path="$PROJECT_DIR$/libs/Smack/smack-integration-test" />
|
||||
<project path="$PROJECT_DIR$/libs/Smack/smack-java7" />
|
||||
<project path="$PROJECT_DIR$/libs/Smack/smack-jingle-old" />
|
||||
<project path="$PROJECT_DIR$/libs/Smack/smack-legacy" />
|
||||
<project path="$PROJECT_DIR$/libs/Smack/smack-omemo" />
|
||||
<project path="$PROJECT_DIR$/libs/Smack/smack-omemo-signal" />
|
||||
<project path="$PROJECT_DIR$/libs/Smack/smack-omemo-signal-integration-test" />
|
||||
<project path="$PROJECT_DIR$/libs/Smack/smack-openpgp" />
|
||||
<project path="$PROJECT_DIR$/libs/Smack/smack-repl" />
|
||||
<project path="$PROJECT_DIR$/libs/Smack/smack-resolver-dnsjava" />
|
||||
<project path="$PROJECT_DIR$/libs/Smack/smack-resolver-javax" />
|
||||
<project path="$PROJECT_DIR$/libs/Smack/smack-resolver-minidns" />
|
||||
<project path="$PROJECT_DIR$/libs/Smack/smack-resolver-minidns-dox" />
|
||||
<project path="$PROJECT_DIR$/libs/Smack/smack-sasl-javax" />
|
||||
<project path="$PROJECT_DIR$/libs/Smack/smack-sasl-provided" />
|
||||
<project path="$PROJECT_DIR$/libs/Smack/smack-tcp" />
|
||||
<project path="$PROJECT_DIR$/libs/Smack/smack-xmlparser" />
|
||||
<project path="$PROJECT_DIR$/libs/Smack/smack-xmlparser-stax" />
|
||||
<project path="$PROJECT_DIR$/libs/Smack/smack-xmlparser-xpp3" />
|
||||
</projects>
|
||||
</build>
|
||||
</builds>
|
||||
</compositeBuild>
|
||||
</compositeConfiguration>
|
||||
<option name="distributionType" value="DEFAULT_WRAPPED" />
|
||||
<option name="externalProjectPath" value="$PROJECT_DIR$" />
|
||||
<option name="modules">
|
||||
<set>
|
||||
<option value="$PROJECT_DIR$" />
|
||||
<option value="$PROJECT_DIR$/app" />
|
||||
<option value="$PROJECT_DIR$/core" />
|
||||
<option value="$PROJECT_DIR$/libs/Smack" />
|
||||
<option value="$PROJECT_DIR$/libs/Smack/smack-android" />
|
||||
<option value="$PROJECT_DIR$/libs/Smack/smack-android-extensions" />
|
||||
<option value="$PROJECT_DIR$/libs/Smack/smack-bosh" />
|
||||
<option value="$PROJECT_DIR$/libs/Smack/smack-compression-jzlib" />
|
||||
<option value="$PROJECT_DIR$/libs/Smack/smack-core" />
|
||||
<option value="$PROJECT_DIR$/libs/Smack/smack-debug" />
|
||||
<option value="$PROJECT_DIR$/libs/Smack/smack-debug-slf4j" />
|
||||
<option value="$PROJECT_DIR$/libs/Smack/smack-experimental" />
|
||||
<option value="$PROJECT_DIR$/libs/Smack/smack-extensions" />
|
||||
<option value="$PROJECT_DIR$/libs/Smack/smack-im" />
|
||||
<option value="$PROJECT_DIR$/libs/Smack/smack-integration-test" />
|
||||
<option value="$PROJECT_DIR$/libs/Smack/smack-java7" />
|
||||
<option value="$PROJECT_DIR$/libs/Smack/smack-jingle-old" />
|
||||
<option value="$PROJECT_DIR$/libs/Smack/smack-legacy" />
|
||||
<option value="$PROJECT_DIR$/libs/Smack/smack-omemo" />
|
||||
<option value="$PROJECT_DIR$/libs/Smack/smack-omemo-signal" />
|
||||
<option value="$PROJECT_DIR$/libs/Smack/smack-omemo-signal-integration-test" />
|
||||
<option value="$PROJECT_DIR$/libs/Smack/smack-openpgp" />
|
||||
<option value="$PROJECT_DIR$/libs/Smack/smack-repl" />
|
||||
<option value="$PROJECT_DIR$/libs/Smack/smack-resolver-dnsjava" />
|
||||
<option value="$PROJECT_DIR$/libs/Smack/smack-resolver-javax" />
|
||||
<option value="$PROJECT_DIR$/libs/Smack/smack-resolver-minidns" />
|
||||
<option value="$PROJECT_DIR$/libs/Smack/smack-resolver-minidns-dox" />
|
||||
<option value="$PROJECT_DIR$/libs/Smack/smack-sasl-javax" />
|
||||
<option value="$PROJECT_DIR$/libs/Smack/smack-sasl-provided" />
|
||||
<option value="$PROJECT_DIR$/libs/Smack/smack-tcp" />
|
||||
<option value="$PROJECT_DIR$/libs/Smack/smack-xmlparser" />
|
||||
<option value="$PROJECT_DIR$/libs/Smack/smack-xmlparser-stax" />
|
||||
<option value="$PROJECT_DIR$/libs/Smack/smack-xmlparser-xpp3" />
|
||||
</set>
|
||||
</option>
|
||||
<option name="resolveModulePerSourceSet" value="false" />
|
||||
<option name="testRunner" value="PLATFORM" />
|
||||
<option name="useQualifiedModuleNames" value="true" />
|
||||
</GradleProjectSettings>
|
||||
</option>
|
||||
</component>
|
||||
</project>
|
|
@ -1,6 +0,0 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ProjectRootManager" version="2" languageLevel="JDK_1_8" project-jdk-name="JDK" project-jdk-type="JavaSDK">
|
||||
<output url="file://$PROJECT_DIR$/classes" />
|
||||
</component>
|
||||
</project>
|
|
@ -1,12 +0,0 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="RunConfigurationProducerService">
|
||||
<option name="ignoredProducers">
|
||||
<set>
|
||||
<option value="org.jetbrains.plugins.gradle.execution.test.runner.AllInPackageGradleConfigurationProducer" />
|
||||
<option value="org.jetbrains.plugins.gradle.execution.test.runner.TestClassGradleConfigurationProducer" />
|
||||
<option value="org.jetbrains.plugins.gradle.execution.test.runner.TestMethodGradleConfigurationProducer" />
|
||||
</set>
|
||||
</option>
|
||||
</component>
|
||||
</project>
|
|
@ -1,7 +0,0 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="VcsDirectoryMappings">
|
||||
<mapping directory="$PROJECT_DIR$" vcs="Git" />
|
||||
<mapping directory="$PROJECT_DIR$/libs/Smack" vcs="Git" />
|
||||
</component>
|
||||
</project>
|
13
README.md
13
README.md
|
@ -2,10 +2,11 @@
|
|||
|
||||
## Used Design Methods:
|
||||
|
||||
* The app is developed using the MVVM (Model View Viewmodel) pattern using LifeCycle aware ViewModels
|
||||
* Components are wired together using Dependency Injection (DI) with Dagger 2
|
||||
* Data is persisted using Googles Room database library
|
||||
* UI is notified by updates to the data through the use of LiveData
|
||||
* Mercury IM's development follows architectural principles know from
|
||||
[Clean Architecture](https://blog.cleancoder.com/uncle-bob/2012/08/13/the-clean-architecture.html).
|
||||
* The app is developed using the MVVM (Model View Viewmodel) pattern
|
||||
* Components are wired together using Dependency Injection (DI) with [Dagger 2](https://dagger.dev)
|
||||
* Data is persisted using the [requery](https://github.com/requery/requery) ORM framework
|
||||
|
||||
## Building
|
||||
|
||||
|
@ -20,5 +21,5 @@ gradle assembleDebug
|
|||
|
||||
* I want to develop, but lots of `org.jivesoftware.smackx.*` classes cannot be found!
|
||||
* You forgot to type `git submodule init && git submodule update` as mentioned above
|
||||
* I'm missing `org.mercury_im.messenger.persistence.requery.*` classes???
|
||||
* In Android Studio select the `persistence-requery` module and then click "Build -> Make Module 'persistence-requery'".
|
||||
* It looks like I'm missing `org.mercury_im.messenger.data.*` classes???
|
||||
* In Android Studio select the `data` module and then click "Build -> Make Module 'data'".
|
||||
|
|
|
@ -23,7 +23,7 @@ android {
|
|||
|
||||
lintOptions {
|
||||
disable 'GoogleAppIndexingWarning', 'AllowBackup'
|
||||
// Warn about invalid packages instead of failing
|
||||
// Warn about invalidUsername packages instead of failing
|
||||
warning 'InvalidPackage'
|
||||
}
|
||||
|
||||
|
@ -73,18 +73,23 @@ check.configure {
|
|||
dependencies {
|
||||
|
||||
// Depend on the core project for XMPP related stuff
|
||||
implementation project(':core')
|
||||
implementation project(":entity")
|
||||
implementation project(":domain")
|
||||
implementation project(":data")
|
||||
|
||||
implementation "io.requery:requery-android:$requeryVersion"
|
||||
implementation 'org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.3.41'
|
||||
implementation 'org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.3.61'
|
||||
|
||||
// Dagger 2 for dependency injection
|
||||
implementation "com.google.dagger:dagger:$daggerVersion"
|
||||
annotationProcessor "com.google.dagger:dagger-compiler:$daggerVersion"
|
||||
|
||||
compileOnly 'org.projectlombok:lombok:1.18.10'
|
||||
annotationProcessor 'org.projectlombok:lombok:1.18.10'
|
||||
|
||||
// ViewModel and LiveData
|
||||
implementation "androidx.lifecycle:lifecycle-extensions:$lifecycleVersion"
|
||||
annotationProcessor "androidx.lifecycle:lifecycle-compiler:$lifecycleVersion"
|
||||
annotationProcessor "androidx.lifecycle:lifecycle-common-java8:$lifecycleVersion"
|
||||
|
||||
// Android extension for rxJava
|
||||
api "io.reactivex.rxjava2:rxandroid:$rxAndroidVersion"
|
||||
|
@ -95,12 +100,12 @@ dependencies {
|
|||
|
||||
// support libraries
|
||||
implementation "androidx.appcompat:appcompat:$appCompatVersion"
|
||||
implementation 'com.google.android.material:material:1.1.0-beta01'
|
||||
implementation 'com.google.android.material:material:1.2.0-alpha03'
|
||||
implementation 'androidx.legacy:legacy-support-v4:1.0.0'
|
||||
implementation 'androidx.vectordrawable:vectordrawable:1.1.0'
|
||||
implementation 'androidx.cardview:cardview:1.0.0'
|
||||
implementation 'androidx.constraintlayout:constraintlayout:2.0.0-beta2'
|
||||
implementation 'androidx.recyclerview:recyclerview:1.0.0'
|
||||
implementation 'androidx.constraintlayout:constraintlayout:2.0.0-beta4'
|
||||
implementation 'androidx.recyclerview:recyclerview:1.1.0'
|
||||
|
||||
// circular image viewer for avatars
|
||||
implementation 'de.hdodenhof:circleimageview:3.0.1'
|
||||
|
|
|
@ -9,13 +9,13 @@ import org.junit.BeforeClass;
|
|||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.jxmpp.jid.impl.JidCreate;
|
||||
import org.mercury_im.messenger.persistence.requery.entity.AccountModel;
|
||||
import org.mercury_im.messenger.persistence.requery.entity.ChatModel;
|
||||
import org.mercury_im.messenger.persistence.requery.entity.ContactModel;
|
||||
import org.mercury_im.messenger.persistence.requery.entity.EntityModel;
|
||||
import org.mercury_im.messenger.persistence.requery.entity.LastReadChatMessageRelation;
|
||||
import org.mercury_im.messenger.persistence.requery.entity.MessageModel;
|
||||
import org.mercury_im.messenger.persistence.requery.entity.Models;
|
||||
import org.mercury_im.messenger.xmpp.requery.entity.AccountModel;
|
||||
import org.mercury_im.messenger.xmpp.requery.entity.ChatModel;
|
||||
import org.mercury_im.messenger.xmpp.requery.entity.ContactModel;
|
||||
import org.mercury_im.messenger.xmpp.requery.entity.EntityModel;
|
||||
import org.mercury_im.messenger.xmpp.requery.entity.LastReadChatMessageRelation;
|
||||
import org.mercury_im.messenger.xmpp.requery.entity.MessageModel;
|
||||
import org.mercury_im.messenger.xmpp.requery.entity.Models;
|
||||
import org.mercury_im.messenger.core.requery.enums.SubscriptionDirection;
|
||||
|
||||
import io.reactivex.disposables.Disposable;
|
||||
|
|
|
@ -33,9 +33,9 @@
|
|||
android:name=".ui.settings.SettingsActivity"
|
||||
android:label="@string/title_activity_settings" />
|
||||
<activity
|
||||
android:name=".ui.login.LoginActivity"
|
||||
android:name=".ui.account.LoginActivity"
|
||||
android:label="@string/title_activity_login" />
|
||||
<service android:name=".service.XmppConnectionService" />
|
||||
<service android:name=".service.MercuryConnectionService" />
|
||||
</application>
|
||||
|
||||
</manifest>
|
|
@ -0,0 +1,52 @@
|
|||
package org.mercury_im.messenger;
|
||||
|
||||
import android.app.Activity;
|
||||
|
||||
import org.mercury_im.messenger.util.AbstractActivityLifecycleCallbacks;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
/**
|
||||
* Keep track of activities in "started" state.
|
||||
* This will come in handy for things like XMPPs CSI.
|
||||
*
|
||||
* @see <a href="https://medium.com/@iamsadesh/android-how-to-detect-when-app-goes-background-foreground-fd5a4d331f8a>
|
||||
* How to detect when app goes foreground/background</a>
|
||||
*/
|
||||
public class ClientStateHandler extends AbstractActivityLifecycleCallbacks {
|
||||
|
||||
private AtomicInteger activityReferences = new AtomicInteger(0);
|
||||
private AtomicBoolean isActivityChangingConfiguration = new AtomicBoolean(false);
|
||||
|
||||
private final List<ClientStateListener> listeners = new ArrayList<>();
|
||||
|
||||
@Override
|
||||
public void onActivityStarted(Activity activity) {
|
||||
if (activityReferences.incrementAndGet() == 1 && !isActivityChangingConfiguration.get()) {
|
||||
for (ClientStateListener listener : listeners) {
|
||||
listener.onClientInForeground();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActivityStopped(Activity activity) {
|
||||
isActivityChangingConfiguration.set(activity.isChangingConfigurations());
|
||||
if (activityReferences.decrementAndGet() == 0 && !isActivityChangingConfiguration.get()) {
|
||||
for (ClientStateListener listener : listeners) {
|
||||
listener.onClientInBackground();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void addClientStateListener(ClientStateListener listener) {
|
||||
this.listeners.add(listener);
|
||||
}
|
||||
|
||||
public void removeClientStateListener(ClientStateListener listener) {
|
||||
this.listeners.remove(listener);
|
||||
}
|
||||
}
|
|
@ -1,49 +1,40 @@
|
|||
package org.mercury_im.messenger;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.app.Activity;
|
||||
import android.app.Application;
|
||||
import android.app.NotificationChannel;
|
||||
import android.app.NotificationManager;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.os.Build;
|
||||
|
||||
import org.mercury_im.messenger.core.centers.ConnectionCenter;
|
||||
import org.mercury_im.messenger.core.connection.MercuryConfiguration;
|
||||
import org.mercury_im.messenger.persistence.util.ChatAndPossiblyContact;
|
||||
import org.mercury_im.messenger.core.util.ContactNameUtil;
|
||||
import org.mercury_im.messenger.data.repository.AccountRepository;
|
||||
import org.mercury_im.messenger.di.component.AppComponent;
|
||||
import org.mercury_im.messenger.di.component.DaggerAppComponent;
|
||||
import org.mercury_im.messenger.di.module.AppModule;
|
||||
import org.mercury_im.messenger.service.XmppConnectionService;
|
||||
import org.mercury_im.messenger.util.AbstractActivityLifecycleCallbacks;
|
||||
import org.mercury_im.messenger.entity.Account;
|
||||
import org.mercury_im.messenger.service.MercuryConnectionService;
|
||||
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.List;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
public class MercuryImApplication extends Application implements org.mercury_im.messenger.core.NotificationManager {
|
||||
import io.reactivex.disposables.CompositeDisposable;
|
||||
|
||||
public static final String TAG = "Mercury-IM";
|
||||
|
||||
public class MercuryImApplication extends Application {
|
||||
|
||||
static {
|
||||
// Initialize Smack etc.
|
||||
new MercuryConfiguration();
|
||||
// new MercuryConfiguration();
|
||||
}
|
||||
|
||||
private static MercuryImApplication INSTANCE;
|
||||
AppComponent appComponent;
|
||||
|
||||
// Keep track of activities in "started" state.
|
||||
// This will come in handy for CSI
|
||||
// see https://medium.com/@iamsadesh/android-how-to-detect-when-app-goes-background-foreground-fd5a4d331f8a
|
||||
private AtomicInteger activityReferences = new AtomicInteger(0);
|
||||
private AtomicBoolean isActivityChangingConfiguration = new AtomicBoolean(false);
|
||||
private AppComponent appComponent;
|
||||
private ClientStateHandler clientStateHandler = new ClientStateHandler();
|
||||
private final CompositeDisposable disposable = new CompositeDisposable();
|
||||
|
||||
@Inject
|
||||
ConnectionCenter connectionCenter;
|
||||
AccountRepository accountRepository;
|
||||
|
||||
@Inject
|
||||
Messenger messenger;
|
||||
|
||||
public static MercuryImApplication getApplication() {
|
||||
return INSTANCE;
|
||||
|
@ -52,29 +43,25 @@ public class MercuryImApplication extends Application implements org.mercury_im.
|
|||
|
||||
@Override
|
||||
public void onCreate() {
|
||||
|
||||
super.onCreate();
|
||||
registerActivityLifecycleCallbacks(lifecycleCallbacks);
|
||||
|
||||
INSTANCE = this;
|
||||
|
||||
appComponent = createAppComponent();
|
||||
appComponent.inject(this);
|
||||
|
||||
initializeNotificationChannels(this);
|
||||
setupClientStateIndication();
|
||||
|
||||
Intent serviceIntent = new Intent(getApplicationContext(), XmppConnectionService.class);
|
||||
Notifications.initializeNotificationChannels(this);
|
||||
|
||||
serviceIntent.setAction(XmppConnectionService.ACTION_START);
|
||||
if (Build.VERSION.SDK_INT < 26) {
|
||||
startService(serviceIntent);
|
||||
} else {
|
||||
startForegroundService(serviceIntent);
|
||||
}
|
||||
subscribeForegroundServiceToActiveAccounts();
|
||||
}
|
||||
|
||||
private void setupClientStateIndication() {
|
||||
clientStateHandler.addClientStateListener(messenger);
|
||||
registerActivityLifecycleCallbacks(clientStateHandler);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the Dependency Injection graph.
|
||||
* For testing, overwrite this method with custom modules.
|
||||
*/
|
||||
public AppComponent createAppComponent() {
|
||||
AppComponent appComponent = DaggerAppComponent.builder()
|
||||
|
@ -86,65 +73,45 @@ public class MercuryImApplication extends Application implements org.mercury_im.
|
|||
return appComponent;
|
||||
}
|
||||
|
||||
public void initializeNotificationChannels(Context context) {
|
||||
// Only necessary on Android O and upwards.
|
||||
if (Build.VERSION.SDK_INT < 26) {
|
||||
return;
|
||||
}
|
||||
|
||||
NotificationManager notificationManager =
|
||||
(NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
|
||||
|
||||
// Foreground notification channel
|
||||
String fName = getResources().getString(R.string.channel_name_foreground);
|
||||
String fDescription = getResources().getString(R.string.channel_description_foreground);
|
||||
|
||||
@SuppressLint("WrongConstant")
|
||||
NotificationChannel foreground = new NotificationChannel(Notifications.NOTIFICATION_CHANNEL__FOREGROUND_SERVICE,
|
||||
fName, NotificationManager.IMPORTANCE_MIN);
|
||||
foreground.setDescription(fDescription);
|
||||
foreground.setShowBadge(false);
|
||||
notificationManager.createNotificationChannel(foreground);
|
||||
|
||||
// Incoming Messages notification channel
|
||||
String mName = getResources().getString(R.string.channel_name_message);
|
||||
String mDescription = getResources().getString(R.string.channel_description_message);
|
||||
|
||||
@SuppressLint("WrongConstant")
|
||||
NotificationChannel messages = new NotificationChannel(Notifications.NOTIFICATION_CHANNEL__NEW_MESSAGE,
|
||||
mName, NotificationManager.IMPORTANCE_DEFAULT);
|
||||
messages.setDescription(mDescription);
|
||||
notificationManager.createNotificationChannel(messages);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int chatMessageReceived(ChatAndPossiblyContact chatAndPossiblyContact, String body) {
|
||||
return Notifications.chatMessageReceived(this,
|
||||
chatAndPossiblyContact.getChat(),
|
||||
ContactNameUtil.displayableNameFrom(chatAndPossiblyContact.getContact()), body);
|
||||
}
|
||||
|
||||
public AppComponent getAppComponent() {
|
||||
return appComponent;
|
||||
}
|
||||
|
||||
private final AbstractActivityLifecycleCallbacks lifecycleCallbacks = new AbstractActivityLifecycleCallbacks() {
|
||||
private void subscribeForegroundServiceToActiveAccounts() {
|
||||
disposable.add(accountRepository.observeAllAccounts()
|
||||
.map(this::listContainsActiveAccount)
|
||||
.distinctUntilChanged()
|
||||
.subscribe(foregroundServiceNeeded -> {
|
||||
if (foregroundServiceNeeded) {
|
||||
startForegroundService();
|
||||
} else {
|
||||
stopForegroundService();
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActivityStarted(Activity activity) {
|
||||
if (activityReferences.incrementAndGet() == 1 && !isActivityChangingConfiguration.get()) {
|
||||
// App enters foreground
|
||||
connectionCenter.clientStateActive();
|
||||
private boolean listContainsActiveAccount(List<Account> accounts) {
|
||||
for (Account account : accounts) {
|
||||
if (account.isEnabled()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActivityStopped(Activity activity) {
|
||||
isActivityChangingConfiguration.set(activity.isChangingConfigurations());
|
||||
if (activityReferences.decrementAndGet() == 0 && !isActivityChangingConfiguration.get()) {
|
||||
// App enters background
|
||||
connectionCenter.clientStateInactive();
|
||||
}
|
||||
private void startForegroundService() {
|
||||
Intent startIntent = new Intent(getApplicationContext(), MercuryConnectionService.class);
|
||||
startIntent.setAction(MercuryConnectionService.ACTION_START);
|
||||
if (Build.VERSION.SDK_INT < 26) {
|
||||
startService(startIntent);
|
||||
} else {
|
||||
startForegroundService(startIntent);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private void stopForegroundService() {
|
||||
Intent stopIntent = new Intent(getApplicationContext(), MercuryConnectionService.class);
|
||||
stopIntent.setAction(MercuryConnectionService.ACTION_STOP);
|
||||
startService(stopIntent);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,15 +1,21 @@
|
|||
package org.mercury_im.messenger;
|
||||
|
||||
import android.annotation.TargetApi;
|
||||
import android.app.NotificationChannel;
|
||||
import android.app.NotificationManager;
|
||||
import android.app.PendingIntent;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.os.Build;
|
||||
|
||||
import androidx.core.app.NotificationCompat;
|
||||
import androidx.core.app.NotificationManagerCompat;
|
||||
|
||||
import org.mercury_im.messenger.persistence.entity.ChatModel;
|
||||
import org.mercury_im.messenger.entity.chat.DirectChat;
|
||||
import org.mercury_im.messenger.ui.chat.ChatActivity;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
public class Notifications {
|
||||
|
||||
public static final String NOTIFICATION_CHANNEL__FOREGROUND_SERVICE = "foreground_service";
|
||||
|
@ -18,15 +24,51 @@ public class Notifications {
|
|||
public static final String NOTIFICATION_CHANNEL__DEBUG = "debug";
|
||||
|
||||
// Notification IDs
|
||||
public static final int FOREGROUND_SERVICE_ID = 1; // must not be 0
|
||||
public static final int FOREGROUND_SERVICE_ID = 1;
|
||||
|
||||
public static int chatMessageReceived(Context context, ChatModel chat, String contactName, String body) {
|
||||
public static void initializeNotificationChannels(Context context) {
|
||||
if (Build.VERSION.SDK_INT < 26) {
|
||||
// No not call below code on lower API levels
|
||||
return;
|
||||
}
|
||||
createForegroundNotificationChannel(context);
|
||||
createMessageNotificationChannel(context);
|
||||
}
|
||||
|
||||
@TargetApi(26)
|
||||
private static void createForegroundNotificationChannel(Context context) {
|
||||
NotificationManager notificationManager =
|
||||
(NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
|
||||
String channelName = context.getResources().getString(R.string.channel_name_foreground);
|
||||
String channelDescription = context.getResources().getString(R.string.channel_description_foreground);
|
||||
|
||||
NotificationChannel channel = new NotificationChannel(
|
||||
NOTIFICATION_CHANNEL__FOREGROUND_SERVICE, channelName, NotificationManager.IMPORTANCE_MIN);
|
||||
channel.setDescription(channelDescription);
|
||||
channel.setShowBadge(false);
|
||||
notificationManager.createNotificationChannel(channel);
|
||||
}
|
||||
|
||||
@TargetApi(26)
|
||||
private static void createMessageNotificationChannel(Context context) {
|
||||
NotificationManager notificationManager =
|
||||
(NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
|
||||
String channelName = context.getResources().getString(R.string.channel_name_message);
|
||||
String channelDescription = context.getResources().getString(R.string.channel_description_message);
|
||||
|
||||
NotificationChannel channel = new NotificationChannel(
|
||||
NOTIFICATION_CHANNEL__NEW_MESSAGE, channelName, NotificationManager.IMPORTANCE_DEFAULT);
|
||||
channel.setDescription(channelDescription);
|
||||
notificationManager.createNotificationChannel(channel);
|
||||
}
|
||||
|
||||
public static int directChatMessageReceived(Context context, DirectChat chat, String contactName, String body) {
|
||||
NotificationManagerCompat notificationManagerCompat = NotificationManagerCompat.from(context);
|
||||
int id = (int) chat.getId();
|
||||
UUID id = chat.getId();
|
||||
|
||||
Intent tapAction = new Intent(context, ChatActivity.class);
|
||||
tapAction.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
|
||||
tapAction.putExtra(ChatActivity.EXTRA_JID, chat.getPeer().getJid().toString());
|
||||
tapAction.putExtra(ChatActivity.EXTRA_JID, chat.getPeer().getAddress());
|
||||
tapAction.putExtra(ChatActivity.EXTRA_ACCOUNT, chat.getPeer().getAccount().getId());
|
||||
PendingIntent pendingIntent = PendingIntent.getActivity(context, 0, tapAction, 0);
|
||||
|
||||
|
@ -38,9 +80,9 @@ public class Notifications {
|
|||
builder.setContentIntent(pendingIntent);
|
||||
builder.setAutoCancel(true);
|
||||
|
||||
notificationManagerCompat.notify(id, builder.build());
|
||||
notificationManagerCompat.notify(id.hashCode(), builder.build());
|
||||
|
||||
return id;
|
||||
return id.hashCode();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -1,86 +0,0 @@
|
|||
package org.mercury_im.messenger;
|
||||
|
||||
import android.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
|
||||
import org.jivesoftware.smack.tcp.XMPPTCPConnectionConfiguration;
|
||||
import org.jxmpp.stringprep.XmppStringprepException;
|
||||
|
||||
public class ParcelableXMPPTCPConnectionConfiguration implements Parcelable {
|
||||
|
||||
private final String username;
|
||||
private final String password;
|
||||
private final String xmppDomain;
|
||||
private final String resourcePart;
|
||||
private final String host;
|
||||
private final int port;
|
||||
|
||||
private XMPPTCPConnectionConfiguration configuration;
|
||||
|
||||
public static final Creator<ParcelableXMPPTCPConnectionConfiguration> CREATOR = new Creator<ParcelableXMPPTCPConnectionConfiguration>() {
|
||||
@Override
|
||||
public ParcelableXMPPTCPConnectionConfiguration createFromParcel(Parcel in) {
|
||||
return new ParcelableXMPPTCPConnectionConfiguration(in);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ParcelableXMPPTCPConnectionConfiguration[] newArray(int size) {
|
||||
return new ParcelableXMPPTCPConnectionConfiguration[size];
|
||||
}
|
||||
};
|
||||
|
||||
public ParcelableXMPPTCPConnectionConfiguration(String username,
|
||||
String password,
|
||||
String xmppDomain,
|
||||
String resourcePart,
|
||||
String host,
|
||||
int port) {
|
||||
this.username = username;
|
||||
this.password = password;
|
||||
this.xmppDomain = xmppDomain;
|
||||
this.resourcePart = resourcePart;
|
||||
this.host = host;
|
||||
this.port = port;
|
||||
}
|
||||
|
||||
private ParcelableXMPPTCPConnectionConfiguration(Parcel in) {
|
||||
this(in.readString(), // username
|
||||
in.readString(), // password
|
||||
in.readString(), // xmppDomain
|
||||
in.readString(), // resourcePart
|
||||
in.readString(), // host
|
||||
in.readInt()); // port
|
||||
}
|
||||
|
||||
@Override
|
||||
public int describeContents() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeToParcel(Parcel parcel, int i) {
|
||||
parcel.writeString(username);
|
||||
parcel.writeString(password);
|
||||
parcel.writeString(xmppDomain);
|
||||
parcel.writeString(resourcePart);
|
||||
parcel.writeString(host);
|
||||
parcel.writeInt(port);
|
||||
}
|
||||
|
||||
public XMPPTCPConnectionConfiguration getConfiguration() throws XmppStringprepException {
|
||||
if (configuration != null) {
|
||||
return configuration;
|
||||
}
|
||||
|
||||
XMPPTCPConnectionConfiguration.Builder builder = XMPPTCPConnectionConfiguration.builder();
|
||||
|
||||
builder.setUsernameAndPassword(username, password);
|
||||
if (xmppDomain != null) builder.setXmppDomain(xmppDomain);
|
||||
if (resourcePart != null) builder.setResource(resourcePart);
|
||||
if (host != null) builder.setHost(host);
|
||||
if (port != -1) builder.setPort(port);
|
||||
|
||||
configuration = builder.build();
|
||||
return configuration;
|
||||
}
|
||||
}
|
|
@ -1,20 +1,20 @@
|
|||
package org.mercury_im.messenger.di.component;
|
||||
|
||||
import org.mercury_im.messenger.MercuryImApplication;
|
||||
import org.mercury_im.messenger.core.di.XmppComponent;
|
||||
import org.mercury_im.messenger.core.stores.PlainMessageStore;
|
||||
import org.mercury_im.messenger.di.module.AppModule;
|
||||
import org.mercury_im.messenger.data.di.RepositoryModule;
|
||||
import org.mercury_im.messenger.di.module.AndroidPersistenceModule;
|
||||
import org.mercury_im.messenger.service.XmppConnectionService;
|
||||
import org.mercury_im.messenger.di.module.AppModule;
|
||||
import org.mercury_im.messenger.service.MercuryConnectionService;
|
||||
import org.mercury_im.messenger.store.MercuryEntityCapsStore;
|
||||
import org.mercury_im.messenger.ui.MainActivity;
|
||||
import org.mercury_im.messenger.ui.chat.ChatActivity;
|
||||
import org.mercury_im.messenger.ui.chat.ChatInputFragment;
|
||||
import org.mercury_im.messenger.ui.chat.ChatInputViewModel;
|
||||
import org.mercury_im.messenger.ui.chat.ChatViewModel;
|
||||
import org.mercury_im.messenger.ui.chatlist.ChatListViewModel;
|
||||
import org.mercury_im.messenger.ui.login.AccountsViewModel;
|
||||
import org.mercury_im.messenger.ui.login.LoginActivity;
|
||||
import org.mercury_im.messenger.ui.login.LoginViewModel;
|
||||
import org.mercury_im.messenger.ui.account.AccountsViewModel;
|
||||
import org.mercury_im.messenger.ui.account.LoginActivity;
|
||||
import org.mercury_im.messenger.ui.account.LoginViewModel;
|
||||
import org.mercury_im.messenger.ui.roster.contacts.ContactListViewModel;
|
||||
|
||||
import javax.inject.Singleton;
|
||||
|
@ -29,7 +29,8 @@ import dagger.Component;
|
|||
@Component(
|
||||
modules = {
|
||||
AppModule.class,
|
||||
AndroidPersistenceModule.class
|
||||
AndroidPersistenceModule.class,
|
||||
RepositoryModule.class
|
||||
})
|
||||
public interface AppComponent {
|
||||
|
||||
|
@ -66,11 +67,8 @@ public interface AppComponent {
|
|||
|
||||
// Services
|
||||
|
||||
void inject(XmppConnectionService service);
|
||||
void inject(MercuryConnectionService service);
|
||||
|
||||
|
||||
// Connectors
|
||||
|
||||
void inject(PlainMessageStore messageHandler);
|
||||
|
||||
void inject(MercuryEntityCapsStore store);
|
||||
}
|
||||
|
|
|
@ -2,9 +2,9 @@ package org.mercury_im.messenger.di.module;
|
|||
|
||||
import android.app.Application;
|
||||
|
||||
import org.mercury_im.messenger.data.model.Models;
|
||||
import org.mercury_im.messenger.util.ThreadUtils;
|
||||
import org.mercury_im.messenger.BuildConfig;
|
||||
import org.mercury_im.messenger.persistence.entity.Models;
|
||||
import org.mercury_im.messenger.thread_utils.ThreadUtils;
|
||||
|
||||
import javax.inject.Named;
|
||||
import javax.inject.Singleton;
|
||||
|
@ -29,16 +29,12 @@ public class AndroidPersistenceModule {
|
|||
@Singleton
|
||||
static ReactiveEntityStore<Persistable> provideDatabase(Application application) {
|
||||
// override onUpgrade to handle migrating to a new version
|
||||
DatabaseSource source = new DatabaseSource(application, Models.DEFAULT,
|
||||
"mercury_req_db", 1);
|
||||
DatabaseSource source = new DatabaseSource(application, Models.DEFAULT, "mercury_req_db", 1);
|
||||
if (BuildConfig.DEBUG) {
|
||||
// use this in development mode to drop and recreate the tables on every upgrade
|
||||
source.setTableCreationMode(TableCreationMode.DROP_CREATE);
|
||||
}
|
||||
Configuration configuration = source.getConfiguration();
|
||||
ReactiveEntityStore<Persistable> dataStore = ReactiveSupport.toReactiveStore(
|
||||
new EntityDataStore<>(configuration));
|
||||
return dataStore;
|
||||
return ReactiveSupport.toReactiveStore(new EntityDataStore<>(configuration));
|
||||
}
|
||||
|
||||
@Provides
|
||||
|
|
|
@ -2,26 +2,22 @@ package org.mercury_im.messenger.di.module;
|
|||
|
||||
import android.app.Application;
|
||||
|
||||
import dagger.Module;
|
||||
import dagger.Provides;
|
||||
|
||||
import org.jivesoftware.smackx.ping.android.ServerPingWithAlarmManager;
|
||||
import org.mercury_im.messenger.MercuryImApplication;
|
||||
import org.mercury_im.messenger.core.NotificationManager;
|
||||
import org.mercury_im.messenger.core.di.CenterModule;
|
||||
import org.mercury_im.messenger.persistence.di.RequeryModule;
|
||||
|
||||
import javax.inject.Singleton;
|
||||
|
||||
@Module(includes = {
|
||||
CenterModule.class,
|
||||
RequeryModule.class
|
||||
})
|
||||
import dagger.Module;
|
||||
import dagger.Provides;
|
||||
|
||||
@Module
|
||||
public class AppModule {
|
||||
|
||||
private MercuryImApplication mApplication;
|
||||
|
||||
public AppModule(MercuryImApplication application) {
|
||||
this.mApplication = application;
|
||||
ServerPingWithAlarmManager.onCreate(application);
|
||||
}
|
||||
|
||||
@Provides
|
||||
|
@ -29,10 +25,4 @@ public class AppModule {
|
|||
Application provideApplication() {
|
||||
return mApplication;
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
NotificationManager providerNotificationManager() {
|
||||
return mApplication;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,7 +6,6 @@ import android.app.Service;
|
|||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.os.IBinder;
|
||||
import android.util.Log;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.core.app.NotificationCompat;
|
||||
|
@ -22,36 +21,16 @@ import org.mercury_im.messenger.ui.MainActivity;
|
|||
* Started, Bound Service, which is responsible for managing {@link XMPPConnection XMPPConnections}
|
||||
* affiliated with registered accounts.
|
||||
*/
|
||||
public class XmppConnectionService extends Service {
|
||||
|
||||
private static final String TAG = MercuryImApplication.TAG;
|
||||
public class MercuryConnectionService extends Service {
|
||||
|
||||
private static final String APP = "org.mercury-im.messenger";
|
||||
private static final String SERVICE = APP + ".XmppConnectionService";
|
||||
private static final String SERVICE = APP + ".MercuryConnectionService";
|
||||
|
||||
private static final String ACTION = SERVICE + ".ACTION";
|
||||
private static final String EVENT = SERVICE + ".EVENT";
|
||||
private static final String EXTRA = SERVICE + ".EXTRA";
|
||||
private static final String STATUS = SERVICE + ".STATUS";
|
||||
|
||||
// ACTIONS
|
||||
public static final String ACTION_START = ACTION + ".START";
|
||||
public static final String ACTION_STOP = ACTION + ".STOP";
|
||||
public static final String ACTION_CONNECT = ACTION + ".CONNECT";
|
||||
public static final String ACTION_DISCONNECT = ACTION + ".DISCONNECT";
|
||||
public static final String ACTION_PING = ACTION + ".PING";
|
||||
|
||||
// EVENTS
|
||||
public static final String EVENT_INCOMING_MESSAGE = EVENT + ".INCOMING_MESSAGE";
|
||||
public static final String EVENT_OUTGOING_MESSAGE = EVENT + ".OUTGOING_MESSAGE";
|
||||
|
||||
// EXTRAS
|
||||
public static final String EXTRA_CONFIGURATION = EXTRA + ".CONFIGURATION";
|
||||
public static final String EXTRA_ACCOUNT_ID = EXTRA + ".ACCOUNT_ID";
|
||||
|
||||
// STATUSES
|
||||
public static final String STATUS_SUCCESS = STATUS + ".SUCCESS";
|
||||
public static final String STATUS_FAILURE = STATUS + ".FAILURE";
|
||||
|
||||
|
||||
@NonNull
|
||||
|
@ -63,27 +42,30 @@ public class XmppConnectionService extends Service {
|
|||
@Override
|
||||
public void onCreate() {
|
||||
super.onCreate();
|
||||
Log.d(TAG, "onCreate()");
|
||||
MercuryImApplication.getApplication().getAppComponent().inject(this);
|
||||
beginLifecycleOfPingManager();
|
||||
}
|
||||
|
||||
// Begin life cycle of Ping Manager.
|
||||
// The Manager will automatically detect newly created connections and ping the server
|
||||
// every half hour if necessary.
|
||||
/**
|
||||
* PingManager will ensure the XMPP connection is kept alive.
|
||||
*/
|
||||
private void beginLifecycleOfPingManager() {
|
||||
ServerPingWithAlarmManager.onCreate(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroy() {
|
||||
super.onDestroy();
|
||||
Log.d(TAG, "onDestroy()");
|
||||
|
||||
// End life cycle of Ping Manager.
|
||||
endLifecycleOfPingManager();
|
||||
}
|
||||
|
||||
private void endLifecycleOfPingManager() {
|
||||
ServerPingWithAlarmManager.onDestroy();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int onStartCommand(Intent intent, int flags, int startId) {
|
||||
Log.d(TAG, "onStartCommand(" + intent + ")");
|
||||
if (intent == null) {
|
||||
startAndDisplayForegroundNotification();
|
||||
} else {
|
||||
|
@ -106,19 +88,16 @@ public class XmppConnectionService extends Service {
|
|||
return START_STICKY_COMPATIBILITY;
|
||||
}
|
||||
|
||||
public void startAndDisplayForegroundNotification() {
|
||||
Log.d(TAG, "startAndDisplayForegroundNotification()");
|
||||
Notification notification = getForegroundNotification(getApplicationContext());
|
||||
|
||||
private void startAndDisplayForegroundNotification() {
|
||||
Notification notification = buildForegroundNotification(getApplicationContext());
|
||||
startForeground(Notifications.FOREGROUND_SERVICE_ID, notification);
|
||||
}
|
||||
|
||||
static Notification getForegroundNotification(Context context) {
|
||||
private static Notification buildForegroundNotification(Context context) {
|
||||
Intent startMainActivityIntent = new Intent(context, MainActivity.class);
|
||||
PendingIntent pendingIntent = PendingIntent.getActivity(context, 0,
|
||||
startMainActivityIntent, 0);
|
||||
return new NotificationCompat.Builder(context, Notifications.NOTIFICATION_CHANNEL__FOREGROUND_SERVICE)
|
||||
.setContentTitle("Mercury")
|
||||
.setSmallIcon(R.drawable.ic_send_black_24dp)
|
||||
.setContentIntent(pendingIntent)
|
||||
.build();
|
||||
|
@ -126,13 +105,13 @@ public class XmppConnectionService extends Service {
|
|||
|
||||
public class Binder extends android.os.Binder {
|
||||
|
||||
private final XmppConnectionService service;
|
||||
private final MercuryConnectionService service;
|
||||
|
||||
public Binder(XmppConnectionService service) {
|
||||
public Binder(MercuryConnectionService service) {
|
||||
this.service = service;
|
||||
}
|
||||
|
||||
public XmppConnectionService getService() {
|
||||
public MercuryConnectionService getService() {
|
||||
return service;
|
||||
}
|
||||
}
|
|
@ -15,10 +15,10 @@ import com.google.android.material.navigation.NavigationView;
|
|||
|
||||
import org.mercury_im.messenger.MercuryImApplication;
|
||||
import org.mercury_im.messenger.R;
|
||||
import org.mercury_im.messenger.persistence.entity.AccountModel;
|
||||
import org.mercury_im.messenger.persistence.repository.ChatRepository;
|
||||
import org.mercury_im.messenger.data.repository.AccountRepository;
|
||||
import org.mercury_im.messenger.entity.Account;
|
||||
import org.mercury_im.messenger.ui.chatlist.ChatListFragment;
|
||||
import org.mercury_im.messenger.ui.login.AccountsFragment;
|
||||
import org.mercury_im.messenger.ui.account.AccountsFragment;
|
||||
import org.mercury_im.messenger.ui.roster.RosterFragment;
|
||||
import org.mercury_im.messenger.ui.settings.SettingsActivity;
|
||||
|
||||
|
@ -31,9 +31,6 @@ public class MainActivity extends AppCompatActivity
|
|||
implements NavigationView.OnNavigationItemSelectedListener,
|
||||
AccountsFragment.OnAccountListItemClickListener {
|
||||
|
||||
@Inject
|
||||
ChatRepository chatRepository;
|
||||
|
||||
@BindView(R.id.toolbar)
|
||||
Toolbar toolbar;
|
||||
|
||||
|
@ -44,6 +41,9 @@ public class MainActivity extends AppCompatActivity
|
|||
private RosterFragment rosterFragment = new RosterFragment();
|
||||
private AccountsFragment accountsFragment = new AccountsFragment();
|
||||
|
||||
@Inject
|
||||
AccountRepository accountRepository;
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
@ -98,12 +98,12 @@ public class MainActivity extends AppCompatActivity
|
|||
}
|
||||
|
||||
@Override
|
||||
public void onAccountListItemClick(AccountModel item) {
|
||||
public void onAccountListItemClick(Account item) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAccountListItemLongClick(AccountModel item) {
|
||||
|
||||
public void onAccountListItemLongClick(Account item) {
|
||||
accountRepository.deleteAccount(item).subscribe();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,9 +1,8 @@
|
|||
package org.mercury_im.messenger.ui.login;
|
||||
package org.mercury_im.messenger.ui.account;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.util.Log;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
@ -15,9 +14,8 @@ import androidx.recyclerview.widget.RecyclerView;
|
|||
|
||||
import com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton;
|
||||
|
||||
import org.mercury_im.messenger.MercuryImApplication;
|
||||
import org.mercury_im.messenger.R;
|
||||
import org.mercury_im.messenger.persistence.entity.AccountModel;
|
||||
import org.mercury_im.messenger.entity.Account;
|
||||
|
||||
import butterknife.BindView;
|
||||
import butterknife.ButterKnife;
|
||||
|
@ -42,45 +40,42 @@ public class AccountsFragment extends Fragment {
|
|||
@BindView(R.id.fab)
|
||||
ExtendedFloatingActionButton fab;
|
||||
|
||||
/**
|
||||
* Mandatory empty constructor for the fragment manager to instantiate the
|
||||
* fragment (e.g. upon screen orientation changes).
|
||||
*/
|
||||
public AccountsFragment() {
|
||||
}
|
||||
|
||||
// TODO: Customize parameter initialization
|
||||
public static AccountsFragment newInstance() {
|
||||
AccountsFragment fragment = new AccountsFragment();
|
||||
return fragment;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
viewModel = new ViewModelProvider(this).get(AccountsViewModel.class);
|
||||
this.adapter = new AccountsRecyclerViewAdapter(viewModel, accountClickListener);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
observeViewModel();
|
||||
}
|
||||
|
||||
private void observeViewModel() {
|
||||
viewModel.getConnections().observe(this, adapter::setValues);
|
||||
}
|
||||
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup container,
|
||||
Bundle savedInstanceState) {
|
||||
Log.d(MercuryImApplication.TAG, "AccountsFragment.onCreateView");
|
||||
View view = inflater.inflate(R.layout.fragment_account_list, container, false);
|
||||
ButterKnife.bind(this, view);
|
||||
viewModel = new ViewModelProvider(this).get(AccountsViewModel.class);
|
||||
|
||||
// Set the adapter
|
||||
Context context = view.getContext();
|
||||
|
||||
fab.setOnClickListener(v -> startActivity(new Intent(context, LoginActivity.class)));
|
||||
recyclerView.setLayoutManager(new LinearLayoutManager(context));
|
||||
this.adapter = new AccountsRecyclerViewAdapter(viewModel, accountClickListener);
|
||||
viewModel.getAccounts().observe(this, roomAccountModels -> adapter.setValues(roomAccountModels));
|
||||
|
||||
recyclerView.setAdapter(adapter);
|
||||
|
||||
return view;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void onAttach(Context context) {
|
||||
super.onAttach(context);
|
||||
|
@ -109,8 +104,8 @@ public class AccountsFragment extends Fragment {
|
|||
* >Communicating with Other Fragments</a> for more information.
|
||||
*/
|
||||
public interface OnAccountListItemClickListener {
|
||||
void onAccountListItemClick(AccountModel item);
|
||||
void onAccountListItemClick(Account item);
|
||||
|
||||
void onAccountListItemLongClick(AccountModel item);
|
||||
void onAccountListItemLongClick(Account item);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,129 @@
|
|||
package org.mercury_im.messenger.ui.account;
|
||||
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.Switch;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.recyclerview.widget.DiffUtil;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.mercury_im.messenger.R;
|
||||
import org.mercury_im.messenger.entity.Account;
|
||||
import org.mercury_im.messenger.ui.avatar.AvatarDrawable;
|
||||
import org.mercury_im.messenger.ui.account.AccountsFragment.OnAccountListItemClickListener;
|
||||
import org.mercury_im.messenger.util.AbstractDiffCallback;
|
||||
import org.mercury_im.messenger.xmpp.MercuryConnection;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import io.reactivex.android.schedulers.AndroidSchedulers;
|
||||
|
||||
public class AccountsRecyclerViewAdapter extends RecyclerView.Adapter<AccountsRecyclerViewAdapter.ViewHolder> {
|
||||
|
||||
private final List<MercuryConnection> connections = new ArrayList<>();
|
||||
private final OnAccountListItemClickListener onAccountClickListener;
|
||||
private final AccountsViewModel viewModel;
|
||||
|
||||
public AccountsRecyclerViewAdapter(AccountsViewModel viewModel, OnAccountListItemClickListener listener) {
|
||||
onAccountClickListener = listener;
|
||||
this.viewModel = viewModel;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
|
||||
View view = LayoutInflater.from(parent.getContext())
|
||||
.inflate(R.layout.list_item_account, parent, false);
|
||||
return new ViewHolder(view);
|
||||
}
|
||||
|
||||
public void setValues(List<MercuryConnection> values) {
|
||||
DiffUtil.DiffResult diffResult = DiffUtil.calculateDiff(
|
||||
new AccountsDiffCallback(values, connections), true);
|
||||
connections.clear();
|
||||
connections.addAll(values);
|
||||
diffResult.dispatchUpdatesTo(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemCount() {
|
||||
return connections.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBindViewHolder(final ViewHolder holder, int position) {
|
||||
MercuryConnection connection = connections.get(position);
|
||||
Account account = connection.getAccount();
|
||||
|
||||
holder.account = account;
|
||||
holder.jid.setText(account.getAddress());
|
||||
holder.avatar.setImageDrawable(new AvatarDrawable(account.getAddress(), account.getAddress()));
|
||||
holder.enabled.setChecked(account.isEnabled());
|
||||
holder.enabled.setOnCheckedChangeListener((compoundButton, checked) ->
|
||||
viewModel.setAccountEnabled(account, checked));
|
||||
|
||||
viewModel.getDisposable().add(connection.getState()
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(state -> holder.status.setText(state.toString())));
|
||||
|
||||
setClickListenersOnViewHolder(holder);
|
||||
}
|
||||
|
||||
private void setClickListenersOnViewHolder(ViewHolder holder) {
|
||||
holder.mView.setOnClickListener(v -> {
|
||||
if (null != onAccountClickListener) {
|
||||
onAccountClickListener.onAccountListItemClick(holder.account);
|
||||
}
|
||||
});
|
||||
|
||||
holder.mView.setOnLongClickListener(v -> {
|
||||
if (null != onAccountClickListener) {
|
||||
onAccountClickListener.onAccountListItemLongClick(holder.account);
|
||||
}
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
class ViewHolder extends RecyclerView.ViewHolder {
|
||||
private Account account;
|
||||
final View mView;
|
||||
final ImageView avatar;
|
||||
final TextView jid;
|
||||
final Switch enabled;
|
||||
final TextView status;
|
||||
|
||||
public ViewHolder(View view) {
|
||||
super(view);
|
||||
mView = view;
|
||||
avatar = view.findViewById(R.id.avatar_account);
|
||||
jid = view.findViewById(R.id.text_account_jid);
|
||||
enabled = view.findViewById(R.id.switch_account_enabled);
|
||||
status = view.findViewById(R.id.text_account_status);
|
||||
}
|
||||
}
|
||||
|
||||
public class AccountsDiffCallback extends AbstractDiffCallback<MercuryConnection> {
|
||||
|
||||
public AccountsDiffCallback(List<MercuryConnection> newItems, List<MercuryConnection> oldItems) {
|
||||
super(newItems, oldItems);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean areItemsTheSame(int oldItemPosition, int newItemPosition) {
|
||||
return oldItems.get(oldItemPosition).getAccount().getId() == newItems.get(newItemPosition).getAccount().getId();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean areContentsTheSame(int oldItemPosition, int newItemPosition) {
|
||||
Account oldM = oldItems.get(oldItemPosition).getAccount();
|
||||
Account newM = newItems.get(newItemPosition).getAccount();
|
||||
return oldM.equals(newM);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,64 @@
|
|||
package org.mercury_im.messenger.ui.account;
|
||||
|
||||
import android.app.Application;
|
||||
|
||||
import androidx.lifecycle.AndroidViewModel;
|
||||
import androidx.lifecycle.LiveData;
|
||||
import androidx.lifecycle.MutableLiveData;
|
||||
|
||||
import org.mercury_im.messenger.MercuryImApplication;
|
||||
import org.mercury_im.messenger.Messenger;
|
||||
import org.mercury_im.messenger.data.repository.AccountRepository;
|
||||
import org.mercury_im.messenger.entity.Account;
|
||||
import org.mercury_im.messenger.xmpp.MercuryConnection;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
import io.reactivex.disposables.CompositeDisposable;
|
||||
import lombok.Getter;
|
||||
|
||||
public class AccountsViewModel extends AndroidViewModel {
|
||||
|
||||
@Inject
|
||||
AccountRepository repository;
|
||||
|
||||
@Inject
|
||||
Messenger messenger;
|
||||
|
||||
private final MutableLiveData<List<MercuryConnection>> connections = new MutableLiveData<>();
|
||||
private final CompositeDisposable compositeDisposable = new CompositeDisposable();
|
||||
|
||||
@Inject
|
||||
public AccountsViewModel(Application application) {
|
||||
super(application);
|
||||
MercuryImApplication.getApplication().getAppComponent().inject(this);
|
||||
compositeDisposable.add(messenger.getConnectionManager()
|
||||
.observeConnections()
|
||||
.map(Map::values)
|
||||
.subscribe(conns -> connections.postValue(new ArrayList<>(conns))));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onCleared() {
|
||||
super.onCleared();
|
||||
compositeDisposable.clear();
|
||||
}
|
||||
|
||||
public CompositeDisposable getDisposable() {
|
||||
return compositeDisposable;
|
||||
}
|
||||
|
||||
public LiveData<List<MercuryConnection>> getConnections() {
|
||||
return connections;
|
||||
}
|
||||
|
||||
public void setAccountEnabled(Account accountModel, boolean enabled) {
|
||||
accountModel.setEnabled(enabled);
|
||||
repository.upsertAccount(accountModel)
|
||||
.subscribe();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,158 @@
|
|||
package org.mercury_im.messenger.ui.account;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.view.KeyEvent;
|
||||
import android.view.inputmethod.EditorInfo;
|
||||
import android.widget.Button;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
import androidx.lifecycle.LiveData;
|
||||
import androidx.lifecycle.ViewModelProvider;
|
||||
|
||||
import com.google.android.material.textfield.TextInputEditText;
|
||||
|
||||
import org.mercury_im.messenger.MercuryImApplication;
|
||||
import org.mercury_im.messenger.R;
|
||||
import org.mercury_im.messenger.account.error.PasswordError;
|
||||
import org.mercury_im.messenger.account.error.UsernameError;
|
||||
import org.mercury_im.messenger.entity.Account;
|
||||
import org.mercury_im.messenger.util.Optional;
|
||||
import org.mercury_im.messenger.util.TextChangedListener;
|
||||
|
||||
import butterknife.BindView;
|
||||
import butterknife.ButterKnife;
|
||||
|
||||
|
||||
/**
|
||||
* A login screen that offers login via email/password.
|
||||
*/
|
||||
public class LoginActivity extends AppCompatActivity implements TextView.OnEditorActionListener {
|
||||
|
||||
@BindView(R.id.username)
|
||||
TextInputEditText addressView;
|
||||
|
||||
@BindView(R.id.password)
|
||||
TextInputEditText passwordView;
|
||||
|
||||
@BindView(R.id.sign_in_button)
|
||||
Button loginButton;
|
||||
|
||||
private LoginViewModel viewModel;
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
setContentView(R.layout.activity_login);
|
||||
ButterKnife.bind(this);
|
||||
|
||||
MercuryImApplication.getApplication().getAppComponent().inject(this);
|
||||
|
||||
viewModel = new ViewModelProvider(this).get(LoginViewModel.class);
|
||||
observeViewModel();
|
||||
setupTextInputListeners();
|
||||
loginButton.setOnClickListener(view -> viewModel.login());
|
||||
}
|
||||
|
||||
private void observeViewModel() {
|
||||
observeUsernameError();
|
||||
observePasswordError();
|
||||
finishOnceLoginWasSuccessful();
|
||||
enableLoginButtonOncePossible();
|
||||
}
|
||||
|
||||
private void observeUsernameError() {
|
||||
viewModel.getUsernameError().observe(this, usernameError -> {
|
||||
addressView.setError(usernameError.isError() ? usernameError.getErrorMessage() : null);
|
||||
});
|
||||
}
|
||||
|
||||
private void observePasswordError() {
|
||||
viewModel.getPasswordError().observe(this, passwordError -> {
|
||||
passwordView.setError(passwordError.isError() ? passwordError.getErrorMessage() : null);
|
||||
});
|
||||
}
|
||||
|
||||
private void finishOnceLoginWasSuccessful() {
|
||||
viewModel.isLoginSuccessful().observe(this, successful -> {
|
||||
if (Boolean.TRUE.equals(successful)) {
|
||||
finish();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void enableLoginButtonOncePossible() {
|
||||
viewModel.isLoginEnabled().observe(this, isEnabled -> {
|
||||
loginButton.setEnabled(isEnabled);
|
||||
});
|
||||
}
|
||||
|
||||
private Optional<String> getUsernameError(UsernameError usernameError) {
|
||||
switch (usernameError) {
|
||||
case none:
|
||||
return new Optional<>(null);
|
||||
case emptyUsername:
|
||||
return new Optional<>(getResources().getString(R.string.error_field_required));
|
||||
case invalidUsername:
|
||||
return new Optional<>(getResources().getString(R.string.error_invalid_username));
|
||||
case unknownUsername:
|
||||
return new Optional<>("Unknown Username!");
|
||||
default:
|
||||
throw new AssertionError("Unknown UsernameError enum value.");
|
||||
}
|
||||
}
|
||||
|
||||
private Optional<String> getPasswordError(PasswordError passwordError) {
|
||||
switch (passwordError) {
|
||||
case none:
|
||||
return new Optional<>(null);
|
||||
case emptyPassword:
|
||||
return new Optional<>(getResources().getString(R.string.error_field_required));
|
||||
case invalidPassword:
|
||||
return new Optional<>(getResources().getString(R.string.error_invalid_password));
|
||||
case incorrectPassword:
|
||||
return new Optional<>(getResources().getString(R.string.error_incorrect_password));
|
||||
default:
|
||||
throw new AssertionError("Unknown PasswordError enum value.");
|
||||
}
|
||||
}
|
||||
|
||||
private void setupTextInputListeners() {
|
||||
addressView.setOnEditorActionListener(this);
|
||||
passwordView.setOnEditorActionListener(this);
|
||||
|
||||
addressView.addTextChangedListener(new TextChangedListener() {
|
||||
@Override
|
||||
public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) {
|
||||
viewModel.setUsername(charSequence.toString());
|
||||
}
|
||||
});
|
||||
|
||||
passwordView.addTextChangedListener(new TextChangedListener() {
|
||||
@Override
|
||||
public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) {
|
||||
viewModel.setPassword(charSequence.toString());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
|
||||
switch (v.getId()) {
|
||||
case R.id.username:
|
||||
if (actionId == EditorInfo.IME_ACTION_NEXT) {
|
||||
passwordView.requestFocus();
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
|
||||
case R.id.password:
|
||||
if (actionId == EditorInfo.IME_ACTION_DONE || actionId == EditorInfo.IME_NULL) {
|
||||
//viewModel.login();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,155 @@
|
|||
package org.mercury_im.messenger.ui.account;
|
||||
|
||||
import android.app.Application;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.lifecycle.AndroidViewModel;
|
||||
import androidx.lifecycle.LiveData;
|
||||
import androidx.lifecycle.MutableLiveData;
|
||||
|
||||
import org.jivesoftware.smack.sasl.SASLErrorException;
|
||||
import org.jxmpp.jid.EntityBareJid;
|
||||
import org.jxmpp.jid.impl.JidCreate;
|
||||
import org.jxmpp.stringprep.XmppStringprepException;
|
||||
import org.mercury_im.messenger.MercuryImApplication;
|
||||
import org.mercury_im.messenger.Messenger;
|
||||
import org.mercury_im.messenger.R;
|
||||
import org.mercury_im.messenger.entity.Account;
|
||||
import org.mercury_im.messenger.entity.IAccount;
|
||||
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
import io.reactivex.android.schedulers.AndroidSchedulers;
|
||||
import io.reactivex.disposables.CompositeDisposable;
|
||||
import io.reactivex.schedulers.Schedulers;
|
||||
|
||||
public class LoginViewModel extends AndroidViewModel {
|
||||
|
||||
private MutableLiveData<Error> usernameError = new MutableLiveData<>(new Error());
|
||||
private MutableLiveData<Error> passwordError = new MutableLiveData<>(new Error());
|
||||
private MutableLiveData<Boolean> loginButtonEnabled = new MutableLiveData<>(false);
|
||||
private MutableLiveData<Boolean> loginCompleted = new MutableLiveData<>(false);
|
||||
|
||||
private final CompositeDisposable disposable = new CompositeDisposable();
|
||||
|
||||
private EntityBareJid username;
|
||||
private String password;
|
||||
|
||||
@Inject
|
||||
Messenger messenger;
|
||||
|
||||
public LoginViewModel(@NonNull Application application) {
|
||||
super(application);
|
||||
((MercuryImApplication) application).getAppComponent().inject(this);
|
||||
}
|
||||
|
||||
public void setUsername(String username) {
|
||||
if (username == null || username.isEmpty()) {
|
||||
this.username = null;
|
||||
usernameError.setValue(new Error(getApplication().getResources().getString(R.string.error_field_required)));
|
||||
} else {
|
||||
try {
|
||||
this.username = JidCreate.entityBareFrom(username);
|
||||
} catch (XmppStringprepException e) {
|
||||
this.username = null;
|
||||
usernameError.setValue(new Error(getApplication().getResources().getString(R.string.error_invalid_username)));
|
||||
}
|
||||
}
|
||||
updateLoginButtonState();
|
||||
}
|
||||
|
||||
public void setPassword(String password) {
|
||||
if (password == null || password.isEmpty()) {
|
||||
this.password = null;
|
||||
passwordError.setValue(new Error(getApplication().getResources().getString(R.string.error_field_required)));
|
||||
} else {
|
||||
this.password = password;
|
||||
}
|
||||
updateLoginButtonState();
|
||||
}
|
||||
|
||||
private void updateLoginButtonState() {
|
||||
loginButtonEnabled.setValue(username != null && !(password == null || password.isEmpty()));
|
||||
}
|
||||
|
||||
public synchronized void login() {
|
||||
Boolean loginEnabled = loginButtonEnabled.getValue();
|
||||
if (loginEnabled != null && !loginEnabled) {
|
||||
// Prevent race condition where account would be logged in twice
|
||||
return;
|
||||
}
|
||||
loginButtonEnabled.setValue(false);
|
||||
Account account = new IAccount();
|
||||
account.setAddress(username.asUnescapedString());
|
||||
account.setPassword(password);
|
||||
disposable.add(messenger.addAccount()
|
||||
.execute(account)
|
||||
.subscribeOn(Schedulers.newThread())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.doOnComplete(this::signalLoginSuccessful)
|
||||
.doOnError(this::signalLoginError)
|
||||
.subscribe());
|
||||
}
|
||||
|
||||
private void signalLoginSuccessful() {
|
||||
Logger.getAnonymousLogger().log(Level.INFO, "Signal Login Successful");
|
||||
loginCompleted.setValue(true);
|
||||
}
|
||||
|
||||
private void signalLoginError(Throwable error) {
|
||||
if (error instanceof SASLErrorException) {
|
||||
passwordError.setValue(new Error(getApplication().getResources().getString(R.string.error_incorrect_password)));
|
||||
loginButtonEnabled.setValue(true);
|
||||
} else {
|
||||
Toast.makeText(getApplication(), "A connection error occurred", Toast.LENGTH_LONG).show();
|
||||
loginButtonEnabled.setValue(true);
|
||||
}
|
||||
}
|
||||
|
||||
public LiveData<Error> getPasswordError() {
|
||||
return passwordError;
|
||||
}
|
||||
|
||||
public LiveData<Error> getUsernameError() {
|
||||
return usernameError;
|
||||
}
|
||||
|
||||
public LiveData<Boolean> isLoginSuccessful() {
|
||||
return loginCompleted;
|
||||
}
|
||||
|
||||
public LiveData<Boolean> isLoginEnabled() {
|
||||
return loginButtonEnabled;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onCleared() {
|
||||
super.onCleared();
|
||||
disposable.clear();
|
||||
}
|
||||
|
||||
public static class Error {
|
||||
|
||||
private String message;
|
||||
|
||||
public Error() {
|
||||
|
||||
}
|
||||
|
||||
public Error(String errorMessage) {
|
||||
this.message = errorMessage;
|
||||
}
|
||||
|
||||
public boolean isError() {
|
||||
return message != null;
|
||||
}
|
||||
|
||||
public String getErrorMessage() {
|
||||
return message;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
/**
|
||||
* Some Javadoc information about the package.
|
||||
*/
|
||||
package org.mercury_im.messenger.ui.login;
|
||||
package org.mercury_im.messenger.ui.account;
|
|
@ -0,0 +1,71 @@
|
|||
/*
|
||||
* Copyright 2019 Daniel Gultsch
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.mercury_im.messenger.ui.avatar;
|
||||
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.Paint;
|
||||
import android.graphics.Rect;
|
||||
import android.graphics.Typeface;
|
||||
import android.graphics.drawable.ColorDrawable;
|
||||
|
||||
import org.mercury_im.messenger.util.ColorUtil;
|
||||
|
||||
/**
|
||||
* Generates a round colored drawable with white initials.
|
||||
* Code courtesy of Daniel Gultsch's lttrs-android application.
|
||||
*
|
||||
* @see <a href="https://github.com/iNPUTmice/lttrs-android">Ltt.rs for Android</a>
|
||||
*/
|
||||
public class AvatarDrawable extends ColorDrawable {
|
||||
|
||||
private final Paint paint;
|
||||
private final Paint textPaint;
|
||||
private String letter;
|
||||
|
||||
public AvatarDrawable(String name, String key) {
|
||||
paint = new Paint();
|
||||
paint.setColor(key == null ? 0xff757575 : ColorUtil.consistentColor(key));
|
||||
paint.setAntiAlias(true);
|
||||
textPaint = new Paint();
|
||||
textPaint.setColor(Color.WHITE);
|
||||
textPaint.setTextAlign(Paint.Align.CENTER);
|
||||
textPaint.setAntiAlias(true);
|
||||
textPaint.setTypeface(Typeface.create(Typeface.DEFAULT, Typeface.NORMAL));
|
||||
this.letter = name == null ? null : String.valueOf(Character.toUpperCase(name.charAt(0)));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void draw(Canvas canvas) {
|
||||
float midx = getBounds().width() / 2.0f;
|
||||
float midy = getBounds().height() / 2.0f;
|
||||
float radius = Math.min(getBounds().width(), getBounds().height()) / 2.0f;
|
||||
textPaint.setTextSize(radius);
|
||||
Rect r = new Rect();
|
||||
canvas.getClipBounds(r);
|
||||
int cHeight = r.height();
|
||||
int cWidth = r.width();
|
||||
canvas.drawCircle(midx, midy, radius, paint);
|
||||
if (letter == null) {
|
||||
return;
|
||||
}
|
||||
textPaint.setTextAlign(Paint.Align.LEFT);
|
||||
textPaint.getTextBounds(letter, 0, letter.length(), r);
|
||||
float x = cWidth / 2f - r.width() / 2f - r.left;
|
||||
float y = cHeight / 2f + r.height() / 2f - r.bottom;
|
||||
canvas.drawText(letter, x, y, textPaint);
|
||||
}
|
||||
}
|
|
@ -11,31 +11,20 @@ import androidx.appcompat.app.AppCompatActivity;
|
|||
import androidx.appcompat.widget.SearchView;
|
||||
import androidx.appcompat.widget.Toolbar;
|
||||
import androidx.lifecycle.ViewModelProvider;
|
||||
import androidx.lifecycle.ViewModelProviders;
|
||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import butterknife.BindView;
|
||||
import butterknife.ButterKnife;
|
||||
|
||||
import io.reactivex.disposables.CompositeDisposable;
|
||||
import io.reactivex.schedulers.Schedulers;
|
||||
|
||||
import org.jivesoftware.smack.SmackException;
|
||||
import org.jivesoftware.smack.chat2.ChatManager;
|
||||
import org.jivesoftware.smack.packet.Message;
|
||||
import org.jxmpp.jid.EntityBareJid;
|
||||
import org.jxmpp.jid.impl.JidCreate;
|
||||
|
||||
import org.mercury_im.messenger.MercuryImApplication;
|
||||
import org.mercury_im.messenger.R;
|
||||
import org.mercury_im.messenger.core.centers.ConnectionCenter;
|
||||
import org.mercury_im.messenger.persistence.repository.ChatRepository;
|
||||
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
import java.util.UUID;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import butterknife.BindView;
|
||||
import butterknife.ButterKnife;
|
||||
import io.reactivex.disposables.CompositeDisposable;
|
||||
import io.reactivex.schedulers.Schedulers;
|
||||
|
||||
public class ChatActivity extends AppCompatActivity
|
||||
implements ChatInputFragment.OnChatInputActionListener, SearchView.OnQueryTextListener {
|
||||
|
@ -43,18 +32,12 @@ public class ChatActivity extends AppCompatActivity
|
|||
public static final String EXTRA_JID = "JID";
|
||||
public static final String EXTRA_ACCOUNT = "ACCOUNT";
|
||||
|
||||
@Inject
|
||||
ConnectionCenter connectionCenter;
|
||||
|
||||
@BindView(R.id.toolbar)
|
||||
Toolbar toolbar;
|
||||
|
||||
@BindView(R.id.recyclerView)
|
||||
RecyclerView recyclerView;
|
||||
|
||||
@Inject
|
||||
ChatRepository chatRepository;
|
||||
|
||||
private final MessagesRecyclerViewAdapter recyclerViewAdapter = new MessagesRecyclerViewAdapter();
|
||||
|
||||
private ChatViewModel chatViewModel;
|
||||
|
@ -63,7 +46,7 @@ public class ChatActivity extends AppCompatActivity
|
|||
|
||||
private EntityBareJid jid;
|
||||
|
||||
private long accountId;
|
||||
private UUID accountId;
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
|
@ -91,7 +74,7 @@ public class ChatActivity extends AppCompatActivity
|
|||
jid = JidCreate.entityBareFromOrThrowUnchecked(jidString);
|
||||
// JID will never change, so just set it once
|
||||
getSupportActionBar().setSubtitle(jid.asUnescapedString());
|
||||
accountId = savedInstanceState.getLong(EXTRA_ACCOUNT);
|
||||
accountId = UUID.fromString(savedInstanceState.getString(EXTRA_ACCOUNT));
|
||||
|
||||
chatViewModel = new ViewModelProvider(this).get(ChatViewModel.class);
|
||||
chatViewModel.init(accountId, jid);
|
||||
|
@ -163,7 +146,7 @@ public class ChatActivity extends AppCompatActivity
|
|||
@Override
|
||||
protected void onSaveInstanceState(@NonNull Bundle outState) {
|
||||
outState.putString(EXTRA_JID, jid.toString());
|
||||
outState.putLong(EXTRA_ACCOUNT, accountId);
|
||||
outState.putString(EXTRA_ACCOUNT, accountId.toString());
|
||||
super.onSaveInstanceState(outState);
|
||||
}
|
||||
|
||||
|
@ -189,6 +172,7 @@ public class ChatActivity extends AppCompatActivity
|
|||
return;
|
||||
}
|
||||
|
||||
/*
|
||||
// TODO: Improve by using rx
|
||||
new Thread() {
|
||||
@Override
|
||||
|
@ -203,6 +187,8 @@ public class ChatActivity extends AppCompatActivity
|
|||
}
|
||||
}
|
||||
}.start();
|
||||
|
||||
*/
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
package org.mercury_im.messenger.ui.chat;
|
||||
|
||||
import static org.mercury_im.messenger.MercuryImApplication.TAG;
|
||||
|
||||
import android.content.Context;
|
||||
import android.os.Bundle;
|
||||
|
@ -14,7 +13,7 @@ import android.widget.ImageButton;
|
|||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.fragment.app.Fragment;
|
||||
import androidx.lifecycle.ViewModelProviders;
|
||||
import androidx.lifecycle.ViewModelProvider;
|
||||
|
||||
import butterknife.BindView;
|
||||
import butterknife.ButterKnife;
|
||||
|
@ -48,7 +47,6 @@ public class ChatInputFragment extends Fragment implements View.OnClickListener
|
|||
@Override
|
||||
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container,
|
||||
@Nullable Bundle savedInstanceState) {
|
||||
Log.d(TAG, "onCreateView");
|
||||
View view = inflater.inflate(R.layout.view_compose, container, false);
|
||||
ButterKnife.bind(this, view);
|
||||
return view;
|
||||
|
@ -57,7 +55,7 @@ public class ChatInputFragment extends Fragment implements View.OnClickListener
|
|||
@Override
|
||||
public void onActivityCreated(@Nullable Bundle savedInstanceState) {
|
||||
super.onActivityCreated(savedInstanceState);
|
||||
mViewModel = ViewModelProviders.of(this).get(ChatInputViewModel.class);
|
||||
mViewModel = new ViewModelProvider(this).get(ChatInputViewModel.class);
|
||||
observeViewModel(mViewModel);
|
||||
}
|
||||
|
||||
|
@ -97,7 +95,6 @@ public class ChatInputFragment extends Fragment implements View.OnClickListener
|
|||
@Override
|
||||
@OnClick({R.id.btn_send, R.id.btn_media, R.id.btn_emoji})
|
||||
public void onClick(View view) {
|
||||
Log.d(TAG, "onClick!");
|
||||
switch (view.getId()) {
|
||||
// Add media
|
||||
case R.id.btn_media:
|
||||
|
|
|
@ -6,69 +6,68 @@ import androidx.lifecycle.ViewModel;
|
|||
|
||||
import org.jxmpp.jid.EntityBareJid;
|
||||
import org.mercury_im.messenger.MercuryImApplication;
|
||||
import org.mercury_im.messenger.core.centers.ConnectionCenter;
|
||||
import org.mercury_im.messenger.persistence.entity.ChatModel;
|
||||
import org.mercury_im.messenger.persistence.entity.ContactModel;
|
||||
import org.mercury_im.messenger.persistence.entity.EntityModel;
|
||||
import org.mercury_im.messenger.persistence.entity.MessageModel;
|
||||
import org.mercury_im.messenger.persistence.repository.ChatRepository;
|
||||
import org.mercury_im.messenger.persistence.repository.MessageRepository;
|
||||
import org.mercury_im.messenger.persistence.repository.RosterRepository;
|
||||
import org.mercury_im.messenger.data.repository.DirectChatRepository;
|
||||
import org.mercury_im.messenger.data.repository.MessageRepository;
|
||||
import org.mercury_im.messenger.data.repository.PeerRepository;
|
||||
import org.mercury_im.messenger.entity.chat.DirectChat;
|
||||
import org.mercury_im.messenger.entity.contact.Peer;
|
||||
import org.mercury_im.messenger.entity.message.Message;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
import io.reactivex.Completable;
|
||||
import io.reactivex.Observable;
|
||||
import io.reactivex.android.schedulers.AndroidSchedulers;
|
||||
import io.reactivex.disposables.CompositeDisposable;
|
||||
import io.reactivex.functions.Consumer;
|
||||
import io.reactivex.schedulers.Schedulers;
|
||||
|
||||
public class ChatViewModel extends ViewModel {
|
||||
|
||||
private final CompositeDisposable disposable = new CompositeDisposable();
|
||||
|
||||
@Inject
|
||||
PeerRepository contactRepository;
|
||||
|
||||
@Inject
|
||||
DirectChatRepository chatRepository;
|
||||
|
||||
@Inject
|
||||
MessageRepository messageRepository;
|
||||
|
||||
@Inject
|
||||
RosterRepository rosterRepository;
|
||||
|
||||
@Inject
|
||||
ChatRepository chatRepository;
|
||||
|
||||
@Inject
|
||||
ConnectionCenter connectionCenter;
|
||||
|
||||
private MutableLiveData<EntityModel> entity = new MutableLiveData<>();
|
||||
private MutableLiveData<ContactModel> contact = new MutableLiveData<>();
|
||||
private MutableLiveData<List<MessageModel>> messages = new MutableLiveData<>();
|
||||
private MutableLiveData<Peer> contact = new MutableLiveData<>();
|
||||
private MutableLiveData<List<Message>> messages = new MutableLiveData<>();
|
||||
private MutableLiveData<String> contactDisplayName = new MutableLiveData<>();
|
||||
private MutableLiveData<ChatModel> chat = new MutableLiveData<>();
|
||||
private MutableLiveData<DirectChat> chat = new MutableLiveData<>();
|
||||
|
||||
public ChatViewModel() {
|
||||
super();
|
||||
MercuryImApplication.getApplication().getAppComponent().inject(this);
|
||||
}
|
||||
|
||||
public void init(long accountId, EntityBareJid jid) {
|
||||
disposable.add(rosterRepository.getOrCreateEntity(accountId, jid)
|
||||
.subscribe((Consumer<EntityModel>) this::init));
|
||||
public void init(UUID accountId, EntityBareJid jid) {
|
||||
disposable.add(contactRepository.getOrCreatePeer(accountId, jid.toString())
|
||||
.subscribe((Consumer<Peer>) this::init));
|
||||
}
|
||||
|
||||
public void init(EntityModel entityModel) {
|
||||
public void init(Peer peer) {
|
||||
disposable.add(chatRepository.getOrCreateChatWithPeer(peer)
|
||||
.subscribe((Consumer<DirectChat>) this::init));
|
||||
}
|
||||
|
||||
disposable.add(rosterRepository.getContact(entityModel.getAccount().getId(), entityModel.getJid())
|
||||
.subscribe(reactiveResult -> {
|
||||
ContactModel model = reactiveResult.first();
|
||||
ChatViewModel.this.contact.setValue(model);
|
||||
contactDisplayName.setValue(model.getRostername());
|
||||
}));
|
||||
public void init(DirectChat chat) {
|
||||
this.chat.setValue(chat);
|
||||
|
||||
disposable.add(messageRepository.getAllMessagesOfEntity(entityModel)
|
||||
.subscribe(reactiveResult -> {
|
||||
List<MessageModel> messages = reactiveResult.toList();
|
||||
ChatViewModel.this.messages.setValue(messages);
|
||||
}));
|
||||
// Subscribe peer
|
||||
disposable.add(contactRepository.observePeer(chat.getPeer().getId())
|
||||
.subscribe(peer -> contactDisplayName.setValue(peer.getItem().getName())));
|
||||
|
||||
// Subscribe messages
|
||||
disposable.add(messageRepository.observeMessages(chat)
|
||||
.subscribe(ChatViewModel.this.messages::setValue));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -77,11 +76,11 @@ public class ChatViewModel extends ViewModel {
|
|||
disposable.clear();
|
||||
}
|
||||
|
||||
public LiveData<List<MessageModel>> getMessages() {
|
||||
public LiveData<List<Message>> getMessages() {
|
||||
return messages;
|
||||
}
|
||||
|
||||
public LiveData<ContactModel> getContact() {
|
||||
public LiveData<Peer> getContact() {
|
||||
return contact;
|
||||
}
|
||||
|
||||
|
@ -90,25 +89,16 @@ public class ChatViewModel extends ViewModel {
|
|||
}
|
||||
|
||||
public void queryTextChanged(String query) {
|
||||
/*
|
||||
if (query.isEmpty()) {
|
||||
disposable.add(messageRepository.getAllMessagesOfChat(accountId, jid)
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe((Consumer<List<MessageModel>>)
|
||||
messages -> ChatViewModel.this.messages.setValue(messages)));
|
||||
}
|
||||
disposable.add(messageRepository.findMessageByQuery(accountId, jid, query)
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe((Consumer<List<MessageModel>>) o -> {
|
||||
messages.setValue(o);
|
||||
}));
|
||||
Observable<List<Message>> observable = query.isEmpty() ?
|
||||
messageRepository.observeMessages(chat.getValue()) :
|
||||
messageRepository.findMessagesWithBody(chat.getValue(), query);
|
||||
|
||||
*/
|
||||
disposable.add(observable.subscribe(messages ->
|
||||
ChatViewModel.this.messages.setValue(messages)));
|
||||
}
|
||||
|
||||
public Completable requestMamMessages() {
|
||||
/*
|
||||
return Completable.fromAction(() -> {
|
||||
ChatModel chatModel = ChatViewModel.this.chat.getValue();
|
||||
if (chatModel == null) {
|
||||
|
@ -117,5 +107,7 @@ public class ChatViewModel extends ViewModel {
|
|||
connectionCenter.requestMamMessagesFor(chatModel);
|
||||
});
|
||||
|
||||
*/
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
package org.mercury_im.messenger.ui.chat;
|
||||
|
||||
import android.util.SparseArray;
|
||||
import android.util.SparseBooleanArray;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
@ -10,7 +10,8 @@ import androidx.annotation.NonNull;
|
|||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import org.mercury_im.messenger.R;
|
||||
import org.mercury_im.messenger.persistence.entity.MessageModel;
|
||||
import org.mercury_im.messenger.entity.message.Message;
|
||||
import org.mercury_im.messenger.entity.message.content.TextPayload;
|
||||
import org.mercury_im.messenger.ui.util.MessageBackgroundDrawable;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
@ -18,14 +19,14 @@ import java.util.List;
|
|||
|
||||
public class MessagesRecyclerViewAdapter extends RecyclerView.Adapter<MessagesRecyclerViewAdapter.MessageViewHolder> {
|
||||
|
||||
private List<MessageModel> messages = new ArrayList<>();
|
||||
private SparseArray<Boolean> checkedItems = new SparseArray<>();
|
||||
private List<Message> messages = new ArrayList<>();
|
||||
private SparseBooleanArray checkedItems = new SparseBooleanArray();
|
||||
|
||||
public MessagesRecyclerViewAdapter() {
|
||||
|
||||
}
|
||||
|
||||
public void updateMessages(List<MessageModel> messages) {
|
||||
public void updateMessages(List<Message> messages) {
|
||||
this.messages.clear();
|
||||
this.messages.addAll(messages);
|
||||
notifyDataSetChanged();
|
||||
|
@ -47,14 +48,14 @@ public class MessagesRecyclerViewAdapter extends RecyclerView.Adapter<MessagesRe
|
|||
|
||||
@Override
|
||||
public void onBindViewHolder(@NonNull MessageViewHolder holder, int position) {
|
||||
MessageModel message = messages.get(position);
|
||||
Message message = messages.get(position);
|
||||
MessageBackgroundDrawable background = getBackgroundDrawable(position);
|
||||
if (message.isIncoming()) {
|
||||
holder.body.setBackgroundResource(background.getIncomingDrawable());
|
||||
} else {
|
||||
holder.body.setBackgroundResource(background.getOutgoingDrawable());
|
||||
holder.body.setBackgroundResource(background.getOutgoingDrawable());
|
||||
}
|
||||
holder.body.setText(message.getBody());
|
||||
holder.body.setText(((TextPayload) message.getMessagePayloads().get(0).getMessageContents().get(0)).getBody());
|
||||
holder.body.requestLayout();
|
||||
}
|
||||
|
||||
|
@ -84,9 +85,9 @@ public class MessagesRecyclerViewAdapter extends RecyclerView.Adapter<MessagesRe
|
|||
}
|
||||
|
||||
public MessageBackgroundDrawable getBackgroundDrawable(int pos) {
|
||||
MessageModel subject = messages.get(pos);
|
||||
MessageModel predecessor = pos != 0 ? messages.get(pos - 1) : null;
|
||||
MessageModel successor = pos != messages.size() - 1 ? messages.get(pos + 1) : null;
|
||||
Message subject = messages.get(pos);
|
||||
Message predecessor = pos != 0 ? messages.get(pos - 1) : null;
|
||||
Message successor = pos != messages.size() - 1 ? messages.get(pos + 1) : null;
|
||||
|
||||
if (predecessor == null || predecessor.isIncoming() != subject.isIncoming()) {
|
||||
if (successor == null || successor.isIncoming() != subject.isIncoming()) {
|
||||
|
|
|
@ -1,8 +1,6 @@
|
|||
package org.mercury_im.messenger.ui.chatlist;
|
||||
|
||||
import static org.mercury_im.messenger.MercuryImApplication.TAG;
|
||||
|
||||
import android.content.Context;
|
||||
import android.os.Bundle;
|
||||
import android.util.Log;
|
||||
import android.view.LayoutInflater;
|
||||
|
@ -13,16 +11,17 @@ import android.widget.Toast;
|
|||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.fragment.app.Fragment;
|
||||
import androidx.lifecycle.ViewModelProviders;
|
||||
import androidx.lifecycle.ViewModelProvider;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton;
|
||||
|
||||
import org.mercury_im.messenger.Messenger;
|
||||
import org.mercury_im.messenger.R;
|
||||
|
||||
import butterknife.BindView;
|
||||
import butterknife.ButterKnife;
|
||||
|
||||
import org.mercury_im.messenger.R;
|
||||
|
||||
public class ChatListFragment extends Fragment {
|
||||
|
||||
private ChatListViewModel viewModel;
|
||||
|
@ -34,8 +33,10 @@ public class ChatListFragment extends Fragment {
|
|||
@BindView(R.id.fab)
|
||||
ExtendedFloatingActionButton fab;
|
||||
|
||||
public ChatListFragment() {
|
||||
|
||||
@Override
|
||||
public void onCreate(@Nullable Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
viewModel = new ViewModelProvider(this).get(ChatListViewModel.class);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
|
@ -53,21 +54,14 @@ public class ChatListFragment extends Fragment {
|
|||
}
|
||||
|
||||
@Override
|
||||
public void onCreate(@Nullable Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
observeViewModel();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAttach(Context context) {
|
||||
super.onAttach(context);
|
||||
viewModel = ViewModelProviders.of(getActivity()).get(ChatListViewModel.class);
|
||||
private void observeViewModel() {
|
||||
viewModel.getChats().observe(this, chatModels -> {
|
||||
if (chatModels == null) {
|
||||
Log.d(TAG, "Displaying null chats");
|
||||
return;
|
||||
}
|
||||
Log.d(TAG, "Displaying " + chatModels.size() + " chats");
|
||||
Log.d(Messenger.TAG, "Displaying " + chatModels.size() + " chats");
|
||||
recyclerViewAdapter.setModels(chatModels);
|
||||
});
|
||||
}
|
||||
|
|
|
@ -16,16 +16,20 @@ import androidx.appcompat.view.ActionMode;
|
|||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import org.mercury_im.messenger.R;
|
||||
import org.mercury_im.messenger.persistence.entity.ChatModel;
|
||||
import org.mercury_im.messenger.entity.chat.DirectChat;
|
||||
import org.mercury_im.messenger.ui.avatar.AvatarDrawable;
|
||||
import org.mercury_im.messenger.ui.chat.ChatActivity;
|
||||
import org.mercury_im.messenger.ui.util.AbstractRecyclerViewAdapter;
|
||||
import org.mercury_im.messenger.util.ColorUtil;
|
||||
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import butterknife.BindView;
|
||||
import butterknife.ButterKnife;
|
||||
|
||||
public class ChatListRecyclerViewAdapter
|
||||
extends AbstractRecyclerViewAdapter<ChatModel, ChatListRecyclerViewAdapter.ChatHolder> {
|
||||
extends AbstractRecyclerViewAdapter<DirectChat, ChatListRecyclerViewAdapter.ChatHolder> {
|
||||
|
||||
public ChatListRecyclerViewAdapter() {
|
||||
super(new ChatMessageDiffCallback(true));
|
||||
|
@ -41,16 +45,17 @@ public class ChatListRecyclerViewAdapter
|
|||
|
||||
@Override
|
||||
public void onBindViewHolder(@NonNull ChatHolder holder, int position) {
|
||||
ChatModel model = getModelAt(position);
|
||||
holder.nameView.setText(model.getPeer().getJid() != null ?
|
||||
model.getPeer().getJid() : model.toString());
|
||||
holder.avatarView.setColorFilter(ColorUtil.consistentColor(model.getPeer().getJid().toString()));
|
||||
|
||||
Logger.getAnonymousLogger().log(Level.INFO, "BIND");
|
||||
DirectChat model = getItemAt(position);
|
||||
String name = model.getPeer().getDisplayName();
|
||||
String address = model.getPeer().getAddress();
|
||||
holder.nameView.setText(name);
|
||||
holder.avatarView.setImageDrawable(new AvatarDrawable(name, address));
|
||||
holder.itemView.setOnClickListener(view -> {
|
||||
|
||||
Intent intent = new Intent(holder.context, ChatActivity.class);
|
||||
intent.putExtra(ChatActivity.EXTRA_JID, model.getPeer().getJid().toString());
|
||||
intent.putExtra(ChatActivity.EXTRA_ACCOUNT, model.getPeer().getAccount().getId());
|
||||
intent.putExtra(ChatActivity.EXTRA_JID, model.getPeer().getAddress());
|
||||
intent.putExtra(ChatActivity.EXTRA_ACCOUNT, model.getPeer().getAccount().getId().toString());
|
||||
|
||||
holder.context.startActivity(intent);
|
||||
});
|
||||
|
@ -84,20 +89,20 @@ public class ChatListRecyclerViewAdapter
|
|||
}
|
||||
}
|
||||
|
||||
private static class ChatMessageDiffCallback extends AbstractDiffCallback<ChatModel> {
|
||||
private static class ChatMessageDiffCallback extends AbstractDiffCallback<DirectChat> {
|
||||
|
||||
ChatMessageDiffCallback(boolean detectMoves) {
|
||||
super(detectMoves);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean areItemsTheSame(ChatModel oldItem, ChatModel newItem) {
|
||||
public boolean areItemsTheSame(DirectChat oldItem, DirectChat newItem) {
|
||||
return oldItem.getId() == newItem.getId();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean areContentsTheSame(ChatModel oldItem, ChatModel newItem) {
|
||||
return areItemsTheSame(oldItem, newItem);
|
||||
public boolean areContentsTheSame(DirectChat oldItem, DirectChat newItem) {
|
||||
return oldItem.getPeer().getName().equals(newItem.getPeer().getName());
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -5,10 +5,10 @@ import androidx.lifecycle.MutableLiveData;
|
|||
import androidx.lifecycle.ViewModel;
|
||||
|
||||
import org.mercury_im.messenger.MercuryImApplication;
|
||||
import org.mercury_im.messenger.persistence.entity.ChatModel;
|
||||
import org.mercury_im.messenger.persistence.repository.ChatRepository;
|
||||
import org.mercury_im.messenger.persistence.repository.MessageRepository;
|
||||
import org.mercury_im.messenger.data.repository.DirectChatRepository;
|
||||
import org.mercury_im.messenger.entity.chat.DirectChat;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
@ -18,23 +18,20 @@ import io.reactivex.disposables.CompositeDisposable;
|
|||
public class ChatListViewModel extends ViewModel {
|
||||
|
||||
@Inject
|
||||
ChatRepository chatRepository;
|
||||
|
||||
@Inject
|
||||
MessageRepository messageRepository;
|
||||
DirectChatRepository chatRepository;
|
||||
|
||||
private CompositeDisposable disposable = new CompositeDisposable();
|
||||
|
||||
private final MutableLiveData<List<ChatModel>> chats = new MutableLiveData<>();
|
||||
private final MutableLiveData<List<DirectChat>> chats = new MutableLiveData<>(new ArrayList<>());
|
||||
|
||||
public ChatListViewModel() {
|
||||
MercuryImApplication.getApplication().getAppComponent().inject(this);
|
||||
|
||||
disposable.add(chatRepository.getVisibleChats()
|
||||
.subscribe(result -> chats.setValue(result.toList())));
|
||||
disposable.add(chatRepository.observeAllDirectChats()
|
||||
.subscribe(chats::setValue));
|
||||
}
|
||||
|
||||
public LiveData<List<ChatModel>> getChats() {
|
||||
public LiveData<List<DirectChat>> getChats() {
|
||||
return chats;
|
||||
}
|
||||
|
||||
|
|
|
@ -1,117 +0,0 @@
|
|||
package org.mercury_im.messenger.ui.login;
|
||||
|
||||
import android.util.Log;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.Switch;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.recyclerview.widget.DiffUtil;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import org.mercury_im.messenger.MercuryImApplication;
|
||||
import org.mercury_im.messenger.R;
|
||||
import org.mercury_im.messenger.persistence.entity.AccountModel;
|
||||
import org.mercury_im.messenger.ui.login.AccountsFragment.OnAccountListItemClickListener;
|
||||
import org.mercury_im.messenger.util.AbstractDiffCallback;
|
||||
import org.mercury_im.messenger.util.ColorUtil;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class AccountsRecyclerViewAdapter extends RecyclerView.Adapter<AccountsRecyclerViewAdapter.ViewHolder> {
|
||||
|
||||
private final List<AccountModel> mValues;
|
||||
private final OnAccountListItemClickListener mListener;
|
||||
private final AccountsViewModel viewModel;
|
||||
|
||||
public AccountsRecyclerViewAdapter(AccountsViewModel viewModel, OnAccountListItemClickListener listener) {
|
||||
mValues = new ArrayList<>();
|
||||
mListener = listener;
|
||||
this.viewModel = viewModel;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
|
||||
View view = LayoutInflater.from(parent.getContext())
|
||||
.inflate(R.layout.list_item_account, parent, false);
|
||||
return new ViewHolder(view);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBindViewHolder(final ViewHolder holder, int position) {
|
||||
AccountModel account = mValues.get(position);
|
||||
holder.jid.setText(account.getJid());
|
||||
holder.avatar.setColorFilter(ColorUtil.consistentColor(account.getJid().toString()));
|
||||
holder.enabled.setChecked(account.isEnabled());
|
||||
holder.accountModel = account;
|
||||
holder.enabled.setOnCheckedChangeListener((compoundButton, b) -> {
|
||||
viewModel.toggleAccountEnabled(account);
|
||||
});
|
||||
holder.mView.setOnClickListener(v -> {
|
||||
if (null != mListener) {
|
||||
// Notify the active callbacks interface (the activity, if the
|
||||
// fragment is attached to one) that an item has been selected.
|
||||
mListener.onAccountListItemClick(holder.accountModel);
|
||||
}
|
||||
});
|
||||
holder.mView.setOnLongClickListener(v -> {
|
||||
if (null != mListener) {
|
||||
mListener.onAccountListItemLongClick(holder.accountModel);
|
||||
}
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
public void setValues(List<AccountModel> values) {
|
||||
DiffUtil.DiffResult diffResult = DiffUtil.calculateDiff(new AccountsDiffCallback(values, mValues), true);
|
||||
mValues.clear();
|
||||
mValues.addAll(values);
|
||||
diffResult.dispatchUpdatesTo(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemCount() {
|
||||
Log.d(MercuryImApplication.TAG, "Accounts Item Count: " + mValues.size());
|
||||
return mValues.size();
|
||||
}
|
||||
|
||||
public class ViewHolder extends RecyclerView.ViewHolder {
|
||||
public AccountModel accountModel;
|
||||
public final View mView;
|
||||
public final ImageView avatar;
|
||||
public final TextView jid;
|
||||
public final Switch enabled;
|
||||
public final TextView status;
|
||||
|
||||
public ViewHolder(View view) {
|
||||
super(view);
|
||||
mView = view;
|
||||
avatar = view.findViewById(R.id.avatar_account);
|
||||
jid = view.findViewById(R.id.text_account_jid);
|
||||
enabled = view.findViewById(R.id.switch_account_enabled);
|
||||
status = view.findViewById(R.id.text_account_status);
|
||||
}
|
||||
}
|
||||
|
||||
public class AccountsDiffCallback extends AbstractDiffCallback<AccountModel> {
|
||||
|
||||
public AccountsDiffCallback(List<AccountModel> newItems, List<AccountModel> oldItems) {
|
||||
super(newItems, oldItems);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean areItemsTheSame(int oldItemPosition, int newItemPosition) {
|
||||
return oldItems.get(oldItemPosition).getId() == newItems.get(newItemPosition).getId();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean areContentsTheSame(int oldItemPosition, int newItemPosition) {
|
||||
AccountModel oldM = oldItems.get(oldItemPosition);
|
||||
AccountModel newM = newItems.get(newItemPosition);
|
||||
return oldM.equals(newM);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,62 +0,0 @@
|
|||
package org.mercury_im.messenger.ui.login;
|
||||
|
||||
import android.app.Application;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.lifecycle.AndroidViewModel;
|
||||
import androidx.lifecycle.LiveData;
|
||||
import androidx.lifecycle.MutableLiveData;
|
||||
|
||||
import org.mercury_im.messenger.MercuryImApplication;
|
||||
import org.mercury_im.messenger.core.centers.ConnectionCenter;
|
||||
import org.mercury_im.messenger.core.connection.MercuryConnection;
|
||||
import org.mercury_im.messenger.persistence.entity.AccountModel;
|
||||
import org.mercury_im.messenger.persistence.repository.AccountRepository;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
import io.reactivex.disposables.CompositeDisposable;
|
||||
|
||||
public class AccountsViewModel extends AndroidViewModel {
|
||||
|
||||
@Inject
|
||||
AccountRepository repository;
|
||||
|
||||
@Inject
|
||||
ConnectionCenter connectionCenter;
|
||||
|
||||
private final MutableLiveData<List<AccountModel>> accounts = new MutableLiveData<>();
|
||||
private final CompositeDisposable compositeDisposable = new CompositeDisposable();
|
||||
|
||||
@Inject
|
||||
public AccountsViewModel(Application application) {
|
||||
super(application);
|
||||
MercuryImApplication.getApplication().getAppComponent().inject(this);
|
||||
compositeDisposable.add(repository.getAll()
|
||||
.subscribe(accountModels -> accounts.setValue(accountModels.toList())));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onCleared() {
|
||||
super.onCleared();
|
||||
compositeDisposable.clear();
|
||||
}
|
||||
|
||||
public LiveData<List<AccountModel>> getAccounts() {
|
||||
return accounts;
|
||||
}
|
||||
|
||||
public void toggleAccountEnabled(AccountModel accountModel) {
|
||||
MercuryConnection connection = connectionCenter.getConnection(accountModel);
|
||||
if (connection == null) {
|
||||
Toast.makeText(this.getApplication(), "MercuryConnection is null!", Toast.LENGTH_LONG).show();
|
||||
return;
|
||||
}
|
||||
|
||||
accountModel.setEnabled(!accountModel.isEnabled());
|
||||
repository.upsert(accountModel)
|
||||
.subscribe();
|
||||
}
|
||||
}
|
|
@ -1,21 +0,0 @@
|
|||
package org.mercury_im.messenger.ui.login;
|
||||
|
||||
import android.content.Context;
|
||||
import android.os.Bundle;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
|
||||
public class AddAccountDialog extends AlertDialog {
|
||||
|
||||
protected AddAccountDialog(@NonNull Context context, int themeResId) {
|
||||
super(context, themeResId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -1,158 +0,0 @@
|
|||
package org.mercury_im.messenger.ui.login;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.util.Log;
|
||||
import android.view.KeyEvent;
|
||||
import android.view.inputmethod.EditorInfo;
|
||||
import android.widget.Button;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
import androidx.lifecycle.LiveData;
|
||||
import androidx.lifecycle.ViewModelProvider;
|
||||
|
||||
import com.google.android.material.textfield.TextInputEditText;
|
||||
|
||||
import org.mercury_im.messenger.MercuryImApplication;
|
||||
import org.mercury_im.messenger.R;
|
||||
import org.mercury_im.messenger.persistence.entity.AccountModel;
|
||||
import org.mercury_im.messenger.util.TextChangedListener;
|
||||
|
||||
import butterknife.BindView;
|
||||
import butterknife.ButterKnife;
|
||||
|
||||
|
||||
/**
|
||||
* A login screen that offers login via email/password.
|
||||
*/
|
||||
public class LoginActivity extends AppCompatActivity implements TextView.OnEditorActionListener {
|
||||
|
||||
// UI references.
|
||||
@BindView(R.id.jid)
|
||||
TextInputEditText mJidView;
|
||||
|
||||
@BindView(R.id.password)
|
||||
TextInputEditText mPasswordView;
|
||||
|
||||
@BindView(R.id.sign_in_button)
|
||||
Button mSignInView;
|
||||
|
||||
private LoginViewModel viewModel;
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.activity_login);
|
||||
|
||||
MercuryImApplication.getApplication().getAppComponent().inject(this);
|
||||
|
||||
// Set up the login form.
|
||||
ButterKnife.bind(this);
|
||||
|
||||
viewModel = new ViewModelProvider(this).get(LoginViewModel.class);
|
||||
|
||||
observeViewModel(viewModel);
|
||||
displayCredentials(viewModel.getAccount());
|
||||
|
||||
mJidView.setOnEditorActionListener(this);
|
||||
mPasswordView.setOnEditorActionListener(this);
|
||||
|
||||
mJidView.addTextChangedListener(new TextChangedListener() {
|
||||
@Override
|
||||
public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) {
|
||||
viewModel.onJidInputChanged(charSequence.toString());
|
||||
Log.d("Mercury", "onTextChanged");
|
||||
}
|
||||
});
|
||||
|
||||
mPasswordView.addTextChangedListener(new TextChangedListener() {
|
||||
@Override
|
||||
public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) {
|
||||
viewModel.onPasswordInputChanged(charSequence.toString());
|
||||
Log.d("Mercury", "onTextChanged");
|
||||
}
|
||||
});
|
||||
|
||||
mSignInView.setOnClickListener(view -> viewModel.loginDetailsEntered());
|
||||
}
|
||||
|
||||
private void observeViewModel(LoginViewModel viewModel) {
|
||||
viewModel.getJidError().observe(this, jidError -> {
|
||||
if (jidError == null) return;
|
||||
String errorMessage = null;
|
||||
switch (jidError) {
|
||||
case none:
|
||||
break;
|
||||
case emptyJid:
|
||||
errorMessage = getResources().getString(R.string.error_field_required);
|
||||
break;
|
||||
case invalidJid:
|
||||
errorMessage = getResources().getString(R.string.error_invalid_jid);
|
||||
break;
|
||||
case unknownJid:
|
||||
errorMessage = "Unknown Jid!";
|
||||
}
|
||||
mJidView.setError(errorMessage);
|
||||
});
|
||||
|
||||
viewModel.getPasswordError().observe(this, passwordError -> {
|
||||
if (passwordError == null) return;
|
||||
String errorMessage = null;
|
||||
switch (passwordError) {
|
||||
case none:
|
||||
break;
|
||||
case emptyPassword:
|
||||
errorMessage = getResources().getString(R.string.error_field_required);
|
||||
break;
|
||||
case invalidPassword:
|
||||
errorMessage = getResources().getString(R.string.error_invalid_password);
|
||||
break;
|
||||
case incorrectPassword:
|
||||
errorMessage = getResources().getString(R.string.error_incorrect_password);
|
||||
break;
|
||||
}
|
||||
mPasswordView.setError(errorMessage);
|
||||
});
|
||||
|
||||
viewModel.getSigninSuccessful().observe(this, aBoolean -> {
|
||||
if (Boolean.TRUE.equals(aBoolean)) {
|
||||
finish();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void displayCredentials(LiveData<AccountModel> account) {
|
||||
account.observe(this, accountModel -> {
|
||||
if (accountModel == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (accountModel.getJid() != null) {
|
||||
mJidView.setText(accountModel.getJid().toString());
|
||||
}
|
||||
|
||||
if (accountModel.getPassword() != null) {
|
||||
mPasswordView.setText(accountModel.getPassword());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
|
||||
switch (v.getId()) {
|
||||
case R.id.jid:
|
||||
if (actionId == EditorInfo.IME_ACTION_NEXT) {
|
||||
mPasswordView.requestFocus();
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
|
||||
case R.id.password:
|
||||
if (actionId == EditorInfo.IME_ACTION_DONE || actionId == EditorInfo.IME_NULL) {
|
||||
viewModel.loginDetailsEntered();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
|
@ -1,153 +0,0 @@
|
|||
package org.mercury_im.messenger.ui.login;
|
||||
|
||||
import static org.mercury_im.messenger.core.connection.MercuryConnection.TAG;
|
||||
|
||||
import android.text.TextUtils;
|
||||
import android.util.Log;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.lifecycle.LiveData;
|
||||
import androidx.lifecycle.MutableLiveData;
|
||||
import androidx.lifecycle.ViewModel;
|
||||
|
||||
import io.reactivex.Single;
|
||||
import io.reactivex.observers.DisposableSingleObserver;
|
||||
|
||||
import org.jxmpp.jid.EntityBareJid;
|
||||
import org.jxmpp.jid.impl.JidCreate;
|
||||
import org.mercury_im.messenger.MercuryImApplication;
|
||||
import org.mercury_im.messenger.core.centers.ConnectionCenter;
|
||||
import org.mercury_im.messenger.persistence.entity.AccountModel;
|
||||
import org.mercury_im.messenger.persistence.repository.AccountRepository;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
public class LoginViewModel extends ViewModel {
|
||||
|
||||
@Inject
|
||||
AccountRepository accountRepository;
|
||||
|
||||
@Inject
|
||||
ConnectionCenter connectionCenter;
|
||||
|
||||
private String jid;
|
||||
private String password;
|
||||
|
||||
private MutableLiveData<JidError> jidError = new MutableLiveData<>();
|
||||
private MutableLiveData<PasswordError> passwordError = new MutableLiveData<>();
|
||||
|
||||
private MutableLiveData<AccountModel> account = new MutableLiveData<>();
|
||||
|
||||
private MutableLiveData<Boolean> signinSuccessful = new MutableLiveData<>();
|
||||
|
||||
public LoginViewModel() {
|
||||
super();
|
||||
MercuryImApplication.getApplication().getAppComponent().inject(this);
|
||||
init(new AccountModel());
|
||||
}
|
||||
|
||||
public LiveData<Boolean> getSigninSuccessful() {
|
||||
return signinSuccessful;
|
||||
}
|
||||
|
||||
public enum JidError {
|
||||
none,
|
||||
emptyJid,
|
||||
invalidJid,
|
||||
unknownJid
|
||||
}
|
||||
|
||||
public enum PasswordError {
|
||||
none,
|
||||
emptyPassword,
|
||||
invalidPassword,
|
||||
incorrectPassword
|
||||
}
|
||||
|
||||
public void onJidInputChanged(String input) {
|
||||
this.jid = input;
|
||||
|
||||
}
|
||||
|
||||
public void onPasswordInputChanged(String input) {
|
||||
this.password = input;
|
||||
}
|
||||
|
||||
public LiveData<JidError> getJidError() {
|
||||
return jidError;
|
||||
}
|
||||
|
||||
public LiveData<PasswordError> getPasswordError() {
|
||||
return passwordError;
|
||||
}
|
||||
|
||||
/**
|
||||
* Try to parse the input string into a {@link EntityBareJid} and return it.
|
||||
* Return null on failure.
|
||||
* @param input input string
|
||||
* @return valid jid or null
|
||||
*/
|
||||
private EntityBareJid asValidJidOrNull(String input) {
|
||||
return JidCreate.entityBareFromOrNull(input);
|
||||
}
|
||||
|
||||
private boolean isPasswordValid(String password) {
|
||||
return !password.isEmpty();
|
||||
}
|
||||
|
||||
public void init(@NonNull AccountModel account) {
|
||||
this.account.setValue(account);
|
||||
}
|
||||
|
||||
public MutableLiveData<AccountModel> getAccount() {
|
||||
return account;
|
||||
}
|
||||
|
||||
public void login() {
|
||||
AccountModel account = getAccount().getValue();
|
||||
if (account != null && account.getJid() != null && !TextUtils.isEmpty(account.getPassword())) {
|
||||
accountRepository.upsert(account);
|
||||
}
|
||||
}
|
||||
|
||||
public void loginDetailsEntered() {
|
||||
boolean loginIntact = true;
|
||||
if (jid.isEmpty()) {
|
||||
jidError.postValue(JidError.emptyJid);
|
||||
loginIntact = false;
|
||||
}
|
||||
|
||||
EntityBareJid bareJid = asValidJidOrNull(jid);
|
||||
if (bareJid == null) {
|
||||
jidError.postValue(JidError.invalidJid);
|
||||
loginIntact = false;
|
||||
}
|
||||
|
||||
if (!isPasswordValid(password)) {
|
||||
passwordError.postValue(PasswordError.invalidPassword);
|
||||
loginIntact = false;
|
||||
}
|
||||
|
||||
if (loginIntact) {
|
||||
AccountModel accountModel = new AccountModel();
|
||||
accountModel.setEnabled(true);
|
||||
accountModel.setJid(bareJid);
|
||||
accountModel.setPassword(password);
|
||||
Single<AccountModel> insert = accountRepository.upsert(accountModel);
|
||||
insert.subscribe(new DisposableSingleObserver<AccountModel>() {
|
||||
@Override
|
||||
public void onSuccess(AccountModel inserted) {
|
||||
Log.d(MercuryImApplication.TAG, "LoginActivity.loginDetailsEntered: Account " + inserted.getId() + " inserted.");
|
||||
connectionCenter.createConnection(accountModel);
|
||||
signinSuccessful.setValue(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(Throwable e) {
|
||||
Log.e(TAG, "Could not insert new Account data.", e);
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
}
|
||||
}
|
|
@ -12,7 +12,6 @@ import android.widget.Toast;
|
|||
|
||||
import androidx.fragment.app.Fragment;
|
||||
import androidx.lifecycle.ViewModelProvider;
|
||||
import androidx.lifecycle.ViewModelProviders;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton;
|
||||
|
|
|
@ -6,17 +6,17 @@ import androidx.annotation.NonNull;
|
|||
import androidx.lifecycle.AndroidViewModel;
|
||||
import androidx.lifecycle.LiveData;
|
||||
|
||||
import org.mercury_im.messenger.persistence.entity.ContactModel;
|
||||
import org.mercury_im.messenger.persistence.repository.RosterRepository;
|
||||
import org.mercury_im.messenger.data.repository.PeerRepository;
|
||||
import org.mercury_im.messenger.entity.contact.Peer;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
public class ContactListItemViewModel extends AndroidViewModel {
|
||||
|
||||
@Inject
|
||||
RosterRepository contactRepository;
|
||||
PeerRepository contactRepository;
|
||||
|
||||
private LiveData<ContactModel> contact;
|
||||
private LiveData<Peer> contact;
|
||||
|
||||
@Inject
|
||||
public ContactListItemViewModel(@NonNull Application application) {
|
||||
|
|
|
@ -11,9 +11,9 @@ import android.widget.TextView;
|
|||
import androidx.annotation.NonNull;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import org.jxmpp.jid.EntityBareJid;
|
||||
import org.mercury_im.messenger.R;
|
||||
import org.mercury_im.messenger.persistence.entity.ContactModel;
|
||||
import org.mercury_im.messenger.entity.contact.Peer;
|
||||
import org.mercury_im.messenger.ui.avatar.AvatarDrawable;
|
||||
import org.mercury_im.messenger.ui.chat.ChatActivity;
|
||||
import org.mercury_im.messenger.ui.util.AbstractRecyclerViewAdapter;
|
||||
import org.mercury_im.messenger.util.ColorUtil;
|
||||
|
@ -24,7 +24,7 @@ import butterknife.BindView;
|
|||
import butterknife.ButterKnife;
|
||||
|
||||
public class ContactListRecyclerViewAdapter
|
||||
extends AbstractRecyclerViewAdapter<ContactModel, ContactListRecyclerViewAdapter.RosterItemViewHolder> {
|
||||
extends AbstractRecyclerViewAdapter<Peer, ContactListRecyclerViewAdapter.RosterItemViewHolder> {
|
||||
|
||||
public ContactListRecyclerViewAdapter() {
|
||||
super(new ContactDiffCallback());
|
||||
|
@ -39,8 +39,8 @@ public class ContactListRecyclerViewAdapter
|
|||
|
||||
@Override
|
||||
public void onBindViewHolder(@NonNull RosterItemViewHolder holder, int position) {
|
||||
ContactModel model = getModelAt(position);
|
||||
holder.bind(model);
|
||||
Peer peer = getItemAt(position);
|
||||
holder.bind(peer);
|
||||
}
|
||||
|
||||
public class RosterItemViewHolder extends RecyclerView.ViewHolder {
|
||||
|
@ -65,38 +65,38 @@ public class ContactListRecyclerViewAdapter
|
|||
ButterKnife.bind(this, view);
|
||||
}
|
||||
|
||||
void bind(ContactModel contactModel) {
|
||||
String name = contactModel.getRostername();
|
||||
nameView.setText(name != null ? name : contactModel.getEntity().getJid().getLocalpart().asUnescapedString());
|
||||
EntityBareJid jid = contactModel.getEntity().getJid();
|
||||
jidView.setText(jid.toString());
|
||||
avatarView.setColorFilter(ColorUtil.consistentColor(jid.toString()));
|
||||
void bind(Peer contact) {
|
||||
String name = contact.getDisplayName();
|
||||
String address = contact.getAddress();
|
||||
nameView.setText(name);
|
||||
jidView.setText(address);
|
||||
avatarView.setImageDrawable(new AvatarDrawable(name, address));
|
||||
view.setOnClickListener(view -> {
|
||||
|
||||
Intent intent = new Intent(context, ChatActivity.class);
|
||||
intent.putExtra(ChatActivity.EXTRA_JID, jid.toString());
|
||||
intent.putExtra(ChatActivity.EXTRA_ACCOUNT, contactModel.getEntity().getAccount().getId());
|
||||
intent.putExtra(ChatActivity.EXTRA_JID, address);
|
||||
intent.putExtra(ChatActivity.EXTRA_ACCOUNT, contact.getAccount().getId().toString());
|
||||
|
||||
context.startActivity(intent);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private static class ContactDiffCallback extends AbstractDiffCallback<ContactModel> {
|
||||
private static class ContactDiffCallback extends AbstractDiffCallback<Peer> {
|
||||
|
||||
ContactDiffCallback() {
|
||||
super(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean areItemsTheSame(ContactModel oldItem, ContactModel newItem) {
|
||||
public boolean areItemsTheSame(Peer oldItem, Peer newItem) {
|
||||
return oldItem.getId() == newItem.getId();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean areContentsTheSame(ContactModel oldItem, ContactModel newItem) {
|
||||
public boolean areContentsTheSame(Peer oldItem, Peer newItem) {
|
||||
return areItemsTheSame(oldItem, newItem) &&
|
||||
Objects.equals(oldItem.getRostername(), newItem.getRostername());
|
||||
Objects.equals(oldItem.getName(), newItem.getName());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,24 +7,22 @@ import androidx.lifecycle.MutableLiveData;
|
|||
import androidx.lifecycle.ViewModel;
|
||||
|
||||
import org.mercury_im.messenger.MercuryImApplication;
|
||||
import org.mercury_im.messenger.persistence.entity.ContactModel;
|
||||
import org.mercury_im.messenger.persistence.repository.RosterRepository;
|
||||
import org.mercury_im.messenger.data.repository.XmppPeerRepository;
|
||||
import org.mercury_im.messenger.entity.contact.Peer;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
import io.reactivex.android.schedulers.AndroidSchedulers;
|
||||
import io.reactivex.disposables.CompositeDisposable;
|
||||
import io.reactivex.schedulers.Schedulers;
|
||||
|
||||
|
||||
public class ContactListViewModel extends ViewModel {
|
||||
|
||||
@Inject
|
||||
RosterRepository rosterRepository;
|
||||
XmppPeerRepository xmppContactRepository;
|
||||
|
||||
private final MutableLiveData<List<ContactModel>> rosterEntryList = new MutableLiveData<>();
|
||||
private final MutableLiveData<List<Peer>> rosterEntryList = new MutableLiveData<>();
|
||||
private final CompositeDisposable compositeDisposable = new CompositeDisposable();
|
||||
|
||||
public ContactListViewModel() {
|
||||
|
@ -32,14 +30,8 @@ public class ContactListViewModel extends ViewModel {
|
|||
MercuryImApplication.getApplication().getAppComponent().inject(this);
|
||||
Log.d("ContactListViewModel", "Start observing database");
|
||||
// Subscribe to changes to the contacts table and update the LiveData object for the UI.
|
||||
compositeDisposable.add(rosterRepository.getAllContacts()
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(o -> {
|
||||
List<ContactModel> list = o.toList();
|
||||
Log.d("ContactListViewModel", "Room changed contacts: " + list.size());
|
||||
rosterEntryList.setValue(list);
|
||||
}));
|
||||
compositeDisposable.add(xmppContactRepository.observeAllPeers()
|
||||
.subscribe(rosterEntryList::setValue));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -48,7 +40,7 @@ public class ContactListViewModel extends ViewModel {
|
|||
compositeDisposable.clear();
|
||||
}
|
||||
|
||||
public LiveData<List<ContactModel>> getRosterEntryList() {
|
||||
public LiveData<List<Peer>> getRosterEntryList() {
|
||||
return rosterEntryList;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -155,7 +155,7 @@ public class SettingsActivity extends AppCompatPreferenceActivity {
|
|||
|
||||
/**
|
||||
* This method stops fragment injection in malicious applications.
|
||||
* Make sure to deny any unknown fragments here.
|
||||
* Make sure to deny any unknownUsername fragments here.
|
||||
*/
|
||||
protected boolean isValidFragment(String fragmentName) {
|
||||
return PreferenceFragment.class.getName().equals(fragmentName)
|
||||
|
|
|
@ -27,7 +27,7 @@ public abstract class AbstractRecyclerViewAdapter<M, VH extends RecyclerView.Vie
|
|||
return models;
|
||||
}
|
||||
|
||||
public M getModelAt(int position) {
|
||||
public M getItemAt(int position) {
|
||||
return getModels().get(position);
|
||||
}
|
||||
|
||||
|
|
|
@ -10,7 +10,7 @@
|
|||
android:paddingTop="@dimen/activity_vertical_margin"
|
||||
android:paddingRight="@dimen/activity_horizontal_margin"
|
||||
android:paddingBottom="@dimen/activity_vertical_margin"
|
||||
tools:context=".ui.login.LoginActivity">
|
||||
tools:context=".ui.account.LoginActivity">
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/jid_login_form"
|
||||
|
@ -24,7 +24,7 @@
|
|||
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox">
|
||||
|
||||
<com.google.android.material.textfield.TextInputEditText
|
||||
android:id="@+id/jid"
|
||||
android:id="@+id/username"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:hint="@string/prompt_jid"
|
||||
|
|
|
@ -14,7 +14,7 @@
|
|||
android:layout_marginLeft="16dp"
|
||||
android:layout_marginRight="16dp"
|
||||
app:layoutManager="LinearLayoutManager"
|
||||
tools:context="org.mercury_im.messenger.ui.login.AccountsFragment"
|
||||
tools:context="org.mercury_im.messenger.ui.account.AccountsFragment"
|
||||
tools:listitem="@layout/list_item_account" />
|
||||
|
||||
<com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton
|
||||
|
|
|
@ -10,7 +10,7 @@
|
|||
<string name="prompt_password">Password</string>
|
||||
<string name="action_sign_in">Sign in</string>
|
||||
<string name="action_sign_in_short">Sign in</string>
|
||||
<string name="error_invalid_jid">This XMPP address is invalid</string>
|
||||
<string name="error_invalid_username">This XMPP address is invalid</string>
|
||||
<string name="error_invalid_password">This password is too short</string>
|
||||
<string name="error_incorrect_password">This password is incorrect</string>
|
||||
<string name="error_field_required">This field is required</string>
|
||||
|
|
|
@ -1,14 +1,13 @@
|
|||
// Top-level build file where you can add configuration options common to all sub-projects/modules.
|
||||
|
||||
buildscript {
|
||||
|
||||
|
||||
repositories {
|
||||
google()
|
||||
jcenter()
|
||||
}
|
||||
dependencies {
|
||||
classpath 'com.android.tools.build:gradle:3.5.1'
|
||||
|
||||
classpath 'com.android.tools.build:gradle:3.5.3'
|
||||
|
||||
// NOTE: Do not place your application dependencies here; they belong
|
||||
// in the individual module build.gradle files
|
||||
|
@ -30,7 +29,7 @@ allprojects {
|
|||
// Smack nightly unique snapshots repo
|
||||
//maven {
|
||||
// url 'https://igniterealtime.org/repo/'
|
||||
// }
|
||||
// }
|
||||
}
|
||||
|
||||
configurations {
|
||||
|
|
|
@ -8,8 +8,7 @@ sourceSets {
|
|||
|
||||
dependencies {
|
||||
|
||||
api project(":thread_utils")
|
||||
api project(":persistence")
|
||||
api project(':entity')
|
||||
|
||||
// Smack
|
||||
// Not all of those are needed, but it may be a good idea to define those versions explicitly
|
||||
|
@ -38,6 +37,7 @@ dependencies {
|
|||
|
||||
// JUnit for testing
|
||||
testImplementation "junit:junit:$junitVersion"
|
||||
compile project(path: ':data')
|
||||
}
|
||||
|
||||
sourceCompatibility = "8"
|
|
@ -0,0 +1,18 @@
|
|||
package org.mercury_im.domain.data.util;
|
||||
|
||||
import org.mercury_im.messenger.entity.contact.Peer;
|
||||
|
||||
public class ContactNameUtil {
|
||||
|
||||
public static String displayableNameFrom(Peer contact) {
|
||||
if (contact == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (contact.getName() != null) {
|
||||
return contact.getName();
|
||||
}
|
||||
|
||||
return contact.getAddress();
|
||||
}
|
||||
}
|
|
@ -1,7 +1,7 @@
|
|||
package org.mercury_im.messenger.core;
|
||||
|
||||
|
||||
import org.mercury_im.messenger.persistence.util.ChatAndPossiblyContact;
|
||||
import org.mercury_im.messenger.xmpp.util.ChatAndPossiblyContact;
|
||||
|
||||
public interface NotificationManager {
|
||||
|
|
@ -11,10 +11,10 @@ import org.jivesoftware.smackx.csi.ClientStateIndicationManager;
|
|||
import org.jivesoftware.smackx.mam.MamManager;
|
||||
import org.mercury_im.messenger.core.connection.MercuryConfiguration;
|
||||
import org.mercury_im.messenger.core.connection.MercuryConnection;
|
||||
import org.mercury_im.messenger.persistence.entity.AccountModel;
|
||||
import org.mercury_im.messenger.persistence.entity.ChatModel;
|
||||
import org.mercury_im.messenger.persistence.repository.AccountRepository;
|
||||
import org.mercury_im.messenger.persistence.repository.RosterRepository;
|
||||
import org.mercury_im.messenger.xmpp.model.AccountModel;
|
||||
import org.mercury_im.messenger.xmpp.model.ChatModel;
|
||||
import org.mercury_im.messenger.xmpp.repository.RequeryAccountRepository;
|
||||
import org.mercury_im.messenger.xmpp.repository.RosterRepository;
|
||||
import org.mercury_im.messenger.core.stores.EntityCapsStore;
|
||||
import org.mercury_im.messenger.core.stores.PlainMessageStore;
|
||||
import org.mercury_im.messenger.core.stores.RosterStore;
|
||||
|
@ -41,7 +41,7 @@ public class ConnectionCenter {
|
|||
private static final Logger LOGGER = Logger.getLogger(ConnectionCenter.class.getName());
|
||||
|
||||
// Injected
|
||||
private final AccountRepository accountRepository;
|
||||
private final RequeryAccountRepository accountRepository;
|
||||
private final RosterRepository rosterRepository;
|
||||
private final PlainMessageStore messageStore;
|
||||
private final EntityCapsStore entityCapsStore;
|
||||
|
@ -58,7 +58,7 @@ public class ConnectionCenter {
|
|||
@Inject
|
||||
public ConnectionCenter(EntityCapsStore capsStore,
|
||||
PlainMessageStore messageStore,
|
||||
AccountRepository accountRepository,
|
||||
RequeryAccountRepository accountRepository,
|
||||
RosterRepository rosterRepository) {
|
||||
LOGGER.log(Level.INFO, "ConnectionCenter initialized");
|
||||
this.entityCapsStore = capsStore;
|
|
@ -2,11 +2,6 @@ package org.mercury_im.messenger.core.di;
|
|||
|
||||
import org.mercury_im.messenger.core.NotificationManager;
|
||||
import org.mercury_im.messenger.core.centers.ConnectionCenter;
|
||||
import org.mercury_im.messenger.persistence.repository.AccountRepository;
|
||||
import org.mercury_im.messenger.persistence.repository.ChatRepository;
|
||||
import org.mercury_im.messenger.persistence.repository.EntityCapsRepository;
|
||||
import org.mercury_im.messenger.persistence.repository.MessageRepository;
|
||||
import org.mercury_im.messenger.persistence.repository.RosterRepository;
|
||||
import org.mercury_im.messenger.core.stores.EntityCapsStore;
|
||||
import org.mercury_im.messenger.core.stores.PlainMessageStore;
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
package org.mercury_im.messenger.core.di;
|
||||
|
||||
import org.mercury_im.messenger.persistence.di.RequeryModule;
|
||||
import org.mercury_im.messenger.xmpp.di.RequeryModule;
|
||||
|
||||
import javax.inject.Singleton;
|
||||
|
|
@ -4,13 +4,12 @@ import org.jivesoftware.smack.util.PacketParserUtils;
|
|||
import org.jivesoftware.smack.xml.XmlPullParser;
|
||||
import org.jivesoftware.smackx.caps.cache.EntityCapsPersistentCache;
|
||||
import org.jivesoftware.smackx.disco.packet.DiscoverInfo;
|
||||
import org.mercury_im.messenger.persistence.entity.EntityCapsModel;
|
||||
import org.mercury_im.messenger.persistence.repository.EntityCapsRepository;
|
||||
import org.mercury_im.messenger.data.model.EntityCapsModel;
|
||||
import org.mercury_im.messenger.data.repository.XmppEntityCapsRepository;
|
||||
|
||||
|
||||
import java.io.StringReader;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.logging.Level;
|
||||
|
@ -24,13 +23,13 @@ public class EntityCapsStore implements EntityCapsPersistentCache {
|
|||
|
||||
private static final Logger LOGGER = Logger.getLogger(EntityCapsStore.class.getName());
|
||||
|
||||
private final EntityCapsRepository entityCapsRepository;
|
||||
private final XmppEntityCapsRepository entityCapsRepository;
|
||||
private final Map<String, DiscoverInfo> discoverInfoMap = new HashMap<>();
|
||||
|
||||
private final CompositeDisposable disposable = new CompositeDisposable();
|
||||
|
||||
@Inject
|
||||
public EntityCapsStore(EntityCapsRepository entityCapsRepository) {
|
||||
public EntityCapsStore(XmppEntityCapsRepository entityCapsRepository) {
|
||||
this.entityCapsRepository = entityCapsRepository;
|
||||
populateFromDatabase();
|
||||
}
|
|
@ -14,15 +14,15 @@ import org.jxmpp.jid.EntityBareJid;
|
|||
import org.jxmpp.jid.impl.JidCreate;
|
||||
import org.mercury_im.messenger.core.NotificationManager;
|
||||
import org.mercury_im.messenger.core.connection.MercuryConnection;
|
||||
import org.mercury_im.messenger.persistence.entity.ChatModel;
|
||||
import org.mercury_im.messenger.persistence.entity.ContactModel;
|
||||
import org.mercury_im.messenger.persistence.entity.EntityModel;
|
||||
import org.mercury_im.messenger.persistence.entity.LastChatMessageRelation;
|
||||
import org.mercury_im.messenger.persistence.entity.MessageModel;
|
||||
import org.mercury_im.messenger.persistence.repository.ChatRepository;
|
||||
import org.mercury_im.messenger.persistence.repository.MessageRepository;
|
||||
import org.mercury_im.messenger.persistence.repository.RosterRepository;
|
||||
import org.mercury_im.messenger.persistence.util.ChatAndPossiblyContact;
|
||||
import org.mercury_im.messenger.xmpp.model.ChatModel;
|
||||
import org.mercury_im.messenger.xmpp.model.ContactModel;
|
||||
import org.mercury_im.messenger.xmpp.model.EntityModel;
|
||||
import org.mercury_im.messenger.xmpp.model.LastChatMessageRelation;
|
||||
import org.mercury_im.messenger.xmpp.model.MessageModel;
|
||||
import org.mercury_im.messenger.xmpp.repository.ChatRepository;
|
||||
import org.mercury_im.messenger.xmpp.repository.MessageRepository;
|
||||
import org.mercury_im.messenger.xmpp.repository.RosterRepository;
|
||||
import org.mercury_im.messenger.xmpp.util.ChatAndPossiblyContact;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
|
@ -2,12 +2,12 @@ package org.mercury_im.messenger.core.stores;
|
|||
|
||||
import org.jivesoftware.smack.roster.packet.RosterPacket;
|
||||
import org.jxmpp.jid.Jid;
|
||||
import org.mercury_im.messenger.persistence.entity.AccountModel;
|
||||
import org.mercury_im.messenger.persistence.entity.ContactModel;
|
||||
import org.mercury_im.messenger.persistence.entity.EntityModel;
|
||||
import org.mercury_im.messenger.persistence.repository.AccountRepository;
|
||||
import org.mercury_im.messenger.persistence.repository.RosterRepository;
|
||||
import org.mercury_im.messenger.persistence.enums.SubscriptionDirection;
|
||||
import org.mercury_im.messenger.xmpp.model.AccountModel;
|
||||
import org.mercury_im.messenger.xmpp.model.ContactModel;
|
||||
import org.mercury_im.messenger.xmpp.model.EntityModel;
|
||||
import org.mercury_im.messenger.xmpp.repository.RequeryAccountRepository;
|
||||
import org.mercury_im.messenger.xmpp.repository.RosterRepository;
|
||||
import org.mercury_im.messenger.xmpp.enums.SubscriptionDirection;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
|
@ -20,9 +20,7 @@ import java.util.logging.Logger;
|
|||
|
||||
import javax.inject.Inject;
|
||||
|
||||
import io.reactivex.Scheduler;
|
||||
import io.reactivex.disposables.CompositeDisposable;
|
||||
import io.reactivex.functions.Action;
|
||||
import io.reactivex.schedulers.Schedulers;
|
||||
|
||||
public class RosterStore implements org.jivesoftware.smack.roster.rosterstore.RosterStore {
|
||||
|
@ -30,7 +28,7 @@ public class RosterStore implements org.jivesoftware.smack.roster.rosterstore.Ro
|
|||
private static final Logger LOGGER = Logger.getLogger(RosterStore.class.getName());
|
||||
|
||||
private final RosterRepository rosterRepository;
|
||||
private final AccountRepository accountRepository;
|
||||
private final RequeryAccountRepository accountRepository;
|
||||
private AccountModel account;
|
||||
private CompositeDisposable disposable = null;
|
||||
|
||||
|
@ -38,7 +36,7 @@ public class RosterStore implements org.jivesoftware.smack.roster.rosterstore.Ro
|
|||
private String rosterVersion;
|
||||
|
||||
@Inject
|
||||
public RosterStore(RosterRepository rosterRepository, AccountRepository accountRepository) {
|
||||
public RosterStore(RosterRepository rosterRepository, RequeryAccountRepository accountRepository) {
|
||||
this.rosterRepository = rosterRepository;
|
||||
this.accountRepository = accountRepository;
|
||||
}
|
|
@ -1,28 +0,0 @@
|
|||
package org.mercury_im.messenger.core.util;
|
||||
|
||||
import org.mercury_im.messenger.persistence.entity.ContactModel;
|
||||
import org.mercury_im.messenger.persistence.entity.EntityModel;
|
||||
|
||||
public class ContactNameUtil {
|
||||
|
||||
public static String displayableNameFrom(ContactModel contactModel) {
|
||||
if (contactModel == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (contactModel.getRostername() != null) {
|
||||
return contactModel.getRostername();
|
||||
}
|
||||
if (contactModel.getEntity() != null) {
|
||||
return contactModel.getEntity().getJid().getLocalpart().asUnescapedString();
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public static String displayableNameFrom(ContactModel contact, EntityModel entity) {
|
||||
if (contact == null) {
|
||||
return entity.getJid().getLocalpart().asUnescapedString();
|
||||
} return displayableNameFrom(contact);
|
||||
}
|
||||
}
|
|
@ -4,19 +4,24 @@ apply plugin: 'java-library'
|
|||
// This is apparently necessary for use with requery.
|
||||
sourceSets {
|
||||
main.java.srcDirs += "${buildDir}/generated/sources/annotationProcessor/java/main/"
|
||||
test.java.srcDirs += "${buildDir}/generated/sources/annotationProcessor/java/test/"
|
||||
}
|
||||
|
||||
dependencies {
|
||||
|
||||
// JXMPP for Jid types. Version comes from smacks version.gradle
|
||||
api("org.jxmpp:jxmpp-jid:$jxmppVersion")
|
||||
implementation project(":entity")
|
||||
implementation project(':domain')
|
||||
|
||||
compileOnly 'org.projectlombok:lombok:1.18.10'
|
||||
annotationProcessor 'org.projectlombok:lombok:1.18.10'
|
||||
|
||||
// RxJava2
|
||||
api "io.reactivex.rxjava2:rxjava:$rxJava2Version"
|
||||
implementation "io.reactivex.rxjava2:rxjava:$rxJava2Version"
|
||||
|
||||
// Dagger 2 for dependency injection
|
||||
implementation "com.google.dagger:dagger:$daggerVersion"
|
||||
annotationProcessor "com.google.dagger:dagger-compiler:$daggerVersion"
|
||||
testAnnotationProcessor "com.google.dagger:dagger-compiler:$daggerVersion"
|
||||
|
||||
// Requery ORM
|
||||
api "io.requery:requery:$requeryVersion"
|
||||
|
@ -24,7 +29,8 @@ dependencies {
|
|||
|
||||
// JUnit for testing
|
||||
testImplementation "junit:junit:$junitVersion"
|
||||
implementation project(':thread_utils')
|
||||
|
||||
testImplementation 'org.xerial:sqlite-jdbc:3.30.1'
|
||||
}
|
||||
|
||||
sourceCompatibility = "8"
|
|
@ -0,0 +1,44 @@
|
|||
package org.mercury_im.messenger.data.converter;
|
||||
|
||||
import org.mercury_im.messenger.entity.message.MessageDirection;
|
||||
|
||||
import io.requery.Converter;
|
||||
|
||||
public class MessageDirectionConverter implements Converter<MessageDirection, Integer> {
|
||||
@Override
|
||||
public Class<MessageDirection> getMappedType() {
|
||||
return MessageDirection.class;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<Integer> getPersistedType() {
|
||||
return Integer.class;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Integer getPersistedSize() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Integer convertToPersisted(MessageDirection value) {
|
||||
switch (value) {
|
||||
case outgoing:
|
||||
return 0;
|
||||
case incoming:
|
||||
return 1;
|
||||
default: return 1;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public MessageDirection convertToMapped(Class<? extends MessageDirection> type, Integer value) {
|
||||
switch (value) {
|
||||
case 0:
|
||||
return MessageDirection.outgoing;
|
||||
case 1:
|
||||
return MessageDirection.incoming;
|
||||
default: return MessageDirection.incoming;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,6 +1,6 @@
|
|||
package org.mercury_im.messenger.persistence.converter;
|
||||
package org.mercury_im.messenger.data.converter;
|
||||
|
||||
import org.mercury_im.messenger.persistence.enums.SaslCondition;
|
||||
import org.mercury_im.messenger.data.enums.SaslCondition;
|
||||
|
||||
import io.requery.Converter;
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
package org.mercury_im.messenger.persistence.converter;
|
||||
package org.mercury_im.messenger.data.converter;
|
||||
|
||||
import org.mercury_im.messenger.persistence.enums.SubscriptionDirection;
|
||||
import org.mercury_im.messenger.entity.contact.SubscriptionDirection;
|
||||
|
||||
import io.requery.Converter;
|
||||
|
|
@ -0,0 +1,41 @@
|
|||
package org.mercury_im.messenger.data.di;
|
||||
|
||||
import org.mercury_im.messenger.data.repository.dao.AccountDao;
|
||||
import org.mercury_im.messenger.data.repository.dao.DirectChatDao;
|
||||
import org.mercury_im.messenger.data.repository.dao.GroupChatDao;
|
||||
import org.mercury_im.messenger.data.repository.dao.MessageDao;
|
||||
|
||||
import javax.inject.Singleton;
|
||||
|
||||
import dagger.Module;
|
||||
import dagger.Provides;
|
||||
import io.requery.Persistable;
|
||||
import io.requery.reactivex.ReactiveEntityStore;
|
||||
|
||||
@Module
|
||||
public class DaoModule {
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
static AccountDao provideAccountDao(ReactiveEntityStore<Persistable> data) {
|
||||
return new AccountDao(data);
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
static DirectChatDao provideDirectChatDao(ReactiveEntityStore<Persistable> data) {
|
||||
return new DirectChatDao(data);
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
static GroupChatDao provideGroupChatDao(ReactiveEntityStore<Persistable> data) {
|
||||
return new GroupChatDao(data);
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
static MessageDao provideMessageDao(ReactiveEntityStore<Persistable> data) {
|
||||
return new MessageDao(data);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,67 @@
|
|||
package org.mercury_im.messenger.data.di;
|
||||
|
||||
import org.mercury_im.messenger.data.mapping.AccountMapping;
|
||||
import org.mercury_im.messenger.data.mapping.DirectChatMapping;
|
||||
import org.mercury_im.messenger.data.mapping.EntityCapsMapping;
|
||||
import org.mercury_im.messenger.data.mapping.GroupChatMapping;
|
||||
import org.mercury_im.messenger.data.mapping.MessagePayloadMapping;
|
||||
import org.mercury_im.messenger.data.mapping.MessageMapping;
|
||||
import org.mercury_im.messenger.data.mapping.MessagePayloadContainerMapping;
|
||||
import org.mercury_im.messenger.data.mapping.PeerMapping;
|
||||
|
||||
import javax.inject.Singleton;
|
||||
|
||||
import dagger.Module;
|
||||
import dagger.Provides;
|
||||
|
||||
@Module
|
||||
public class MappingModule {
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
static AccountMapping provideAccountMapping() {
|
||||
return new AccountMapping();
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
static PeerMapping providePeerMapping() {
|
||||
return new PeerMapping(provideAccountMapping());
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
static DirectChatMapping provideDirectChatMapping() {
|
||||
return new DirectChatMapping(providePeerMapping());
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
static GroupChatMapping provideGroupChatMapping() {
|
||||
return new GroupChatMapping(provideAccountMapping());
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
static MessageMapping provideMessageMapping() {
|
||||
return new MessageMapping(provideMessagePayloadMapping());
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
static MessagePayloadContainerMapping provideMessagePayloadMapping() {
|
||||
return new MessagePayloadContainerMapping(provideMessageContentMapping());
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
static MessagePayloadMapping provideMessageContentMapping() {
|
||||
return new MessagePayloadMapping();
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
static EntityCapsMapping provideEntityCapsMapping() {
|
||||
return new EntityCapsMapping();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,115 @@
|
|||
package org.mercury_im.messenger.data.di;
|
||||
|
||||
import org.mercury_im.messenger.data.mapping.AccountMapping;
|
||||
import org.mercury_im.messenger.data.mapping.DirectChatMapping;
|
||||
import org.mercury_im.messenger.data.mapping.EntityCapsMapping;
|
||||
import org.mercury_im.messenger.data.mapping.GroupChatMapping;
|
||||
import org.mercury_im.messenger.data.mapping.MessageMapping;
|
||||
import org.mercury_im.messenger.data.mapping.PeerMapping;
|
||||
import org.mercury_im.messenger.data.repository.AccountRepository;
|
||||
import org.mercury_im.messenger.data.repository.EntityCapsRepository;
|
||||
import org.mercury_im.messenger.data.repository.GroupChatRepository;
|
||||
import org.mercury_im.messenger.data.repository.MessageRepository;
|
||||
import org.mercury_im.messenger.data.repository.PeerRepository;
|
||||
import org.mercury_im.messenger.data.repository.DirectChatRepository;
|
||||
import org.mercury_im.messenger.data.repository.XmppEntityCapsRepository;
|
||||
import org.mercury_im.messenger.data.repository.Repositories;
|
||||
import org.mercury_im.messenger.data.repository.XmppAccountRepository;
|
||||
import org.mercury_im.messenger.data.repository.XmppDirectChatRepository;
|
||||
import org.mercury_im.messenger.data.repository.XmppGroupChatRepository;
|
||||
import org.mercury_im.messenger.data.repository.XmppMessageRepository;
|
||||
import org.mercury_im.messenger.data.repository.XmppPeerRepository;
|
||||
import org.mercury_im.messenger.util.ThreadUtils;
|
||||
|
||||
import javax.inject.Named;
|
||||
import javax.inject.Singleton;
|
||||
|
||||
import dagger.Module;
|
||||
import dagger.Provides;
|
||||
import io.reactivex.Scheduler;
|
||||
import io.requery.Persistable;
|
||||
import io.requery.reactivex.ReactiveEntityStore;
|
||||
|
||||
@Module(includes = {
|
||||
MappingModule.class,
|
||||
DaoModule.class
|
||||
})
|
||||
public class RepositoryModule {
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
static AccountRepository provideAccountRepository(
|
||||
ReactiveEntityStore<Persistable> data,
|
||||
@Named(value = ThreadUtils.SCHEDULER_IO) Scheduler ioScheduler,
|
||||
@Named(value = ThreadUtils.SCHEDULER_UI) Scheduler uiScheduler,
|
||||
AccountMapping accountMapping) {
|
||||
return new XmppAccountRepository(data, ioScheduler, uiScheduler, accountMapping);
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
static PeerRepository providePeerRepository(
|
||||
ReactiveEntityStore<Persistable> data,
|
||||
@Named(value = ThreadUtils.SCHEDULER_IO) Scheduler ioScheduler,
|
||||
@Named(value = ThreadUtils.SCHEDULER_UI) Scheduler uiScheduler,
|
||||
PeerMapping peerMapping,
|
||||
AccountRepository accountRepository) {
|
||||
return new XmppPeerRepository(data, ioScheduler, uiScheduler, peerMapping, accountRepository);
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
static DirectChatRepository provideDirectChatRepository(
|
||||
ReactiveEntityStore<Persistable> data,
|
||||
@Named(value = ThreadUtils.SCHEDULER_IO) Scheduler ioScheduler,
|
||||
@Named(value = ThreadUtils.SCHEDULER_UI) Scheduler uiScheduler,
|
||||
DirectChatMapping directChatMapping) {
|
||||
return new XmppDirectChatRepository(data, ioScheduler, uiScheduler, directChatMapping);
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
static GroupChatRepository provideGroupChatRepository(
|
||||
ReactiveEntityStore<Persistable> data,
|
||||
@Named(value = ThreadUtils.SCHEDULER_IO) Scheduler ioScheduler,
|
||||
@Named(value = ThreadUtils.SCHEDULER_UI) Scheduler uiScheduler,
|
||||
GroupChatMapping groupChatMapping) {
|
||||
return new XmppGroupChatRepository(data, ioScheduler, uiScheduler, groupChatMapping);
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
static MessageRepository provideMessageRepository(
|
||||
ReactiveEntityStore<Persistable> data,
|
||||
@Named(value = ThreadUtils.SCHEDULER_IO) Scheduler ioScheduler,
|
||||
@Named(value = ThreadUtils.SCHEDULER_UI) Scheduler uiScheduler,
|
||||
MessageMapping messageMapping,
|
||||
DirectChatMapping directChatMapping,
|
||||
GroupChatMapping groupChatMapping) {
|
||||
return new XmppMessageRepository(data, ioScheduler, uiScheduler,
|
||||
messageMapping, directChatMapping, groupChatMapping);
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
static EntityCapsRepository provideCapsRepository(
|
||||
ReactiveEntityStore<Persistable> data,
|
||||
@Named(value = ThreadUtils.SCHEDULER_IO) Scheduler ioScheduler,
|
||||
@Named(value = ThreadUtils.SCHEDULER_UI) Scheduler uiScheduler,
|
||||
EntityCapsMapping entityCapsMapping) {
|
||||
return new XmppEntityCapsRepository(data, ioScheduler, uiScheduler, entityCapsMapping);
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
static Repositories provideRepositories(
|
||||
AccountRepository accountRepository,
|
||||
DirectChatRepository directChatRepository,
|
||||
GroupChatRepository groupChatRepository,
|
||||
MessageRepository messageRepository,
|
||||
PeerRepository peerRepository,
|
||||
XmppEntityCapsRepository entityCapsRepository) {
|
||||
return new Repositories(accountRepository, directChatRepository, groupChatRepository,
|
||||
messageRepository, peerRepository, entityCapsRepository);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
package org.mercury_im.messenger.data.enums;
|
||||
|
||||
public enum MessageContentType {
|
||||
/**
|
||||
* Content is a message body.
|
||||
*/
|
||||
body
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
package org.mercury_im.messenger.persistence.enums;
|
||||
package org.mercury_im.messenger.data.enums;
|
||||
|
||||
public enum SaslCondition {
|
||||
|
||||
|
@ -59,12 +59,12 @@ public enum SaslCondition {
|
|||
incorrect_encoding,
|
||||
|
||||
/**
|
||||
* The authzid provided by the initiating entity is invalid, either because it is incorrectly
|
||||
* The authzid provided by the initiating entity is invalidUsername, either because it is incorrectly
|
||||
* formatted or because the initiating entity does not have permissions to authorize that ID;
|
||||
* sent in reply to a <pre><response/></pre> element or an <pre><auth/></pre> element with
|
||||
* initial response data.
|
||||
*
|
||||
* @see <a href="https://xmpp.org/rfcs/rfc6120.html#sasl-errors-invalid-authzid">rfc6120 §6.5.6: invalid-authzid</a>
|
||||
* @see <a href="https://xmpp.org/rfcs/rfc6120.html#sasl-errors-invalid-authzid">rfc6120 §6.5.6: invalidUsername-authzid</a>
|
||||
*/
|
||||
invalid_authzid,
|
||||
|
||||
|
@ -72,7 +72,7 @@ public enum SaslCondition {
|
|||
* The initiating entity did not specify a mechanism, or requested a mechanism that is not
|
||||
* supported by the receiving entity; sent in reply to an <pre><auth/></pre> element.
|
||||
*
|
||||
* @see <a href="https://xmpp.org/rfcs/rfc6120.html#sasl-errors-invalid-mechanism">rfc6120 §6.5.7: invalid-mechanism</a>
|
||||
* @see <a href="https://xmpp.org/rfcs/rfc6120.html#sasl-errors-invalid-mechanism">rfc6120 §6.5.7: invalidUsername-mechanism</a>
|
||||
*/
|
||||
invalid_mechanism,
|
||||
|
|
@ -0,0 +1,96 @@
|
|||
package org.mercury_im.messenger.data.mapping;
|
||||
|
||||
import org.mercury_im.messenger.util.Optional;
|
||||
|
||||
import lombok.NonNull;
|
||||
|
||||
public abstract class AbstractMapping<E, M> implements Mapping<E, M> {
|
||||
|
||||
@Override
|
||||
public M toModel(E entity) {
|
||||
if (entity == null) {
|
||||
return null;
|
||||
}
|
||||
return toModel(entity, newModel(entity));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<M> toModel(Optional<E> entity) {
|
||||
if (entity.isPresent()) {
|
||||
return new Optional<>(toModel(entity.getItem()));
|
||||
} else {
|
||||
return new Optional<>();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public E toEntity(M model) {
|
||||
if (model == null) {
|
||||
return null;
|
||||
}
|
||||
return toEntity(model, newEntity(model));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<E> toEntity(Optional<M> model) {
|
||||
if (model.isPresent()) {
|
||||
return new Optional<>(toEntity(model.getItem()));
|
||||
} else {
|
||||
return new Optional<>();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public M toModel(E entity, M model) {
|
||||
if (entity == null) {
|
||||
return null;
|
||||
}
|
||||
if (model == null) {
|
||||
model = newModel(entity);
|
||||
}
|
||||
return mapToModel(entity, model);
|
||||
}
|
||||
|
||||
@Override
|
||||
public E toEntity(M model, E entity) {
|
||||
if (model == null) {
|
||||
return null;
|
||||
}
|
||||
if (entity == null) {
|
||||
entity = newEntity(model);
|
||||
}
|
||||
return mapToEntity(model, entity);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a new entity object.
|
||||
*
|
||||
* @return entity
|
||||
*/
|
||||
protected abstract E newEntity(@NonNull M model);
|
||||
|
||||
/**
|
||||
* Return a new database model object.
|
||||
*
|
||||
* @return model
|
||||
*/
|
||||
protected abstract M newModel(@NonNull E entity);
|
||||
|
||||
/**
|
||||
* Copy data from the entity to the given model.
|
||||
*
|
||||
* @param entity application entity
|
||||
* @param model database model
|
||||
* @return the database model
|
||||
*/
|
||||
protected abstract M mapToModel(@NonNull E entity, @NonNull M model);
|
||||
|
||||
/**
|
||||
* Copy data from the database model to the entity.
|
||||
*
|
||||
* @param model database model
|
||||
* @param entity entity
|
||||
* @return the application entity
|
||||
*/
|
||||
protected abstract E mapToEntity(@NonNull M model, @NonNull E entity);
|
||||
}
|
|
@ -0,0 +1,49 @@
|
|||
package org.mercury_im.messenger.data.mapping;
|
||||
|
||||
import org.mercury_im.messenger.data.model.AccountModel;
|
||||
import org.mercury_im.messenger.entity.Account;
|
||||
import org.mercury_im.messenger.entity.IAccount;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
public class AccountMapping extends AbstractMapping<Account, AccountModel> {
|
||||
|
||||
@Inject
|
||||
public AccountMapping() {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public Account newEntity(AccountModel model) {
|
||||
return new IAccount();
|
||||
}
|
||||
|
||||
@Override
|
||||
public AccountModel newModel(Account entity) {
|
||||
return new AccountModel();
|
||||
}
|
||||
|
||||
@Override
|
||||
public AccountModel mapToModel(Account entity, AccountModel model) {
|
||||
model.setId(entity.getId());
|
||||
model.setAddress(entity.getAddress());
|
||||
model.setPassword(entity.getPassword());
|
||||
model.setHost(entity.getHost());
|
||||
model.setPort(entity.getPort());
|
||||
model.setEnabled(entity.isEnabled());
|
||||
|
||||
return model;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Account mapToEntity(AccountModel model, Account entity) {
|
||||
entity.setId(model.getId());
|
||||
entity.setAddress(model.getAddress());
|
||||
entity.setPassword(model.getPassword());
|
||||
entity.setHost(model.getHost());
|
||||
entity.setPort(model.getPort());
|
||||
entity.setEnabled(model.isEnabled());
|
||||
|
||||
return entity;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,44 @@
|
|||
package org.mercury_im.messenger.data.mapping;
|
||||
|
||||
import org.mercury_im.messenger.data.model.DirectChatModel;
|
||||
import org.mercury_im.messenger.entity.chat.DirectChat;
|
||||
import org.mercury_im.messenger.entity.chat.IDirectChat;
|
||||
import org.mercury_im.messenger.entity.contact.Peer;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
public class DirectChatMapping extends AbstractMapping<DirectChat, DirectChatModel> {
|
||||
|
||||
private final PeerMapping peerMapping;
|
||||
|
||||
@Inject
|
||||
public DirectChatMapping(PeerMapping peerMapping) {
|
||||
this.peerMapping = peerMapping;
|
||||
}
|
||||
|
||||
@Override
|
||||
public DirectChat newEntity(DirectChatModel model) {
|
||||
return new IDirectChat();
|
||||
}
|
||||
|
||||
@Override
|
||||
public DirectChatModel newModel(DirectChat entity) {
|
||||
return new DirectChatModel();
|
||||
}
|
||||
|
||||
@Override
|
||||
public DirectChatModel mapToModel(DirectChat entity, DirectChatModel model) {
|
||||
model.setId(entity.getId());
|
||||
model.setPeer(peerMapping.toModel(entity.getPeer(), model.getPeer()));
|
||||
return model;
|
||||
}
|
||||
|
||||
@Override
|
||||
public DirectChat mapToEntity(DirectChatModel model, DirectChat entity) {
|
||||
entity.setId(model.getId());
|
||||
Peer peer = peerMapping.toEntity(model.getPeer(), entity.getPeer());
|
||||
entity.setPeer(peer);
|
||||
entity.setAccount(peer.getAccount());
|
||||
return entity;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,41 @@
|
|||
package org.mercury_im.messenger.data.mapping;
|
||||
|
||||
import org.mercury_im.messenger.data.model.EntityCapsModel;
|
||||
import org.mercury_im.messenger.entity.caps.EntityCapsRecord;
|
||||
import org.mercury_im.messenger.entity.caps.IEntityCapsRecord;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
import lombok.NonNull;
|
||||
|
||||
public class EntityCapsMapping extends AbstractMapping<EntityCapsRecord, EntityCapsModel> {
|
||||
|
||||
@Inject
|
||||
public EntityCapsMapping() {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
protected EntityCapsRecord newEntity(@NonNull EntityCapsModel model) {
|
||||
return new IEntityCapsRecord();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected EntityCapsModel newModel(@NonNull EntityCapsRecord entity) {
|
||||
return new EntityCapsModel();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected EntityCapsModel mapToModel(@NonNull EntityCapsRecord entity, @NonNull EntityCapsModel model) {
|
||||
model.setNodeVer(entity.getNodeVer());
|
||||
model.setXml(entity.getXml());
|
||||
return model;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected EntityCapsRecord mapToEntity(@NonNull EntityCapsModel model, @NonNull EntityCapsRecord entity) {
|
||||
entity.setNodeVer(model.getNodeVer());
|
||||
entity.setXml(model.getXml());
|
||||
return entity;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,44 @@
|
|||
package org.mercury_im.messenger.data.mapping;
|
||||
|
||||
import org.mercury_im.messenger.data.model.GroupChatModel;
|
||||
import org.mercury_im.messenger.entity.chat.GroupChat;
|
||||
import org.mercury_im.messenger.entity.chat.IGroupChat;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
public class GroupChatMapping extends AbstractMapping<GroupChat, GroupChatModel> {
|
||||
|
||||
private final AccountMapping accountMapping;
|
||||
|
||||
@Inject
|
||||
public GroupChatMapping(AccountMapping accountMapping) {
|
||||
this.accountMapping = accountMapping;
|
||||
}
|
||||
|
||||
@Override
|
||||
public GroupChat newEntity(GroupChatModel model) {
|
||||
return new IGroupChat();
|
||||
}
|
||||
|
||||
@Override
|
||||
public GroupChatModel newModel(GroupChat entity) {
|
||||
return new GroupChatModel();
|
||||
}
|
||||
|
||||
@Override
|
||||
public GroupChatModel mapToModel(GroupChat entity, GroupChatModel model) {
|
||||
model.setAccount(accountMapping.toModel(entity.getAccount(), model.getAccount()));
|
||||
model.setAddress(entity.getRoomAddress());
|
||||
model.setName(entity.getRoomName());
|
||||
return model;
|
||||
}
|
||||
|
||||
@Override
|
||||
public GroupChat mapToEntity(GroupChatModel model, GroupChat entity) {
|
||||
entity.setId(model.getId());
|
||||
entity.setAccount(accountMapping.toEntity(model.getAccount(), entity.getAccount()));
|
||||
entity.setRoomAddress(model.getAddress());
|
||||
entity.setRoomName(model.getName());
|
||||
return entity;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,51 @@
|
|||
package org.mercury_im.messenger.data.mapping;
|
||||
|
||||
import org.mercury_im.messenger.util.Optional;
|
||||
|
||||
/**
|
||||
* Interface that defines a mapping between entities and database models.
|
||||
*
|
||||
* @param <E> Entity
|
||||
* @param <M> Model
|
||||
*/
|
||||
public interface Mapping<E, M> {
|
||||
|
||||
/**
|
||||
* Map data from the entity to a new model.
|
||||
*
|
||||
* @param entity application entity
|
||||
* @return new database model
|
||||
*/
|
||||
M toModel(E entity);
|
||||
|
||||
Optional<M> toModel(Optional<E> entity);
|
||||
|
||||
/**
|
||||
* Copy data from the model to a new entity.
|
||||
* @param model database model
|
||||
* @return new application entity
|
||||
*/
|
||||
E toEntity(M model);
|
||||
|
||||
Optional<E> toEntity(Optional<M> model);
|
||||
|
||||
/**
|
||||
* Map an entity to a model.
|
||||
*
|
||||
* @param entity entity
|
||||
* @param model model
|
||||
* @return model
|
||||
*/
|
||||
M toModel(E entity, M model);
|
||||
|
||||
/**
|
||||
* Map a model to an entity.
|
||||
*
|
||||
* @param model model
|
||||
* @param entity entity
|
||||
* @return entity
|
||||
*/
|
||||
E toEntity(M model, E entity);
|
||||
|
||||
|
||||
}
|
|
@ -0,0 +1,65 @@
|
|||
package org.mercury_im.messenger.data.mapping;
|
||||
|
||||
import org.mercury_im.messenger.data.model.MessageModel;
|
||||
import org.mercury_im.messenger.data.model.MessagePayloadContainerModel;
|
||||
import org.mercury_im.messenger.entity.message.IMessage;
|
||||
import org.mercury_im.messenger.entity.message.Message;
|
||||
import org.mercury_im.messenger.entity.message.PayloadContainer;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
public class MessageMapping extends AbstractMapping<Message, MessageModel> {
|
||||
|
||||
private final MessagePayloadContainerMapping messagePayloadContainerMapping;
|
||||
|
||||
@Inject
|
||||
public MessageMapping(MessagePayloadContainerMapping messagePayloadContainerMapping) {
|
||||
this.messagePayloadContainerMapping = messagePayloadContainerMapping;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Message newEntity(MessageModel model) {
|
||||
return new IMessage();
|
||||
}
|
||||
|
||||
@Override
|
||||
public MessageModel newModel(Message entity) {
|
||||
return new MessageModel();
|
||||
}
|
||||
|
||||
@Override
|
||||
public MessageModel mapToModel(Message entity, MessageModel model) {
|
||||
model.setSender(entity.getSender());
|
||||
model.setRecipient(entity.getRecipient());
|
||||
model.setTimestamp(entity.getTimestamp());
|
||||
model.setDirection(entity.getDirection());
|
||||
|
||||
model.getPayloads().clear();
|
||||
for (PayloadContainer payload : entity.getMessagePayloads()) {
|
||||
MessagePayloadContainerModel payloadModel = messagePayloadContainerMapping.toModel(payload, new MessagePayloadContainerModel());
|
||||
payloadModel.setMessage(model);
|
||||
model.getPayloads().add(payloadModel);
|
||||
}
|
||||
|
||||
return model;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Message mapToEntity(MessageModel model, Message entity) {
|
||||
entity.setId(model.getId());
|
||||
entity.setSender(model.getSender());
|
||||
entity.setRecipient(model.getRecipient());
|
||||
entity.setTimestamp(model.getTimestamp());
|
||||
entity.setDirection(model.getDirection());
|
||||
|
||||
List<PayloadContainer> payloadContainers = new ArrayList<>(entity.getMessagePayloads().size());
|
||||
for (MessagePayloadContainerModel containerModel : model.getPayloads()) {
|
||||
payloadContainers.add(messagePayloadContainerMapping.toEntity(containerModel));
|
||||
}
|
||||
entity.setMessagePayloads(payloadContainers);
|
||||
return entity;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,58 @@
|
|||
package org.mercury_im.messenger.data.mapping;
|
||||
|
||||
import org.mercury_im.messenger.data.model.MessagePayloadModel;
|
||||
import org.mercury_im.messenger.data.model.MessagePayloadContainerModel;
|
||||
import org.mercury_im.messenger.entity.message.IPayloadContainer;
|
||||
import org.mercury_im.messenger.entity.message.PayloadContainer;
|
||||
import org.mercury_im.messenger.entity.message.content.Payload;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
public class MessagePayloadContainerMapping extends AbstractMapping<PayloadContainer, MessagePayloadContainerModel> {
|
||||
|
||||
private final MessagePayloadMapping messagePayloadMapping;
|
||||
|
||||
@Inject
|
||||
public MessagePayloadContainerMapping(MessagePayloadMapping messagePayloadMapping) {
|
||||
this.messagePayloadMapping = messagePayloadMapping;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PayloadContainer newEntity(MessagePayloadContainerModel model) {
|
||||
return new IPayloadContainer();
|
||||
}
|
||||
|
||||
@Override
|
||||
public MessagePayloadContainerModel newModel(PayloadContainer entity) {
|
||||
return new MessagePayloadContainerModel();
|
||||
}
|
||||
|
||||
@Override
|
||||
public MessagePayloadContainerModel mapToModel(PayloadContainer entity, MessagePayloadContainerModel model) {
|
||||
model.getContents().clear();
|
||||
for (Payload contentEntity : entity.getMessageContents()) {
|
||||
MessagePayloadModel contentModel = messagePayloadMapping.toModel(contentEntity, new MessagePayloadModel());
|
||||
contentModel.setPayloadContainer(model);
|
||||
model.getContents().add(contentModel);
|
||||
}
|
||||
|
||||
return model;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PayloadContainer mapToEntity(MessagePayloadContainerModel model, PayloadContainer entity) {
|
||||
entity.setId(model.getId());
|
||||
|
||||
List<Payload> contents = new ArrayList<>(model.getContents().size());
|
||||
for (MessagePayloadModel contentModel : model.getContents()) {
|
||||
Payload contentEntity = messagePayloadMapping.toEntity(contentModel, null);
|
||||
contents.add(contentEntity);
|
||||
}
|
||||
entity.setMessageContents(contents);
|
||||
|
||||
return entity;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,47 @@
|
|||
package org.mercury_im.messenger.data.mapping;
|
||||
|
||||
import org.mercury_im.messenger.data.model.MessagePayloadModel;
|
||||
import org.mercury_im.messenger.entity.message.content.Payload;
|
||||
import org.mercury_im.messenger.entity.message.content.TextPayload;
|
||||
|
||||
import static org.mercury_im.messenger.data.enums.MessageContentType.body;
|
||||
|
||||
public class MessagePayloadMapping extends AbstractMapping<Payload, MessagePayloadModel> {
|
||||
|
||||
@Override
|
||||
public Payload newEntity(MessagePayloadModel model) {
|
||||
switch (model.getType()) {
|
||||
case body:
|
||||
return new TextPayload();
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public MessagePayloadModel newModel(Payload entity) {
|
||||
return new MessagePayloadModel();
|
||||
}
|
||||
|
||||
@Override
|
||||
public MessagePayloadModel mapToModel(Payload entity, MessagePayloadModel model) {
|
||||
if (entity instanceof TextPayload) {
|
||||
model.setType(body);
|
||||
model.setBody(((TextPayload) entity).getBody());
|
||||
}
|
||||
// else if (...)
|
||||
return model;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Payload mapToEntity(MessagePayloadModel model, Payload entity) {
|
||||
entity.setId(model.getId());
|
||||
switch (model.getType()) {
|
||||
case body:
|
||||
TextPayload body = (TextPayload) entity;
|
||||
body.setBody(model.getBody());
|
||||
break;
|
||||
}
|
||||
return entity;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,55 @@
|
|||
package org.mercury_im.messenger.data.mapping;
|
||||
|
||||
import org.mercury_im.messenger.data.model.PeerModel;
|
||||
import org.mercury_im.messenger.entity.contact.IPeer;
|
||||
import org.mercury_im.messenger.entity.contact.Peer;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
public class PeerMapping extends AbstractMapping<Peer, PeerModel> {
|
||||
|
||||
private final AccountMapping accountMapping;
|
||||
|
||||
@Inject
|
||||
public PeerMapping(AccountMapping accountMapping) {
|
||||
this.accountMapping = accountMapping;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Peer newEntity(PeerModel model) {
|
||||
return new IPeer();
|
||||
}
|
||||
|
||||
@Override
|
||||
public PeerModel newModel(Peer entity) {
|
||||
return new PeerModel();
|
||||
}
|
||||
|
||||
@Override
|
||||
public PeerModel mapToModel(Peer entity, PeerModel model) {
|
||||
model.setId(entity.getId());
|
||||
model.setAccount(accountMapping.toModel(entity.getAccount(), model.getAccount()));
|
||||
model.setAddress(entity.getAddress());
|
||||
model.setName(entity.getName());
|
||||
|
||||
model.setSubscriptionDirection(entity.getSubscriptionDirection());
|
||||
model.setSubscriptionPending(entity.isSubscriptionPending());
|
||||
model.setSubscriptionPreApproved(entity.isSubscriptionApproved());
|
||||
|
||||
return model;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Peer mapToEntity(PeerModel model, Peer entity) {
|
||||
entity.setId(model.getId());
|
||||
entity.setAccount(accountMapping.toEntity(model.getAccount(), entity.getAccount()));
|
||||
entity.setAddress(model.getAddress());
|
||||
entity.setName(model.getName());
|
||||
|
||||
entity.setSubscriptionDirection(model.getSubscriptionDirection());
|
||||
entity.setSubscriptionPending(model.isSubscriptionPending());
|
||||
entity.setSubscriptionApproved(model.isSubscriptionPreApproved());
|
||||
|
||||
return entity;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
/**
|
||||
* The mapping package contains mapper classes that map database models to entities.
|
||||
* The models can be found in {@link org.mercury_im.messenger.data.model} in this module,
|
||||
* while the entity classes are located in the <pre>entity</pre> module.
|
||||
*
|
||||
* The mapper classes define an architectural boundary that separates the entities from
|
||||
* their database model counterparts. This is done in order to keep the database logic separated
|
||||
* from the domain logic and to allow for quick replacement of the database implementation.
|
||||
*
|
||||
*/
|
||||
package org.mercury_im.messenger.data.mapping;
|
|
@ -1,7 +1,6 @@
|
|||
package org.mercury_im.messenger.persistence.entity;
|
||||
package org.mercury_im.messenger.data.model;
|
||||
|
||||
import org.jxmpp.jid.EntityBareJid;
|
||||
import org.mercury_im.messenger.persistence.converter.EntityBareJidConverter;
|
||||
import java.util.UUID;
|
||||
|
||||
import io.requery.Column;
|
||||
import io.requery.Convert;
|
||||
|
@ -10,29 +9,38 @@ import io.requery.Generated;
|
|||
import io.requery.Key;
|
||||
import io.requery.Persistable;
|
||||
import io.requery.Table;
|
||||
import io.requery.converter.UUIDConverter;
|
||||
|
||||
@Table(name = "accounts")
|
||||
@Entity
|
||||
public abstract class AbstractAccountModel implements Persistable {
|
||||
|
||||
@Key @Generated
|
||||
long id;
|
||||
@Key
|
||||
@Convert(UUIDConverter.class)
|
||||
UUID id;
|
||||
|
||||
@Column(nullable = false)
|
||||
@Convert(EntityBareJidConverter.class)
|
||||
EntityBareJid jid;
|
||||
String address;
|
||||
|
||||
@Column(nullable = false)
|
||||
String password;
|
||||
|
||||
@Column
|
||||
String host;
|
||||
|
||||
@Column
|
||||
int port;
|
||||
|
||||
@Column
|
||||
boolean enabled;
|
||||
|
||||
|
||||
@Column
|
||||
String rosterVersion;
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Account[" + id + ", " +
|
||||
jid + ", " +
|
||||
address + ", " +
|
||||
(enabled ? "enabled" : "disabled") + "]";
|
||||
}
|
||||
}
|
|
@ -0,0 +1,29 @@
|
|||
package org.mercury_im.messenger.data.model;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
import io.requery.CascadeAction;
|
||||
import io.requery.Convert;
|
||||
import io.requery.Entity;
|
||||
import io.requery.ForeignKey;
|
||||
import io.requery.Generated;
|
||||
import io.requery.Key;
|
||||
import io.requery.OneToOne;
|
||||
import io.requery.Persistable;
|
||||
import io.requery.Table;
|
||||
import io.requery.converter.UUIDConverter;
|
||||
|
||||
@Entity
|
||||
@Table(name = "chats")
|
||||
public abstract class AbstractDirectChatModel implements Persistable {
|
||||
|
||||
@Key
|
||||
@Convert(UUIDConverter.class)
|
||||
UUID id;
|
||||
|
||||
@OneToOne(cascade = CascadeAction.NONE)
|
||||
@ForeignKey(referencedColumn = "id")
|
||||
PeerModel peer;
|
||||
|
||||
boolean displayed;
|
||||
}
|
|
@ -0,0 +1,26 @@
|
|||
package org.mercury_im.messenger.data.model;
|
||||
|
||||
import io.requery.CascadeAction;
|
||||
import io.requery.Entity;
|
||||
import io.requery.ForeignKey;
|
||||
import io.requery.Generated;
|
||||
import io.requery.Key;
|
||||
import io.requery.ManyToOne;
|
||||
import io.requery.ReferentialAction;
|
||||
import io.requery.Table;
|
||||
|
||||
@Entity
|
||||
@Table(name = "direct_messages")
|
||||
public abstract class AbstractDirectMessagesRelation {
|
||||
|
||||
@Key @Generated
|
||||
long id;
|
||||
|
||||
@ManyToOne(cascade = {CascadeAction.SAVE})
|
||||
@ForeignKey(referencedColumn = "id", delete = ReferentialAction.CASCADE)
|
||||
DirectChatModel chat;
|
||||
|
||||
@ManyToOne(cascade = {CascadeAction.SAVE, CascadeAction.DELETE})
|
||||
@ForeignKey(referencedColumn = "id", delete = ReferentialAction.CASCADE)
|
||||
MessageModel message;
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
package org.mercury_im.messenger.persistence.entity;
|
||||
package org.mercury_im.messenger.data.model;
|
||||
|
||||
import io.requery.Column;
|
||||
import io.requery.Entity;
|
|
@ -0,0 +1,35 @@
|
|||
package org.mercury_im.messenger.data.model;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
import io.requery.Column;
|
||||
import io.requery.Convert;
|
||||
import io.requery.Entity;
|
||||
import io.requery.Generated;
|
||||
import io.requery.Key;
|
||||
import io.requery.ManyToOne;
|
||||
import io.requery.Persistable;
|
||||
import io.requery.Table;
|
||||
import io.requery.converter.UUIDConverter;
|
||||
|
||||
@Entity
|
||||
@Table(name = "groupchats")
|
||||
public abstract class AbstractGroupChatModel implements Persistable {
|
||||
|
||||
@Key
|
||||
@Convert(UUIDConverter.class)
|
||||
UUID id;
|
||||
|
||||
@Column(nullable = false)
|
||||
@ManyToOne
|
||||
AccountModel account;
|
||||
|
||||
@Column(nullable = false)
|
||||
String address;
|
||||
|
||||
@Column
|
||||
String name;
|
||||
|
||||
@Column
|
||||
boolean auto_join;
|
||||
}
|
|
@ -0,0 +1,27 @@
|
|||
package org.mercury_im.messenger.data.model;
|
||||
|
||||
import io.requery.CascadeAction;
|
||||
import io.requery.Entity;
|
||||
import io.requery.ForeignKey;
|
||||
import io.requery.Generated;
|
||||
import io.requery.Key;
|
||||
import io.requery.ManyToOne;
|
||||
import io.requery.OneToOne;
|
||||
import io.requery.ReferentialAction;
|
||||
import io.requery.Table;
|
||||
|
||||
@Entity
|
||||
@Table(name = "group_messages")
|
||||
public abstract class AbstractGroupMessagesRelation {
|
||||
|
||||
@Key @Generated
|
||||
long id;
|
||||
|
||||
@ManyToOne(cascade = {CascadeAction.SAVE})
|
||||
@ForeignKey(referencedColumn = "id", delete = ReferentialAction.CASCADE)
|
||||
GroupChatModel chat;
|
||||
|
||||
@ManyToOne(cascade = {CascadeAction.SAVE, CascadeAction.DELETE})
|
||||
@ForeignKey(referencedColumn = "id", delete = ReferentialAction.CASCADE)
|
||||
MessageModel message;
|
||||
}
|
|
@ -0,0 +1,55 @@
|
|||
package org.mercury_im.messenger.data.model;
|
||||
|
||||
import org.mercury_im.messenger.data.converter.MessageDirectionConverter;
|
||||
import org.mercury_im.messenger.entity.message.MessageDirection;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
|
||||
import io.requery.Column;
|
||||
import io.requery.Convert;
|
||||
import io.requery.Entity;
|
||||
import io.requery.Generated;
|
||||
import io.requery.Key;
|
||||
import io.requery.OneToMany;
|
||||
import io.requery.Persistable;
|
||||
import io.requery.Table;
|
||||
import io.requery.converter.UUIDConverter;
|
||||
|
||||
@Entity
|
||||
@Table(name = "messages")
|
||||
public abstract class AbstractMessageModel implements Persistable {
|
||||
|
||||
@Key
|
||||
@Convert(UUIDConverter.class)
|
||||
UUID id;
|
||||
|
||||
@Column(nullable = false)
|
||||
String sender;
|
||||
|
||||
@Column(nullable = false)
|
||||
String recipient;
|
||||
|
||||
@Column(name = "\"timestamp\"", nullable = false)
|
||||
Date timestamp;
|
||||
|
||||
@Column(nullable = false)
|
||||
@Convert(MessageDirectionConverter.class)
|
||||
MessageDirection direction;
|
||||
|
||||
@OneToMany
|
||||
Set<MessagePayloadContainerModel> payloads;
|
||||
|
||||
@Column
|
||||
String legacyId;
|
||||
|
||||
@Column
|
||||
String originId;
|
||||
|
||||
@Column
|
||||
String stanzaId;
|
||||
|
||||
@Column
|
||||
String stanzaIdBy;
|
||||
}
|
|
@ -0,0 +1,24 @@
|
|||
package org.mercury_im.messenger.data.model;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
import io.requery.Entity;
|
||||
import io.requery.Generated;
|
||||
import io.requery.Key;
|
||||
import io.requery.ManyToOne;
|
||||
import io.requery.OneToMany;
|
||||
import io.requery.Table;
|
||||
|
||||
@Entity
|
||||
@Table(name = "msg_payload_containers")
|
||||
public abstract class AbstractMessagePayloadContainerModel {
|
||||
|
||||
@Key @Generated
|
||||
long id;
|
||||
|
||||
@ManyToOne
|
||||
MessageModel message;
|
||||
|
||||
@OneToMany
|
||||
Set<MessagePayloadModel> contents;
|
||||
}
|
|
@ -0,0 +1,27 @@
|
|||
package org.mercury_im.messenger.data.model;
|
||||
|
||||
import org.mercury_im.messenger.data.enums.MessageContentType;
|
||||
|
||||
import io.requery.Column;
|
||||
import io.requery.Entity;
|
||||
import io.requery.Generated;
|
||||
import io.requery.Key;
|
||||
import io.requery.ManyToOne;
|
||||
import io.requery.Table;
|
||||
|
||||
@Entity
|
||||
@Table(name = "msg_payloads")
|
||||
public abstract class AbstractMessagePayloadModel {
|
||||
|
||||
@Key @Generated
|
||||
long id;
|
||||
|
||||
@ManyToOne
|
||||
MessagePayloadContainerModel payloadContainer;
|
||||
|
||||
@Column
|
||||
String body;
|
||||
|
||||
@Column(nullable = false)
|
||||
MessageContentType type;
|
||||
}
|
|
@ -0,0 +1,56 @@
|
|||
package org.mercury_im.messenger.data.model;
|
||||
|
||||
import org.mercury_im.messenger.data.converter.SubscriptionDirectionConverter;
|
||||
import org.mercury_im.messenger.entity.contact.SubscriptionDirection;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
import io.requery.CascadeAction;
|
||||
import io.requery.Column;
|
||||
import io.requery.Convert;
|
||||
import io.requery.Entity;
|
||||
import io.requery.ForeignKey;
|
||||
import io.requery.Generated;
|
||||
import io.requery.Index;
|
||||
import io.requery.Key;
|
||||
import io.requery.ManyToOne;
|
||||
import io.requery.Persistable;
|
||||
import io.requery.ReferentialAction;
|
||||
import io.requery.Table;
|
||||
import io.requery.converter.UUIDConverter;
|
||||
|
||||
@Entity
|
||||
@Table(name = "contacts", uniqueIndexes = "unique_address")
|
||||
public abstract class AbstractPeerModel implements Persistable {
|
||||
|
||||
@Key
|
||||
@Convert(UUIDConverter.class)
|
||||
UUID id;
|
||||
|
||||
@Index("unique_address")
|
||||
@ManyToOne(cascade = CascadeAction.NONE)
|
||||
@ForeignKey(referencedColumn = "id")
|
||||
AccountModel account;
|
||||
|
||||
@Index("unique_address")
|
||||
@Column(nullable = false)
|
||||
String address;
|
||||
|
||||
@Column
|
||||
String name;
|
||||
|
||||
@Convert(SubscriptionDirectionConverter.class)
|
||||
SubscriptionDirection subscriptionDirection;
|
||||
|
||||
boolean subscriptionPending;
|
||||
|
||||
boolean subscriptionPreApproved;
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Peer[" + id + ", " +
|
||||
name + ", " +
|
||||
address + ", " +
|
||||
account + "]";
|
||||
}
|
||||
}
|
|
@ -1,6 +1,6 @@
|
|||
package org.mercury_im.messenger.persistence.entity;
|
||||
package org.mercury_im.messenger.data.model;
|
||||
|
||||
import org.mercury_im.messenger.persistence.enums.SaslCondition;
|
||||
import org.mercury_im.messenger.data.enums.SaslCondition;
|
||||
|
||||
import io.requery.Entity;
|
||||
import io.requery.Key;
|
|
@ -0,0 +1,12 @@
|
|||
/**
|
||||
* The model package contains requery model definitions.
|
||||
* All of those classes are abstract, since the requery framework generates concrete implementations
|
||||
* during compilation. Those files can later be found in
|
||||
* <pre>build/generated/sources/annotationProcessor/java/main/...</pre>.
|
||||
*
|
||||
* The structure of the model classes closely mimics the structure of their entity pendants
|
||||
* declared in the <pre>entity</pre> module.
|
||||
*
|
||||
* @see <a href="https://github.com/requery/requery/wiki/Defining-Entities">requery wiki on model definitions</a>
|
||||
*/
|
||||
package org.mercury_im.messenger.data.model;
|
|
@ -1,4 +1,8 @@
|
|||
package org.mercury_im.messenger.persistence.repository;
|
||||
package org.mercury_im.messenger.data.repository;
|
||||
|
||||
import org.mercury_im.messenger.util.ThreadUtils;
|
||||
|
||||
import javax.inject.Named;
|
||||
|
||||
import io.reactivex.Scheduler;
|
||||
import io.requery.Persistable;
|
||||
|
@ -12,8 +16,8 @@ public abstract class RequeryRepository {
|
|||
private final ReactiveEntityStore<Persistable> data;
|
||||
|
||||
protected RequeryRepository(ReactiveEntityStore<Persistable> data,
|
||||
Scheduler subscriberScheduler,
|
||||
Scheduler observerScheduler) {
|
||||
@Named(value = ThreadUtils.SCHEDULER_IO) Scheduler subscriberScheduler,
|
||||
@Named(value = ThreadUtils.SCHEDULER_UI) Scheduler observerScheduler) {
|
||||
this.data = data;
|
||||
this.subscriberScheduler = subscriberScheduler;
|
||||
this.observerScheduler = observerScheduler;
|
|
@ -0,0 +1,146 @@
|
|||
package org.mercury_im.messenger.data.repository;
|
||||
|
||||
import org.mercury_im.messenger.data.mapping.AccountMapping;
|
||||
import org.mercury_im.messenger.data.model.AccountModel;
|
||||
import org.mercury_im.messenger.data.repository.dao.AccountDao;
|
||||
import org.mercury_im.messenger.util.Optional;
|
||||
import org.mercury_im.messenger.entity.Account;
|
||||
import org.mercury_im.messenger.util.ThreadUtils;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Named;
|
||||
|
||||
import io.reactivex.Completable;
|
||||
import io.reactivex.Maybe;
|
||||
import io.reactivex.Observable;
|
||||
import io.reactivex.Scheduler;
|
||||
import io.reactivex.Single;
|
||||
import io.requery.Persistable;
|
||||
import io.requery.query.ResultDelegate;
|
||||
import io.requery.reactivex.ReactiveEntityStore;
|
||||
import io.requery.reactivex.ReactiveResult;
|
||||
|
||||
public class XmppAccountRepository
|
||||
extends RequeryRepository
|
||||
implements AccountRepository {
|
||||
|
||||
private final AccountMapping accountMapping;
|
||||
private final AccountDao dao;
|
||||
|
||||
@Inject
|
||||
public XmppAccountRepository(ReactiveEntityStore<Persistable> data,
|
||||
@Named(value = ThreadUtils.SCHEDULER_IO) Scheduler subscriberScheduler,
|
||||
@Named(value = ThreadUtils.SCHEDULER_UI) Scheduler observerScheduler,
|
||||
AccountMapping accountMapping) {
|
||||
super(data, subscriberScheduler, observerScheduler);
|
||||
this.accountMapping = accountMapping;
|
||||
this.dao = new AccountDao(data);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Single<Account> insertAccount(Account account) {
|
||||
return Single.just(account)
|
||||
.map(accountMapping::toModel)
|
||||
.flatMap(dao::insert)
|
||||
.map(model -> accountMapping.toEntity(model, account))
|
||||
.subscribeOn(subscriberScheduler())
|
||||
.observeOn(observerScheduler());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Observable<Optional<Account>> observeAccount(UUID accountId) {
|
||||
return dao.get(accountId).observableResult()
|
||||
.map(result -> new Optional<>(result.firstOrNull()))
|
||||
.map(accountMapping::toEntity)
|
||||
.subscribeOn(subscriberScheduler())
|
||||
.observeOn(observerScheduler());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Maybe<Account> getAccount(UUID accountId) {
|
||||
return dao.get(accountId).maybe()
|
||||
.map(accountMapping::toEntity)
|
||||
.subscribeOn(subscriberScheduler())
|
||||
.observeOn(observerScheduler());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Observable<Optional<Account>> observeAccountByAddress(String address) {
|
||||
return dao.get(address).observableResult()
|
||||
.map(result -> new Optional<>(result.firstOrNull()))
|
||||
.map(accountMapping::toEntity)
|
||||
.subscribeOn(subscriberScheduler())
|
||||
.observeOn(observerScheduler());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Maybe<Account> getAccountByAddress(String address) {
|
||||
return dao.get(address).maybe()
|
||||
.map(accountMapping::toEntity)
|
||||
.subscribeOn(subscriberScheduler())
|
||||
.observeOn(observerScheduler());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Observable<List<Account>> observeAllAccounts() {
|
||||
return dao.getAll().observableResult()
|
||||
.map(ResultDelegate::toList)
|
||||
.map(this::modelsToEntities)
|
||||
.subscribeOn(subscriberScheduler())
|
||||
.observeOn(observerScheduler());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Observable<Account> observeAccounts() {
|
||||
return dao.getAll().observableResult()
|
||||
.flatMap(ReactiveResult::observable)
|
||||
.map(accountMapping::toEntity)
|
||||
.subscribeOn(subscriberScheduler())
|
||||
.observeOn(observerScheduler());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Single<Account> updateAccount(Account account) {
|
||||
// Since we cannot access setId() of AccountModel, we have to query the model by ID and update it manually.
|
||||
// https://github.com/requery/requery/issues/616#issuecomment-315685460
|
||||
|
||||
return dao.get(account.getId()).maybe().toSingle()
|
||||
.map(model -> accountMapping.toModel(account, model))
|
||||
.flatMap(updatedModel -> data().update(updatedModel))
|
||||
.map(model -> accountMapping.toEntity(model, account))
|
||||
.subscribeOn(subscriberScheduler())
|
||||
.observeOn(observerScheduler());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Single<Account> upsertAccount(Account account) {
|
||||
return dao.get(account.getId()).maybe()
|
||||
.switchIfEmpty(
|
||||
Single.just(account).map(accountMapping::toModel).flatMap(dao::insert))
|
||||
.map(model -> accountMapping.toModel(account, model))
|
||||
.flatMap(data()::update)
|
||||
.map(model -> accountMapping.toEntity(model, account))
|
||||
.subscribeOn(subscriberScheduler())
|
||||
.observeOn(observerScheduler());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Completable deleteAccount(UUID accountId) {
|
||||
return dao.delete(accountId).ignoreElement()
|
||||
.subscribeOn(subscriberScheduler())
|
||||
.observeOn(observerScheduler());
|
||||
}
|
||||
|
||||
private List<Account> modelsToEntities(List<AccountModel> models) {
|
||||
List<Account> entities = new ArrayList<>(models.size());
|
||||
for (AccountModel model : models) {
|
||||
entities.add(accountMapping.toEntity(model));
|
||||
}
|
||||
return entities;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,153 @@
|
|||
package org.mercury_im.messenger.data.repository;
|
||||
|
||||
import org.mercury_im.messenger.data.mapping.DirectChatMapping;
|
||||
import org.mercury_im.messenger.data.model.DirectChatModel;
|
||||
import org.mercury_im.messenger.data.repository.dao.DirectChatDao;
|
||||
import org.mercury_im.messenger.util.Optional;
|
||||
import org.mercury_im.messenger.entity.chat.DirectChat;
|
||||
import org.mercury_im.messenger.entity.chat.IDirectChat;
|
||||
import org.mercury_im.messenger.entity.contact.Peer;
|
||||
import org.mercury_im.messenger.util.ThreadUtils;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Named;
|
||||
|
||||
import io.reactivex.Completable;
|
||||
import io.reactivex.Maybe;
|
||||
import io.reactivex.Observable;
|
||||
import io.reactivex.Scheduler;
|
||||
import io.reactivex.Single;
|
||||
import io.requery.Persistable;
|
||||
import io.requery.query.ResultDelegate;
|
||||
import io.requery.reactivex.ReactiveEntityStore;
|
||||
import io.requery.reactivex.ReactiveResult;
|
||||
|
||||
public class XmppDirectChatRepository
|
||||
extends RequeryRepository
|
||||
implements DirectChatRepository {
|
||||
|
||||
private final DirectChatMapping directChatMapping;
|
||||
|
||||
private final DirectChatDao dao;
|
||||
|
||||
@Inject
|
||||
public XmppDirectChatRepository(
|
||||
ReactiveEntityStore<Persistable> data,
|
||||
@Named(value = ThreadUtils.SCHEDULER_IO) Scheduler subscriberScheduler,
|
||||
@Named(value = ThreadUtils.SCHEDULER_UI) Scheduler observerScheduler,
|
||||
DirectChatMapping directChatMapping) {
|
||||
super(data, subscriberScheduler, observerScheduler);
|
||||
this.directChatMapping = directChatMapping;
|
||||
this.dao = new DirectChatDao(data);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Single<DirectChat> insertDirectChat(DirectChat chat) {
|
||||
return Single.just(chat)
|
||||
// map entity to model
|
||||
.map(directChatMapping::toModel)
|
||||
.flatMap(dao::insert)
|
||||
// map back to entity
|
||||
.map(model -> directChatMapping.toEntity(model, chat))
|
||||
.subscribeOn(subscriberScheduler())
|
||||
.observeOn(observerScheduler());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Observable<Optional<DirectChat>> observeDirectChat(UUID chatId) {
|
||||
return dao.get(chatId).observableResult()
|
||||
.map(result -> new Optional<>(result.firstOrNull()))
|
||||
.map(directChatMapping::toEntity)
|
||||
.subscribeOn(subscriberScheduler())
|
||||
.observeOn(observerScheduler());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Maybe<DirectChat> getDirectChat(UUID chatId) {
|
||||
return dao.get(chatId).maybe()
|
||||
.map(directChatMapping::toEntity)
|
||||
.subscribeOn(subscriberScheduler())
|
||||
.observeOn(observerScheduler());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Single<DirectChat> getOrCreateChatWithPeer(Peer peer) {
|
||||
return getDirectChatByPeer(peer)
|
||||
.switchIfEmpty(Single.just(new IDirectChat())
|
||||
.map(chat -> {
|
||||
chat.setAccount(peer.getAccount());
|
||||
chat.setPeer(peer);
|
||||
return chat;
|
||||
})
|
||||
.flatMap(this::insertDirectChat))
|
||||
.subscribeOn(subscriberScheduler())
|
||||
.observeOn(observerScheduler());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Observable<Optional<DirectChat>> observeDirectChatByPeer(Peer peer) {
|
||||
return dao.getByPeer(peer.getId()).observableResult()
|
||||
.map(result -> new Optional<>(result.firstOrNull()))
|
||||
.map(directChatMapping::toEntity)
|
||||
.subscribeOn(subscriberScheduler())
|
||||
.observeOn(observerScheduler());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Maybe<DirectChat> getDirectChatByPeer(Peer peer) {
|
||||
return dao.getByPeer(peer.getId()).maybe()
|
||||
.map(directChatMapping::toEntity)
|
||||
.subscribeOn(subscriberScheduler())
|
||||
.observeOn(observerScheduler());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Observable<List<DirectChat>> observeAllDirectChats() {
|
||||
return dao.getAll().observableResult()
|
||||
.map(ResultDelegate::toList)
|
||||
.map(this::chatModelsToEntities)
|
||||
.subscribeOn(subscriberScheduler())
|
||||
.observeOn(observerScheduler());
|
||||
}
|
||||
|
||||
private List<DirectChat> chatModelsToEntities(List<DirectChatModel> models) {
|
||||
List<DirectChat> entities = new ArrayList<>(models.size());
|
||||
for (DirectChatModel model : models) {
|
||||
entities.add(directChatMapping.toEntity(model));
|
||||
}
|
||||
return entities;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Single<DirectChat> updateDirectChat(DirectChat chat) {
|
||||
return dao.get(chat.getId()).maybe().toSingle()
|
||||
.map(model -> directChatMapping.toModel(chat, model))
|
||||
.flatMap(data()::update)
|
||||
.map(model -> directChatMapping.toEntity(model, chat))
|
||||
.subscribeOn(subscriberScheduler())
|
||||
.observeOn(observerScheduler());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Single<DirectChat> upsertDirectChat(DirectChat chat) {
|
||||
return dao.get(chat.getId()).maybe()
|
||||
.switchIfEmpty(dao.insert(directChatMapping.toModel(chat)))
|
||||
.map(directChatModel -> directChatMapping.toModel(chat, directChatModel))
|
||||
.flatMap(data()::update)
|
||||
.map(model -> directChatMapping.toEntity(model, chat))
|
||||
.subscribeOn(subscriberScheduler())
|
||||
.observeOn(observerScheduler());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Completable deleteDirectChat(UUID chatId) {
|
||||
return dao.delete(chatId)
|
||||
.ignoreElement()
|
||||
.subscribeOn(subscriberScheduler())
|
||||
.observeOn(observerScheduler());
|
||||
}
|
||||
}
|
|
@ -0,0 +1,91 @@
|
|||
package org.mercury_im.messenger.data.repository;
|
||||
|
||||
import org.mercury_im.messenger.data.mapping.EntityCapsMapping;
|
||||
import org.mercury_im.messenger.data.model.EntityCapsModel;
|
||||
import org.mercury_im.messenger.entity.caps.EntityCapsRecord;
|
||||
import org.mercury_im.messenger.util.ThreadUtils;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Named;
|
||||
|
||||
import io.reactivex.Completable;
|
||||
import io.reactivex.Maybe;
|
||||
import io.reactivex.Observable;
|
||||
import io.reactivex.Scheduler;
|
||||
import io.requery.Persistable;
|
||||
import io.requery.query.Expression;
|
||||
import io.requery.query.ResultDelegate;
|
||||
import io.requery.reactivex.ReactiveEntityStore;
|
||||
import io.requery.reactivex.ReactiveResult;
|
||||
|
||||
public class XmppEntityCapsRepository extends RequeryRepository implements EntityCapsRepository {
|
||||
|
||||
private final EntityCapsMapping entityCapsMapping;
|
||||
|
||||
@Inject
|
||||
public XmppEntityCapsRepository(
|
||||
ReactiveEntityStore<Persistable> data,
|
||||
@Named(value = ThreadUtils.SCHEDULER_IO) Scheduler subscriberScheduler,
|
||||
@Named(value = ThreadUtils.SCHEDULER_UI) Scheduler observerScheduler,
|
||||
EntityCapsMapping mapping) {
|
||||
super(data, subscriberScheduler, observerScheduler);
|
||||
this.entityCapsMapping = mapping;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Observable<Map<String, EntityCapsRecord>> observeAllEntityCapsRecords() {
|
||||
return data().select(EntityCapsModel.class).get()
|
||||
.observableResult()
|
||||
.map(result -> result.toMap(EntityCapsModel.NODE_VER, new ConcurrentHashMap<>()))
|
||||
.map(this::mapModelsToEntities)
|
||||
.subscribeOn(subscriberScheduler())
|
||||
.observeOn(observerScheduler());
|
||||
}
|
||||
|
||||
private Map<String, EntityCapsRecord> mapModelsToEntities(Map<String, EntityCapsModel> models) {
|
||||
Map<String, EntityCapsRecord> entities = new ConcurrentHashMap<>();
|
||||
for (String key : models.keySet()) {
|
||||
entities.put(key, entityCapsMapping.toEntity(models.get(key)));
|
||||
}
|
||||
return entities;
|
||||
}
|
||||
|
||||
private Map<String, EntityCapsModel> mapEntitiesToModels(Map<String, EntityCapsRecord> entities) {
|
||||
Map<String, EntityCapsModel> models = new ConcurrentHashMap<>();
|
||||
for (String key : entities.keySet()) {
|
||||
models.put(key, entityCapsMapping.toModel(entities.get(key)));
|
||||
}
|
||||
return models;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Observable<EntityCapsRecord> observeEntityCapsRecords() {
|
||||
return data().select(EntityCapsModel.class)
|
||||
.get().observableResult()
|
||||
.flatMap(ReactiveResult::observable)
|
||||
.map(entityCapsMapping::toEntity)
|
||||
.subscribeOn(subscriberScheduler())
|
||||
.observeOn(observerScheduler());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Maybe<EntityCapsRecord> maybeGetEntityCapsRecord(String nodeVer) {
|
||||
return data().select(EntityCapsModel.class)
|
||||
.where(EntityCapsModel.NODE_VER.eq(nodeVer))
|
||||
.get().maybe()
|
||||
.map(entityCapsMapping::toEntity)
|
||||
.subscribeOn(subscriberScheduler())
|
||||
.observeOn(observerScheduler());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Completable insertEntityCapsRecord(EntityCapsRecord entityCapsRecord) {
|
||||
return data().upsert(entityCapsMapping.toModel(entityCapsRecord))
|
||||
.ignoreElement()
|
||||
.subscribeOn(subscriberScheduler())
|
||||
.observeOn(observerScheduler());
|
||||
}
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue