Browse Source

Change persistence backend to requery (fix #8)

requery
Paul Schaub 1 month ago
parent
commit
e5dfa3c030
Signed by: Paul Schaub <vanitasvitae@fsfe.org> GPG Key ID: 62BEE9264BF17311
100 changed files with 1331 additions and 3970 deletions
  1. +0
    -2
      .idea/gradle.xml
  2. +7
    -0
      README.md
  3. +6
    -3
      app/build.gradle
  4. +149
    -0
      app/src/androidTest/java/org/mercury_im/messenger/RequeryDatabaseTest.java
  5. +10
    -7
      app/src/main/java/org/mercury_im/messenger/MercuryImApplication.java
  6. +5
    -5
      app/src/main/java/org/mercury_im/messenger/Notifications.java
  7. +7
    -5
      app/src/main/java/org/mercury_im/messenger/di/component/AppComponent.java
  8. +57
    -0
      app/src/main/java/org/mercury_im/messenger/di/module/AndroidPersistenceModule.java
  9. +5
    -1
      app/src/main/java/org/mercury_im/messenger/di/module/AppModule.java
  10. +1
    -2
      app/src/main/java/org/mercury_im/messenger/ui/MainActivity.java
  11. +3
    -1
      app/src/main/java/org/mercury_im/messenger/ui/chat/ChatActivity.java
  12. +27
    -33
      app/src/main/java/org/mercury_im/messenger/ui/chat/ChatViewModel.java
  13. +1
    -1
      app/src/main/java/org/mercury_im/messenger/ui/chat/MessagesRecyclerViewAdapter.java
  14. +50
    -13
      app/src/main/java/org/mercury_im/messenger/ui/chatlist/ChatListRecyclerViewAdapter.java
  15. +7
    -22
      app/src/main/java/org/mercury_im/messenger/ui/chatlist/ChatListViewModel.java
  16. +1
    -1
      app/src/main/java/org/mercury_im/messenger/ui/login/AccountsFragment.java
  17. +2
    -5
      app/src/main/java/org/mercury_im/messenger/ui/login/AccountsRecyclerViewAdapter.java
  18. +7
    -16
      app/src/main/java/org/mercury_im/messenger/ui/login/AccountsViewModel.java
  19. +7
    -7
      app/src/main/java/org/mercury_im/messenger/ui/login/LoginActivity.java
  20. +8
    -14
      app/src/main/java/org/mercury_im/messenger/ui/login/LoginViewModel.java
  21. +4
    -4
      app/src/main/java/org/mercury_im/messenger/ui/roster/contacts/ContactListItemViewModel.java
  22. +13
    -15
      app/src/main/java/org/mercury_im/messenger/ui/roster/contacts/ContactListRecyclerViewAdapter.java
  23. +11
    -12
      app/src/main/java/org/mercury_im/messenger/ui/roster/contacts/ContactListViewModel.java
  24. +11
    -0
      app/src/main/res/menu/actionmode_chat_single.xml
  25. +8
    -0
      app/src/main/res/menu/actionmode_chatlist.xml
  26. +2
    -0
      app/src/main/res/values/strings.xml
  27. +1
    -0
      app/src/main/res/values/themes.xml
  28. +1
    -1
      build.gradle
  29. +16
    -0
      core/build.gradle
  30. +3
    -2
      core/src/main/java/org/mercury_im/messenger/core/NotificationManager.java
  31. +31
    -35
      core/src/main/java/org/mercury_im/messenger/core/centers/ConnectionCenter.java
  32. +15
    -8
      core/src/main/java/org/mercury_im/messenger/core/di/CenterModule.java
  33. +6
    -1
      core/src/main/java/org/mercury_im/messenger/core/di/XmppComponent.java
  34. +51
    -35
      core/src/main/java/org/mercury_im/messenger/core/stores/EntityCapsStore.java
  35. +117
    -61
      core/src/main/java/org/mercury_im/messenger/core/stores/PlainMessageStore.java
  36. +93
    -75
      core/src/main/java/org/mercury_im/messenger/core/stores/RosterStore.java
  37. +15
    -3
      core/src/main/java/org/mercury_im/messenger/core/util/ContactNameUtil.java
  38. +0
    -3
      persistence-room/.gitignore
  39. +0
    -31
      persistence-room/README.md
  40. +0
    -56
      persistence-room/build.gradle
  41. +0
    -21
      persistence-room/proguard-rules.pro
  42. +0
    -518
      persistence-room/schemas/org.mercury_im.messenger.persistence.room.AppDatabase/1.json
  43. +0
    -382
      persistence-room/schemas/org.mercury_im.messenger.persistence.room.AppDatabase/2.json
  44. +0
    -51
      persistence-room/src/androidTest/java/org/mercury_im/messenger/persistence/room/AbstractDatabaseTest.java
  45. +0
    -111
      persistence-room/src/androidTest/java/org/mercury_im/messenger/persistence/room/ExampleInstrumentedTest.java
  46. +0
    -2
      persistence-room/src/main/AndroidManifest.xml
  47. +0
    -112
      persistence-room/src/main/java/org/mercury_im/messenger/persistence/room/AppDatabase.java
  48. +0
    -86
      persistence-room/src/main/java/org/mercury_im/messenger/persistence/room/RoomModule.java
  49. +0
    -67
      persistence-room/src/main/java/org/mercury_im/messenger/persistence/room/RoomRepositoryModule.java
  50. +0
    -52
      persistence-room/src/main/java/org/mercury_im/messenger/persistence/room/dao/AccountDao.java
  51. +0
    -29
      persistence-room/src/main/java/org/mercury_im/messenger/persistence/room/dao/AvatarDao.java
  52. +0
    -40
      persistence-room/src/main/java/org/mercury_im/messenger/persistence/room/dao/BaseDao.java
  53. +0
    -56
      persistence-room/src/main/java/org/mercury_im/messenger/persistence/room/dao/ChatDao.java
  54. +0
    -79
      persistence-room/src/main/java/org/mercury_im/messenger/persistence/room/dao/ContactDao.java
  55. +0
    -27
      persistence-room/src/main/java/org/mercury_im/messenger/persistence/room/dao/EntityCapsDao.java
  56. +0
    -26
      persistence-room/src/main/java/org/mercury_im/messenger/persistence/room/dao/EntityDao.java
  57. +0
    -56
      persistence-room/src/main/java/org/mercury_im/messenger/persistence/room/dao/MessageDao.java
  58. +0
    -27
      persistence-room/src/main/java/org/mercury_im/messenger/persistence/room/dao/RosterInformationDao.java
  59. +0
    -107
      persistence-room/src/main/java/org/mercury_im/messenger/persistence/room/model/RoomAccountModel.java
  60. +0
    -87
      persistence-room/src/main/java/org/mercury_im/messenger/persistence/room/model/RoomAvatarModel.java
  61. +0
    -126
      persistence-room/src/main/java/org/mercury_im/messenger/persistence/room/model/RoomChatModel.java
  62. +0
    -153
      persistence-room/src/main/java/org/mercury_im/messenger/persistence/room/model/RoomContactModel.java
  63. +0
    -51
      persistence-room/src/main/java/org/mercury_im/messenger/persistence/room/model/RoomEntityCapsModel.java
  64. +0
    -94
      persistence-room/src/main/java/org/mercury_im/messenger/persistence/room/model/RoomEntityModel.java
  65. +0
    -141
      persistence-room/src/main/java/org/mercury_im/messenger/persistence/room/model/RoomMessageModel.java
  66. +0
    -58
      persistence-room/src/main/java/org/mercury_im/messenger/persistence/room/model/RoomRosterInformationModel.java
  67. +0
    -77
      persistence-room/src/main/java/org/mercury_im/messenger/persistence/room/repository/IAccountRepository.java
  68. +0
    -46
      persistence-room/src/main/java/org/mercury_im/messenger/persistence/room/repository/IAvatarRepository.java
  69. +0
    -120
      persistence-room/src/main/java/org/mercury_im/messenger/persistence/room/repository/IChatRepository.java
  70. +0
    -66
      persistence-room/src/main/java/org/mercury_im/messenger/persistence/room/repository/IEntityCapsRepository.java
  71. +0
    -84
      persistence-room/src/main/java/org/mercury_im/messenger/persistence/room/repository/IMessageRepository.java
  72. +0
    -263
      persistence-room/src/main/java/org/mercury_im/messenger/persistence/room/repository/IRosterRepository.java
  73. +0
    -18
      persistence-room/src/main/java/org/mercury_im/messenger/persistence/room/type_converter/DateConverter.java
  74. +0
    -18
      persistence-room/src/main/java/org/mercury_im/messenger/persistence/room/type_converter/DirectionConverter.java
  75. +0
    -25
      persistence-room/src/main/java/org/mercury_im/messenger/persistence/room/type_converter/EntityBareJidConverter.java
  76. +0
    -24
      persistence-room/src/main/java/org/mercury_im/messenger/persistence/room/type_converter/FileConverter.java
  77. +0
    -3
      persistence-room/src/main/res/values/strings.xml
  78. +0
    -17
      persistence-room/src/test/java/org/mercury_im/messenger/persistence/room/ExampleUnitTest.java
  79. +0
    -19
      persistence/README.md
  80. +17
    -5
      persistence/build.gradle
  81. +0
    -21
      persistence/proguard-rules.pro
  82. +0
    -27
      persistence/src/androidTest/java/org/mercury_im/messenger/persistence/ExampleInstrumentedTest.java
  83. +0
    -2
      persistence/src/main/AndroidManifest.xml
  84. +33
    -0
      persistence/src/main/java/org/mercury_im/messenger/persistence/converter/EntityBareJidConverter.java
  85. +32
    -0
      persistence/src/main/java/org/mercury_im/messenger/persistence/converter/SaslConditionConverter.java
  86. +32
    -0
      persistence/src/main/java/org/mercury_im/messenger/persistence/converter/SubscriptionDirectionConverter.java
  87. +52
    -0
      persistence/src/main/java/org/mercury_im/messenger/persistence/di/RequeryModule.java
  88. +38
    -0
      persistence/src/main/java/org/mercury_im/messenger/persistence/entity/AbstractAccountModel.java
  89. +23
    -0
      persistence/src/main/java/org/mercury_im/messenger/persistence/entity/AbstractChatModel.java
  90. +44
    -0
      persistence/src/main/java/org/mercury_im/messenger/persistence/entity/AbstractContactModel.java
  91. +19
    -0
      persistence/src/main/java/org/mercury_im/messenger/persistence/entity/AbstractEntityCapsModel.java
  92. +35
    -0
      persistence/src/main/java/org/mercury_im/messenger/persistence/entity/AbstractEntityModel.java
  93. +22
    -0
      persistence/src/main/java/org/mercury_im/messenger/persistence/entity/AbstractLastChatMessageRelation.java
  94. +22
    -0
      persistence/src/main/java/org/mercury_im/messenger/persistence/entity/AbstractLastReadChatMessageRelation.java
  95. +55
    -0
      persistence/src/main/java/org/mercury_im/messenger/persistence/entity/AbstractMessageModel.java
  96. +22
    -0
      persistence/src/main/java/org/mercury_im/messenger/persistence/entity/AbstractSaslAuthenticationResultModel.java
  97. +118
    -0
      persistence/src/main/java/org/mercury_im/messenger/persistence/enums/SaslCondition.java
  98. +9
    -0
      persistence/src/main/java/org/mercury_im/messenger/persistence/enums/SubscriptionDirection.java
  99. +0
    -22
      persistence/src/main/java/org/mercury_im/messenger/persistence/model/AbstractAccountModel.java
  100. +0
    -74
      persistence/src/main/java/org/mercury_im/messenger/persistence/model/AccountModel.java

+ 0
- 2
.idea/gradle.xml View File

@@ -80,8 +80,6 @@
<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" />
<option value="$PROJECT_DIR$/persistence" />
<option value="$PROJECT_DIR$/persistence-room" />
</set>
</option>
<option name="resolveModulePerSourceSet" value="false" />

+ 7
- 0
README.md View File

@@ -15,3 +15,10 @@ cd <project-directory>
git submodule init && git submodule update
gradle assembleDebug
```

## FAQ

* 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'".

+ 6
- 3
app/build.gradle View File

@@ -64,7 +64,7 @@ task checkstyleAndroidTest(type: Checkstyle) {
}

check.configure {
dependsOn(checkstyleMain)
// dependsOn(checkstyleMain)
// dependsOn(checkstyleTest)
// dependsOn(checkstyleAndroidTest)
}
@@ -75,8 +75,8 @@ dependencies {
// Depend on the core project for XMPP related stuff
implementation project(':core')

// Plug a Room based implementation into the persistence api
implementation project(':persistence-room')
implementation "io.requery:requery-android:$requeryVersion"
implementation 'org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.3.41'

// Dagger 2 for dependency injection
implementation "com.google.dagger:dagger:$daggerVersion"
@@ -86,6 +86,9 @@ dependencies {
implementation "androidx.lifecycle:lifecycle-extensions:$lifecycleVersion"
annotationProcessor "androidx.lifecycle:lifecycle-compiler:$lifecycleVersion"

// Android extension for rxJava
api "io.reactivex.rxjava2:rxandroid:$rxAndroidVersion"

// ButterKnife for View Binding
implementation "com.jakewharton:butterknife:$butterKnifeVersion"
annotationProcessor "com.jakewharton:butterknife-compiler:$butterKnifeVersion"

+ 149
- 0
app/src/androidTest/java/org/mercury_im/messenger/RequeryDatabaseTest.java View File

@@ -0,0 +1,149 @@
package org.mercury_im.messenger;

import android.content.Context;

import androidx.test.InstrumentationRegistry;
import androidx.test.runner.AndroidJUnit4;

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.core.requery.enums.SubscriptionDirection;

import io.reactivex.disposables.Disposable;
import io.reactivex.schedulers.Schedulers;
import io.requery.Persistable;
import io.requery.android.sqlite.DatabaseSource;
import io.requery.reactivex.ReactiveEntityStore;
import io.requery.reactivex.ReactiveSupport;
import io.requery.sql.Configuration;
import io.requery.sql.EntityDataStore;
import io.requery.sql.TableCreationMode;

@RunWith(AndroidJUnit4.class)
public class RequeryDatabaseTest {

static ReactiveEntityStore<Persistable> dataStore;

@BeforeClass
public static void setup() {
Context appContext = InstrumentationRegistry.getTargetContext();
DatabaseSource source = new DatabaseSource(appContext, Models.DEFAULT,
"mercury_test_db", 1);
// use this in development mode to drop and recreate the tables on every upgrade
source.setTableCreationMode(TableCreationMode.DROP_CREATE);
source.setLoggingEnabled(true);
Configuration configuration = source.getConfiguration();
dataStore = ReactiveSupport.toReactiveStore(
new EntityDataStore<>(configuration));
}

@Test
public void databaseTest() throws InterruptedException {
AccountModel accountModel = new AccountModel();
accountModel.setJid(JidCreate.entityBareFromOrThrowUnchecked("juliet@capulet.lit"));
accountModel.setPassword("romeo0romeo");
accountModel.setRosterVersion(null);
accountModel.setEnabled(true);

Disposable accounts = dataStore.select(AccountModel.class).get().observableResult().subscribe(
accountModels -> accountModels.forEach(System.out::println));

Disposable entities = dataStore.select(EntityModel.class).get().observableResult()
.subscribeOn(Schedulers.io())
.subscribe(entityModels -> entityModels.forEach(System.out::println));

Disposable contacts = dataStore.select(ContactModel.class).get()
.observableResult().subscribeOn(Schedulers.io()).subscribe(
contactModels -> contactModels.forEach(System.out::println));

Thread.sleep(100);

dataStore.upsert(accountModel).subscribeOn(Schedulers.io())
.subscribe();

ContactModel contactModel = new ContactModel();
EntityModel entityModel = new EntityModel();
entityModel.setAccount(accountModel);
entityModel.setJid(JidCreate.entityBareFromOrThrowUnchecked("romeo@capulet.lit"));
contactModel.setEntity(entityModel);
contactModel.setRostername("Romeo");
contactModel.setSub_direction(SubscriptionDirection.both);
dataStore.insert(contactModel).blockingGet();

dataStore.select(AccountModel.ENABLED, ContactModel.ROSTERNAME)
.from(AccountModel.class)
.join(EntityModel.class).on(AccountModel.ID.eq(EntityModel.ACCOUNT_ID))
.join(ContactModel.class).on(EntityModel.ID.eq(ContactModel.ENTITY_ID))
.get().observableResult().blockingForEach(e -> e.forEach(System.out::println));

Thread.sleep(10000);
accounts.dispose();
entities.dispose();
contacts.dispose();
}

public static class ContactDetail {
private boolean enabled;
private String rostername;
}

@Test
public void test2() {
AccountModel account = new AccountModel();
account.setJid(JidCreate.entityBareFromOrThrowUnchecked("omemouser@jabberhead.tk"));
account.setEnabled(true);
account.setPassword("nöö");

EntityModel entity = new EntityModel();
entity.setJid(JidCreate.entityBareFromOrThrowUnchecked("jabbertest@test.test"));
entity.setAccount(account);

ContactModel contact = new ContactModel();
contact.setEntity(entity);
contact.setRostername("Olaf");
contact.setSub_direction(SubscriptionDirection.both);
contact.setSub_approved(false);
contact.setSub_pending(true);

dataStore.upsert(contact).blockingGet();

ChatModel chat = new ChatModel();
chat.setPeer(entity);
chat.setDisplayed(true);

dataStore.upsert(chat).blockingGet();

MessageModel message = new MessageModel();
message.setBody("Hallo Welt!");
message.setChat(chat);

dataStore.upsert(message).blockingGet();

LastReadChatMessageRelation lastRead = new LastReadChatMessageRelation();
lastRead.setChat(chat);
lastRead.setMessage(message);

dataStore.upsert(lastRead).blockingGet();

MessageModel message2 = new MessageModel();
message2.setChat(chat);
message2.setBody("How are you?");

dataStore.upsert(message2).blockingGet();

LastReadChatMessageRelation lastRead2 = new LastReadChatMessageRelation();
lastRead2.setChat(chat);
lastRead2.setMessage(message2);

dataStore.upsert(lastRead2).blockingGet();
}
}

+ 10
- 7
app/src/main/java/org/mercury_im/messenger/MercuryImApplication.java View File

@@ -1,5 +1,6 @@
package org.mercury_im.messenger;

import android.annotation.SuppressLint;
import android.app.Activity;
import android.app.Application;
import android.app.NotificationChannel;
@@ -10,12 +11,11 @@ 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.di.component.AppComponent;
import org.mercury_im.messenger.di.component.DaggerAppComponent;
import org.mercury_im.messenger.di.module.AppModule;
import org.mercury_im.messenger.persistence.pojo.Chat;
import org.mercury_im.messenger.persistence.room.RoomModule;
import org.mercury_im.messenger.persistence.room.RoomRepositoryModule;
import org.mercury_im.messenger.service.XmppConnectionService;
import org.mercury_im.messenger.util.AbstractActivityLifecycleCallbacks;

@@ -38,6 +38,7 @@ public class MercuryImApplication extends Application implements org.mercury_im.

// 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);

@@ -78,8 +79,6 @@ public class MercuryImApplication extends Application implements org.mercury_im.
public AppComponent createAppComponent() {
AppComponent appComponent = DaggerAppComponent.builder()
.appModule(new AppModule(this))
.roomModule(new RoomModule(this))
.roomRepositoryModule(new RoomRepositoryModule())
.build();

appComponent.inject(this);
@@ -100,6 +99,7 @@ public class MercuryImApplication extends Application implements org.mercury_im.
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);
@@ -110,6 +110,7 @@ public class MercuryImApplication extends Application implements org.mercury_im.
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);
@@ -117,8 +118,10 @@ public class MercuryImApplication extends Application implements org.mercury_im.
}

@Override
public int chatMessageReceived(Chat chat, String contactName, String body) {
return Notifications.chatMessageReceived(this, chat, contactName, body);
public int chatMessageReceived(ChatAndPossiblyContact chatAndPossiblyContact, String body) {
return Notifications.chatMessageReceived(this,
chatAndPossiblyContact.getChat(),
ContactNameUtil.displayableNameFrom(chatAndPossiblyContact.getContact()), body);
}

public AppComponent getAppComponent() {

+ 5
- 5
app/src/main/java/org/mercury_im/messenger/Notifications.java View File

@@ -7,7 +7,7 @@ import android.content.Intent;
import androidx.core.app.NotificationCompat;
import androidx.core.app.NotificationManagerCompat;

import org.mercury_im.messenger.persistence.pojo.Chat;
import org.mercury_im.messenger.persistence.entity.ChatModel;
import org.mercury_im.messenger.ui.chat.ChatActivity;

public class Notifications {
@@ -20,14 +20,14 @@ public class Notifications {
// Notification IDs
public static final int FOREGROUND_SERVICE_ID = 1; // must not be 0

public static int chatMessageReceived(Context context, Chat chat, String contactName, String body) {
public static int chatMessageReceived(Context context, ChatModel chat, String contactName, String body) {
NotificationManagerCompat notificationManagerCompat = NotificationManagerCompat.from(context);
int id = (chat.accountId + " " + chat.jid.toString()).hashCode();
int id = (int) 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.jid.toString());
tapAction.putExtra(ChatActivity.EXTRA_ACCOUNT, chat.accountId);
tapAction.putExtra(ChatActivity.EXTRA_JID, chat.getPeer().getJid().toString());
tapAction.putExtra(ChatActivity.EXTRA_ACCOUNT, chat.getPeer().getAccount().getId());
PendingIntent pendingIntent = PendingIntent.getActivity(context, 0, tapAction, 0);

NotificationCompat.Builder builder = new NotificationCompat.Builder(context,

+ 7
- 5
app/src/main/java/org/mercury_im/messenger/di/component/AppComponent.java View File

@@ -1,9 +1,10 @@
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.persistence.room.RoomModule;
import org.mercury_im.messenger.di.module.AndroidPersistenceModule;
import org.mercury_im.messenger.service.XmppConnectionService;
import org.mercury_im.messenger.ui.MainActivity;
import org.mercury_im.messenger.ui.chat.ChatActivity;
@@ -25,10 +26,11 @@ import dagger.Component;
* application.
*/
@Singleton
@Component(modules = {
AppModule.class,
RoomModule.class
})
@Component(
modules = {
AppModule.class,
AndroidPersistenceModule.class
})
public interface AppComponent {

// Application

+ 57
- 0
app/src/main/java/org/mercury_im/messenger/di/module/AndroidPersistenceModule.java View File

@@ -0,0 +1,57 @@
package org.mercury_im.messenger.di.module;

import android.app.Application;

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;

import dagger.Module;
import dagger.Provides;
import io.reactivex.Scheduler;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.schedulers.Schedulers;
import io.requery.Persistable;
import io.requery.android.sqlite.DatabaseSource;
import io.requery.reactivex.ReactiveEntityStore;
import io.requery.reactivex.ReactiveSupport;
import io.requery.sql.Configuration;
import io.requery.sql.EntityDataStore;
import io.requery.sql.TableCreationMode;

@Module
public class AndroidPersistenceModule {

@Provides
@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);
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;
}

@Provides
@Named(value = ThreadUtils.SCHEDULER_IO)
@Singleton
static Scheduler provideDatabaseThread() {
return Schedulers.io();
}

@Provides
@Named(value = ThreadUtils.SCHEDULER_UI)
@Singleton
static Scheduler providerUIThread() {
return AndroidSchedulers.mainThread();
}
}

+ 5
- 1
app/src/main/java/org/mercury_im/messenger/di/module/AppModule.java View File

@@ -8,10 +8,14 @@ import dagger.Provides;
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)
@Module(includes = {
CenterModule.class,
RequeryModule.class
})
public class AppModule {

private MercuryImApplication mApplication;

+ 1
- 2
app/src/main/java/org/mercury_im/messenger/ui/MainActivity.java View File

@@ -15,11 +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.model.AccountModel;
import org.mercury_im.messenger.persistence.entity.AccountModel;
import org.mercury_im.messenger.persistence.repository.ChatRepository;
import org.mercury_im.messenger.ui.chatlist.ChatListFragment;
import org.mercury_im.messenger.ui.login.AccountsFragment;
import org.mercury_im.messenger.ui.login.LoginActivity;
import org.mercury_im.messenger.ui.roster.RosterFragment;
import org.mercury_im.messenger.ui.settings.SettingsActivity;


+ 3
- 1
app/src/main/java/org/mercury_im/messenger/ui/chat/ChatActivity.java View File

@@ -10,6 +10,7 @@ import androidx.annotation.NonNull;
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;
@@ -22,6 +23,7 @@ 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;

@@ -91,7 +93,7 @@ public class ChatActivity extends AppCompatActivity
getSupportActionBar().setSubtitle(jid.asUnescapedString());
accountId = savedInstanceState.getLong(EXTRA_ACCOUNT);

chatViewModel = ViewModelProviders.of(this).get(ChatViewModel.class);
chatViewModel = new ViewModelProvider(this).get(ChatViewModel.class);
chatViewModel.init(accountId, jid);
// Listen for updates to contact information and messages
observeViewModel(chatViewModel);

+ 27
- 33
app/src/main/java/org/mercury_im/messenger/ui/chat/ChatViewModel.java View File

@@ -4,19 +4,13 @@ import androidx.lifecycle.LiveData;
import androidx.lifecycle.MutableLiveData;
import androidx.lifecycle.ViewModel;

import io.reactivex.Completable;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.disposables.CompositeDisposable;
import io.reactivex.functions.Consumer;
import io.reactivex.schedulers.Schedulers;

import org.jxmpp.jid.EntityBareJid;
import org.mercury_im.messenger.MercuryImApplication;
import org.mercury_im.messenger.core.centers.ConnectionCenter;
import org.mercury_im.messenger.core.util.ContactNameUtil;
import org.mercury_im.messenger.persistence.model.ChatModel;
import org.mercury_im.messenger.persistence.model.ContactModel;
import org.mercury_im.messenger.persistence.model.MessageModel;
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;
@@ -25,6 +19,10 @@ import java.util.List;

import javax.inject.Inject;

import io.reactivex.Completable;
import io.reactivex.disposables.CompositeDisposable;
import io.reactivex.functions.Consumer;

public class ChatViewModel extends ViewModel {

private final CompositeDisposable disposable = new CompositeDisposable();
@@ -41,9 +39,7 @@ public class ChatViewModel extends ViewModel {
@Inject
ConnectionCenter connectionCenter;

private long accountId;
private EntityBareJid jid;

private MutableLiveData<EntityModel> entity = new MutableLiveData<>();
private MutableLiveData<ContactModel> contact = new MutableLiveData<>();
private MutableLiveData<List<MessageModel>> messages = new MutableLiveData<>();
private MutableLiveData<String> contactDisplayName = new MutableLiveData<>();
@@ -55,29 +51,24 @@ public class ChatViewModel extends ViewModel {
}

public void init(long accountId, EntityBareJid jid) {
this.accountId = accountId;
this.jid = jid;
disposable.add(rosterRepository.getOrCreateEntity(accountId, jid)
.subscribe((Consumer<EntityModel>) this::init));
}

disposable.add(rosterRepository.getContact(accountId, jid)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe((Consumer<ContactModel>)
contactModel -> {
ChatViewModel.this.contact.setValue(contactModel);
contactDisplayName.setValue(ContactNameUtil.displayableNameFrom(contactModel));
}));
public void init(EntityModel entityModel) {

disposable.add(messageRepository.getAllMessagesOfChat(accountId, jid)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe((Consumer<List<MessageModel>>)
messages -> ChatViewModel.this.messages.setValue(messages)));
disposable.add(rosterRepository.getContact(entityModel.getAccount().getId(), entityModel.getJid())
.subscribe(reactiveResult -> {
ContactModel model = reactiveResult.first();
ChatViewModel.this.contact.setValue(model);
contactDisplayName.setValue(model.getRostername());
}));

disposable.add(chatRepository.getOrCreateChatWith(accountId, jid)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe((Consumer<ChatModel>)
chatModel -> this.chat.setValue(chatModel)));
disposable.add(messageRepository.getAllMessagesOfEntity(entityModel)
.subscribe(reactiveResult -> {
List<MessageModel> messages = reactiveResult.toList();
ChatViewModel.this.messages.setValue(messages);
}));
}

@Override
@@ -99,6 +90,7 @@ public class ChatViewModel extends ViewModel {
}

public void queryTextChanged(String query) {
/*
if (query.isEmpty()) {
disposable.add(messageRepository.getAllMessagesOfChat(accountId, jid)
.subscribeOn(Schedulers.io())
@@ -112,6 +104,8 @@ public class ChatViewModel extends ViewModel {
.subscribe((Consumer<List<MessageModel>>) o -> {
messages.setValue(o);
}));

*/
}

public Completable requestMamMessages() {

+ 1
- 1
app/src/main/java/org/mercury_im/messenger/ui/chat/MessagesRecyclerViewAdapter.java View File

@@ -10,7 +10,7 @@ import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;

import org.mercury_im.messenger.R;
import org.mercury_im.messenger.persistence.model.MessageModel;
import org.mercury_im.messenger.persistence.entity.MessageModel;
import org.mercury_im.messenger.ui.util.MessageBackgroundDrawable;

import java.util.ArrayList;

+ 50
- 13
app/src/main/java/org/mercury_im/messenger/ui/chatlist/ChatListRecyclerViewAdapter.java View File

@@ -3,16 +3,20 @@ package org.mercury_im.messenger.ui.chatlist;
import android.content.Context;
import android.content.Intent;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TextView;

import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.view.ActionMode;
import androidx.recyclerview.widget.RecyclerView;

import org.mercury_im.messenger.R;
import org.mercury_im.messenger.persistence.pojo.Chat;
import org.mercury_im.messenger.persistence.entity.ChatModel;
import org.mercury_im.messenger.ui.chat.ChatActivity;
import org.mercury_im.messenger.ui.util.AbstractRecyclerViewAdapter;
import org.mercury_im.messenger.util.ColorUtil;
@@ -21,7 +25,7 @@ import butterknife.BindView;
import butterknife.ButterKnife;

public class ChatListRecyclerViewAdapter
extends AbstractRecyclerViewAdapter<Chat, ChatListRecyclerViewAdapter.ChatHolder> {
extends AbstractRecyclerViewAdapter<ChatModel, ChatListRecyclerViewAdapter.ChatHolder> {

public ChatListRecyclerViewAdapter() {
super(new ChatMessageDiffCallback(true));
@@ -37,20 +41,24 @@ public class ChatListRecyclerViewAdapter

@Override
public void onBindViewHolder(@NonNull ChatHolder holder, int position) {
Chat model = getModelAt(position);
holder.nameView.setText(model.contactName != null ?
model.contactName : model.jid.toString());
holder.avatarView.setColorFilter(ColorUtil.consistentColor(model.jid.toString()));
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()));

holder.itemView.setOnClickListener(view -> {

Intent intent = new Intent(holder.context, ChatActivity.class);
intent.putExtra(ChatActivity.EXTRA_JID, model.jid.toString());
intent.putExtra(ChatActivity.EXTRA_ACCOUNT, model.accountId);
intent.putExtra(ChatActivity.EXTRA_JID, model.getPeer().getJid().toString());
intent.putExtra(ChatActivity.EXTRA_ACCOUNT, model.getPeer().getAccount().getId());

holder.context.startActivity(intent);
});
// TODO: Better bindable model pls

holder.itemView.setOnLongClickListener(v -> {
((AppCompatActivity) v.getContext()).startSupportActionMode(actionModeCallback);
return true;
});
}

public static class ChatHolder extends RecyclerView.ViewHolder {
@@ -76,20 +84,49 @@ public class ChatListRecyclerViewAdapter
}
}

private static class ChatMessageDiffCallback extends AbstractDiffCallback<Chat> {
private static class ChatMessageDiffCallback extends AbstractDiffCallback<ChatModel> {

ChatMessageDiffCallback(boolean detectMoves) {
super(detectMoves);
}

@Override
public boolean areItemsTheSame(Chat oldItem, Chat newItem) {
return oldItem.chatId == newItem.chatId;
public boolean areItemsTheSame(ChatModel oldItem, ChatModel newItem) {
return oldItem.getId() == newItem.getId();
}

@Override
public boolean areContentsTheSame(Chat oldItem, Chat newItem) {
public boolean areContentsTheSame(ChatModel oldItem, ChatModel newItem) {
return areItemsTheSame(oldItem, newItem);
}
}

private androidx.appcompat.view.ActionMode.Callback actionModeCallback = new androidx.appcompat.view.ActionMode.Callback() {
private boolean multiSelect = false;

@Override
public boolean onCreateActionMode(ActionMode mode, Menu menu) {
mode.getMenuInflater().inflate(R.menu.actionmode_chatlist, menu);
multiSelect = true;
return true;
}

@Override
public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
return false;
}

@Override
public boolean onActionItemClicked(ActionMode mode, MenuItem item) {

mode.finish();

return true;
}

@Override
public void onDestroyActionMode(ActionMode mode) {
notifyDataSetChanged();
}
};
}

+ 7
- 22
app/src/main/java/org/mercury_im/messenger/ui/chatlist/ChatListViewModel.java View File

@@ -4,14 +4,8 @@ import androidx.lifecycle.LiveData;
import androidx.lifecycle.MutableLiveData;
import androidx.lifecycle.ViewModel;

import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.disposables.CompositeDisposable;
import io.reactivex.functions.Consumer;
import io.reactivex.schedulers.Schedulers;

import org.mercury_im.messenger.MercuryImApplication;
import org.mercury_im.messenger.persistence.model.MessageModel;
import org.mercury_im.messenger.persistence.pojo.Chat;
import org.mercury_im.messenger.persistence.entity.ChatModel;
import org.mercury_im.messenger.persistence.repository.ChatRepository;
import org.mercury_im.messenger.persistence.repository.MessageRepository;

@@ -19,6 +13,8 @@ import java.util.List;

import javax.inject.Inject;

import io.reactivex.disposables.CompositeDisposable;

public class ChatListViewModel extends ViewModel {

@Inject
@@ -29,27 +25,16 @@ public class ChatListViewModel extends ViewModel {

private CompositeDisposable disposable = new CompositeDisposable();

private final MutableLiveData<List<Chat>> chats = new MutableLiveData<>();
private final MutableLiveData<List<ChatModel>> chats = new MutableLiveData<>();

public ChatListViewModel() {
MercuryImApplication.getApplication().getAppComponent().inject(this);

disposable.add(chatRepository.getDisplayableChats()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe((Consumer<List<Chat>>) chats::setValue));

disposable.add(messageRepository.getAllMessages()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe((Consumer<List<MessageModel>>) messageModels -> {
for (MessageModel message : messageModels) {

}
}));
disposable.add(chatRepository.getVisibleChats()
.subscribe(result -> chats.setValue(result.toList())));
}

public LiveData<List<Chat>> getChats() {
public LiveData<List<ChatModel>> getChats() {
return chats;
}


+ 1
- 1
app/src/main/java/org/mercury_im/messenger/ui/login/AccountsFragment.java View File

@@ -17,7 +17,7 @@ import com.google.android.material.floatingactionbutton.ExtendedFloatingActionBu

import org.mercury_im.messenger.MercuryImApplication;
import org.mercury_im.messenger.R;
import org.mercury_im.messenger.persistence.model.AccountModel;
import org.mercury_im.messenger.persistence.entity.AccountModel;

import butterknife.BindView;
import butterknife.ButterKnife;

+ 2
- 5
app/src/main/java/org/mercury_im/messenger/ui/login/AccountsRecyclerViewAdapter.java View File

@@ -11,11 +11,9 @@ import android.widget.TextView;
import androidx.recyclerview.widget.DiffUtil;
import androidx.recyclerview.widget.RecyclerView;

import de.hdodenhof.circleimageview.CircleImageView;

import org.mercury_im.messenger.MercuryImApplication;
import org.mercury_im.messenger.R;
import org.mercury_im.messenger.persistence.model.AccountModel;
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;
@@ -47,9 +45,8 @@ public class AccountsRecyclerViewAdapter extends RecyclerView.Adapter<AccountsRe
AccountModel account = mValues.get(position);
holder.jid.setText(account.getJid());
holder.avatar.setColorFilter(ColorUtil.consistentColor(account.getJid().toString()));
holder.enabled.setChecked(account.getEnabled());
holder.enabled.setChecked(account.isEnabled());
holder.accountModel = account;
holder.status.setText(account.getState());
holder.enabled.setOnCheckedChangeListener((compoundButton, b) -> {
viewModel.toggleAccountEnabled(account);
});

+ 7
- 16
app/src/main/java/org/mercury_im/messenger/ui/login/AccountsViewModel.java View File

@@ -6,25 +6,19 @@ import android.widget.Toast;
import androidx.lifecycle.AndroidViewModel;
import androidx.lifecycle.LiveData;
import androidx.lifecycle.MutableLiveData;
import androidx.lifecycle.ViewModel;

import io.reactivex.Scheduler;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.disposables.CompositeDisposable;
import io.reactivex.functions.Consumer;
import io.reactivex.schedulers.Schedulers;

import org.jivesoftware.smack.XMPPConnection;
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.model.AccountModel;
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
@@ -40,10 +34,8 @@ public class AccountsViewModel extends AndroidViewModel {
public AccountsViewModel(Application application) {
super(application);
MercuryImApplication.getApplication().getAppComponent().inject(this);
compositeDisposable.add(repository.getAllAccounts()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe((Consumer<List<AccountModel>>) accounts::setValue));
compositeDisposable.add(repository.getAll()
.subscribe(accountModels -> accounts.setValue(accountModels.toList())));
}

@Override
@@ -63,9 +55,8 @@ public class AccountsViewModel extends AndroidViewModel {
return;
}

accountModel.setEnabled(!accountModel.getEnabled());
repository.updateAccount(accountModel)
.subscribeOn(Schedulers.io())
accountModel.setEnabled(!accountModel.isEnabled());
repository.upsert(accountModel)
.subscribe();
}
}

+ 7
- 7
app/src/main/java/org/mercury_im/messenger/ui/login/LoginActivity.java View File

@@ -9,18 +9,18 @@ import android.widget.TextView;

import androidx.appcompat.app.AppCompatActivity;
import androidx.lifecycle.LiveData;
import androidx.lifecycle.ViewModelProviders;

import butterknife.BindView;
import butterknife.ButterKnife;
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.model.AccountModel;
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.
@@ -49,7 +49,7 @@ public class LoginActivity extends AppCompatActivity implements TextView.OnEdito
// Set up the login form.
ButterKnife.bind(this);

viewModel = ViewModelProviders.of(this).get(LoginViewModel.class);
viewModel = new ViewModelProvider(this).get(LoginViewModel.class);

observeViewModel(viewModel);
displayCredentials(viewModel.getAccount());
@@ -121,7 +121,7 @@ public class LoginActivity extends AppCompatActivity implements TextView.OnEdito
});
}

private void displayCredentials(LiveData<? extends AccountModel> account) {
private void displayCredentials(LiveData<AccountModel> account) {
account.observe(this, accountModel -> {
if (accountModel == null) {
return;

+ 8
- 14
app/src/main/java/org/mercury_im/messenger/ui/login/LoginViewModel.java View File

@@ -11,17 +11,14 @@ import androidx.lifecycle.MutableLiveData;
import androidx.lifecycle.ViewModel;

import io.reactivex.Single;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.observers.DisposableSingleObserver;
import io.reactivex.schedulers.Schedulers;

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.model.AccountModel;
import org.mercury_im.messenger.persistence.entity.AccountModel;
import org.mercury_im.messenger.persistence.repository.AccountRepository;
import org.mercury_im.messenger.persistence.room.model.RoomAccountModel;

import javax.inject.Inject;

@@ -46,7 +43,7 @@ public class LoginViewModel extends ViewModel {
public LoginViewModel() {
super();
MercuryImApplication.getApplication().getAppComponent().inject(this);
init(accountRepository.newAccountModel());
init(new AccountModel());
}

public LiveData<Boolean> getSigninSuccessful() {
@@ -109,7 +106,7 @@ public class LoginViewModel extends ViewModel {
public void login() {
AccountModel account = getAccount().getValue();
if (account != null && account.getJid() != null && !TextUtils.isEmpty(account.getPassword())) {
accountRepository.insertAccount(account);
accountRepository.upsert(account);
}
}

@@ -132,18 +129,15 @@ public class LoginViewModel extends ViewModel {
}

if (loginIntact) {
RoomAccountModel accountModel = new RoomAccountModel();
AccountModel accountModel = new AccountModel();
accountModel.setEnabled(true);
accountModel.setJid(bareJid);
accountModel.setPassword(password);
Single<Long> id = accountRepository.insertAccount(accountModel);
id.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new DisposableSingleObserver<Long>() {
Single<AccountModel> insert = accountRepository.upsert(accountModel);
insert.subscribe(new DisposableSingleObserver<AccountModel>() {
@Override
public void onSuccess(Long aLong) {
accountModel.setId(aLong);
Log.d(MercuryImApplication.TAG, "LoginActivity.loginDetailsEntered: Account " + aLong + " inserted.");
public void onSuccess(AccountModel inserted) {
Log.d(MercuryImApplication.TAG, "LoginActivity.loginDetailsEntered: Account " + inserted.getId() + " inserted.");
connectionCenter.createConnection(accountModel);
signinSuccessful.setValue(true);
}

+ 4
- 4
app/src/main/java/org/mercury_im/messenger/ui/roster/contacts/ContactListItemViewModel.java View File

@@ -6,17 +6,17 @@ import androidx.annotation.NonNull;
import androidx.lifecycle.AndroidViewModel;
import androidx.lifecycle.LiveData;

import org.mercury_im.messenger.persistence.room.model.RoomContactModel;
import org.mercury_im.messenger.persistence.room.repository.IRosterRepository;
import org.mercury_im.messenger.persistence.entity.ContactModel;
import org.mercury_im.messenger.persistence.repository.RosterRepository;

import javax.inject.Inject;

public class ContactListItemViewModel extends AndroidViewModel {

@Inject
IRosterRepository contactRepository;
RosterRepository contactRepository;

private LiveData<RoomContactModel> contact;
private LiveData<ContactModel> contact;

@Inject
public ContactListItemViewModel(@NonNull Application application) {

+ 13
- 15
app/src/main/java/org/mercury_im/messenger/ui/roster/contacts/ContactListRecyclerViewAdapter.java View File

@@ -11,22 +11,20 @@ import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;

import butterknife.BindView;
import butterknife.ButterKnife;
import de.hdodenhof.circleimageview.CircleImageView;

import org.jivesoftware.smackx.colors.ConsistentColor;
import org.jxmpp.jid.EntityBareJid;
import org.mercury_im.messenger.R;
import org.mercury_im.messenger.persistence.room.model.RoomContactModel;
import org.mercury_im.messenger.persistence.entity.ContactModel;
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.Objects;

import butterknife.BindView;
import butterknife.ButterKnife;

public class ContactListRecyclerViewAdapter
extends AbstractRecyclerViewAdapter<RoomContactModel, ContactListRecyclerViewAdapter.RosterItemViewHolder> {
extends AbstractRecyclerViewAdapter<ContactModel, ContactListRecyclerViewAdapter.RosterItemViewHolder> {

public ContactListRecyclerViewAdapter() {
super(new ContactDiffCallback());
@@ -41,7 +39,7 @@ public class ContactListRecyclerViewAdapter

@Override
public void onBindViewHolder(@NonNull RosterItemViewHolder holder, int position) {
RoomContactModel model = getModelAt(position);
ContactModel model = getModelAt(position);
holder.bind(model);
}

@@ -67,8 +65,8 @@ public class ContactListRecyclerViewAdapter
ButterKnife.bind(this, view);
}

void bind(RoomContactModel contactModel) {
String name = contactModel.getRosterName();
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());
@@ -77,28 +75,28 @@ public class ContactListRecyclerViewAdapter

Intent intent = new Intent(context, ChatActivity.class);
intent.putExtra(ChatActivity.EXTRA_JID, jid.toString());
intent.putExtra(ChatActivity.EXTRA_ACCOUNT, contactModel.getEntity().getAccountId());
intent.putExtra(ChatActivity.EXTRA_ACCOUNT, contactModel.getEntity().getAccount().getId());

context.startActivity(intent);
});
}
}

private static class ContactDiffCallback extends AbstractDiffCallback<RoomContactModel> {
private static class ContactDiffCallback extends AbstractDiffCallback<ContactModel> {

ContactDiffCallback() {
super(true);
}

@Override
public boolean areItemsTheSame(RoomContactModel oldItem, RoomContactModel newItem) {
public boolean areItemsTheSame(ContactModel oldItem, ContactModel newItem) {
return oldItem.getId() == newItem.getId();
}

@Override
public boolean areContentsTheSame(RoomContactModel oldItem, RoomContactModel newItem) {
public boolean areContentsTheSame(ContactModel oldItem, ContactModel newItem) {
return areItemsTheSame(oldItem, newItem) &&
Objects.equals(oldItem.getRosterName(), newItem.getRosterName());
Objects.equals(oldItem.getRostername(), newItem.getRostername());
}
}
}

+ 11
- 12
app/src/main/java/org/mercury_im/messenger/ui/roster/contacts/ContactListViewModel.java View File

@@ -6,27 +6,25 @@ import androidx.lifecycle.LiveData;
import androidx.lifecycle.MutableLiveData;
import androidx.lifecycle.ViewModel;

import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.disposables.CompositeDisposable;
import io.reactivex.functions.Consumer;
import io.reactivex.schedulers.Schedulers;

import org.mercury_im.messenger.MercuryImApplication;
import org.mercury_im.messenger.persistence.model.ContactModel;
import org.mercury_im.messenger.persistence.entity.ContactModel;
import org.mercury_im.messenger.persistence.repository.RosterRepository;
import org.mercury_im.messenger.persistence.room.model.RoomContactModel;

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;

private final MutableLiveData<List<RoomContactModel>> rosterEntryList = new MutableLiveData<>();
private final MutableLiveData<List<ContactModel>> rosterEntryList = new MutableLiveData<>();
private final CompositeDisposable compositeDisposable = new CompositeDisposable();

public ContactListViewModel() {
@@ -37,9 +35,10 @@ public class ContactListViewModel extends ViewModel {
compositeDisposable.add(rosterRepository.getAllContacts()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe((Consumer<List<? extends ContactModel>>) o -> {
Log.d("ContactListViewModel", "Room changed contacts: " + o.size());
rosterEntryList.setValue((List<RoomContactModel>) o);
.subscribe(o -> {
List<ContactModel> list = o.toList();
Log.d("ContactListViewModel", "Room changed contacts: " + list.size());
rosterEntryList.setValue(list);
}));
}

@@ -49,7 +48,7 @@ public class ContactListViewModel extends ViewModel {
compositeDisposable.clear();
}

public LiveData<List<RoomContactModel>> getRosterEntryList() {
public LiveData<List<ContactModel>> getRosterEntryList() {
return rosterEntryList;
}
}

+ 11
- 0
app/src/main/res/menu/actionmode_chat_single.xml View File

@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">

<item android:id="@+id/action_copy"
android:title="@string/action_copy"
android:icon="@drawable/ic_content_copy_black_24dp" />

<item android:id="@+id/action_reply_message"
android:title="@string/action_reply_message"
android:icon="@drawable/ic_reply_black_24dp" />
</menu>

+ 8
- 0
app/src/main/res/menu/actionmode_chatlist.xml View File

@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">

<item android:id="@+id/action_close_chat"
android:title="@string/action_close_chat"
android:icon="@drawable/ic_delete_black_24dp" />

</menu>

+ 2
- 0
app/src/main/res/values/strings.xml View File

@@ -129,4 +129,6 @@
<string name="action_add_account">Add Account</string>
<string name="action_add_contact">Add Contact</string>
<string name="action_add_bookmark">Add Bookmark</string>
<string name="action_close_chat">Close Chat</string>
<string name="action_copy">Copy</string>
</resources>

+ 1
- 0
app/src/main/res/values/themes.xml View File

@@ -21,6 +21,7 @@
<item name="messageBubbleColor">@color/white</item>
<item name="messageTextColor">@color/textBlack</item>

<item name="windowActionModeOverlay">true</item>
</style>

<!-- Light Theme -->

+ 1
- 1
build.gradle View File

@@ -7,7 +7,7 @@ buildscript {
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:3.5.0'
classpath 'com.android.tools.build:gradle:3.5.1'

// NOTE: Do not place your application dependencies here; they belong

+ 16
- 0
core/build.gradle View File

@@ -1,7 +1,14 @@
apply plugin: 'java-library'

// Add the generated folder to the source directories so that we can work with generated classes
// This is apparently necessary for use with requery.
sourceSets {
main.java.srcDirs += "${buildDir}/generated/sources/annotationProcessor/java/main/"
}

dependencies {

api project(":thread_utils")
api project(":persistence")

// Smack
@@ -17,10 +24,19 @@ dependencies {
// api "org.igniterealtime.smack:smack-openpgp:$smackOpenpgpVersion"
// api "org.igniterealtime.smack:smack-resolver-minidns:$smackResolverMiniDnsVersion"


// RxJava2
api "io.reactivex.rxjava2:rxjava:$rxJava2Version"

// Dagger 2 for dependency injection
implementation "com.google.dagger:dagger:$daggerVersion"
annotationProcessor "com.google.dagger:dagger-compiler:$daggerVersion"

// Requery ORM
api "io.requery:requery:$requeryVersion"
annotationProcessor "io.requery:requery-processor:$requeryVersion"

// JUnit for testing
testImplementation "junit:junit:$junitVersion"
}


+ 3
- 2
core/src/main/java/org/mercury_im/messenger/core/NotificationManager.java View File

@@ -1,9 +1,10 @@
package org.mercury_im.messenger.core;

import org.mercury_im.messenger.persistence.pojo.Chat;

import org.mercury_im.messenger.persistence.util.ChatAndPossiblyContact;

public interface NotificationManager {

int chatMessageReceived(Chat chat, String contactName, String body);
int chatMessageReceived(ChatAndPossiblyContact chatAndPossiblyContact, String body);

}

+ 31
- 35
core/src/main/java/org/mercury_im/messenger/core/centers/ConnectionCenter.java View File

@@ -3,26 +3,25 @@ package org.mercury_im.messenger.core.centers;
import org.jivesoftware.smack.AbstractXMPPConnection;
import org.jivesoftware.smack.SmackException;
import org.jivesoftware.smack.XMPPConnection;
import org.jivesoftware.smack.XMPPException;
import org.jivesoftware.smack.tcp.XMPPTCPConnection;
import org.jivesoftware.smack.tcp.XMPPTCPConnectionConfiguration;
import org.jivesoftware.smackx.caps.EntityCapsManager;
import org.jivesoftware.smackx.csi.ClientStateIndicationManager;
import org.jivesoftware.smackx.mam.MamManager;
import org.mercury_im.messenger.core.stores.EntityCapsStore;
import org.mercury_im.messenger.core.connection.MercuryConfiguration;
import org.mercury_im.messenger.core.connection.MercuryConnection;
import org.mercury_im.messenger.core.stores.PlainMessageStore;
import org.mercury_im.messenger.core.stores.RosterStore;
import org.mercury_im.messenger.persistence.model.AccountModel;
import org.mercury_im.messenger.persistence.model.ChatModel;
import org.mercury_im.messenger.persistence.model.EntityModel;
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.core.stores.EntityCapsStore;
import org.mercury_im.messenger.core.stores.PlainMessageStore;
import org.mercury_im.messenger.core.stores.RosterStore;

import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.atomic.AtomicBoolean;
@@ -34,7 +33,6 @@ import javax.inject.Singleton;

import io.reactivex.disposables.CompositeDisposable;
import io.reactivex.disposables.Disposable;
import io.reactivex.functions.Consumer;
import io.reactivex.schedulers.Schedulers;

@Singleton
@@ -44,9 +42,9 @@ public class ConnectionCenter {

// Injected
private final AccountRepository accountRepository;
private final RosterRepository rosterRepository;
private final PlainMessageStore messageStore;
private final EntityCapsStore entityCapsStore;
private final RosterRepository rosterRepository;

// Connections
private final Map<Long, MercuryConnection> connectionMap =
@@ -86,15 +84,14 @@ public class ConnectionCenter {
}

// otherwise subscribe to accounts and create connections.
Disposable allAccounts = accountRepository.getAllAccounts()
.observeOn(Schedulers.io())
.subscribeOn(Schedulers.computation())
.subscribe((Consumer<List<? extends AccountModel>>) accounts -> {
Disposable allAccounts = accountRepository.getAll()
.observeOn(Schedulers.newThread())
.subscribe(accounts -> {
LOGGER.log(Level.INFO, "Accounts changed.");
Set<Long> accountIds = new HashSet<>();

// Add missing connections to the map
for (AccountModel account : accounts) {
for (AccountModel account : accounts.toList()) {
accountIds.add(account.getId());
if (connectionMap.get(account.getId()) != null) {
continue;
@@ -121,7 +118,7 @@ public class ConnectionCenter {

for (AccountModel account : accounts) {
MercuryConnection connection = connectionMap.get(account.getId());
if (account.getEnabled()) {
if (account.isEnabled()) {
if (connection.getConnection().isConnected()) {
continue;
}
@@ -139,7 +136,6 @@ public class ConnectionCenter {
});

disposable.add(allAccounts);

}

public MercuryConnection getConnection(AccountModel account) {
@@ -179,15 +175,13 @@ public class ConnectionCenter {

public void initializeConnection(MercuryConnection connection) {
// Register roster store
RosterStore rosterStore = new RosterStore(rosterRepository);
RosterStore rosterStore = new RosterStore(rosterRepository, accountRepository);
rosterStore.setAccountId(connection.getAccountId());
rosterStore.subscribe();
connection.getRoster().setRosterStore(rosterStore);

// Register message store
messageStore.registerForMercuryConnection(connection);

//
}

/**
@@ -229,21 +223,23 @@ public class ConnectionCenter {
}

public void requestMamMessagesFor(ChatModel chat) {
disposable.add(rosterRepository.getEntity(chat.getPeerEntityId())
.subscribe((Consumer<EntityModel>) entity -> {
MercuryConnection connection = connectionMap.get(entity.getAccountId());
if (connection == null) return;
MamManager mamManager = MamManager.getInstanceFor(connection.getConnection());
MamManager.MamQuery query;
//if (chat.getEarliestMamMessageId() == null) {
query = mamManager.queryMostRecentPage(entity.getJid(), 100);
//} else {
//MamManager.MamQueryArgs queryArgs = MamManager.MamQueryArgs.builder()
// .beforeUid()
// .build();
//query = mamManager.queryArchive()
//}
messageStore.onMamResult(entity.getAccountId(), entity.getJid(), query);
}));

MercuryConnection connection = connectionMap.get(chat.getPeer().getAccount().getId());
if (connection == null) return;
MamManager mamManager = MamManager.getInstanceFor(connection.getConnection());
MamManager.MamQuery query;
//if (chat.getEarliestMamMessageId() == null) {
try {
query = mamManager.queryMostRecentPage(chat.getPeer().getJid(), 100);
messageStore.handleMamResult(chat.getPeer().getAccount().getId(), chat.getPeer().getJid(), query);
} catch (SmackException.NoResponseException | XMPPException.XMPPErrorException | SmackException.NotConnectedException | SmackException.NotLoggedInException | InterruptedException e) {
e.printStackTrace();
}
//} else {
//MamManager.MamQueryArgs queryArgs = MamManager.MamQueryArgs.builder()
// .beforeUid()
// .build();
//query = mamManager.queryArchive()
//}
}
}

+ 15
- 8
core/src/main/java/org/mercury_im/messenger/core/di/CenterModule.java View File

@@ -2,12 +2,13 @@ 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.core.stores.EntityCapsStore;
import org.mercury_im.messenger.core.stores.PlainMessageStore;
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.persistence.repository.EntityCapsRepository;
import org.mercury_im.messenger.core.stores.EntityCapsStore;
import org.mercury_im.messenger.core.stores.PlainMessageStore;

import javax.inject.Singleton;

@@ -19,20 +20,26 @@ public class CenterModule {

@Singleton
@Provides
static ConnectionCenter provideConnectionCenter(EntityCapsStore capsStore, PlainMessageStore messageStore, AccountRepository accountRepository, RosterRepository rosterRepository) {
static ConnectionCenter provideConnectionCenter(EntityCapsStore capsStore,
PlainMessageStore messageStore,
AccountRepository accountRepository,
RosterRepository rosterRepository) {
return new ConnectionCenter(capsStore, messageStore, accountRepository, rosterRepository);
}

@Singleton
@Provides
static EntityCapsStore providerEntityCapsStore(EntityCapsRepository capsRepository) {
return new EntityCapsStore(capsRepository);
static EntityCapsStore providerEntityCapsStore(EntityCapsRepository entityCapsRepository) {
return new EntityCapsStore(entityCapsRepository);
}

@Singleton
@Provides
static PlainMessageStore provideMessageStore(MessageRepository messageRepository, NotificationManager notificationManager) {
return new PlainMessageStore(messageRepository, notificationManager);
static PlainMessageStore provideMessageStore(RosterRepository rosterRepository,
ChatRepository chatRepository,
MessageRepository messageRepository,
NotificationManager notificationManager) {
return new PlainMessageStore(rosterRepository, chatRepository, messageRepository, notificationManager);
}

}

+ 6
- 1
core/src/main/java/org/mercury_im/messenger/core/di/XmppComponent.java View File

@@ -1,11 +1,16 @@
package org.mercury_im.messenger.core.di;

import org.mercury_im.messenger.persistence.di.RequeryModule;

import javax.inject.Singleton;

import dagger.Component;

@Singleton
@Component(modules = CenterModule.class)
@Component(modules = {
CenterModule.class,
RequeryModule.class
})
public interface XmppComponent {

}

+ 51
- 35
core/src/main/java/org/mercury_im/messenger/core/stores/EntityCapsStore.java View File

@@ -4,24 +4,21 @@ 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.model.EntityCapsModel;
import org.mercury_im.messenger.persistence.entity.EntityCapsModel;
import org.mercury_im.messenger.persistence.repository.EntityCapsRepository;


import java.io.StringReader;
import java.util.HashMap;
import java.util.List;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;

import javax.inject.Inject;

import io.reactivex.Observable;
import io.reactivex.Single;
import io.reactivex.disposables.CompositeDisposable;
import io.reactivex.functions.Consumer;
import io.reactivex.observers.DisposableSingleObserver;
import io.reactivex.schedulers.Schedulers;

public class EntityCapsStore implements EntityCapsPersistentCache {

@@ -31,56 +28,75 @@ public class EntityCapsStore implements EntityCapsPersistentCache {
private final Map<String, DiscoverInfo> discoverInfoMap = new HashMap<>();

private final CompositeDisposable disposable = new CompositeDisposable();
private Observable<List<EntityCapsModel>> allEntityCaps;

@Inject
public EntityCapsStore(EntityCapsRepository repository) {
this.entityCapsRepository = repository;
public EntityCapsStore(EntityCapsRepository entityCapsRepository) {
this.entityCapsRepository = entityCapsRepository;
populateFromDatabase();
}

/*
* Since nodeVers are - if ever - only deleted all at once but added one by one and never
* modified, we can simply determine the set of newly added nodeVers, process those and add
* them to the database.
*/
private void populateFromDatabase() {
allEntityCaps = entityCapsRepository.getAllEntityCaps();
disposable.add(allEntityCaps.subscribeOn(Schedulers.io())
.observeOn(Schedulers.io())
.subscribe(entityCapsModels -> {
discoverInfoMap.clear();
for (EntityCapsModel c : entityCapsModels) {
DiscoverInfo info;
try {
XmlPullParser parser = PacketParserUtils.getParserFor(new StringReader(c.getXml()));
info = (DiscoverInfo) PacketParserUtils.parseIQ(parser);
discoverInfoMap.put(c.getNodeVer(), info);
} catch (Exception e) {
LOGGER.log(Level.SEVERE, "Error parsing EntityCaps: ", e);
}
}
}, throwable -> LOGGER.log(Level.SEVERE, "Error accessing database", throwable)));
disposable.add(entityCapsRepository.getAll()
.subscribe(
entityCapsModels -> {
Map<String, EntityCapsModel> nextEntityCaps = entityCapsModels.toMap(EntityCapsModel.NODE_VER);

// New set of nodeVers
Set<String> nextKeys = nextEntityCaps.keySet();
// Old set of nodeVers
Set<String> previousKeys = discoverInfoMap.keySet();

// Added nodeVers
nextKeys.removeAll(previousKeys);

for (String key : nextKeys) {
// Only add new items. Items itself cannot change, so we don't have to deal
// with changed items.
EntityCapsModel addedModel = nextEntityCaps.get(key);
DiscoverInfo info;
try {
XmlPullParser parser = PacketParserUtils.getParserFor(new StringReader(addedModel.getXml()));
info = (DiscoverInfo) PacketParserUtils.parseIQ(parser);
discoverInfoMap.put(addedModel.getNodeVer(), info);
} catch (Exception e) {
LOGGER.log(Level.SEVERE, "Error parsing EntityCaps: ", e);
}
}
},
error -> LOGGER.log(Level.WARNING, "An error occurred while updating the EntityCaps cache.", error)));
}

@Override
public void addDiscoverInfoByNodePersistent(String nodeVer, DiscoverInfo info) {
EntityCapsModel model = entityCapsRepository.newEntityCapsModel(nodeVer);
EntityCapsModel model = new EntityCapsModel();
model.setNodeVer(nodeVer);
CharSequence xml = info.toXML();
String string = xml.toString();
model.setXml(string);
disposable.add(entityCapsRepository.insertOrReplaceEntityCaps(model)
.subscribeOn(Schedulers.io())
.subscribe());
disposable.add(entityCapsRepository.upsert(model).subscribe(
success -> LOGGER.log(Level.FINE, "Upserted EntityCaps model " + success),
error -> LOGGER.log(Level.WARNING, "An error occurred upserting EntityCaps model", error)
));
}

@Override
public DiscoverInfo lookup(String nodeVer) {
LOGGER.log(Level.INFO, "Looking up caps for " + nodeVer + " in cache...");
LOGGER.log(Level.FINE, "Looking up caps for " + nodeVer + " in cache...");
DiscoverInfo info = discoverInfoMap.get(nodeVer);
LOGGER.log(Level.INFO, "Entry found: " + (info != null ? info.toXML().toString() : "null"));
LOGGER.log(Level.FINE, "Entry found: " + (info != null ? info.toXML().toString() : "null"));
return info;
}

@Override
public void emptyCache() {
entityCapsRepository.deleteAllEntityCaps()
.subscribeOn(Schedulers.io())
.subscribe();
disposable.add(entityCapsRepository.deleteAll().subscribe(
success -> LOGGER.log(Level.FINE, "EntityCaps table cleared successfully."),
error -> LOGGER.log(Level.WARNING, "An error occurred while clearing EntityCaps table.", error)
));
}
}

+ 117
- 61
core/src/main/java/org/mercury_im/messenger/core/stores/PlainMessageStore.java View File

@@ -5,13 +5,24 @@ import org.jivesoftware.smack.chat2.ChatManager;
import org.jivesoftware.smack.packet.Message;
import org.jivesoftware.smackx.carbons.CarbonManager;
import org.jivesoftware.smackx.carbons.packet.CarbonExtension;
import org.jivesoftware.smackx.delay.DelayInformationManager;
import org.jivesoftware.smackx.delay.packet.DelayInformation;
import org.jivesoftware.smackx.mam.MamManager;
import org.jivesoftware.smackx.sid.element.OriginIdElement;
import org.jivesoftware.smackx.sid.element.StanzaIdElement;
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.model.MessageModel;
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 java.util.ArrayList;
import java.util.Date;
@@ -19,8 +30,8 @@ import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;

import io.reactivex.Completable;
import io.reactivex.disposables.CompositeDisposable;
import io.reactivex.functions.Consumer;
import io.reactivex.schedulers.Schedulers;

public class PlainMessageStore {
@@ -28,75 +39,95 @@ public class PlainMessageStore {
private static final Logger LOGGER = Logger.getLogger(PlainMessageStore.class.getName());
private static final CompositeDisposable disposable = new CompositeDisposable();

private final RosterRepository rosterRepository;
private final ChatRepository chatRepository;
private final MessageRepository messageRepository;

private final NotificationManager notificationManager;

public PlainMessageStore(MessageRepository messageRepository, NotificationManager notificationManager) {
public PlainMessageStore(RosterRepository rosterRepository, ChatRepository chatRepository, MessageRepository messageRepository, NotificationManager notificationManager) {
this.rosterRepository = rosterRepository;
this.chatRepository = chatRepository;
this.messageRepository = messageRepository;
this.notificationManager = notificationManager;
}

public void newIncomingMessage(long accountId, EntityBareJid from, Message message, Chat chat) {
public void handleIncomingMessage(long accountId, EntityBareJid from, Message message, Chat chat) {
if (message.getBody() == null) {
return;
}

org.mercury_im.messenger.persistence.pojo.Chat chatPojo = new org.mercury_im.messenger.persistence.pojo.Chat();
chatPojo.jid = from;
chatPojo.accountId = accountId;
chatPojo.contactName = null;
notificationManager.chatMessageReceived(chatPojo, null, message.getBody());

MessageModel messageModel = messageRepository.newMessageModel();
messageModel.setAccountId(accountId);
messageModel.setFrom(chat.getXmppAddressOfChatPartner());
messageModel.setTo(message.getTo().asEntityBareJidIfPossible());
messageModel.setIncoming(true);
messageModel.setBody(message.getBody());
messageModel.setSendDate(new Date());

disposable.add(
messageRepository.insertMessage(messageModel)
.subscribeOn(Schedulers.io())
.subscribe(messageId ->
LOGGER.log(Level.INFO, "Inserted incoming message " + messageId)));
Completable.fromAction(() -> {
EntityModel entityModel = rosterRepository.getOrCreateEntity(accountId, from)
.blockingGet();
ContactModel contactModel = rosterRepository.getContact(accountId, entityModel.getJid()).blockingFirst().firstOrNull();
ChatModel chatModel = chatRepository.getChatWith(entityModel).blockingFirst().firstOr(() -> {
ChatModel freshChatModel = new ChatModel();
freshChatModel.setPeer(entityModel);
freshChatModel.setDisplayed(true);
return freshChatModel;
});

chatModel = chatRepository.upsert(chatModel).blockingGet();

MessageModel messageModel = setCommonMessageAttributes(message, chatModel);
messageModel.setSender(from);
messageModel.setIncoming(true);

final ChatModel fChatModel = chatModel;
disposable.add(messageRepository.insert(messageModel)
.subscribe(insertedMessageModel -> {
if (message.getBody() != null) {
notificationManager.chatMessageReceived(new ChatAndPossiblyContact(fChatModel, contactModel), message.getBody());
}

LastChatMessageRelation lastMessage = new LastChatMessageRelation();
lastMessage.setChat(fChatModel);
lastMessage.setMessage(insertedMessageModel);
}));
}).subscribeOn(Schedulers.io())
.subscribe();
}

public void newOutgoingMessage(long accountId, EntityBareJid to, Message message, Chat chat) {
MessageModel messageModel = messageRepository.newMessageModel();
messageModel.setAccountId(accountId);
messageModel.setFrom(message.getFrom() != null ? message.getFrom().asEntityBareJidIfPossible() : null);
messageModel.setTo(chat.getXmppAddressOfChatPartner());
messageModel.setIncoming(false);
messageModel.setBody(message.getBody());
messageModel.setSendDate(new Date());

disposable.add(
messageRepository.insertMessage(messageModel)
.subscribeOn(Schedulers.io())
.subscribe(messageId ->
LOGGER.log(Level.INFO, "Inserted outgoing message " + messageId)));
public void handleOutgoingMessage(long accountId, EntityBareJid to, Message message, Chat chat) {
MessageModel model = setCommonMessageAttributes(message, null);
EntityModel entityModel = rosterRepository.getOrCreateEntity(accountId, to).blockingGet();

model.setIncoming(false);
model.setTimestamp(new Date());
model.setSender(entityModel.getAccount().getJid());
model.setRecipient(to);

ChatModel chatModel = chatRepository.getChatWith(entityModel).blockingFirst().firstOr(() -> {
ChatModel freshChatModel = new ChatModel();
freshChatModel.setPeer(entityModel);
freshChatModel.setDisplayed(true);
return freshChatModel;
});

model.setChat(chatModel);

disposable.add(messageRepository.upsert(model)
.subscribe(messageId ->
LOGGER.log(Level.INFO, "Inserted outgoing message " + messageId)));
}

public void onCarbonCopyReceived(long accountId, CarbonExtension.Direction direction, Message carbonCopy, Message wrappingMessage) {
public void handleCarbonCopy(long accountId, CarbonExtension.Direction direction, Message carbonCopy, Message wrappingMessage) {
if (carbonCopy.getBody() == null) {
return;
}
MessageModel messageModel = messageRepository.newMessageModel();
messageModel.setAccountId(accountId);
messageModel.setFrom(carbonCopy.getFrom() != null ? carbonCopy.getFrom().asEntityBareJidIfPossible() : null);
messageModel.setTo(carbonCopy.getTo() != null ? carbonCopy.getTo().asEntityBareJidIfPossible() : null);
MessageModel messageModel = new MessageModel();
messageModel.setSender(carbonCopy.getFrom() != null ? carbonCopy.getFrom().asEntityBareJidIfPossible() : null);
messageModel.setRecipient(carbonCopy.getTo() != null ? carbonCopy.getTo().asEntityBareJidIfPossible() : null);

messageModel.setIncoming(direction == CarbonExtension.Direction.received);

messageModel.setBody(carbonCopy.getBody());
messageModel.setSendDate(new Date());
disposable.add(
messageRepository.insertMessage(messageModel)
.subscribeOn(Schedulers.io())
.subscribe(messageId ->
LOGGER.log(Level.INFO, "Inserted carbon message " + messageId)));
messageModel.setTimestamp(new Date());

disposable.add(messageRepository.upsert(messageModel)
.subscribe(messageId ->
LOGGER.log(Level.INFO, "Inserted carbon message " + messageId)));
}

public void registerForMercuryConnection(MercuryConnection connection) {
@@ -105,13 +136,15 @@ public class PlainMessageStore {

// Add account ID to
chatManager.addIncomingListener((from, message, chat) ->
PlainMessageStore.this.newIncomingMessage(
PlainMessageStore.this.handleIncomingMessage(
connection.getAccountId(), from, message, chat));

chatManager.addOutgoingListener((to, message, chat) ->
PlainMessageStore.this.newOutgoingMessage(
PlainMessageStore.this.handleOutgoingMessage(
connection.getAccountId(), to, message, chat));

carbonManager.addCarbonCopyReceivedListener((direction, carbonCopy, wrappingMessage) ->
PlainMessageStore.this.onCarbonCopyReceived(
PlainMessageStore.this.handleCarbonCopy(
connection.getAccountId(), direction, carbonCopy, wrappingMessage));
}

@@ -119,7 +152,7 @@ public class PlainMessageStore {
disposable.clear();
}

public void onMamResult(long accountId, EntityBareJid peerJid, MamManager.MamQuery query) {
public void handleMamResult(long accountId, EntityBareJid peerJid, MamManager.MamQuery query) {
List<MessageModel> messageModels = new ArrayList<>();
for (Message message : query.getMessages()) {
Date date = new Date();
@@ -128,19 +161,42 @@ public class PlainMessageStore {
date = delay.getStamp();
}

MessageModel messageModel = messageRepository.newMessageModel();
messageModel.setAccountId(accountId);
MessageModel messageModel = new MessageModel();
messageModel.setBody(message.getBody());
messageModel.setFrom(message.getFrom().asEntityBareJidOrThrow());
messageModel.setTo(message.getTo().asEntityBareJidOrThrow());
messageModel.setSender(message.getFrom().asEntityBareJidOrThrow());
messageModel.setRecipient(message.getTo().asEntityBareJidOrThrow());
messageModel.setIncoming(peerJid.equals(message.getFrom().asEntityBareJidOrThrow()));
messageModel.setSendDate(date);
messageModel.setTimestamp(date);
messageModels.add(messageModel);
}

disposable.add(
messageRepository.insertMessages(messageModels)
.subscribeOn(Schedulers.io())
.subscribe());
disposable.add(messageRepository.upsert(messageModels).subscribe());
}

private MessageModel incomingMessageToModel(Message message, ChatModel chat) {
MessageModel model = setCommonMessageAttributes(message, chat);
model.setIncoming(true);
return model;
}

private MessageModel setCommonMessageAttributes(Message message, ChatModel chat) {
MessageModel model = new MessageModel();

model.setBody(message.getBody());
Date timestamp = DelayInformationManager.getDelayTimestamp(message);
model.setTimestamp(timestamp == null ? new Date() : timestamp);
model.setThread(message.getThread());
model.setLegacyId(message.getStanzaId());
model.setChat(chat);
model.setRecipient(message.getTo().asEntityBareJidOrThrow());
model.setSender(message.getFrom() != null ? message.getFrom().asEntityBareJidIfPossible() : null);
OriginIdElement originId = OriginIdElement.getOriginId(message);
model.setOriginId(originId != null ? originId.getId() : null);
StanzaIdElement stanzaId = StanzaIdElement.getStanzaId(message);
model.setStanzaId(stanzaId != null ? stanzaId.getId() : null);
model.setStanzaIdBy(stanzaId != null ? JidCreate.entityBareFromOrThrowUnchecked(stanzaId.getBy()) : null);


return model;
}
}

+ 93
- 75
core/src/main/java/org/mercury_im/messenger/core/stores/RosterStore.java View File

@@ -2,10 +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.model.ContactModel;
import org.mercury_im.messenger.persistence.model.EntityModel;
import org.mercury_im.messenger.persistence.model.RosterInformationModel;
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 java.util.ArrayList;
import java.util.Arrays;
@@ -18,8 +20,9 @@ import java.util.logging.Logger;

import javax.inject.Inject;

import io.reactivex.Scheduler;
import io.reactivex.disposables.CompositeDisposable;
import io.reactivex.functions.Consumer;
import io.reactivex.functions.Action;
import io.reactivex.schedulers.Schedulers;

public class RosterStore implements org.jivesoftware.smack.roster.rosterstore.RosterStore {
@@ -27,16 +30,17 @@ 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 long accountId;
private final AccountRepository accountRepository;
private AccountModel account;
private CompositeDisposable disposable = null;

private final Map<Jid, RosterPacket.Item> itemMap = new HashMap<>();
private String rosterVersion;

@Inject
public RosterStore(RosterRepository rosterRepository) {
public RosterStore(RosterRepository rosterRepository, AccountRepository accountRepository) {
this.rosterRepository = rosterRepository;
this.accountRepository = accountRepository;
}

public void subscribe() {
@@ -46,30 +50,23 @@ public class RosterStore implements org.jivesoftware.smack.roster.rosterstore.Ro
}
disposable = new CompositeDisposable();

disposable.add(rosterRepository.getAllContactsOfAccount(accountId)
.subscribeOn(Schedulers.io())
disposable.add(rosterRepository.getAllContactsOfAccount(account)
.observeOn(Schedulers.computation())
.subscribe((Consumer<List<? extends ContactModel>>) contactsList -> {
itemMap.clear();
for (ContactModel contactModel : contactsList) {
rosterRepository.getEntityForContact(contactModel.getId())
.subscribeOn(Schedulers.io())
.subscribe((Consumer<EntityModel>) entityModel -> {
RosterPacket.Item item = fromModel(contactModel);
itemMap.put(entityModel.getJid(), item);
});
LOGGER.log(Level.INFO, "Populate itemMap with " + contactsList.size() + " items");

}
}));

disposable.add(rosterRepository.getRosterInformationForAccount(accountId)
.subscribeOn(Schedulers.io())
.subscribe(contactsList -> {
itemMap.clear();
for (ContactModel contactModel : contactsList) {
itemMap.put(contactModel.getEntity().getJid(), fromModel(contactModel));
LOGGER.log(Level.INFO, "Populate itemMap with " + contactsList.toList().size() + " items");

}
},
error -> LOGGER.log(Level.WARNING, "An error occurred while updating roster cache", error)));

disposable.add(rosterRepository.getRosterVersion(account)
.observeOn(Schedulers.computation())
.subscribe((Consumer<RosterInformationModel>) s -> {
LOGGER.log(Level.INFO, "Set rosterVer = " + s.getRosterVersion());
rosterVersion = s.getRosterVersion();
}));
.subscribe(
result -> setRosterVersion(result),
error -> LOGGER.log(Level.WARNING, "An error occurred updating cached roster version", error)));
}

public void unsubscribe() {
@@ -81,7 +78,13 @@ public class RosterStore implements org.jivesoftware.smack.roster.rosterstore.Ro
}

public void setAccountId(long accountId) {
this.accountId = accountId;
this.account = accountRepository.getAccount(accountId)
.doOnSubscribe(subscribe -> LOGGER.log(Level.FINE, "Fetching account " + accountId))
.blockingFirst().first();
}

private void setRosterVersion(String rosterVersion) {
this.rosterVersion = rosterVersion;