diff --git a/app/src/main/java/org/mercury_im/messenger/ui/chat/ChatActivity.java b/app/src/main/java/org/mercury_im/messenger/ui/chat/ChatActivity.java index 8df166b..b281fc9 100644 --- a/app/src/main/java/org/mercury_im/messenger/ui/chat/ChatActivity.java +++ b/app/src/main/java/org/mercury_im/messenger/ui/chat/ChatActivity.java @@ -7,8 +7,6 @@ import android.view.Menu; import androidx.annotation.NonNull; import androidx.appcompat.app.AppCompatActivity; import androidx.appcompat.widget.Toolbar; -import androidx.lifecycle.LiveData; -import androidx.lifecycle.Observer; import androidx.lifecycle.ViewModelProviders; import androidx.recyclerview.widget.LinearLayoutManager; 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.R; 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.repository.AccountRepository; 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.logging.Level; +import java.util.logging.Logger; import javax.inject.Inject; import butterknife.BindView; import butterknife.ButterKnife; 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 { @@ -41,6 +49,9 @@ public class ChatActivity extends AppCompatActivity implements ChatInputFragment @Inject MessageRepository messageRepository; + @Inject + RosterRepository rosterRepository; + @BindView(R.id.toolbar) Toolbar toolbar; @@ -49,6 +60,8 @@ public class ChatActivity extends AppCompatActivity implements ChatInputFragment private ChatViewModel chatViewModel; + private final CompositeDisposable disposable = new CompositeDisposable(); + private EntityBareJid jid; private long accountId; @@ -74,37 +87,53 @@ public class ChatActivity extends AppCompatActivity implements ChatInputFragment String jidString = savedInstanceState.getString(EXTRA_JID); if (jidString != null) { - getSupportActionBar().setTitle(jidString); jid = JidCreate.entityBareFromOrThrowUnchecked(jidString); accountId = savedInstanceState.getLong(EXTRA_ACCOUNT); - Maybe accountModel = accountRepository.getAccount(accountId); + Maybe account = accountRepository.getAccount(accountId); chatViewModel = ViewModelProviders.of(this).get(ChatViewModel.class); - /* - accountModel.observe(this, new Observer() { - @Override - public void onChanged(AccountModel accountModel) { - chatViewModel.init(accountModel, jid); - } - }); + disposable.add( + account.subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(accountModel -> { + chatViewModel.init(accountModel, jid); + })); - LiveData> messages = messageRepository.getAllMessagesOfChat(accountId, jid); - messages.observe(this, new Observer>() { - @Override - public void onChanged(List messageModels) { - Log.d(MercuryImApplication.TAG, "Updating messages: " + messageModels.size()); - adapter.updateMessages(messageModels); - recyclerView.scrollToPosition(messageModels.size() - 1); - } - }); + disposable.add( + rosterRepository.getContact(accountId, jid) + .toObservable() + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe((Consumer) o -> { + 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()) - .commitAllowingStateLoss(); - */ + Observable> messages = messageRepository.getAllMessagesOfChat(accountId, jid); + 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 public boolean onCreateOptionsMenu(Menu menu) { // 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()) { 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() { @Override diff --git a/app/src/main/java/org/mercury_im/messenger/ui/login/AccountsActivity.java b/app/src/main/java/org/mercury_im/messenger/ui/login/AccountsActivity.java index 39aba5a..0352d67 100644 --- a/app/src/main/java/org/mercury_im/messenger/ui/login/AccountsActivity.java +++ b/app/src/main/java/org/mercury_im/messenger/ui/login/AccountsActivity.java @@ -11,10 +11,18 @@ import androidx.appcompat.app.AppCompatActivity; import org.mercury_im.messenger.MercuryImApplication; import org.mercury_im.messenger.R; import org.mercury_im.messenger.persistence.model.AccountModel; +import org.mercury_im.messenger.persistence.repository.AccountRepository; + +import javax.inject.Inject; + +import io.reactivex.schedulers.Schedulers; public class AccountsActivity extends AppCompatActivity implements AccountsFragment.OnAccountListItemClickListener { + @Inject + AccountRepository accountRepository; + @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); @@ -40,4 +48,11 @@ public class AccountsActivity extends AppCompatActivity }); builder.create().show(); } + + @Override + public void onAccountListItemLongClick(AccountModel item) { + accountRepository.deleteAccount(item) + .subscribeOn(Schedulers.io()) + .subscribe(); + } } diff --git a/app/src/main/java/org/mercury_im/messenger/ui/login/AccountsFragment.java b/app/src/main/java/org/mercury_im/messenger/ui/login/AccountsFragment.java index f31e394..2eb964c 100644 --- a/app/src/main/java/org/mercury_im/messenger/ui/login/AccountsFragment.java +++ b/app/src/main/java/org/mercury_im/messenger/ui/login/AccountsFragment.java @@ -24,7 +24,7 @@ import org.mercury_im.messenger.persistence.model.AccountModel; */ public class AccountsFragment extends Fragment { - private OnAccountListItemClickListener onAccountListItemClickListener; + private OnAccountListItemClickListener accountClickListener; AccountsViewModel viewModel; @@ -40,7 +40,6 @@ public class AccountsFragment extends Fragment { } // TODO: Customize parameter initialization - @SuppressWarnings("unused") public static AccountsFragment newInstance() { AccountsFragment fragment = new AccountsFragment(); return fragment; @@ -63,7 +62,7 @@ public class AccountsFragment extends Fragment { recyclerView = (RecyclerView) view; Context context = view.getContext(); 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)); recyclerView.setAdapter(adapter); } @@ -76,7 +75,7 @@ public class AccountsFragment extends Fragment { public void onAttach(Context context) { super.onAttach(context); if (context instanceof OnAccountListItemClickListener) { - onAccountListItemClickListener = (OnAccountListItemClickListener) context; + accountClickListener = (OnAccountListItemClickListener) context; } else { throw new RuntimeException(context.toString() + " must implement OnAccountListItemClickListener"); @@ -86,7 +85,7 @@ public class AccountsFragment extends Fragment { @Override public void onDetach() { super.onDetach(); - onAccountListItemClickListener = null; + accountClickListener = null; } /** @@ -101,5 +100,7 @@ public class AccountsFragment extends Fragment { */ public interface OnAccountListItemClickListener { void onAccountListItemClick(AccountModel item); + + void onAccountListItemLongClick(AccountModel item); } } diff --git a/app/src/main/java/org/mercury_im/messenger/ui/login/AccountsRecyclerViewAdapter.java b/app/src/main/java/org/mercury_im/messenger/ui/login/AccountsRecyclerViewAdapter.java index 3379e6a..44315e7 100644 --- a/app/src/main/java/org/mercury_im/messenger/ui/login/AccountsRecyclerViewAdapter.java +++ b/app/src/main/java/org/mercury_im/messenger/ui/login/AccountsRecyclerViewAdapter.java @@ -56,6 +56,12 @@ public class AccountsRecyclerViewAdapter extends RecyclerView.Adapter { + if (null != mListener) { + mListener.onAccountListItemLongClick(holder.accountModel); + } + return true; + }); } public void setValues(List values) { diff --git a/app/src/main/java/org/mercury_im/messenger/ui/login/LoginActivity.java b/app/src/main/java/org/mercury_im/messenger/ui/login/LoginActivity.java index d52abc6..519cc0f 100644 --- a/app/src/main/java/org/mercury_im/messenger/ui/login/LoginActivity.java +++ b/app/src/main/java/org/mercury_im/messenger/ui/login/LoginActivity.java @@ -9,6 +9,7 @@ import android.widget.TextView; import androidx.appcompat.app.AppCompatActivity; import androidx.lifecycle.LiveData; +import androidx.lifecycle.Observer; import androidx.lifecycle.ViewModelProviders; import com.google.android.material.textfield.TextInputEditText; @@ -49,8 +50,33 @@ public class LoginActivity extends AppCompatActivity implements TextView.OnEdito ButterKnife.bind(this); viewModel = ViewModelProviders.of(this).get(LoginViewModel.class); + + observeViewModel(viewModel); displayCredentials(viewModel.getAccount()); + mJidView.setOnEditorActionListener(this); + mPasswordView.setOnEditorActionListener(this); + + mJidView.addTextChangedListener(new TextChangedListener() { + @Override + public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) { + viewModel.onJidInputChanged(charSequence.toString()); + Log.d("Mercury", "onTextChanged"); + } + }); + + mPasswordView.addTextChangedListener(new TextChangedListener() { + @Override + public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) { + viewModel.onPasswordInputChanged(charSequence.toString()); + Log.d("Mercury", "onTextChanged"); + } + }); + + mSignInView.setOnClickListener(view -> viewModel.loginDetailsEntered()); + } + + private void observeViewModel(LoginViewModel viewModel) { viewModel.getJidError().observe(this, jidError -> { if (jidError == null) return; String errorMessage = null; @@ -88,26 +114,11 @@ public class LoginActivity extends AppCompatActivity implements TextView.OnEdito mPasswordView.setError(errorMessage); }); - 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"); + viewModel.getSigninSuccessful().observe(this, aBoolean -> { + if (Boolean.TRUE.equals(aBoolean)) { + finish(); } }); - - 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 account) { diff --git a/app/src/main/java/org/mercury_im/messenger/ui/login/LoginViewModel.java b/app/src/main/java/org/mercury_im/messenger/ui/login/LoginViewModel.java index 235a2de..155724f 100644 --- a/app/src/main/java/org/mercury_im/messenger/ui/login/LoginViewModel.java +++ b/app/src/main/java/org/mercury_im/messenger/ui/login/LoginViewModel.java @@ -18,6 +18,7 @@ import org.mercury_im.messenger.persistence.room.model.RoomAccountModel; import javax.inject.Inject; +import io.reactivex.ObservableSource; import io.reactivex.Single; import io.reactivex.android.schedulers.AndroidSchedulers; import io.reactivex.observers.DisposableSingleObserver; @@ -42,12 +43,18 @@ public class LoginViewModel extends ViewModel { private MutableLiveData account = new MutableLiveData<>(); + private MutableLiveData signinSuccessful = new MutableLiveData<>(); + public LoginViewModel() { super(); MercuryImApplication.getApplication().getAppComponent().inject(this); init(accountRepository.newAccountModel()); } + public LiveData getSigninSuccessful() { + return signinSuccessful; + } + public enum JidError { none, emptyJid, @@ -140,6 +147,7 @@ public class LoginViewModel extends ViewModel { accountModel.setId(aLong); Log.d(MercuryImApplication.TAG, "LoginActivity.loginDetailsEntered: Account " + aLong + " inserted."); connectionCenter.createConnection(accountModel); + signinSuccessful.setValue(true); } @Override diff --git a/app/src/main/res/layout/activity_chat.xml b/app/src/main/res/layout/activity_chat.xml index 31aa869..00ae087 100644 --- a/app/src/main/res/layout/activity_chat.xml +++ b/app/src/main/res/layout/activity_chat.xml @@ -1,33 +1,34 @@ - - + android:layout_height="match_parent" + tools:menu="@menu/menu_chat" + tools:context=".ui.chat.ChatActivity"> - + android:layout_height="wrap_content" + app:layout_constraintTop_toTopOf="parent"> - + - + - + + + - + \ No newline at end of file diff --git a/core/src/main/java/org/mercury_im/messenger/core/RosterStore.java b/core/src/main/java/org/mercury_im/messenger/core/RosterStore.java index d3c8176..50aea16 100644 --- a/core/src/main/java/org/mercury_im/messenger/core/RosterStore.java +++ b/core/src/main/java/org/mercury_im/messenger/core/RosterStore.java @@ -1,5 +1,6 @@ package org.mercury_im.messenger.core; +import org.jivesoftware.smack.roster.Roster; import org.jivesoftware.smack.roster.packet.RosterPacket; import org.jxmpp.jid.Jid; 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 java.util.ArrayList; +import java.util.Arrays; import java.util.Collection; import java.util.HashMap; import java.util.List; @@ -17,6 +19,7 @@ import java.util.logging.Logger; import javax.inject.Inject; +import io.reactivex.Observable; import io.reactivex.disposables.CompositeDisposable; import io.reactivex.functions.Consumer; import io.reactivex.schedulers.Schedulers; @@ -48,12 +51,16 @@ public class RosterStore implements org.jivesoftware.smack.roster.rosterstore.Ro disposable.add(rosterRepository.getAllContactsOfAccount(accountId) .subscribeOn(Schedulers.io()) .observeOn(Schedulers.computation()) - .subscribe((Consumer>) o -> { + .subscribe((Consumer>) contactsList -> { itemMap.clear(); - for (ContactModel c : o) { - rosterRepository.getEntityForContact(c.getId()).subscribeOn(Schedulers.io()) - .subscribe((Consumer) o1 -> itemMap.put(o1.getJid(), fromModel(c))); - LOGGER.log(Level.INFO, "Populate itemMap with " + o.size() + " items"); + for (ContactModel contactModel : contactsList) { + rosterRepository.getEntityForContact(contactModel.getId()) + .subscribeOn(Schedulers.io()) + .subscribe((Consumer) 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 public boolean resetEntries(Collection items, String version) { + LOGGER.log(Level.INFO, "Reset Entries: " + Arrays.toString(items.toArray())); // Update database for (RosterPacket.Item item : items) { - rosterRepository.updateOrInsertContact(toModel(item)) + ContactModel model = toModel(item); + rosterRepository.updateOrInsertContact(model) .subscribeOn(Schedulers.io()) - .observeOn(Schedulers.computation()) .subscribe(); } rosterRepository.updateRosterVersionForAccount(accountId, version) .subscribeOn(Schedulers.io()) - .observeOn(Schedulers.computation()) .subscribe(); return true; @@ -130,6 +137,7 @@ public class RosterStore implements org.jivesoftware.smack.roster.rosterstore.Ro @Override public boolean removeEntry(Jid bareJid, String version) { + LOGGER.log(Level.INFO, "Remove entry " + bareJid.toString()); rosterRepository.deleteContact(accountId, bareJid.asEntityBareJidOrThrow()) .subscribeOn(Schedulers.io()) .observeOn(Schedulers.computation()) @@ -143,6 +151,7 @@ public class RosterStore implements org.jivesoftware.smack.roster.rosterstore.Ro @Override public void resetStore() { + LOGGER.log(Level.INFO, "Reset Store"); rosterRepository.deleteAllContactsOfAccount(accountId) .subscribeOn(Schedulers.io()) .observeOn(Schedulers.computation()) @@ -169,13 +178,12 @@ public class RosterStore implements org.jivesoftware.smack.roster.rosterstore.Ro ContactModel contact = rosterRepository.newContactModel(); contact.setAccountId(accountId); - ContactModel attributes = rosterRepository.newContactModel(); - attributes.setRosterName(item.getName()); + contact.setRosterName(item.getName()); if (item.getItemType() != null) { - attributes.setDirection(convert(item.getItemType())); + contact.setDirection(convert(item.getItemType())); } - attributes.setApproved(item.isApproved()); - attributes.setSubscriptionPending(item.isSubscriptionPending()); + contact.setApproved(item.isApproved()); + contact.setSubscriptionPending(item.isSubscriptionPending()); EntityModel entity = rosterRepository.newEntityModel(); entity.setAccountId(accountId); diff --git a/persistence-room/schemas/org.mercury_im.messenger.persistence.room.AppDatabase/1.json b/persistence-room/schemas/org.mercury_im.messenger.persistence.room.AppDatabase/1.json index 6b2d3d5..233993a 100644 --- a/persistence-room/schemas/org.mercury_im.messenger.persistence.room.AppDatabase/1.json +++ b/persistence-room/schemas/org.mercury_im.messenger.persistence.room.AppDatabase/1.json @@ -2,8 +2,129 @@ "formatVersion": 1, "database": { "version": 1, - "identityHash": "cc1de3c4fd3350ea0e894d02f30312f3", + "identityHash": "fdd5152ff14dba77ae4dba978fc0bb11", "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", "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", - "unique": false, + "unique": true, "columnNames": [ "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", @@ -124,36 +245,18 @@ ] }, { - "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)", + "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": "id", + "fieldPath": "accountId", "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", + "fieldPath": "rosterVersion", + "columnName": "roster_version", "affinity": "TEXT", "notNull": false } @@ -162,16 +265,16 @@ "columnNames": [ "pk_account_id" ], - "autoGenerate": true + "autoGenerate": false }, "indices": [ { - "name": "index_accounts_pk_account_id", - "unique": false, + "name": "index_roster_information_pk_account_id", + "unique": true, "columnNames": [ "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": [] @@ -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", "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": [] - }, - { - "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": [], "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, 'cc1de3c4fd3350ea0e894d02f30312f3')" + "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, 'fdd5152ff14dba77ae4dba978fc0bb11')" ] } } \ No newline at end of file diff --git a/persistence-room/src/main/java/org/mercury_im/messenger/persistence/room/dao/AccountDao.java b/persistence-room/src/main/java/org/mercury_im/messenger/persistence/room/dao/AccountDao.java index 2ccafc8..9d36031 100644 --- a/persistence-room/src/main/java/org/mercury_im/messenger/persistence/room/dao/AccountDao.java +++ b/persistence-room/src/main/java/org/mercury_im/messenger/persistence/room/dao/AccountDao.java @@ -43,4 +43,7 @@ public interface AccountDao extends BaseDao { @Query("update accounts set state = :state where pk_account_id = :accountId") Completable updateConnectionState(long accountId, String state); + + @Query("DELETE FROM accounts WHERE pk_account_id = :accountId") + Completable deleteAccount(long accountId); } diff --git a/persistence-room/src/main/java/org/mercury_im/messenger/persistence/room/model/RoomContactModel.java b/persistence-room/src/main/java/org/mercury_im/messenger/persistence/room/model/RoomContactModel.java index 5a9874d..56bbfd2 100644 --- a/persistence-room/src/main/java/org/mercury_im/messenger/persistence/room/model/RoomContactModel.java +++ b/persistence-room/src/main/java/org/mercury_im/messenger/persistence/room/model/RoomContactModel.java @@ -24,7 +24,7 @@ import static org.mercury_im.messenger.persistence.room.model.RoomContactModel.T indices = { @Index(value = KEY_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) }, foreignKeys = { diff --git a/persistence-room/src/main/java/org/mercury_im/messenger/persistence/room/repository/IAccountRepository.java b/persistence-room/src/main/java/org/mercury_im/messenger/persistence/room/repository/IAccountRepository.java index 1dfdf50..6d15c5b 100644 --- a/persistence-room/src/main/java/org/mercury_im/messenger/persistence/room/repository/IAccountRepository.java +++ b/persistence-room/src/main/java/org/mercury_im/messenger/persistence/room/repository/IAccountRepository.java @@ -1,5 +1,6 @@ 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.room.dao.AccountDao; import org.mercury_im.messenger.persistence.room.model.RoomAccountModel; @@ -47,4 +48,14 @@ public class IAccountRepository implements AccountRepository { public Completable updateState(long accountId, String 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); + } } diff --git a/persistence-room/src/main/java/org/mercury_im/messenger/persistence/room/repository/IRosterRepository.java b/persistence-room/src/main/java/org/mercury_im/messenger/persistence/room/repository/IRosterRepository.java index bd8bcf5..0867679 100644 --- a/persistence-room/src/main/java/org/mercury_im/messenger/persistence/room/repository/IRosterRepository.java +++ b/persistence-room/src/main/java/org/mercury_im/messenger/persistence/room/repository/IRosterRepository.java @@ -1,5 +1,7 @@ package org.mercury_im.messenger.persistence.room.repository; +import android.util.Log; + import org.jxmpp.jid.EntityBareJid; import org.mercury_im.messenger.persistence.repository.RosterRepository; import org.mercury_im.messenger.persistence.room.dao.ContactDao; @@ -18,6 +20,9 @@ import io.reactivex.CompletableSource; import io.reactivex.Maybe; import io.reactivex.Observable; import io.reactivex.Single; +import io.reactivex.SingleObserver; +import io.reactivex.SingleSource; +import io.reactivex.functions.Consumer; import io.reactivex.functions.Function; public class IRosterRepository extends RosterRepository { @@ -58,20 +63,28 @@ public class IRosterRepository extends RosterRepository> getAllContactsOfAccount(long accountId) { return contactDao.getContactsForAccount(accountId) .flatMap(list -> { - for (RoomContactModel contact : list) { - RoomEntityModel entity = getEntityForContact(contact).blockingGet(); - contact.setEntity(entity); - } - return Observable.just(list); - }); + for (RoomContactModel contact : list) { + RoomEntityModel entity = getEntityForContact(contact).blockingGet(); + contact.setEntity(entity); + } + return Observable.just(list); + }); } @Override public Single updateOrInsertContact(RoomContactModel contact) { - return entityDao.insert((RoomEntityModel) contact.getEntity()) - .flatMap(entityId -> { - contact.getEntity().setId(entityId); - contact.setEntityId(entityId); + //noinspection unchecked + return entityDao.getEntityFor(contact.getAccountId(), contact.getEntity().getJid()) + .switchIfEmpty(insertOrReplaceEntity((RoomEntityModel) contact.getEntity()) + .flatMap(entityId -> { + Log.d("Mercury", "Insert entity since maybe did not emit: " + entityId); + contact.getEntity().setId(entityId); + return (SingleSource) observer -> + observer.onSuccess((RoomEntityModel) contact.getEntity()); + })) + .flatMap(entityModel -> { + contact.setEntityId(entityModel.getId()); + contact.setEntity(entityModel); return contactDao.insert(contact); }); } diff --git a/persistence/src/main/java/org/mercury_im/messenger/persistence/repository/AccountRepository.java b/persistence/src/main/java/org/mercury_im/messenger/persistence/repository/AccountRepository.java index 3a592f0..998e8e3 100644 --- a/persistence/src/main/java/org/mercury_im/messenger/persistence/repository/AccountRepository.java +++ b/persistence/src/main/java/org/mercury_im/messenger/persistence/repository/AccountRepository.java @@ -20,4 +20,8 @@ public interface AccountRepository { Single insertAccount(E accountModel); Completable updateState(long accountId, String state); + + Completable deleteAccount(E item); + + Completable deleteAccount(long accountId); } diff --git a/persistence/src/main/java/org/mercury_im/messenger/persistence/repository/RosterRepository.java b/persistence/src/main/java/org/mercury_im/messenger/persistence/repository/RosterRepository.java index ad03179..c629864 100644 --- a/persistence/src/main/java/org/mercury_im/messenger/persistence/repository/RosterRepository.java +++ b/persistence/src/main/java/org/mercury_im/messenger/persistence/repository/RosterRepository.java @@ -12,6 +12,7 @@ import io.reactivex.Completable; import io.reactivex.Maybe; import io.reactivex.Observable; import io.reactivex.Single; +import io.reactivex.functions.Consumer; public abstract class RosterRepository { @@ -73,6 +74,10 @@ public abstract class RosterRepository getContact(long id); + public Maybe 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}. *