This commit is contained in:
Paul Schaub 2019-08-24 23:06:06 +02:00
parent f7697b9ba8
commit ab66770fd7
Signed by: vanitasvitae
GPG key ID: 62BEE9264BF17311
15 changed files with 362 additions and 230 deletions

View file

@ -7,8 +7,6 @@ import android.view.Menu;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity; import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.widget.Toolbar; import androidx.appcompat.widget.Toolbar;
import androidx.lifecycle.LiveData;
import androidx.lifecycle.Observer;
import androidx.lifecycle.ViewModelProviders; import androidx.lifecycle.ViewModelProviders;
import androidx.recyclerview.widget.LinearLayoutManager; import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView; import androidx.recyclerview.widget.RecyclerView;
@ -18,17 +16,27 @@ import org.jxmpp.jid.impl.JidCreate;
import org.mercury_im.messenger.MercuryImApplication; import org.mercury_im.messenger.MercuryImApplication;
import org.mercury_im.messenger.R; import org.mercury_im.messenger.R;
import org.mercury_im.messenger.persistence.model.AccountModel; import org.mercury_im.messenger.persistence.model.AccountModel;
import org.mercury_im.messenger.persistence.model.ContactModel;
import org.mercury_im.messenger.persistence.model.MessageModel; import org.mercury_im.messenger.persistence.model.MessageModel;
import org.mercury_im.messenger.persistence.repository.AccountRepository; import org.mercury_im.messenger.persistence.repository.AccountRepository;
import org.mercury_im.messenger.persistence.repository.MessageRepository; import org.mercury_im.messenger.persistence.repository.MessageRepository;
import org.mercury_im.messenger.persistence.repository.RosterRepository;
import java.util.Date;
import java.util.List; import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.inject.Inject; import javax.inject.Inject;
import butterknife.BindView; import butterknife.BindView;
import butterknife.ButterKnife; import butterknife.ButterKnife;
import io.reactivex.Maybe; import io.reactivex.Maybe;
import io.reactivex.Observable;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.disposables.CompositeDisposable;
import io.reactivex.functions.Consumer;
import io.reactivex.schedulers.Schedulers;
public class ChatActivity extends AppCompatActivity implements ChatInputFragment.OnChatInputActionListener { public class ChatActivity extends AppCompatActivity implements ChatInputFragment.OnChatInputActionListener {
@ -41,6 +49,9 @@ public class ChatActivity extends AppCompatActivity implements ChatInputFragment
@Inject @Inject
MessageRepository messageRepository; MessageRepository messageRepository;
@Inject
RosterRepository rosterRepository;
@BindView(R.id.toolbar) @BindView(R.id.toolbar)
Toolbar toolbar; Toolbar toolbar;
@ -49,6 +60,8 @@ public class ChatActivity extends AppCompatActivity implements ChatInputFragment
private ChatViewModel chatViewModel; private ChatViewModel chatViewModel;
private final CompositeDisposable disposable = new CompositeDisposable();
private EntityBareJid jid; private EntityBareJid jid;
private long accountId; private long accountId;
@ -74,37 +87,53 @@ public class ChatActivity extends AppCompatActivity implements ChatInputFragment
String jidString = savedInstanceState.getString(EXTRA_JID); String jidString = savedInstanceState.getString(EXTRA_JID);
if (jidString != null) { if (jidString != null) {
getSupportActionBar().setTitle(jidString);
jid = JidCreate.entityBareFromOrThrowUnchecked(jidString); jid = JidCreate.entityBareFromOrThrowUnchecked(jidString);
accountId = savedInstanceState.getLong(EXTRA_ACCOUNT); accountId = savedInstanceState.getLong(EXTRA_ACCOUNT);
Maybe<AccountModel> accountModel = accountRepository.getAccount(accountId); Maybe<AccountModel> account = accountRepository.getAccount(accountId);
chatViewModel = ViewModelProviders.of(this).get(ChatViewModel.class); chatViewModel = ViewModelProviders.of(this).get(ChatViewModel.class);
/* disposable.add(
accountModel.observe(this, new Observer<AccountModel>() { account.subscribeOn(Schedulers.io())
@Override .observeOn(AndroidSchedulers.mainThread())
public void onChanged(AccountModel accountModel) { .subscribe(accountModel -> {
chatViewModel.init(accountModel, jid); chatViewModel.init(accountModel, jid);
} }));
});
LiveData<List<MessageModel>> messages = messageRepository.getAllMessagesOfChat(accountId, jid); disposable.add(
messages.observe(this, new Observer<List<MessageModel>>() { rosterRepository.getContact(accountId, jid)
@Override .toObservable()
public void onChanged(List<MessageModel> messageModels) { .subscribeOn(Schedulers.io())
Log.d(MercuryImApplication.TAG, "Updating messages: " + messageModels.size()); .observeOn(AndroidSchedulers.mainThread())
adapter.updateMessages(messageModels); .subscribe((Consumer<ContactModel>) o -> {
recyclerView.scrollToPosition(messageModels.size() - 1); Logger.getLogger("AAAAAAAA").log(Level.INFO, "Title change?");
} String title = o.getRosterName();
}); if (title == null) {
title = jidString;
}
getSupportActionBar().setTitle(title);
}));
getSupportFragmentManager().beginTransaction().replace(R.id.stub_compose, ChatInputFragment.newInstance()) Observable<List<MessageModel>> messages = messageRepository.getAllMessagesOfChat(accountId, jid);
.commitAllowingStateLoss(); disposable.add(
*/ messages.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.doOnNext(messageModels -> Log.d(MercuryImApplication.TAG, "Updating messages: " + messageModels.size()))
.subscribe(messageModels -> {
adapter.updateMessages(messageModels);
recyclerView.scrollToPosition(messageModels.size() - 1);
}));
ChatInputFragment composer = (ChatInputFragment) getSupportFragmentManager()
.findFragmentById(R.id.fragment_compose);
} }
} }
public void onStop() {
super.onStop();
disposable.clear();
}
@Override @Override
public boolean onCreateOptionsMenu(Menu menu) { public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present. // Inflate the menu; this adds items to the action bar if it is present.
@ -135,6 +164,21 @@ public class ChatActivity extends AppCompatActivity implements ChatInputFragment
if (msg.isEmpty()) { if (msg.isEmpty()) {
return; return;
} }
MessageModel messageModel = messageRepository.newMessageModel();
AccountModel account = (AccountModel) accountRepository.getAccount(accountId)
.subscribeOn(Schedulers.io()).blockingGet();
messageModel.setFrom(account.getJid());
messageModel.setTo(jid);
messageModel.setSendDate(new Date());
messageModel.setBody(msg);
messageModel.setIncoming(false);
messageModel.setAccountId(accountId);
disposable.add(messageRepository
.insertMessage(messageModel)
.subscribeOn(Schedulers.io())
.subscribe());
/* /*
new Thread() { new Thread() {
@Override @Override

View file

@ -11,10 +11,18 @@ import androidx.appcompat.app.AppCompatActivity;
import org.mercury_im.messenger.MercuryImApplication; import org.mercury_im.messenger.MercuryImApplication;
import org.mercury_im.messenger.R; import org.mercury_im.messenger.R;
import org.mercury_im.messenger.persistence.model.AccountModel; import org.mercury_im.messenger.persistence.model.AccountModel;
import org.mercury_im.messenger.persistence.repository.AccountRepository;
import javax.inject.Inject;
import io.reactivex.schedulers.Schedulers;
public class AccountsActivity extends AppCompatActivity public class AccountsActivity extends AppCompatActivity
implements AccountsFragment.OnAccountListItemClickListener { implements AccountsFragment.OnAccountListItemClickListener {
@Inject
AccountRepository accountRepository;
@Override @Override
protected void onCreate(Bundle savedInstanceState) { protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
@ -40,4 +48,11 @@ public class AccountsActivity extends AppCompatActivity
}); });
builder.create().show(); builder.create().show();
} }
@Override
public void onAccountListItemLongClick(AccountModel item) {
accountRepository.deleteAccount(item)
.subscribeOn(Schedulers.io())
.subscribe();
}
} }

View file

@ -24,7 +24,7 @@ import org.mercury_im.messenger.persistence.model.AccountModel;
*/ */
public class AccountsFragment extends Fragment { public class AccountsFragment extends Fragment {
private OnAccountListItemClickListener onAccountListItemClickListener; private OnAccountListItemClickListener accountClickListener;
AccountsViewModel viewModel; AccountsViewModel viewModel;
@ -40,7 +40,6 @@ public class AccountsFragment extends Fragment {
} }
// TODO: Customize parameter initialization // TODO: Customize parameter initialization
@SuppressWarnings("unused")
public static AccountsFragment newInstance() { public static AccountsFragment newInstance() {
AccountsFragment fragment = new AccountsFragment(); AccountsFragment fragment = new AccountsFragment();
return fragment; return fragment;
@ -63,7 +62,7 @@ public class AccountsFragment extends Fragment {
recyclerView = (RecyclerView) view; recyclerView = (RecyclerView) view;
Context context = view.getContext(); Context context = view.getContext();
recyclerView.setLayoutManager(new LinearLayoutManager(context)); recyclerView.setLayoutManager(new LinearLayoutManager(context));
this.adapter = new AccountsRecyclerViewAdapter(viewModel.getAccounts().getValue(), onAccountListItemClickListener); this.adapter = new AccountsRecyclerViewAdapter(viewModel.getAccounts().getValue(), accountClickListener);
viewModel.getAccounts().observe(this, roomAccountModels -> adapter.setValues(roomAccountModels)); viewModel.getAccounts().observe(this, roomAccountModels -> adapter.setValues(roomAccountModels));
recyclerView.setAdapter(adapter); recyclerView.setAdapter(adapter);
} }
@ -76,7 +75,7 @@ public class AccountsFragment extends Fragment {
public void onAttach(Context context) { public void onAttach(Context context) {
super.onAttach(context); super.onAttach(context);
if (context instanceof OnAccountListItemClickListener) { if (context instanceof OnAccountListItemClickListener) {
onAccountListItemClickListener = (OnAccountListItemClickListener) context; accountClickListener = (OnAccountListItemClickListener) context;
} else { } else {
throw new RuntimeException(context.toString() throw new RuntimeException(context.toString()
+ " must implement OnAccountListItemClickListener"); + " must implement OnAccountListItemClickListener");
@ -86,7 +85,7 @@ public class AccountsFragment extends Fragment {
@Override @Override
public void onDetach() { public void onDetach() {
super.onDetach(); super.onDetach();
onAccountListItemClickListener = null; accountClickListener = null;
} }
/** /**
@ -101,5 +100,7 @@ public class AccountsFragment extends Fragment {
*/ */
public interface OnAccountListItemClickListener { public interface OnAccountListItemClickListener {
void onAccountListItemClick(AccountModel item); void onAccountListItemClick(AccountModel item);
void onAccountListItemLongClick(AccountModel item);
} }
} }

View file

@ -56,6 +56,12 @@ public class AccountsRecyclerViewAdapter extends RecyclerView.Adapter<AccountsRe
mListener.onAccountListItemClick(holder.accountModel); mListener.onAccountListItemClick(holder.accountModel);
} }
}); });
holder.mView.setOnLongClickListener(v -> {
if (null != mListener) {
mListener.onAccountListItemLongClick(holder.accountModel);
}
return true;
});
} }
public void setValues(List<AccountModel> values) { public void setValues(List<AccountModel> values) {

View file

@ -9,6 +9,7 @@ import android.widget.TextView;
import androidx.appcompat.app.AppCompatActivity; import androidx.appcompat.app.AppCompatActivity;
import androidx.lifecycle.LiveData; import androidx.lifecycle.LiveData;
import androidx.lifecycle.Observer;
import androidx.lifecycle.ViewModelProviders; import androidx.lifecycle.ViewModelProviders;
import com.google.android.material.textfield.TextInputEditText; import com.google.android.material.textfield.TextInputEditText;
@ -49,8 +50,33 @@ public class LoginActivity extends AppCompatActivity implements TextView.OnEdito
ButterKnife.bind(this); ButterKnife.bind(this);
viewModel = ViewModelProviders.of(this).get(LoginViewModel.class); viewModel = ViewModelProviders.of(this).get(LoginViewModel.class);
observeViewModel(viewModel);
displayCredentials(viewModel.getAccount()); displayCredentials(viewModel.getAccount());
mJidView.setOnEditorActionListener(this);
mPasswordView.setOnEditorActionListener(this);
mJidView.addTextChangedListener(new TextChangedListener() {
@Override
public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) {
viewModel.onJidInputChanged(charSequence.toString());
Log.d("Mercury", "onTextChanged");
}
});
mPasswordView.addTextChangedListener(new TextChangedListener() {
@Override
public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) {
viewModel.onPasswordInputChanged(charSequence.toString());
Log.d("Mercury", "onTextChanged");
}
});
mSignInView.setOnClickListener(view -> viewModel.loginDetailsEntered());
}
private void observeViewModel(LoginViewModel viewModel) {
viewModel.getJidError().observe(this, jidError -> { viewModel.getJidError().observe(this, jidError -> {
if (jidError == null) return; if (jidError == null) return;
String errorMessage = null; String errorMessage = null;
@ -88,26 +114,11 @@ public class LoginActivity extends AppCompatActivity implements TextView.OnEdito
mPasswordView.setError(errorMessage); mPasswordView.setError(errorMessage);
}); });
mJidView.setOnEditorActionListener(this); viewModel.getSigninSuccessful().observe(this, aBoolean -> {
mPasswordView.setOnEditorActionListener(this); if (Boolean.TRUE.equals(aBoolean)) {
finish();
mJidView.addTextChangedListener(new TextChangedListener() {
@Override
public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) {
viewModel.onJidInputChanged(charSequence.toString());
Log.d("Mercury", "onTextChanged");
} }
}); });
mPasswordView.addTextChangedListener(new TextChangedListener() {
@Override
public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) {
viewModel.onPasswordInputChanged(charSequence.toString());
Log.d("Mercury", "onTextChanged");
}
});
mSignInView.setOnClickListener(view -> viewModel.loginDetailsEntered());
} }
private void displayCredentials(LiveData<? extends AccountModel> account) { private void displayCredentials(LiveData<? extends AccountModel> account) {

View file

@ -18,6 +18,7 @@ import org.mercury_im.messenger.persistence.room.model.RoomAccountModel;
import javax.inject.Inject; import javax.inject.Inject;
import io.reactivex.ObservableSource;
import io.reactivex.Single; import io.reactivex.Single;
import io.reactivex.android.schedulers.AndroidSchedulers; import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.observers.DisposableSingleObserver; import io.reactivex.observers.DisposableSingleObserver;
@ -42,12 +43,18 @@ public class LoginViewModel extends ViewModel {
private MutableLiveData<AccountModel> account = new MutableLiveData<>(); private MutableLiveData<AccountModel> account = new MutableLiveData<>();
private MutableLiveData<Boolean> signinSuccessful = new MutableLiveData<>();
public LoginViewModel() { public LoginViewModel() {
super(); super();
MercuryImApplication.getApplication().getAppComponent().inject(this); MercuryImApplication.getApplication().getAppComponent().inject(this);
init(accountRepository.newAccountModel()); init(accountRepository.newAccountModel());
} }
public LiveData<Boolean> getSigninSuccessful() {
return signinSuccessful;
}
public enum JidError { public enum JidError {
none, none,
emptyJid, emptyJid,
@ -140,6 +147,7 @@ public class LoginViewModel extends ViewModel {
accountModel.setId(aLong); accountModel.setId(aLong);
Log.d(MercuryImApplication.TAG, "LoginActivity.loginDetailsEntered: Account " + aLong + " inserted."); Log.d(MercuryImApplication.TAG, "LoginActivity.loginDetailsEntered: Account " + aLong + " inserted.");
connectionCenter.createConnection(accountModel); connectionCenter.createConnection(accountModel);
signinSuccessful.setValue(true);
} }
@Override @Override

View file

@ -1,33 +1,34 @@
<?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_height="match_parent"
tools:context=".ui.chat.ChatActivity">
<com.google.android.material.appbar.AppBarLayout
android:id="@+id/appbar_layout"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="match_parent"
app:layout_constraintTop_toTopOf="parent"> tools:menu="@menu/menu_chat"
tools:context=".ui.chat.ChatActivity">
<androidx.appcompat.widget.Toolbar <com.google.android.material.appbar.AppBarLayout
android:id="@+id/toolbar" android:id="@+id/appbar_layout"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize" android:layout_height="wrap_content"
android:background="?attr/colorPrimary"> app:layout_constraintTop_toTopOf="parent">
<de.hdodenhof.circleimageview.CircleImageView <androidx.appcompat.widget.Toolbar
android:layout_width="40dp" android:id="@+id/toolbar"
android:layout_height="40dp" android:layout_width="match_parent"
android:src="@drawable/aldrin" android:layout_height="?attr/actionBarSize"
android:transitionName="avatar"/> android:background="?attr/colorPrimary">
</androidx.appcompat.widget.Toolbar> <de.hdodenhof.circleimageview.CircleImageView
android:layout_width="40dp"
android:layout_height="40dp"
android:src="@drawable/aldrin"
android:transitionName="avatar"/>
</com.google.android.material.appbar.AppBarLayout> </androidx.appcompat.widget.Toolbar>
</com.google.android.material.appbar.AppBarLayout>
<FrameLayout <FrameLayout
android:layout_width="match_parent" android:layout_width="match_parent"
@ -39,10 +40,12 @@ tools:context=".ui.chat.ChatActivity">
</FrameLayout> </FrameLayout>
<FrameLayout <fragment
android:id="@+id/stub_compose" android:id="@+id/fragment_compose"
android:layout_width="match_parent" android:name="org.mercury_im.messenger.ui.chat.ChatInputFragment"
android:layout_height="wrap_content" android:layout_width="match_parent"
app:layout_constraintBottom_toBottomOf="parent" /> android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="parent"
tools:layout="@layout/view_compose"/>
</androidx.constraintlayout.widget.ConstraintLayout> </androidx.constraintlayout.widget.ConstraintLayout>

View file

@ -1,5 +1,6 @@
package org.mercury_im.messenger.core; package org.mercury_im.messenger.core;
import org.jivesoftware.smack.roster.Roster;
import org.jivesoftware.smack.roster.packet.RosterPacket; import org.jivesoftware.smack.roster.packet.RosterPacket;
import org.jxmpp.jid.Jid; import org.jxmpp.jid.Jid;
import org.mercury_im.messenger.persistence.model.ContactModel; import org.mercury_im.messenger.persistence.model.ContactModel;
@ -8,6 +9,7 @@ import org.mercury_im.messenger.persistence.model.RosterInformationModel;
import org.mercury_im.messenger.persistence.repository.RosterRepository; import org.mercury_im.messenger.persistence.repository.RosterRepository;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection; import java.util.Collection;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
@ -17,6 +19,7 @@ import java.util.logging.Logger;
import javax.inject.Inject; import javax.inject.Inject;
import io.reactivex.Observable;
import io.reactivex.disposables.CompositeDisposable; import io.reactivex.disposables.CompositeDisposable;
import io.reactivex.functions.Consumer; import io.reactivex.functions.Consumer;
import io.reactivex.schedulers.Schedulers; import io.reactivex.schedulers.Schedulers;
@ -48,12 +51,16 @@ public class RosterStore implements org.jivesoftware.smack.roster.rosterstore.Ro
disposable.add(rosterRepository.getAllContactsOfAccount(accountId) disposable.add(rosterRepository.getAllContactsOfAccount(accountId)
.subscribeOn(Schedulers.io()) .subscribeOn(Schedulers.io())
.observeOn(Schedulers.computation()) .observeOn(Schedulers.computation())
.subscribe((Consumer<List<? extends ContactModel>>) o -> { .subscribe((Consumer<List<? extends ContactModel>>) contactsList -> {
itemMap.clear(); itemMap.clear();
for (ContactModel c : o) { for (ContactModel contactModel : contactsList) {
rosterRepository.getEntityForContact(c.getId()).subscribeOn(Schedulers.io()) rosterRepository.getEntityForContact(contactModel.getId())
.subscribe((Consumer<EntityModel>) o1 -> itemMap.put(o1.getJid(), fromModel(c))); .subscribeOn(Schedulers.io())
LOGGER.log(Level.INFO, "Populate itemMap with " + o.size() + " items"); .subscribe((Consumer<EntityModel>) entityModel -> {
RosterPacket.Item item = fromModel(contactModel);
itemMap.put(entityModel.getJid(), item);
});
LOGGER.log(Level.INFO, "Populate itemMap with " + contactsList.size() + " items");
} }
})); }));
@ -113,16 +120,16 @@ public class RosterStore implements org.jivesoftware.smack.roster.rosterstore.Ro
@Override @Override
public boolean resetEntries(Collection<RosterPacket.Item> items, String version) { public boolean resetEntries(Collection<RosterPacket.Item> items, String version) {
LOGGER.log(Level.INFO, "Reset Entries: " + Arrays.toString(items.toArray()));
// Update database // Update database
for (RosterPacket.Item item : items) { for (RosterPacket.Item item : items) {
rosterRepository.updateOrInsertContact(toModel(item)) ContactModel model = toModel(item);
rosterRepository.updateOrInsertContact(model)
.subscribeOn(Schedulers.io()) .subscribeOn(Schedulers.io())
.observeOn(Schedulers.computation())
.subscribe(); .subscribe();
} }
rosterRepository.updateRosterVersionForAccount(accountId, version) rosterRepository.updateRosterVersionForAccount(accountId, version)
.subscribeOn(Schedulers.io()) .subscribeOn(Schedulers.io())
.observeOn(Schedulers.computation())
.subscribe(); .subscribe();
return true; return true;
@ -130,6 +137,7 @@ public class RosterStore implements org.jivesoftware.smack.roster.rosterstore.Ro
@Override @Override
public boolean removeEntry(Jid bareJid, String version) { public boolean removeEntry(Jid bareJid, String version) {
LOGGER.log(Level.INFO, "Remove entry " + bareJid.toString());
rosterRepository.deleteContact(accountId, bareJid.asEntityBareJidOrThrow()) rosterRepository.deleteContact(accountId, bareJid.asEntityBareJidOrThrow())
.subscribeOn(Schedulers.io()) .subscribeOn(Schedulers.io())
.observeOn(Schedulers.computation()) .observeOn(Schedulers.computation())
@ -143,6 +151,7 @@ public class RosterStore implements org.jivesoftware.smack.roster.rosterstore.Ro
@Override @Override
public void resetStore() { public void resetStore() {
LOGGER.log(Level.INFO, "Reset Store");
rosterRepository.deleteAllContactsOfAccount(accountId) rosterRepository.deleteAllContactsOfAccount(accountId)
.subscribeOn(Schedulers.io()) .subscribeOn(Schedulers.io())
.observeOn(Schedulers.computation()) .observeOn(Schedulers.computation())
@ -169,13 +178,12 @@ public class RosterStore implements org.jivesoftware.smack.roster.rosterstore.Ro
ContactModel contact = rosterRepository.newContactModel(); ContactModel contact = rosterRepository.newContactModel();
contact.setAccountId(accountId); contact.setAccountId(accountId);
ContactModel attributes = rosterRepository.newContactModel(); contact.setRosterName(item.getName());
attributes.setRosterName(item.getName());
if (item.getItemType() != null) { if (item.getItemType() != null) {
attributes.setDirection(convert(item.getItemType())); contact.setDirection(convert(item.getItemType()));
} }
attributes.setApproved(item.isApproved()); contact.setApproved(item.isApproved());
attributes.setSubscriptionPending(item.isSubscriptionPending()); contact.setSubscriptionPending(item.isSubscriptionPending());
EntityModel entity = rosterRepository.newEntityModel(); EntityModel entity = rosterRepository.newEntityModel();
entity.setAccountId(accountId); entity.setAccountId(accountId);

View file

@ -2,8 +2,129 @@
"formatVersion": 1, "formatVersion": 1,
"database": { "database": {
"version": 1, "version": 1,
"identityHash": "cc1de3c4fd3350ea0e894d02f30312f3", "identityHash": "fdd5152ff14dba77ae4dba978fc0bb11",
"entities": [ "entities": [
{
"tableName": "accounts",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`pk_account_id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `jid` TEXT, `password` TEXT, `enabled` INTEGER NOT NULL, `state` TEXT)",
"fields": [
{
"fieldPath": "id",
"columnName": "pk_account_id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "jid",
"columnName": "jid",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "password",
"columnName": "password",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "enabled",
"columnName": "enabled",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "state",
"columnName": "state",
"affinity": "TEXT",
"notNull": false
}
],
"primaryKey": {
"columnNames": [
"pk_account_id"
],
"autoGenerate": true
},
"indices": [
{
"name": "index_accounts_pk_account_id",
"unique": false,
"columnNames": [
"pk_account_id"
],
"createSql": "CREATE INDEX `index_accounts_pk_account_id` ON `${TABLE_NAME}` (`pk_account_id`)"
}
],
"foreignKeys": []
},
{
"tableName": "entities",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`pk_entity_id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `fk_account_id` INTEGER NOT NULL, `jid` TEXT NOT NULL, `avatar` TEXT, FOREIGN KEY(`fk_account_id`) REFERENCES `accounts`(`pk_account_id`) ON UPDATE NO ACTION ON DELETE CASCADE )",
"fields": [
{
"fieldPath": "id",
"columnName": "pk_entity_id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "accountId",
"columnName": "fk_account_id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "jid",
"columnName": "jid",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "avatarFile",
"columnName": "avatar",
"affinity": "TEXT",
"notNull": false
}
],
"primaryKey": {
"columnNames": [
"pk_entity_id"
],
"autoGenerate": true
},
"indices": [
{
"name": "index_entities_pk_entity_id",
"unique": false,
"columnNames": [
"pk_entity_id"
],
"createSql": "CREATE INDEX `index_entities_pk_entity_id` ON `${TABLE_NAME}` (`pk_entity_id`)"
},
{
"name": "index_entities_fk_account_id_jid",
"unique": true,
"columnNames": [
"fk_account_id",
"jid"
],
"createSql": "CREATE UNIQUE INDEX `index_entities_fk_account_id_jid` ON `${TABLE_NAME}` (`fk_account_id`, `jid`)"
}
],
"foreignKeys": [
{
"table": "accounts",
"onDelete": "CASCADE",
"onUpdate": "NO ACTION",
"columns": [
"fk_account_id"
],
"referencedColumns": [
"pk_account_id"
]
}
]
},
{ {
"tableName": "contacts", "tableName": "contacts",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`pk_contact_id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `fk_account_id` INTEGER NOT NULL, `fk_entity_id` INTEGER NOT NULL, `rostername` TEXT, `nickname` TEXT, `direction` TEXT, `sub_pending` INTEGER NOT NULL, `approved` INTEGER NOT NULL, FOREIGN KEY(`fk_account_id`) REFERENCES `accounts`(`pk_account_id`) ON UPDATE NO ACTION ON DELETE CASCADE , FOREIGN KEY(`fk_entity_id`) REFERENCES `entities`(`pk_entity_id`) ON UPDATE NO ACTION ON DELETE RESTRICT )", "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`pk_contact_id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `fk_account_id` INTEGER NOT NULL, `fk_entity_id` INTEGER NOT NULL, `rostername` TEXT, `nickname` TEXT, `direction` TEXT, `sub_pending` INTEGER NOT NULL, `approved` INTEGER NOT NULL, FOREIGN KEY(`fk_account_id`) REFERENCES `accounts`(`pk_account_id`) ON UPDATE NO ACTION ON DELETE CASCADE , FOREIGN KEY(`fk_entity_id`) REFERENCES `entities`(`pk_entity_id`) ON UPDATE NO ACTION ON DELETE RESTRICT )",
@ -82,11 +203,11 @@
}, },
{ {
"name": "index_contacts_fk_entity_id", "name": "index_contacts_fk_entity_id",
"unique": false, "unique": true,
"columnNames": [ "columnNames": [
"fk_entity_id" "fk_entity_id"
], ],
"createSql": "CREATE INDEX `index_contacts_fk_entity_id` ON `${TABLE_NAME}` (`fk_entity_id`)" "createSql": "CREATE UNIQUE INDEX `index_contacts_fk_entity_id` ON `${TABLE_NAME}` (`fk_entity_id`)"
}, },
{ {
"name": "index_contacts_pk_contact_id_fk_entity_id", "name": "index_contacts_pk_contact_id_fk_entity_id",
@ -124,36 +245,18 @@
] ]
}, },
{ {
"tableName": "accounts", "tableName": "roster_information",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`pk_account_id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `jid` TEXT, `password` TEXT, `enabled` INTEGER NOT NULL, `state` TEXT)", "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`pk_account_id` INTEGER NOT NULL, `roster_version` TEXT, PRIMARY KEY(`pk_account_id`))",
"fields": [ "fields": [
{ {
"fieldPath": "id", "fieldPath": "accountId",
"columnName": "pk_account_id", "columnName": "pk_account_id",
"affinity": "INTEGER", "affinity": "INTEGER",
"notNull": true "notNull": true
}, },
{ {
"fieldPath": "jid", "fieldPath": "rosterVersion",
"columnName": "jid", "columnName": "roster_version",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "password",
"columnName": "password",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "enabled",
"columnName": "enabled",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "state",
"columnName": "state",
"affinity": "TEXT", "affinity": "TEXT",
"notNull": false "notNull": false
} }
@ -162,16 +265,16 @@
"columnNames": [ "columnNames": [
"pk_account_id" "pk_account_id"
], ],
"autoGenerate": true "autoGenerate": false
}, },
"indices": [ "indices": [
{ {
"name": "index_accounts_pk_account_id", "name": "index_roster_information_pk_account_id",
"unique": false, "unique": true,
"columnNames": [ "columnNames": [
"pk_account_id" "pk_account_id"
], ],
"createSql": "CREATE INDEX `index_accounts_pk_account_id` ON `${TABLE_NAME}` (`pk_account_id`)" "createSql": "CREATE UNIQUE INDEX `index_roster_information_pk_account_id` ON `${TABLE_NAME}` (`pk_account_id`)"
} }
], ],
"foreignKeys": [] "foreignKeys": []
@ -322,74 +425,6 @@
} }
] ]
}, },
{
"tableName": "entities",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`pk_entity_id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `fk_account_id` INTEGER NOT NULL, `jid` TEXT NOT NULL, `avatar` TEXT, FOREIGN KEY(`fk_account_id`) REFERENCES `accounts`(`pk_account_id`) ON UPDATE NO ACTION ON DELETE CASCADE )",
"fields": [
{
"fieldPath": "id",
"columnName": "pk_entity_id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "accountId",
"columnName": "fk_account_id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "jid",
"columnName": "jid",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "avatarFile",
"columnName": "avatar",
"affinity": "TEXT",
"notNull": false
}
],
"primaryKey": {
"columnNames": [
"pk_entity_id"
],
"autoGenerate": true
},
"indices": [
{
"name": "index_entities_pk_entity_id",
"unique": false,
"columnNames": [
"pk_entity_id"
],
"createSql": "CREATE INDEX `index_entities_pk_entity_id` ON `${TABLE_NAME}` (`pk_entity_id`)"
},
{
"name": "index_entities_fk_account_id_jid",
"unique": true,
"columnNames": [
"fk_account_id",
"jid"
],
"createSql": "CREATE UNIQUE INDEX `index_entities_fk_account_id_jid` ON `${TABLE_NAME}` (`fk_account_id`, `jid`)"
}
],
"foreignKeys": [
{
"table": "accounts",
"onDelete": "CASCADE",
"onUpdate": "NO ACTION",
"columns": [
"fk_account_id"
],
"referencedColumns": [
"pk_account_id"
]
}
]
},
{ {
"tableName": "avatars", "tableName": "avatars",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`pk_avatar_id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `fk_entity_id` INTEGER NOT NULL, `sha1sum` TEXT, `bytes` BLOB, FOREIGN KEY(`fk_entity_id`) REFERENCES `entities`(`pk_entity_id`) ON UPDATE NO ACTION ON DELETE RESTRICT )", "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`pk_avatar_id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `fk_entity_id` INTEGER NOT NULL, `sha1sum` TEXT, `bytes` BLOB, FOREIGN KEY(`fk_entity_id`) REFERENCES `entities`(`pk_entity_id`) ON UPDATE NO ACTION ON DELETE RESTRICT )",
@ -491,47 +526,12 @@
} }
], ],
"foreignKeys": [] "foreignKeys": []
},
{
"tableName": "roster_information",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`pk_account_id` INTEGER NOT NULL, `roster_version` TEXT, PRIMARY KEY(`pk_account_id`))",
"fields": [
{
"fieldPath": "accountId",
"columnName": "pk_account_id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "rosterVersion",
"columnName": "roster_version",
"affinity": "TEXT",
"notNull": false
}
],
"primaryKey": {
"columnNames": [
"pk_account_id"
],
"autoGenerate": false
},
"indices": [
{
"name": "index_roster_information_pk_account_id",
"unique": true,
"columnNames": [
"pk_account_id"
],
"createSql": "CREATE UNIQUE INDEX `index_roster_information_pk_account_id` ON `${TABLE_NAME}` (`pk_account_id`)"
}
],
"foreignKeys": []
} }
], ],
"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, 'cc1de3c4fd3350ea0e894d02f30312f3')" "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, 'fdd5152ff14dba77ae4dba978fc0bb11')"
] ]
} }
} }

View file

@ -43,4 +43,7 @@ public interface AccountDao extends BaseDao<RoomAccountModel> {
@Query("update accounts set state = :state where pk_account_id = :accountId") @Query("update accounts set state = :state where pk_account_id = :accountId")
Completable updateConnectionState(long accountId, String state); Completable updateConnectionState(long accountId, String state);
@Query("DELETE FROM accounts WHERE pk_account_id = :accountId")
Completable deleteAccount(long accountId);
} }

View file

@ -24,7 +24,7 @@ import static org.mercury_im.messenger.persistence.room.model.RoomContactModel.T
indices = { indices = {
@Index(value = KEY_ID), @Index(value = KEY_ID),
@Index(value = KEY_ACCOUNT_ID), @Index(value = KEY_ACCOUNT_ID),
@Index(value = KEY_ENTITY_ID), @Index(value = KEY_ENTITY_ID, unique = true),
@Index(value = {KEY_ID, KEY_ENTITY_ID}, unique = true) @Index(value = {KEY_ID, KEY_ENTITY_ID}, unique = true)
}, },
foreignKeys = { foreignKeys = {

View file

@ -1,5 +1,6 @@
package org.mercury_im.messenger.persistence.room.repository; package org.mercury_im.messenger.persistence.room.repository;
import org.mercury_im.messenger.persistence.model.AccountModel;
import org.mercury_im.messenger.persistence.repository.AccountRepository; import org.mercury_im.messenger.persistence.repository.AccountRepository;
import org.mercury_im.messenger.persistence.room.dao.AccountDao; import org.mercury_im.messenger.persistence.room.dao.AccountDao;
import org.mercury_im.messenger.persistence.room.model.RoomAccountModel; import org.mercury_im.messenger.persistence.room.model.RoomAccountModel;
@ -47,4 +48,14 @@ public class IAccountRepository implements AccountRepository<RoomAccountModel> {
public Completable updateState(long accountId, String state) { public Completable updateState(long accountId, String state) {
return accountDao.updateConnectionState(accountId, state); return accountDao.updateConnectionState(accountId, state);
} }
@Override
public Completable deleteAccount(long accountId) {
return accountDao.deleteAccount(accountId);
}
@Override
public Completable deleteAccount(RoomAccountModel item) {
return accountDao.delete(item);
}
} }

View file

@ -1,5 +1,7 @@
package org.mercury_im.messenger.persistence.room.repository; package org.mercury_im.messenger.persistence.room.repository;
import android.util.Log;
import org.jxmpp.jid.EntityBareJid; import org.jxmpp.jid.EntityBareJid;
import org.mercury_im.messenger.persistence.repository.RosterRepository; import org.mercury_im.messenger.persistence.repository.RosterRepository;
import org.mercury_im.messenger.persistence.room.dao.ContactDao; import org.mercury_im.messenger.persistence.room.dao.ContactDao;
@ -18,6 +20,9 @@ import io.reactivex.CompletableSource;
import io.reactivex.Maybe; import io.reactivex.Maybe;
import io.reactivex.Observable; import io.reactivex.Observable;
import io.reactivex.Single; import io.reactivex.Single;
import io.reactivex.SingleObserver;
import io.reactivex.SingleSource;
import io.reactivex.functions.Consumer;
import io.reactivex.functions.Function; import io.reactivex.functions.Function;
public class IRosterRepository extends RosterRepository<RoomEntityModel, RoomContactModel, RoomRosterInformationModel> { public class IRosterRepository extends RosterRepository<RoomEntityModel, RoomContactModel, RoomRosterInformationModel> {
@ -58,20 +63,28 @@ public class IRosterRepository extends RosterRepository<RoomEntityModel, RoomCon
public Observable<List<RoomContactModel>> getAllContactsOfAccount(long accountId) { public Observable<List<RoomContactModel>> getAllContactsOfAccount(long accountId) {
return contactDao.getContactsForAccount(accountId) return contactDao.getContactsForAccount(accountId)
.flatMap(list -> { .flatMap(list -> {
for (RoomContactModel contact : list) { for (RoomContactModel contact : list) {
RoomEntityModel entity = getEntityForContact(contact).blockingGet(); RoomEntityModel entity = getEntityForContact(contact).blockingGet();
contact.setEntity(entity); contact.setEntity(entity);
} }
return Observable.just(list); return Observable.just(list);
}); });
} }
@Override @Override
public Single<Long> updateOrInsertContact(RoomContactModel contact) { public Single<Long> updateOrInsertContact(RoomContactModel contact) {
return entityDao.insert((RoomEntityModel) contact.getEntity()) //noinspection unchecked
.flatMap(entityId -> { return entityDao.getEntityFor(contact.getAccountId(), contact.getEntity().getJid())
contact.getEntity().setId(entityId); .switchIfEmpty(insertOrReplaceEntity((RoomEntityModel) contact.getEntity())
contact.setEntityId(entityId); .flatMap(entityId -> {
Log.d("Mercury", "Insert entity since maybe did not emit: " + entityId);
contact.getEntity().setId(entityId);
return (SingleSource<RoomEntityModel>) observer ->
observer.onSuccess((RoomEntityModel) contact.getEntity());
}))
.flatMap(entityModel -> {
contact.setEntityId(entityModel.getId());
contact.setEntity(entityModel);
return contactDao.insert(contact); return contactDao.insert(contact);
}); });
} }

View file

@ -20,4 +20,8 @@ public interface AccountRepository<E extends AccountModel> {
Single<Long> insertAccount(E accountModel); Single<Long> insertAccount(E accountModel);
Completable updateState(long accountId, String state); Completable updateState(long accountId, String state);
Completable deleteAccount(E item);
Completable deleteAccount(long accountId);
} }

View file

@ -12,6 +12,7 @@ import io.reactivex.Completable;
import io.reactivex.Maybe; import io.reactivex.Maybe;
import io.reactivex.Observable; import io.reactivex.Observable;
import io.reactivex.Single; import io.reactivex.Single;
import io.reactivex.functions.Consumer;
public abstract class RosterRepository<E extends EntityModel, C extends ContactModel, V extends RosterInformationModel> { public abstract class RosterRepository<E extends EntityModel, C extends ContactModel, V extends RosterInformationModel> {
@ -73,6 +74,10 @@ public abstract class RosterRepository<E extends EntityModel, C extends ContactM
*/ */
public abstract Maybe<C> getContact(long id); public abstract Maybe<C> getContact(long id);
public Maybe<C> getContact(long accountId, EntityBareJid jid) {
return getEntity(accountId, jid).flatMap(this::getContactForEntity);
}
/** /**
* Return the {@link ContactModel} which belongs to the given entityId, wrapped in a {@link Maybe}. * Return the {@link ContactModel} which belongs to the given entityId, wrapped in a {@link Maybe}.
* *