This commit is contained in:
Paul Schaub 2019-06-10 02:52:08 +02:00
parent d5153f400e
commit fafd8b9f6f
Signed by: vanitasvitae
GPG Key ID: 62BEE9264BF17311
26 changed files with 367 additions and 156 deletions

View File

@ -99,9 +99,12 @@ dependencies {
implementation 'androidx.recyclerview:recyclerview:1.0.0' implementation 'androidx.recyclerview:recyclerview:1.0.0'
testImplementation 'junit:junit:4.12' testImplementation 'junit:junit:4.12'
androidTestImplementation 'androidx.test:runner:1.1.1' androidTestImplementation 'androidx.test:runner:1.2.0'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.1' androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
// circular image viewer for avatars // circular image viewer for avatars
implementation 'de.hdodenhof:circleimageview:2.2.0' implementation 'de.hdodenhof:circleimageview:2.2.0'
// Smack Android
implementation "org.igniterealtime.smack:smack-android-extensions:$smackAndroidExtensionsVersion"
} }

View File

@ -24,11 +24,10 @@
android:theme="@style/AppTheme.NoActionBar"/> android:theme="@style/AppTheme.NoActionBar"/>
<activity <activity
android:name=".ui.MainActivity" android:name=".ui.MainActivity"
android:label="@string/title_activity_main" android:label="@string/app_name"
android:theme="@style/AppTheme.NoActionBar"> android:theme="@style/AppTheme.NoActionBar">
<intent-filter> <intent-filter>
<action android:name="android.intent.action.MAIN" /> <action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" /> <category android:name="android.intent.category.LAUNCHER" />
</intent-filter> </intent-filter>
</activity> </activity>

View File

@ -15,6 +15,7 @@ import android.util.Log;
import android.util.LongSparseArray; import android.util.LongSparseArray;
import android.widget.Toast; import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.core.app.NotificationCompat; import androidx.core.app.NotificationCompat;
import androidx.core.app.NotificationManagerCompat; import androidx.core.app.NotificationManagerCompat;
@ -31,6 +32,7 @@ import org.jivesoftware.smack.roster.RosterEntry;
import org.jivesoftware.smack.roster.RosterLoadedListener; import org.jivesoftware.smack.roster.RosterLoadedListener;
import org.jivesoftware.smack.tcp.XMPPTCPConnection; import org.jivesoftware.smack.tcp.XMPPTCPConnection;
import org.jivesoftware.smack.tcp.XMPPTCPConnectionConfiguration; import org.jivesoftware.smack.tcp.XMPPTCPConnectionConfiguration;
import org.jivesoftware.smackx.ping.android.ServerPingWithAlarmManager;
import org.jxmpp.jid.EntityBareJid; import org.jxmpp.jid.EntityBareJid;
import org.mercury_im.messenger.MercuryImApplication; import org.mercury_im.messenger.MercuryImApplication;
import org.mercury_im.messenger.Notifications; import org.mercury_im.messenger.Notifications;
@ -96,11 +98,10 @@ public class XmppConnectionService extends Service {
private Handler uiHandler; private Handler uiHandler;
@Nullable @NonNull
@Override @Override
public final IBinder onBind(Intent intent) { public final IBinder onBind(Intent intent) {
// We are a started service, so we don't provide a binding and always return null. return new Binder(this);
return null;
} }
@Override @Override
@ -108,6 +109,20 @@ public class XmppConnectionService extends Service {
super.onCreate(); super.onCreate();
Log.d(TAG, "onCreate()"); Log.d(TAG, "onCreate()");
MercuryImApplication.getApplication().getAppComponent().inject(this); MercuryImApplication.getApplication().getAppComponent().inject(this);
// Begin life cycle of Ping Manager.
// The Manager will automatically detect newly created connections and ping the server
// every half hour if necessary.
ServerPingWithAlarmManager.onCreate(this);
}
@Override
public void onDestroy() {
super.onDestroy();
Log.d(TAG, "onDestroy()");
// End life cycle of Ping Manager.
ServerPingWithAlarmManager.onDestroy();
} }
@Override @Override
@ -240,9 +255,10 @@ public class XmppConnectionService extends Service {
Set<RosterEntry> entries = roster.getEntries(); Set<RosterEntry> entries = roster.getEntries();
for (RosterEntry e : entries) { for (RosterEntry e : entries) {
Log.d(TAG, "Inserting Roster entry " + e.getJid().toString()); Log.d(TAG, "Inserting Roster entry " + e.getJid().toString());
RoomContactModel m = new RoomContactModel(e.getJid().asEntityBareJidIfPossible(), e.getName(), e.getName());
m.setAccountId(accountId); RoomContactModel contact = new RoomContactModel();
rosterRepository.updateOrInsertRosterEntry(m); contact.setAccountId(accountId);
// TODO: Fix
} }
} }
@ -281,4 +297,17 @@ public class XmppConnectionService extends Service {
public void putConnection(int accountId, XMPPConnection connection) { public void putConnection(int accountId, XMPPConnection connection) {
connections.put(accountId, connection); connections.put(accountId, connection);
} }
public class Binder extends android.os.Binder {
private final XmppConnectionService service;
public Binder(XmppConnectionService service) {
this.service = service;
}
public XmppConnectionService getService() {
return service;
}
}
} }

View File

@ -1,7 +0,0 @@
package org.mercury_im.messenger.service;
import android.os.Binder;
public class XmppConnectionServiceBinder extends Binder {
}

View File

@ -43,7 +43,7 @@ public class RosterRecyclerViewAdapter
// switch (holder.getItemViewType()) { // switch (holder.getItemViewType()) {
// case RosterItemViewHolder.TYPE: // case RosterItemViewHolder.TYPE:
RosterItemViewHolder rosterItem = (RosterItemViewHolder) holder; RosterItemViewHolder rosterItem = (RosterItemViewHolder) holder;
rosterItem.jidView.setText(model.getJid().toString()); //rosterItem.jidView.setText(model.getJid().toString());
rosterItem.nicknameView.setText(model.getNickname()); rosterItem.nicknameView.setText(model.getNickname());
rosterItem.itemView.setTag(model); rosterItem.itemView.setTag(model);
// break; // break;

View File

@ -1,6 +1,5 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent" android:layout_width="match_parent"
@ -12,25 +11,23 @@
android:layout_height="48dp" android:layout_height="48dp"
android:layout_marginStart="8dp" android:layout_marginStart="8dp"
android:layout_marginTop="8dp" android:layout_marginTop="8dp"
android:layout_marginBottom="8dp"
android:src="@drawable/aldrin" android:src="@drawable/aldrin"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" /> app:layout_constraintTop_toTopOf="parent" />
<androidx.cardview.widget.CardView xmlns:card_view="http://schemas.android.com/apk/res-auto" <androidx.cardview.widget.CardView xmlns:card_view="http://schemas.android.com/apk/res-auto"
android:id="@+id/msg_bubble"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginStart="8dp" android:layout_marginStart="8dp"
android:layout_marginTop="8dp" android:layout_marginTop="8dp"
android:layout_marginBottom="8dp"
android:minHeight="48dp" android:minHeight="48dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toEndOf="@+id/msg_avatar" app:layout_constraintStart_toEndOf="@+id/msg_avatar"
app:layout_constraintTop_toTopOf="parent" app:layout_constraintTop_toTopOf="parent"
card_view:cardCornerRadius="24dp"> card_view:cardCornerRadius="24dp">
<TextView <TextView
android:id="@+id/msg_body"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_margin="8dp" android:layout_margin="8dp"
@ -39,5 +36,4 @@
</androidx.cardview.widget.CardView> </androidx.cardview.widget.CardView>
</androidx.constraintlayout.widget.ConstraintLayout> </androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -1,11 +1,14 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" <androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent">
xmlns:tools="http://schemas.android.com/tools">
<androidx.cardview.widget.CardView xmlns:card_view="http://schemas.android.com/apk/res-auto" <androidx.cardview.widget.CardView
xmlns:card_view="http://schemas.android.com/apk/res-auto"
android:id="@+id/msg_bubble"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginTop="8dp" android:layout_marginTop="8dp"
@ -16,6 +19,7 @@
card_view:layout_constraintTop_toTopOf="parent"> card_view:layout_constraintTop_toTopOf="parent">
<TextView <TextView
android:id="@+id/msg_body"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:maxWidth="300dp" android:maxWidth="300dp"

View File

@ -1,7 +1,11 @@
<resources> <resources>
<string name="app_name">Mercury</string> <string name="app_name">Mercury</string>
<!-- Main Activity -->
<string name="title_activity_main">MainActivity</string>
<!-- Login -->
<string name="title_activity_login">Log in</string> <string name="title_activity_login">Log in</string>
<!-- Strings related to login -->
<string name="prompt_jid">XMPP Address</string> <string name="prompt_jid">XMPP Address</string>
<string name="prompt_password">Password</string> <string name="prompt_password">Password</string>
<string name="action_sign_in">Sign in</string> <string name="action_sign_in">Sign in</string>
@ -10,11 +14,21 @@
<string name="error_invalid_password">This password is too short</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_incorrect_password">This password is incorrect</string>
<string name="error_field_required">This field is required</string> <string name="error_field_required">This field is required</string>
<string name="password_show_password">Show Password</string>
<!-- Settings -->
<string name="title_activity_settings">Settings</string> <string name="title_activity_settings">Settings</string>
<string name="action_settings">Settings</string>
<!-- General -->
<string name="not_yet_implemented">Not yet implemented!</string> <string name="not_yet_implemented">Not yet implemented!</string>
<!-- Strings related to Settings --> <!-- Notifications -->
<string name="channel_name_foreground">Foreground Service</string>
<string name="channel_description_foreground">Service that keeps the app running</string>
<!-- TODO: DELETE BELOW STUFF -->
<!-- Example General settings --> <!-- Example General settings -->
<string name="pref_header_general">General</string> <string name="pref_header_general">General</string>
@ -84,9 +98,5 @@
<string name="pref_ringtone_silent">Silent</string> <string name="pref_ringtone_silent">Silent</string>
<string name="pref_title_vibrate">Vibrate</string> <string name="pref_title_vibrate">Vibrate</string>
<string name="title_activity_main">MainActivity</string>
<string name="action_settings">Settings</string>
<string name="channel_name_foreground">Foreground Service</string>
<string name="channel_description_foreground">Service that keeps the app running</string>
<string name="password_show_password">Show Password</string>
</resources> </resources>

View File

@ -7,7 +7,7 @@ buildscript {
jcenter() jcenter()
} }
dependencies { dependencies {
classpath 'com.android.tools.build:gradle:3.4.0' classpath 'com.android.tools.build:gradle:3.4.1'
// NOTE: Do not place your application dependencies here; they belong // NOTE: Do not place your application dependencies here; they belong

View File

@ -0,0 +1,31 @@
# Room Persistence Layer of Mercury
This Android Module contains an implementation of the interfaces of the `persistence` module.
In particular it defines SQL schemes and provides DAOs as well as implementations of Model classes
and Repositories that utilize the [Room Database Framework](https://developer.android.com/topic/libraries/architecture/room).
## Packages and Classes
### `AppDatabase`
defines what makes up the database itself. It lists all available entities and provides dao classes.
### `dao` package
Contains data access objects (DAOs) which provide CRUD methods (Create, Read, Update, Delete) for
all the models.
Note, that the `BaseDao` interface already defines methods for creating, updating and deleting
entities and is extended by most DAO classes.
### `model` package
Contains classes that represent the data itself in form of models.
### `repository` package
Contains implementations of data repositories. Repositories are single sources of truth and provide
a user-friendly separation layer between the application and the DAOs.
Ideally this layer would also provide access to the XMPP domain, but this is still subject to
discussion.
### `type_converter` package
Contains Room specific type converter that convert non-basic data types into basic data types which
can be handled by the database and vice versa.

View File

@ -2,12 +2,18 @@
"formatVersion": 1, "formatVersion": 1,
"database": { "database": {
"version": 1, "version": 1,
"identityHash": "8cbb04481870b8e1320677566696f9ad", "identityHash": "fb56e6a5615c4d1baa6c08d919560267",
"entities": [ "entities": [
{ {
"tableName": "RoomContactModel", "tableName": "contacts",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`accountId` INTEGER NOT NULL, `jid` TEXT NOT NULL, `rosterName` TEXT, `nickname` TEXT, `avatarFile` TEXT, PRIMARY KEY(`accountId`, `jid`), FOREIGN KEY(`accountId`) REFERENCES `accounts`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE , FOREIGN KEY(`accountId`, `jid`) REFERENCES `entities`(`accountId`, `jid`) ON UPDATE NO ACTION ON DELETE RESTRICT )", "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `accountId` INTEGER NOT NULL, `xmppId` INTEGER NOT NULL, `rosterName` TEXT, `nickname` TEXT, PRIMARY KEY(`id`), FOREIGN KEY(`accountId`) REFERENCES `accounts`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE , FOREIGN KEY(`xmppId`) REFERENCES `entities`(`id`) ON UPDATE NO ACTION ON DELETE RESTRICT )",
"fields": [ "fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "INTEGER",
"notNull": true
},
{ {
"fieldPath": "accountId", "fieldPath": "accountId",
"columnName": "accountId", "columnName": "accountId",
@ -15,9 +21,9 @@
"notNull": true "notNull": true
}, },
{ {
"fieldPath": "jid", "fieldPath": "xmppIdentityId",
"columnName": "jid", "columnName": "xmppId",
"affinity": "TEXT", "affinity": "INTEGER",
"notNull": true "notNull": true
}, },
{ {
@ -31,30 +37,38 @@
"columnName": "nickname", "columnName": "nickname",
"affinity": "TEXT", "affinity": "TEXT",
"notNull": false "notNull": false
},
{
"fieldPath": "avatarFile",
"columnName": "avatarFile",
"affinity": "TEXT",
"notNull": false
} }
], ],
"primaryKey": { "primaryKey": {
"columnNames": [ "columnNames": [
"accountId", "id"
"jid"
], ],
"autoGenerate": false "autoGenerate": false
}, },
"indices": [ "indices": [
{ {
"name": "index_RoomContactModel_accountId_jid", "name": "index_contacts_id",
"unique": true, "unique": false,
"columnNames": [ "columnNames": [
"accountId", "id"
"jid"
], ],
"createSql": "CREATE UNIQUE INDEX `index_RoomContactModel_accountId_jid` ON `${TABLE_NAME}` (`accountId`, `jid`)" "createSql": "CREATE INDEX `index_contacts_id` ON `${TABLE_NAME}` (`id`)"
},
{
"name": "index_contacts_accountId",
"unique": false,
"columnNames": [
"accountId"
],
"createSql": "CREATE INDEX `index_contacts_accountId` ON `${TABLE_NAME}` (`accountId`)"
},
{
"name": "index_contacts_xmppId",
"unique": false,
"columnNames": [
"xmppId"
],
"createSql": "CREATE INDEX `index_contacts_xmppId` ON `${TABLE_NAME}` (`xmppId`)"
} }
], ],
"foreignKeys": [ "foreignKeys": [
@ -74,12 +88,10 @@
"onDelete": "RESTRICT", "onDelete": "RESTRICT",
"onUpdate": "NO ACTION", "onUpdate": "NO ACTION",
"columns": [ "columns": [
"accountId", "xmppId"
"jid"
], ],
"referencedColumns": [ "referencedColumns": [
"accountId", "id"
"jid"
] ]
} }
] ]
@ -186,6 +198,14 @@
"id" "id"
], ],
"createSql": "CREATE INDEX `index_messages_id` ON `${TABLE_NAME}` (`id`)" "createSql": "CREATE INDEX `index_messages_id` ON `${TABLE_NAME}` (`id`)"
},
{
"name": "index_messages_accountId",
"unique": false,
"columnNames": [
"accountId"
],
"createSql": "CREATE INDEX `index_messages_accountId` ON `${TABLE_NAME}` (`accountId`)"
} }
], ],
"foreignKeys": [ "foreignKeys": [
@ -204,8 +224,14 @@
}, },
{ {
"tableName": "entities", "tableName": "entities",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`accountId` INTEGER NOT NULL, `jid` TEXT NOT NULL, `avatarFile` TEXT, PRIMARY KEY(`accountId`, `jid`), FOREIGN KEY(`accountId`) REFERENCES `accounts`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )", "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `accountId` INTEGER NOT NULL, `jid` TEXT NOT NULL, `avatar` TEXT, PRIMARY KEY(`id`), FOREIGN KEY(`accountId`) REFERENCES `accounts`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )",
"fields": [ "fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "INTEGER",
"notNull": true
},
{ {
"fieldPath": "accountId", "fieldPath": "accountId",
"columnName": "accountId", "columnName": "accountId",
@ -220,19 +246,26 @@
}, },
{ {
"fieldPath": "avatarFile", "fieldPath": "avatarFile",
"columnName": "avatarFile", "columnName": "avatar",
"affinity": "TEXT", "affinity": "TEXT",
"notNull": false "notNull": false
} }
], ],
"primaryKey": { "primaryKey": {
"columnNames": [ "columnNames": [
"accountId", "id"
"jid"
], ],
"autoGenerate": false "autoGenerate": false
}, },
"indices": [ "indices": [
{
"name": "index_entities_id",
"unique": false,
"columnNames": [
"id"
],
"createSql": "CREATE INDEX `index_entities_id` ON `${TABLE_NAME}` (`id`)"
},
{ {
"name": "index_entities_accountId_jid", "name": "index_entities_accountId_jid",
"unique": true, "unique": true,
@ -261,7 +294,7 @@
"views": [], "views": [],
"setupQueries": [ "setupQueries": [
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)", "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, \"8cbb04481870b8e1320677566696f9ad\")" "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, 'fb56e6a5615c4d1baa6c08d919560267')"
] ]
} }
} }

View File

@ -10,8 +10,14 @@ public interface BaseDao<T> {
long insert(T entity); long insert(T entity);
@Update @Update
void upddate(T... entity); void update(T entity);
@Update
void update(T[] entities);
@Delete @Delete
void delete(T... entity); void delete(T entity);
@Delete
void delete(T[] entities);
} }

View File

@ -4,17 +4,25 @@ import androidx.lifecycle.LiveData;
import androidx.room.Query; import androidx.room.Query;
import org.jxmpp.jid.EntityBareJid; import org.jxmpp.jid.EntityBareJid;
import org.mercury_im.messenger.persistence.model.XmppIdentityModel;
import org.mercury_im.messenger.persistence.room.model.RoomChatModel; import org.mercury_im.messenger.persistence.room.model.RoomChatModel;
import java.util.List; import java.util.List;
public interface ChatDao extends BaseDao<RoomChatModel> { public interface ChatDao extends BaseDao<RoomChatModel> {
@Query("SELECT * FROM RoomChatModel") @Query("SELECT * FROM chats")
LiveData<List<RoomChatModel>> getAllChats(); LiveData<List<RoomChatModel>> getAllChats();
@Query("SELECT * FROM RoomChatModel WHERE ") @Query("SELECT chats.* FROM chats JOIN entities WHERE accountId = :accountId")
LiveData<RoomChatModel> getChatWith(EntityBareJid contact); LiveData<List<RoomChatModel>> getAllChatsOf(long accountId);
@Query("SELECT * FROM chats WHERE xmppId = :identityId")
LiveData<RoomChatModel> getChatWithIdentity(long identityId);
@Query("SELECT chats.* FROM chats JOIN entities WHERE accountId = :accountId AND jid = :jid")
LiveData<RoomChatModel> getChatWithJid(long accountId, EntityBareJid jid);
@Query("SELECT * FROM chats JOIN contacts WHERE contacts.id = :contactId")
LiveData<RoomChatModel> getChatWithContact(long contactId);
} }

View File

@ -18,14 +18,15 @@ public interface ContactDao extends BaseDao<RoomContactModel> {
/** /**
* Return a {@link LiveData} wrapping a {@link List} of all {@link RoomContactModel RosterEntries} * Return a {@link LiveData} wrapping a {@link List} of all {@link RoomContactModel RosterEntries}
* which are currently found in the database. * which are currently found in the database.
*
* @return * @return
*/ */
@Query("select * from RoomContactModel") @Query("SELECT * FROM contacts")
LiveData<List<RoomContactModel>> getAllRosterEntries(); LiveData<List<RoomContactModel>> getAllRosterEntries();
@Query("select * from RoomContactModel where jid = :jid") @Query("SELECT contacts.* FROM contacts JOIN entities WHERE jid = :jid")
RoomContactModel getRosterEntryByJid(EntityBareJid jid); RoomContactModel getRosterEntryByJid(EntityBareJid jid);
@Query("select * from RoomContactModel where accountId = :accountId") @Query("SELECT * FROM contacts WHERE accountId = :accountId")
LiveData<List<RoomContactModel>> getRosterEntriesForAccount(long accountId); LiveData<List<RoomContactModel>> getRosterEntriesForAccount(long accountId);
} }

View File

@ -1,67 +1,61 @@
package org.mercury_im.messenger.persistence.room.model; package org.mercury_im.messenger.persistence.room.model;
import androidx.annotation.NonNull; import androidx.room.ColumnInfo;
import androidx.room.Entity; import androidx.room.Entity;
import androidx.room.ForeignKey; import androidx.room.ForeignKey;
import androidx.room.Index; import androidx.room.Index;
import androidx.room.TypeConverters; import androidx.room.PrimaryKey;
import org.jxmpp.jid.EntityBareJid;
import org.mercury_im.messenger.persistence.model.ContactModel; import org.mercury_im.messenger.persistence.model.ContactModel;
import org.mercury_im.messenger.persistence.room.type_converter.EntityBareJidConverter;
import org.mercury_im.messenger.persistence.room.type_converter.FileConverter;
import java.io.File;
import static androidx.room.ForeignKey.CASCADE; import static androidx.room.ForeignKey.CASCADE;
import static androidx.room.ForeignKey.RESTRICT; import static androidx.room.ForeignKey.RESTRICT;
import static org.mercury_im.messenger.persistence.room.model.RoomContactModel.KEY_ACCOUNT_ID;
import static org.mercury_im.messenger.persistence.room.model.RoomContactModel.KEY_ID;
import static org.mercury_im.messenger.persistence.room.model.RoomContactModel.KEY_XMPP_ID;
import static org.mercury_im.messenger.persistence.room.model.RoomContactModel.TABLE;
@Entity(primaryKeys = {"accountId", "jid"}, @Entity(tableName = TABLE,
indices = {@Index(value = {"accountId", "jid"}, unique = true)}, indices = {
@Index(value = KEY_ID),
@Index(value = KEY_ACCOUNT_ID),
@Index(value = KEY_XMPP_ID)
},
foreignKeys = { foreignKeys = {
@ForeignKey(entity = RoomAccountModel.class, @ForeignKey(entity = RoomAccountModel.class,
parentColumns = "id", parentColumns = RoomAccountModel.KEY_ID,
childColumns = "accountId", childColumns = KEY_ACCOUNT_ID,
onDelete = CASCADE), onDelete = CASCADE),
@ForeignKey(entity = RoomXmppIdentityModel.class, @ForeignKey(entity = RoomXmppIdentityModel.class,
parentColumns = {"accountId", "jid"}, parentColumns = RoomXmppIdentityModel.KEY_ID,
childColumns = {"accountId", "jid"}, childColumns = KEY_XMPP_ID,
onDelete = RESTRICT)}) onDelete = RESTRICT)})
public class RoomContactModel implements ContactModel { public class RoomContactModel implements ContactModel {
public static final String TABLE = "contacts";
public static final String KEY_ID = "id";
public static final String KEY_ACCOUNT_ID = "accountId";
public static final String KEY_XMPP_ID = "xmppId";
public static final String KEY_ROSTER_NAME = "rosterName";
public static final String KEY_NICKNAME = "nickname";
@PrimaryKey
@ColumnInfo(name = KEY_ID)
private long id;
@ColumnInfo(name = KEY_ACCOUNT_ID)
private long accountId; private long accountId;
@NonNull @ColumnInfo(name = KEY_XMPP_ID)
@TypeConverters(EntityBareJidConverter.class) private long xmppIdentityId;
private EntityBareJid jid;
@ColumnInfo(name = KEY_ROSTER_NAME)
private String rosterName; private String rosterName;
@ColumnInfo(name = KEY_NICKNAME)
private String nickname; private String nickname;
@TypeConverters(FileConverter.class)
private File avatarFile;
public RoomContactModel(@NonNull EntityBareJid jid,
String rosterName,
String nickname) {
this.jid = jid;
this.nickname = nickname;
this.rosterName = rosterName;
}
@NonNull
@Override
public EntityBareJid getJid() {
return jid;
}
@Override
public void setJid(EntityBareJid jid) {
}
@Override @Override
public String getRosterName() { public String getRosterName() {
return rosterName; return rosterName;
@ -82,6 +76,16 @@ public class RoomContactModel implements ContactModel {
this.nickname = nickname; this.nickname = nickname;
} }
@Override
public long getId() {
return id;
}
@Override
public void setId(long id) {
this.id = id;
}
@Override @Override
public long getAccountId() { public long getAccountId() {
return accountId; return accountId;
@ -92,13 +96,13 @@ public class RoomContactModel implements ContactModel {
this.accountId = accountId; this.accountId = accountId;
} }
public File getAvatarFile() { @Override
return avatarFile; public long getXmppIdentityId() {
return xmppIdentityId;
} }
@Override @Override
public void setAvatarFile(File file) { public void setXmppIdentityId(long id) {
this.avatarFile = file; this.xmppIdentityId = id;
} }
} }

View File

@ -1,5 +1,6 @@
package org.mercury_im.messenger.persistence.room.model; package org.mercury_im.messenger.persistence.room.model;
import androidx.room.ColumnInfo;
import androidx.room.Entity; import androidx.room.Entity;
import androidx.room.ForeignKey; import androidx.room.ForeignKey;
import androidx.room.Index; import androidx.room.Index;
@ -14,26 +15,50 @@ import org.mercury_im.messenger.persistence.room.type_converter.EntityBareJidCon
import java.util.Date; import java.util.Date;
import static androidx.room.ForeignKey.CASCADE; import static androidx.room.ForeignKey.CASCADE;
import static org.mercury_im.messenger.persistence.room.model.RoomMessageModel.KEY_ACCOUNT_ID;
import static org.mercury_im.messenger.persistence.room.model.RoomMessageModel.KEY_ID;
import static org.mercury_im.messenger.persistence.room.model.RoomMessageModel.TABLE;
@Entity(tableName = "messages", foreignKeys = @ForeignKey(entity = RoomAccountModel.class, @Entity(tableName = TABLE,
parentColumns = "id", childColumns = "accountId", onDelete = CASCADE), foreignKeys = {
indices = {@Index("id")}) @ForeignKey(entity = RoomAccountModel.class,
parentColumns = RoomAccountModel.KEY_ID,
childColumns = KEY_ACCOUNT_ID,
onDelete = CASCADE)},
indices = {
@Index(KEY_ID),
@Index(KEY_ACCOUNT_ID)
})
public class RoomMessageModel implements MessageModel { public class RoomMessageModel implements MessageModel {
public static final String TABLE = "messages";
public static final String KEY_ID = "id";
public static final String KEY_ACCOUNT_ID = "accountId";
public static final String KEY_BODY = "body";
public static final String KEY_SEND_DATE = "sendDate";
public static final String KEY_FROM = "from";
public static final String KEY_TO = "to";
@PrimaryKey(autoGenerate = true) @PrimaryKey(autoGenerate = true)
@ColumnInfo(name = KEY_ID)
private long id; private long id;
@ColumnInfo(name = KEY_ACCOUNT_ID)
private long accountId; private long accountId;
@ColumnInfo(name = KEY_BODY)
private String body; private String body;
@TypeConverters(DateConverter.class) @TypeConverters(DateConverter.class)
@ColumnInfo(name = KEY_SEND_DATE)
private Date sendDate; private Date sendDate;
@TypeConverters(EntityBareJidConverter.class) @TypeConverters(EntityBareJidConverter.class)
@ColumnInfo(name = KEY_FROM)
private EntityBareJid from; private EntityBareJid from;
@TypeConverters(EntityBareJidConverter.class) @TypeConverters(EntityBareJidConverter.class)
@ColumnInfo(name = KEY_TO)
private EntityBareJid to; private EntityBareJid to;
public RoomMessageModel() { public RoomMessageModel() {

View File

@ -1,9 +1,11 @@
package org.mercury_im.messenger.persistence.room.model; package org.mercury_im.messenger.persistence.room.model;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.room.ColumnInfo;
import androidx.room.Entity; import androidx.room.Entity;
import androidx.room.ForeignKey; import androidx.room.ForeignKey;
import androidx.room.Index; import androidx.room.Index;
import androidx.room.PrimaryKey;
import androidx.room.TypeConverters; import androidx.room.TypeConverters;
import org.jxmpp.jid.EntityBareJid; import org.jxmpp.jid.EntityBareJid;
@ -14,38 +16,54 @@ import org.mercury_im.messenger.persistence.room.type_converter.FileConverter;
import java.io.File; import java.io.File;
import static androidx.room.ForeignKey.CASCADE; import static androidx.room.ForeignKey.CASCADE;
import static org.mercury_im.messenger.persistence.room.model.RoomXmppIdentityModel.KEY_ACCOUNT_ID;
import static org.mercury_im.messenger.persistence.room.model.RoomXmppIdentityModel.KEY_ID;
import static org.mercury_im.messenger.persistence.room.model.RoomXmppIdentityModel.KEY_JID;
import static org.mercury_im.messenger.persistence.room.model.RoomXmppIdentityModel.TABLE; import static org.mercury_im.messenger.persistence.room.model.RoomXmppIdentityModel.TABLE;
@Entity(tableName = TABLE, primaryKeys = {"accountId", "jid"}, @Entity(tableName = TABLE,
indices = {@Index(value = {"accountId", "jid"}, unique = true)}, indices = {
foreignKeys = @ForeignKey(entity = RoomAccountModel.class, @Index(value = KEY_ID),
parentColumns = "id", @Index(value = {KEY_ACCOUNT_ID, KEY_JID}, unique = true)
childColumns = "accountId", },
onDelete = CASCADE)) foreignKeys = {
@ForeignKey(entity = RoomAccountModel.class,
parentColumns = RoomAccountModel.KEY_ID,
childColumns = KEY_ACCOUNT_ID,
onDelete = CASCADE)
})
public class RoomXmppIdentityModel implements XmppIdentityModel { public class RoomXmppIdentityModel implements XmppIdentityModel {
public static final String TABLE = "entities"; public static final String TABLE = "entities";
public static final String KEY_ID = "id";
public static final String KEY_ACCOUNT_ID = "accountId"; public static final String KEY_ACCOUNT_ID = "accountId";
public static final String KEY_JID = "jid"; public static final String KEY_JID = "jid";
public static final String KEY_AVATAR = "avatar"; public static final String KEY_AVATAR = "avatar";
@PrimaryKey
@ColumnInfo(name = KEY_ID)
protected long id;
@ColumnInfo(name = KEY_ACCOUNT_ID)
protected long accountId; protected long accountId;
@NonNull @NonNull
@TypeConverters(EntityBareJidConverter.class) @TypeConverters(EntityBareJidConverter.class)
@ColumnInfo(name = KEY_JID)
protected EntityBareJid jid; protected EntityBareJid jid;
@TypeConverters(FileConverter.class) @TypeConverters(FileConverter.class)
@ColumnInfo(name = KEY_AVATAR)
protected File avatarFile; protected File avatarFile;
@Override @Override
public long getId() { public long getId() {
return 0; return id;
} }
@Override @Override
public void setId(long id) { public void setId(long id) {
this.id = id;
} }
@NonNull @NonNull
@ -55,7 +73,7 @@ public class RoomXmppIdentityModel implements XmppIdentityModel {
} }
@Override @Override
public void setJid(EntityBareJid jid) { public void setJid(@NonNull EntityBareJid jid) {
this.jid = jid; this.jid = jid;
} }

View File

@ -1,27 +1,57 @@
package org.mercury_im.messenger.persistence.room.repository; package org.mercury_im.messenger.persistence.room.repository;
import androidx.lifecycle.LiveData; import androidx.lifecycle.LiveData;
import androidx.room.Index;
import org.jxmpp.jid.EntityBareJid;
import org.mercury_im.messenger.persistence.model.AccountModel;
import org.mercury_im.messenger.persistence.model.ChatModel; import org.mercury_im.messenger.persistence.model.ChatModel;
import org.mercury_im.messenger.persistence.model.ContactModel;
import org.mercury_im.messenger.persistence.model.XmppIdentityModel; import org.mercury_im.messenger.persistence.model.XmppIdentityModel;
import org.mercury_im.messenger.persistence.repository.ChatRepository; import org.mercury_im.messenger.persistence.repository.ChatRepository;
import org.mercury_im.messenger.persistence.room.dao.ChatDao;
import org.mercury_im.messenger.persistence.room.model.RoomChatModel;
import java.util.List; import java.util.List;
public class IChatRepository implements ChatRepository { import javax.inject.Inject;
@Override public class IChatRepository implements ChatRepository<RoomChatModel> {
public LiveData<List<ChatModel>> getAllChats() {
return null; private final ChatDao chatDao;
@Inject
public IChatRepository(ChatDao dao) {
this.chatDao = dao;
} }
@Override @Override
public void getChatWith(XmppIdentityModel identity) { public LiveData<List<RoomChatModel>> getAllChats() {
return chatDao.getAllChats();
} }
@Override @Override
public void closeChat(ChatModel chat) { public LiveData<List<RoomChatModel>> getAllChatsOf(AccountModel accountModel) {
return chatDao.getAllChatsOf(accountModel.getId());
}
@Override
public LiveData<RoomChatModel> getChatWith(XmppIdentityModel identity) {
return chatDao.getChatWithIdentity(identity.getId());
}
@Override
public LiveData<RoomChatModel> getChatWith(AccountModel account, EntityBareJid jid) {
return chatDao.getChatWithJid(account.getId(), jid);
}
@Override
public LiveData<RoomChatModel> getChatWith(ContactModel contact) {
return chatDao.getChatWithContact(contact.getId());
}
@Override
public void closeChat(RoomChatModel chat) {
chatDao.delete(chat);
} }
} }

19
persistence/README.md Normal file
View File

@ -0,0 +1,19 @@
# Abstract Persistence Layer of Mercury
This Android module defines interfaces for a persistence backend.
Ideally this module would at some point be a plain java (non-Android) module to allow for
non-Android implementations. This is currently being blocked by [LiveData](https://developer.android.com/topic/libraries/architecture/livedata)
being an Android library. We could fix this by replacing LiveData with RxJava, but this would
require us to manually handle LifeCycles of ViewModels etc. so for now we stick with LiveData.
## Packages
### `model` package
Contains interfaces that define the structure of data classes.
### `repository` package
Repositories build a user-friendly interface to query and modify data in the backend.
## Implementations
Currently the module is only being implemented by the `persistence-room` Android module.

View File

@ -1,7 +1,5 @@
package org.mercury_im.messenger.persistence.model; package org.mercury_im.messenger.persistence.model;
import org.jxmpp.jid.EntityBareJid;
public interface ChatModel { public interface ChatModel {
long getId(); long getId();

View File

@ -1,9 +1,5 @@
package org.mercury_im.messenger.persistence.model; package org.mercury_im.messenger.persistence.model;
import org.jxmpp.jid.EntityBareJid;
import java.io.File;
public interface ContactModel { public interface ContactModel {
long getId(); long getId();
@ -14,9 +10,9 @@ public interface ContactModel {
void setAccountId(long id); void setAccountId(long id);
EntityBareJid getJid(); long getXmppIdentityId();
void setJid(EntityBareJid jid); void setXmppIdentityId(long id);
String getRosterName(); String getRosterName();
@ -25,8 +21,4 @@ public interface ContactModel {
String getNickname(); String getNickname();
void setNickname(String nickname); void setNickname(String nickname);
File getAvatarFile();
void setAvatarFile(File file);
} }

View File

@ -2,17 +2,26 @@ package org.mercury_im.messenger.persistence.repository;
import androidx.lifecycle.LiveData; import androidx.lifecycle.LiveData;
import org.jxmpp.jid.EntityBareJid;
import org.mercury_im.messenger.persistence.model.AccountModel;
import org.mercury_im.messenger.persistence.model.ChatModel; import org.mercury_im.messenger.persistence.model.ChatModel;
import org.mercury_im.messenger.persistence.model.ContactModel; import org.mercury_im.messenger.persistence.model.ContactModel;
import org.mercury_im.messenger.persistence.model.XmppIdentityModel; import org.mercury_im.messenger.persistence.model.XmppIdentityModel;
import org.w3c.dom.Entity;
import java.util.List; import java.util.List;
public interface ChatRepository { public interface ChatRepository<E extends ChatModel> {
LiveData<List<ChatModel>> getAllChats(); LiveData<List<E>> getAllChats();
LiveData<ChatModel> getChatWith(XmppIdentityModel identity); LiveData<List<E>> getAllChatsOf(AccountModel account);
void closeChat(ChatModel chat); LiveData<E> getChatWith(AccountModel account, EntityBareJid jid);
LiveData<E> getChatWith(XmppIdentityModel identity);
LiveData<E> getChatWith(ContactModel contact);
void closeChat(E chat);
} }

View File

@ -20,6 +20,8 @@ ext {
/*/ /*/
// Version strings for unique versions // Version strings for unique versions
// https://github.com/igniterealtime/Smack/wiki/How-to-use-Smack-snapshots
smackAndroidVersion = "4.4.0-alpha2-20190324.010646-51" smackAndroidVersion = "4.4.0-alpha2-20190324.010646-51"
smackAndroidExtensionsVersion = "4.4.0-alpha2-20190324.010646-51" smackAndroidExtensionsVersion = "4.4.0-alpha2-20190324.010646-51"
smackCoreVersion = "4.4.0-alpha2-20190324.010647-51" smackCoreVersion = "4.4.0-alpha2-20190324.010647-51"

View File

@ -1,6 +1,7 @@
package org.mercury_im.messenger.xmpp_android; package org.mercury_im.messenger.xmpp_android;
import org.jivesoftware.smack.XMPPConnection; import org.jivesoftware.smack.XMPPConnection;
import org.jivesoftware.smackx.ping.android.ServerPingWithAlarmManager;
import org.mercury_im.messenger.xmpp_core.MercuryConnection; import org.mercury_im.messenger.xmpp_core.MercuryConnection;
public class AndroidMercuryConnection extends MercuryConnection { public class AndroidMercuryConnection extends MercuryConnection {

View File

@ -11,8 +11,8 @@ dependencies {
api "org.igniterealtime.smack:smack-omemo:$smackOmemoVersion" api "org.igniterealtime.smack:smack-omemo:$smackOmemoVersion"
api "org.igniterealtime.smack:smack-omemo-signal:$smackOmemoSignalVersion" api "org.igniterealtime.smack:smack-omemo-signal:$smackOmemoSignalVersion"
api "org.igniterealtime.smack:smack-openpgp:$smackOpenPGPVersion" api "org.igniterealtime.smack:smack-openpgp:$smackOpenPGPVersion"
api "org.igniterealtime.smack:smack-resolver-minidns:$smackResolverMiniDnsVersion" // api "org.igniterealtime.smack:smack-resolver-minidns:$smackResolverMiniDnsVersion"
api "org.igniterealtime.smack:smack-tcp:$smackTcpVersion" // api "org.igniterealtime.smack:smack-tcp:$smackTcpVersion"
// Exclude XmlPullParser from Smack dependencies, as its now provided by Android // Exclude XmlPullParser from Smack dependencies, as its now provided by Android
// https://stackoverflow.com/questions/48488563/gradle-xpp3-error/48746294#48746294 // https://stackoverflow.com/questions/48488563/gradle-xpp3-error/48746294#48746294

View File

@ -5,9 +5,9 @@ import org.jivesoftware.smack.roster.Roster;
public class MercuryConnection { public class MercuryConnection {
private final XMPPConnection connection; protected final XMPPConnection connection;
private final long accountId; protected final long accountId;
private final Roster roster; protected final Roster roster;
public MercuryConnection(XMPPConnection connection, long accountId) { public MercuryConnection(XMPPConnection connection, long accountId) {
this.connection = connection; this.connection = connection;