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'
testImplementation 'junit:junit:4.12'
androidTestImplementation 'androidx.test:runner:1.1.1'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.1'
androidTestImplementation 'androidx.test:runner:1.2.0'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
// circular image viewer for avatars
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"/>
<activity
android:name=".ui.MainActivity"
android:label="@string/title_activity_main"
android:label="@string/app_name"
android:theme="@style/AppTheme.NoActionBar">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>

View File

@ -15,6 +15,7 @@ import android.util.Log;
import android.util.LongSparseArray;
import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.core.app.NotificationCompat;
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.tcp.XMPPTCPConnection;
import org.jivesoftware.smack.tcp.XMPPTCPConnectionConfiguration;
import org.jivesoftware.smackx.ping.android.ServerPingWithAlarmManager;
import org.jxmpp.jid.EntityBareJid;
import org.mercury_im.messenger.MercuryImApplication;
import org.mercury_im.messenger.Notifications;
@ -96,11 +98,10 @@ public class XmppConnectionService extends Service {
private Handler uiHandler;
@Nullable
@NonNull
@Override
public final IBinder onBind(Intent intent) {
// We are a started service, so we don't provide a binding and always return null.
return null;
return new Binder(this);
}
@Override
@ -108,6 +109,20 @@ public class XmppConnectionService extends Service {
super.onCreate();
Log.d(TAG, "onCreate()");
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
@ -240,9 +255,10 @@ public class XmppConnectionService extends Service {
Set<RosterEntry> entries = roster.getEntries();
for (RosterEntry e : entries) {
Log.d(TAG, "Inserting Roster entry " + e.getJid().toString());
RoomContactModel m = new RoomContactModel(e.getJid().asEntityBareJidIfPossible(), e.getName(), e.getName());
m.setAccountId(accountId);
rosterRepository.updateOrInsertRosterEntry(m);
RoomContactModel contact = new RoomContactModel();
contact.setAccountId(accountId);
// TODO: Fix
}
}
@ -281,4 +297,17 @@ public class XmppConnectionService extends Service {
public void putConnection(int accountId, XMPPConnection 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()) {
// case RosterItemViewHolder.TYPE:
RosterItemViewHolder rosterItem = (RosterItemViewHolder) holder;
rosterItem.jidView.setText(model.getJid().toString());
//rosterItem.jidView.setText(model.getJid().toString());
rosterItem.nicknameView.setText(model.getNickname());
rosterItem.itemView.setTag(model);
// break;

View File

@ -1,6 +1,5 @@
<?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:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
@ -12,25 +11,23 @@
android:layout_height="48dp"
android:layout_marginStart="8dp"
android:layout_marginTop="8dp"
android:layout_marginBottom="8dp"
android:src="@drawable/aldrin"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<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_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginTop="8dp"
android:layout_marginBottom="8dp"
android:minHeight="48dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toEndOf="@+id/msg_avatar"
app:layout_constraintTop_toTopOf="parent"
card_view:cardCornerRadius="24dp">
<TextView
android:id="@+id/msg_body"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="8dp"
@ -39,5 +36,4 @@
</androidx.cardview.widget.CardView>
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -1,11 +1,14 @@
<?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:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
xmlns:tools="http://schemas.android.com/tools">
android:layout_height="match_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_height="wrap_content"
android:layout_marginTop="8dp"
@ -16,6 +19,7 @@
card_view:layout_constraintTop_toTopOf="parent">
<TextView
android:id="@+id/msg_body"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:maxWidth="300dp"

View File

@ -1,7 +1,11 @@
<resources>
<string name="app_name">Mercury</string>
<!-- Main Activity -->
<string name="title_activity_main">MainActivity</string>
<!-- Login -->
<string name="title_activity_login">Log in</string>
<!-- Strings related to login -->
<string name="prompt_jid">XMPP Address</string>
<string name="prompt_password">Password</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_incorrect_password">This password is incorrect</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="action_settings">Settings</string>
<!-- General -->
<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 -->
<string name="pref_header_general">General</string>
@ -84,9 +98,5 @@
<string name="pref_ringtone_silent">Silent</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>

View File

@ -7,7 +7,7 @@ buildscript {
jcenter()
}
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

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,
"database": {
"version": 1,
"identityHash": "8cbb04481870b8e1320677566696f9ad",
"identityHash": "fb56e6a5615c4d1baa6c08d919560267",
"entities": [
{
"tableName": "RoomContactModel",
"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 )",
"tableName": "contacts",
"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": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "accountId",
"columnName": "accountId",
@ -15,9 +21,9 @@
"notNull": true
},
{
"fieldPath": "jid",
"columnName": "jid",
"affinity": "TEXT",
"fieldPath": "xmppIdentityId",
"columnName": "xmppId",
"affinity": "INTEGER",
"notNull": true
},
{
@ -31,30 +37,38 @@
"columnName": "nickname",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "avatarFile",
"columnName": "avatarFile",
"affinity": "TEXT",
"notNull": false
}
],
"primaryKey": {
"columnNames": [
"accountId",
"jid"
"id"
],
"autoGenerate": false
},
"indices": [
{
"name": "index_RoomContactModel_accountId_jid",
"unique": true,
"name": "index_contacts_id",
"unique": false,
"columnNames": [
"accountId",
"jid"
"id"
],
"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": [
@ -74,12 +88,10 @@
"onDelete": "RESTRICT",
"onUpdate": "NO ACTION",
"columns": [
"accountId",
"jid"
"xmppId"
],
"referencedColumns": [
"accountId",
"jid"
"id"
]
}
]
@ -186,6 +198,14 @@
"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": [
@ -204,8 +224,14 @@
},
{
"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": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "accountId",
"columnName": "accountId",
@ -220,19 +246,26 @@
},
{
"fieldPath": "avatarFile",
"columnName": "avatarFile",
"columnName": "avatar",
"affinity": "TEXT",
"notNull": false
}
],
"primaryKey": {
"columnNames": [
"accountId",
"jid"
"id"
],
"autoGenerate": false
},
"indices": [
{
"name": "index_entities_id",
"unique": false,
"columnNames": [
"id"
],
"createSql": "CREATE INDEX `index_entities_id` ON `${TABLE_NAME}` (`id`)"
},
{
"name": "index_entities_accountId_jid",
"unique": true,
@ -261,7 +294,7 @@
"views": [],
"setupQueries": [
"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);
@Update
void upddate(T... entity);
void update(T entity);
@Update
void update(T[] entities);
@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 org.jxmpp.jid.EntityBareJid;
import org.mercury_im.messenger.persistence.model.XmppIdentityModel;
import org.mercury_im.messenger.persistence.room.model.RoomChatModel;
import java.util.List;
public interface ChatDao extends BaseDao<RoomChatModel> {
@Query("SELECT * FROM RoomChatModel")
@Query("SELECT * FROM chats")
LiveData<List<RoomChatModel>> getAllChats();
@Query("SELECT * FROM RoomChatModel WHERE ")
LiveData<RoomChatModel> getChatWith(EntityBareJid contact);
@Query("SELECT chats.* FROM chats JOIN entities WHERE accountId = :accountId")
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}
* which are currently found in the database.
*
* @return
*/
@Query("select * from RoomContactModel")
@Query("SELECT * FROM contacts")
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);
@Query("select * from RoomContactModel where accountId = :accountId")
@Query("SELECT * FROM contacts WHERE accountId = :accountId")
LiveData<List<RoomContactModel>> getRosterEntriesForAccount(long accountId);
}

View File

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

View File

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

View File

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

View File

@ -1,27 +1,57 @@
package org.mercury_im.messenger.persistence.room.repository;
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.ContactModel;
import org.mercury_im.messenger.persistence.model.XmppIdentityModel;
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;
public class IChatRepository implements ChatRepository {
import javax.inject.Inject;
@Override
public LiveData<List<ChatModel>> getAllChats() {
return null;
public class IChatRepository implements ChatRepository<RoomChatModel> {
private final ChatDao chatDao;
@Inject
public IChatRepository(ChatDao dao) {
this.chatDao = dao;
}
@Override
public void getChatWith(XmppIdentityModel identity) {
public LiveData<List<RoomChatModel>> getAllChats() {
return chatDao.getAllChats();
}
@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;
import org.jxmpp.jid.EntityBareJid;
public interface ChatModel {
long getId();

View File

@ -1,9 +1,5 @@
package org.mercury_im.messenger.persistence.model;
import org.jxmpp.jid.EntityBareJid;
import java.io.File;
public interface ContactModel {
long getId();
@ -14,9 +10,9 @@ public interface ContactModel {
void setAccountId(long id);
EntityBareJid getJid();
long getXmppIdentityId();
void setJid(EntityBareJid jid);
void setXmppIdentityId(long id);
String getRosterName();
@ -25,8 +21,4 @@ public interface ContactModel {
String getNickname();
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 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.ContactModel;
import org.mercury_im.messenger.persistence.model.XmppIdentityModel;
import org.w3c.dom.Entity;
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
// https://github.com/igniterealtime/Smack/wiki/How-to-use-Smack-snapshots
smackAndroidVersion = "4.4.0-alpha2-20190324.010646-51"
smackAndroidExtensionsVersion = "4.4.0-alpha2-20190324.010646-51"
smackCoreVersion = "4.4.0-alpha2-20190324.010647-51"

View File

@ -1,6 +1,7 @@
package org.mercury_im.messenger.xmpp_android;
import org.jivesoftware.smack.XMPPConnection;
import org.jivesoftware.smackx.ping.android.ServerPingWithAlarmManager;
import org.mercury_im.messenger.xmpp_core.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-signal:$smackOmemoSignalVersion"
api "org.igniterealtime.smack:smack-openpgp:$smackOpenPGPVersion"
api "org.igniterealtime.smack:smack-resolver-minidns:$smackResolverMiniDnsVersion"
api "org.igniterealtime.smack:smack-tcp:$smackTcpVersion"
// api "org.igniterealtime.smack:smack-resolver-minidns:$smackResolverMiniDnsVersion"
// api "org.igniterealtime.smack:smack-tcp:$smackTcpVersion"
// Exclude XmlPullParser from Smack dependencies, as its now provided by Android
// 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 {
private final XMPPConnection connection;
private final long accountId;
private final Roster roster;
protected final XMPPConnection connection;
protected final long accountId;
protected final Roster roster;
public MercuryConnection(XMPPConnection connection, long accountId) {
this.connection = connection;