Work on contact list - wip!

This commit is contained in:
Paul Schaub 2019-12-21 05:34:19 +01:00
parent ff04832614
commit 687288e697
Signed by: vanitasvitae
GPG Key ID: 62BEE9264BF17311
25 changed files with 420 additions and 190 deletions

View File

@ -18,8 +18,11 @@ import java.util.List;
import javax.inject.Inject; import javax.inject.Inject;
import io.reactivex.Completable; import io.reactivex.Completable;
import io.reactivex.Observable;
import io.reactivex.android.schedulers.AndroidSchedulers;
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;
public class ChatViewModel extends ViewModel { public class ChatViewModel extends ViewModel {
@ -85,22 +88,12 @@ public class ChatViewModel extends ViewModel {
} }
public void queryTextChanged(String query) { public void queryTextChanged(String query) {
/* Observable<List<Message>> observable = query.isEmpty() ?
if (query.isEmpty()) { messageRepository.observeMessages(chat.getValue()) :
disposable.add(messageRepository.getAllMessagesOfChat(accountId, jid) messageRepository.findMessagesWithBody(chat.getValue(), query);
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe((Consumer<List<MessageModel>>)
messages -> ChatViewModel.this.messages.setValue(messages)));
}
disposable.add(messageRepository.findMessageByQuery(accountId, jid, query)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe((Consumer<List<MessageModel>>) o -> {
messages.setValue(o);
}));
*/ disposable.add(observable.subscribe(messages ->
ChatViewModel.this.messages.setValue(messages)));
} }
public Completable requestMamMessages() { public Completable requestMamMessages() {

View File

@ -1,6 +1,6 @@
package org.mercury_im.messenger.ui.chat; package org.mercury_im.messenger.ui.chat;
import android.util.SparseArray; import android.util.SparseBooleanArray;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
@ -20,7 +20,7 @@ import java.util.List;
public class MessagesRecyclerViewAdapter extends RecyclerView.Adapter<MessagesRecyclerViewAdapter.MessageViewHolder> { public class MessagesRecyclerViewAdapter extends RecyclerView.Adapter<MessagesRecyclerViewAdapter.MessageViewHolder> {
private List<Message> messages = new ArrayList<>(); private List<Message> messages = new ArrayList<>();
private SparseArray<Boolean> checkedItems = new SparseArray<>(); private SparseBooleanArray checkedItems = new SparseBooleanArray();
public MessagesRecyclerViewAdapter() { public MessagesRecyclerViewAdapter() {
@ -50,22 +50,22 @@ public class MessagesRecyclerViewAdapter extends RecyclerView.Adapter<MessagesRe
public void onBindViewHolder(@NonNull MessageViewHolder holder, int position) { public void onBindViewHolder(@NonNull MessageViewHolder holder, int position) {
Message message = messages.get(position); Message message = messages.get(position);
MessageBackgroundDrawable background = getBackgroundDrawable(position); MessageBackgroundDrawable background = getBackgroundDrawable(position);
// if (message.isIncoming()) { if (message.isIncoming()) {
holder.body.setBackgroundResource(background.getIncomingDrawable()); holder.body.setBackgroundResource(background.getIncomingDrawable());
// } else { } else {
// holder.body.setBackgroundResource(background.getOutgoingDrawable()); holder.body.setBackgroundResource(background.getOutgoingDrawable());
// } }
holder.body.setText(((TextPayload) message.getMessagePayloads().get(0).getMessageContents().get(0)).getBody()); holder.body.setText(((TextPayload) message.getMessagePayloads().get(0).getMessageContents().get(0)).getBody());
holder.body.requestLayout(); holder.body.requestLayout();
} }
@Override @Override
public int getItemViewType(int position) { public int getItemViewType(int position) {
// if (messages.get(position).isIncoming()) { if (messages.get(position).isIncoming()) {
return R.layout.view_message_text_in; return R.layout.view_message_text_in;
// } else { } else {
// return R.layout.view_message_text_out; return R.layout.view_message_text_out;
// } }
} }
@Override @Override
@ -89,22 +89,22 @@ public class MessagesRecyclerViewAdapter extends RecyclerView.Adapter<MessagesRe
Message predecessor = pos != 0 ? messages.get(pos - 1) : null; Message predecessor = pos != 0 ? messages.get(pos - 1) : null;
Message successor = pos != messages.size() - 1 ? messages.get(pos + 1) : null; Message successor = pos != messages.size() - 1 ? messages.get(pos + 1) : null;
// if (predecessor == null || predecessor.isIncoming() != subject.isIncoming()) { if (predecessor == null || predecessor.isIncoming() != subject.isIncoming()) {
// if (successor == null || successor.isIncoming() != subject.isIncoming()) { if (successor == null || successor.isIncoming() != subject.isIncoming()) {
// This is a single message // This is a single message
return MessageBackgroundDrawable.SINGLE; return MessageBackgroundDrawable.SINGLE;
// } else { } else {
// This is the first message of a group // This is the first message of a group
// return MessageBackgroundDrawable.FIRST; return MessageBackgroundDrawable.FIRST;
// } }
// } else { } else {
// if (successor == null || successor.isIncoming() != subject.isIncoming()) { if (successor == null || successor.isIncoming() != subject.isIncoming()) {
// This is the last message of a group // This is the last message of a group
// return MessageBackgroundDrawable.LAST; return MessageBackgroundDrawable.LAST;
// } else { } else {
// This message is in the middle of a group // This message is in the middle of a group
// return MessageBackgroundDrawable.MID; return MessageBackgroundDrawable.MID;
// } }
// } }
} }
} }

View File

@ -23,6 +23,8 @@ import butterknife.ButterKnife;
import org.mercury_im.messenger.Messenger; import org.mercury_im.messenger.Messenger;
import org.mercury_im.messenger.R; import org.mercury_im.messenger.R;
import java.util.ArrayList;
public class ChatListFragment extends Fragment { public class ChatListFragment extends Fragment {
private ChatListViewModel viewModel; private ChatListViewModel viewModel;
@ -63,10 +65,6 @@ public class ChatListFragment extends Fragment {
super.onAttach(context); super.onAttach(context);
viewModel = new ViewModelProvider(getActivity()).get(ChatListViewModel.class); viewModel = new ViewModelProvider(getActivity()).get(ChatListViewModel.class);
viewModel.getChats().observe(this, chatModels -> { viewModel.getChats().observe(this, chatModels -> {
if (chatModels == null) {
Log.d(Messenger.TAG, "Displaying null chats");
return;
}
Log.d(Messenger.TAG, "Displaying " + chatModels.size() + " chats"); Log.d(Messenger.TAG, "Displaying " + chatModels.size() + " chats");
recyclerViewAdapter.setModels(chatModels); recyclerViewAdapter.setModels(chatModels);
}); });

View File

@ -8,6 +8,7 @@ import org.mercury_im.messenger.MercuryImApplication;
import org.mercury_im.messenger.data.repository.DirectChatRepository; import org.mercury_im.messenger.data.repository.DirectChatRepository;
import org.mercury_im.messenger.entity.chat.DirectChat; import org.mercury_im.messenger.entity.chat.DirectChat;
import java.util.ArrayList;
import java.util.List; import java.util.List;
import javax.inject.Inject; import javax.inject.Inject;
@ -21,7 +22,7 @@ public class ChatListViewModel extends ViewModel {
private CompositeDisposable disposable = new CompositeDisposable(); private CompositeDisposable disposable = new CompositeDisposable();
private final MutableLiveData<List<DirectChat>> chats = new MutableLiveData<>(); private final MutableLiveData<List<DirectChat>> chats = new MutableLiveData<>(new ArrayList<>());
public ChatListViewModel() { public ChatListViewModel() {
MercuryImApplication.getApplication().getAppComponent().inject(this); MercuryImApplication.getApplication().getAppComponent().inject(this);

View File

@ -31,10 +31,7 @@ public class ContactListViewModel extends ViewModel {
Log.d("ContactListViewModel", "Start observing database"); Log.d("ContactListViewModel", "Start observing database");
// Subscribe to changes to the contacts table and update the LiveData object for the UI. // Subscribe to changes to the contacts table and update the LiveData object for the UI.
compositeDisposable.add(xmppContactRepository.observeAllPeers() compositeDisposable.add(xmppContactRepository.observeAllPeers()
.subscribe(list -> { .subscribe(rosterEntryList::setValue));
Log.d("ContactListViewModel", "Room changed contacts: " + list.size());
rosterEntryList.setValue(list);
}));
} }
@Override @Override

View File

@ -0,0 +1,44 @@
package org.mercury_im.messenger.data.converter;
import org.mercury_im.messenger.entity.message.MessageDirection;
import io.requery.Converter;
public class MessageDirectionConverter implements Converter<MessageDirection, Integer> {
@Override
public Class<MessageDirection> getMappedType() {
return MessageDirection.class;
}
@Override
public Class<Integer> getPersistedType() {
return Integer.class;
}
@Override
public Integer getPersistedSize() {
return null;
}
@Override
public Integer convertToPersisted(MessageDirection value) {
switch (value) {
case outgoing:
return 0;
case incoming:
return 1;
default: return 1;
}
}
@Override
public MessageDirection convertToMapped(Class<? extends MessageDirection> type, Integer value) {
switch (value) {
case 0:
return MessageDirection.outgoing;
case 1:
return MessageDirection.incoming;
default: return MessageDirection.incoming;
}
}
}

View File

@ -1,6 +1,6 @@
package org.mercury_im.messenger.data.converter; package org.mercury_im.messenger.data.converter;
import org.mercury_im.messenger.data.enums.SubscriptionDirection; import org.mercury_im.messenger.entity.contact.SubscriptionDirection;
import io.requery.Converter; import io.requery.Converter;

View File

@ -35,6 +35,7 @@ public class MessageMapping extends AbstractMapping<Message, MessageModel> {
model.setSender(entity.getSender()); model.setSender(entity.getSender());
model.setRecipient(entity.getRecipient()); model.setRecipient(entity.getRecipient());
model.setTimestamp(entity.getTimestamp()); model.setTimestamp(entity.getTimestamp());
model.setDirection(entity.getDirection());
model.getPayloads().clear(); model.getPayloads().clear();
for (PayloadContainer payload : entity.getMessagePayloads()) { for (PayloadContainer payload : entity.getMessagePayloads()) {
@ -52,6 +53,7 @@ public class MessageMapping extends AbstractMapping<Message, MessageModel> {
entity.setSender(model.getSender()); entity.setSender(model.getSender());
entity.setRecipient(model.getRecipient()); entity.setRecipient(model.getRecipient());
entity.setTimestamp(model.getTimestamp()); entity.setTimestamp(model.getTimestamp());
entity.setDirection(model.getDirection());
List<PayloadContainer> payloadContainers = new ArrayList<>(entity.getMessagePayloads().size()); List<PayloadContainer> payloadContainers = new ArrayList<>(entity.getMessagePayloads().size());
for (MessagePayloadContainerModel containerModel : model.getPayloads()) { for (MessagePayloadContainerModel containerModel : model.getPayloads()) {

View File

@ -1,6 +1,5 @@
package org.mercury_im.messenger.data.mapping; package org.mercury_im.messenger.data.mapping;
import org.mercury_im.messenger.data.enums.SubscriptionDirection;
import org.mercury_im.messenger.data.model.PeerModel; import org.mercury_im.messenger.data.model.PeerModel;
import org.mercury_im.messenger.entity.contact.IPeer; import org.mercury_im.messenger.entity.contact.IPeer;
import org.mercury_im.messenger.entity.contact.Peer; import org.mercury_im.messenger.entity.contact.Peer;
@ -32,7 +31,9 @@ public class PeerMapping extends AbstractMapping<Peer, PeerModel> {
model.setAddress(entity.getAddress()); model.setAddress(entity.getAddress());
model.setName(entity.getName()); model.setName(entity.getName());
// TODO: sub direction model.setSubscriptionDirection(entity.getSubscriptionDirection());
model.setSubscriptionPending(entity.isSubscriptionPending());
model.setSubscriptionPreApproved(entity.isSubscriptionApproved());
return model; return model;
} }
@ -43,75 +44,10 @@ public class PeerMapping extends AbstractMapping<Peer, PeerModel> {
entity.setAddress(model.getAddress()); entity.setAddress(model.getAddress());
entity.setId(model.getId()); entity.setId(model.getId());
// TODO: Sub direction entity.setSubscriptionDirection(model.getSubscriptionDirection());
entity.setSubscriptionPending(model.isSubscriptionPending());
entity.setSubscriptionApproved(model.isSubscriptionPreApproved());
return entity; return entity;
} }
private void setSubscriptionDirectionInModel(PeerModel model, Peer entity) {
switch (entity.getSubscriptionMode()) {
case NONE:
model.setSubscriptionDirection(SubscriptionDirection.none);
model.setSubscriptionPreApproved(false);
model.setSubscriptionPending(false);
break;
case NONE_PREAPPROVED:
model.setSubscriptionDirection(SubscriptionDirection.none);
model.setSubscriptionPreApproved(true);
model.setSubscriptionPending(false);
break;
case TO_THEM_PENDING:
model.setSubscriptionDirection(SubscriptionDirection.to);
model.setSubscriptionPending(true);
model.setSubscriptionPreApproved(false);
break;
case TO_THEM_PENDING_PREAPPROVED:
model.setSubscriptionDirection(SubscriptionDirection.to);
model.setSubscriptionPending(true);
model.setSubscriptionPreApproved(true);
break;
case TO_THEM_ACCEPTED:
model.setSubscriptionDirection(SubscriptionDirection.to);
model.setSubscriptionPending(false);
model.setSubscriptionPreApproved(false);
break;
case TO_THEM_ACCEPTED_PREAPPROVED:
model.setSubscriptionDirection(SubscriptionDirection.to);
model.setSubscriptionPending(false);
model.setSubscriptionPreApproved(true);
break;
case FROM_THEM_PENDING:
model.setSubscriptionDirection(SubscriptionDirection.from);
model.setSubscriptionPending(true);
model.setSubscriptionPreApproved(false);
break;
case FROM_THEM_ACCEPTED:
model.setSubscriptionDirection(SubscriptionDirection.from);
model.setSubscriptionPending(false);
model.setSubscriptionPreApproved(false);
break;
case BIDIRECTIONAL:
model.setSubscriptionDirection(SubscriptionDirection.both);
model.setSubscriptionPending(false);
model.setSubscriptionPreApproved(false);
break;
}
}
private void setSubscriptionDirectionInEntity(Peer entity, PeerModel model) {
switch (model.getSubscriptionDirection()) {
case none:
break;
case to:
break;
case from:
break;
case both:
break;
case remove:
break;
}
}
} }

View File

@ -1,9 +1,13 @@
package org.mercury_im.messenger.data.model; package org.mercury_im.messenger.data.model;
import org.mercury_im.messenger.data.converter.MessageDirectionConverter;
import org.mercury_im.messenger.entity.message.MessageDirection;
import java.util.Date; import java.util.Date;
import java.util.Set; import java.util.Set;
import io.requery.Column; import io.requery.Column;
import io.requery.Convert;
import io.requery.Entity; import io.requery.Entity;
import io.requery.Generated; import io.requery.Generated;
import io.requery.Key; import io.requery.Key;
@ -27,6 +31,10 @@ public abstract class AbstractMessageModel implements Persistable {
@Column(name = "\"timestamp\"", nullable = false) @Column(name = "\"timestamp\"", nullable = false)
Date timestamp; Date timestamp;
@Column(nullable = false)
@Convert(MessageDirectionConverter.class)
MessageDirection direction;
@OneToMany @OneToMany
Set<MessagePayloadContainerModel> payloads; Set<MessagePayloadContainerModel> payloads;

View File

@ -1,7 +1,7 @@
package org.mercury_im.messenger.data.model; package org.mercury_im.messenger.data.model;
import org.mercury_im.messenger.data.converter.SubscriptionDirectionConverter; import org.mercury_im.messenger.data.converter.SubscriptionDirectionConverter;
import org.mercury_im.messenger.data.enums.SubscriptionDirection; import org.mercury_im.messenger.entity.contact.SubscriptionDirection;
import io.requery.Column; import io.requery.Column;
import io.requery.Convert; import io.requery.Convert;

View File

@ -180,7 +180,7 @@ public class XmppMessageRepository
.and(DirectMessagesRelation.MESSAGE_ID.eq(message.getId())) .and(DirectMessagesRelation.MESSAGE_ID.eq(message.getId()))
.get().maybe() .get().maybe()
.switchIfEmpty(Single.just(message) .switchIfEmpty(Single.just(message)
.map(messageMapping::toModel) .map(messageMapping::toEntity)
.flatMap(messageModel -> ) .flatMap(messageModel -> )
*/ */
} }

View File

@ -2,6 +2,7 @@ package org.mercury_im.messenger.data.repository;
import org.mercury_im.messenger.data.mapping.PeerMapping; import org.mercury_im.messenger.data.mapping.PeerMapping;
import org.mercury_im.messenger.data.model.PeerModel; import org.mercury_im.messenger.data.model.PeerModel;
import org.mercury_im.messenger.entity.contact.SubscriptionDirection;
import org.mercury_im.messenger.util.Optional; import org.mercury_im.messenger.util.Optional;
import org.mercury_im.messenger.entity.Account; import org.mercury_im.messenger.entity.Account;
import org.mercury_im.messenger.entity.contact.IPeer; import org.mercury_im.messenger.entity.contact.IPeer;
@ -9,6 +10,8 @@ import org.mercury_im.messenger.entity.contact.Peer;
import org.mercury_im.messenger.util.ThreadUtils; import org.mercury_im.messenger.util.ThreadUtils;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List; import java.util.List;
import javax.inject.Inject; import javax.inject.Inject;
@ -130,6 +133,25 @@ public class XmppPeerRepository
.observeOn(observerScheduler()); .observeOn(observerScheduler());
} }
@Override
public Observable<List<Peer>> observeAllContactsOfAccount(long accountId) {
return data().select(PeerModel.class)
.where(PeerModel.ACCOUNT_ID.eq(accountId))
.and(PeerModel.SUBSCRIPTION_DIRECTION.in(
Arrays.asList(SubscriptionDirection.both, SubscriptionDirection.to)))
.get().observableResult()
.map(ResultDelegate::toList)
.map(peerModels -> {
List<Peer> peerEntities = new ArrayList<>(peerModels.size());
for (PeerModel model : peerModels) {
peerEntities.add(peerMapping.toEntity(model));
}
return peerEntities;
})
.subscribeOn(subscriberScheduler())
.observeOn(observerScheduler());
}
@Override @Override
public Single<Peer> updatePeer(Peer peer) { public Single<Peer> updatePeer(Peer peer) {
// In order to update, we fetch the model, update it and write it back. // In order to update, we fetch the model, update it and write it back.
@ -170,4 +192,9 @@ public class XmppPeerRepository
.subscribeOn(subscriberScheduler()) .subscribeOn(subscriberScheduler())
.observeOn(observerScheduler()); .observeOn(observerScheduler());
} }
@Override
public Completable deletePeer(long accountId, String address) {
return null;
}
} }

View File

@ -6,7 +6,6 @@ import org.mercury_im.messenger.data.model.AccountModel;
import org.mercury_im.messenger.data.model.PeerModel; import org.mercury_im.messenger.data.model.PeerModel;
import org.mercury_im.messenger.entity.contact.IPeer; import org.mercury_im.messenger.entity.contact.IPeer;
import org.mercury_im.messenger.entity.contact.Peer; import org.mercury_im.messenger.entity.contact.Peer;
import org.mercury_im.messenger.entity.contact.SubscriptionMode;
import javax.inject.Inject; import javax.inject.Inject;
@ -25,7 +24,7 @@ public class PeerMappingTest {
PEER_GORDO.setId(1); PEER_GORDO.setId(1);
PEER_GORDO.setAddress("gordo@big.joe"); PEER_GORDO.setAddress("gordo@big.joe");
PEER_GORDO.setName("Gordo"); PEER_GORDO.setName("Gordo");
PEER_GORDO.setSubscriptionMode(SubscriptionMode.TO_THEM_ACCEPTED_PREAPPROVED); PEER_GORDO.setSubscriptionDirection(SubscriptionMode.TO_THEM_ACCEPTED_PREAPPROVED);
} }
public PeerMappingTest() { public PeerMappingTest() {

View File

@ -47,7 +47,7 @@ public class AccountRepositoryTest {
Account a1 = new IAccount(); Account a1 = new IAccount();
a1.setAddress("a1@example.com"); a1.setAddress("a1@example.com");
a1.setAuthentication(new PasswordAuthentication("a1a1a1")); a1.setPassword("a1a1a1");
a1.setEnabled(true); a1.setEnabled(true);
d.add(accountRepository.insertAccount(a1).subscribe()); d.add(accountRepository.insertAccount(a1).subscribe());
@ -57,7 +57,7 @@ public class AccountRepositoryTest {
Account a2 = new IAccount(); Account a2 = new IAccount();
a2.setAddress("a2@example.com"); a2.setAddress("a2@example.com");
a2.setAuthentication(new PasswordAuthentication("a2a2a2")); a2.setPassword("a2a2a2");
a2.setEnabled(false); a2.setEnabled(false);
d.add(accountRepository.insertAccount(a2).subscribe()); d.add(accountRepository.insertAccount(a2).subscribe());
@ -66,7 +66,7 @@ public class AccountRepositoryTest {
Account a3 = new IAccount(); Account a3 = new IAccount();
a3.setAddress("a3@example.com"); a3.setAddress("a3@example.com");
a3.setAuthentication(new PasswordAuthentication("a3a3a3")); a3.setPassword("a3a3a3");
a3.setEnabled(false); a3.setEnabled(false);
d.add(accountRepository.insertAccount(a3).subscribe()); d.add(accountRepository.insertAccount(a3).subscribe());

View File

@ -2,9 +2,11 @@ package org.mercury_im.messenger;
import org.jivesoftware.smack.SmackException; import org.jivesoftware.smack.SmackException;
import org.jivesoftware.smack.XMPPConnection; import org.jivesoftware.smack.XMPPConnection;
import org.jivesoftware.smack.roster.Roster;
import org.jivesoftware.smackx.csi.ClientStateIndicationManager; import org.jivesoftware.smackx.csi.ClientStateIndicationManager;
import org.mercury_im.messenger.data.repository.Repositories; import org.mercury_im.messenger.data.repository.Repositories;
import org.mercury_im.messenger.entity.Account; import org.mercury_im.messenger.entity.Account;
import org.mercury_im.messenger.store.MercuryRosterStore;
import org.mercury_im.messenger.usecase.AddAccount; import org.mercury_im.messenger.usecase.AddAccount;
import org.mercury_im.messenger.xmpp.MercuryConnection; import org.mercury_im.messenger.xmpp.MercuryConnection;
@ -30,6 +32,13 @@ public class Messenger implements ClientStateListener {
public void addConnection(MercuryConnection connection) { public void addConnection(MercuryConnection connection) {
connections.put(connection.getAccount().getId(), connection); connections.put(connection.getAccount().getId(), connection);
initRosterStore(connection);
}
private void initRosterStore(MercuryConnection connection) {
Roster.getInstanceFor(connection.getConnection())
.setRosterStore(new MercuryRosterStore(connection.getAccount(),
repositories.getPeerRepository(), repositories.getAccountRepository()));
} }
public MercuryConnection getConnection(Account account) { public MercuryConnection getConnection(Account account) {

View File

@ -41,9 +41,17 @@ public interface PeerRepository {
Observable<List<Peer>> observeAllPeers(); Observable<List<Peer>> observeAllPeers();
default Observable<List<Peer>> observeAllContactsOfAccount(Account account) {
return observeAllContactsOfAccount(account.getId());
}
Observable<List<Peer>> observeAllContactsOfAccount(long accountId);
Single<Peer> updatePeer(Peer Peer); Single<Peer> updatePeer(Peer Peer);
Single<Peer> upsertPeer(Peer Peer); Single<Peer> upsertPeer(Peer Peer);
Completable deletePeer(Peer Peer); Completable deletePeer(Peer Peer);
Completable deletePeer(long accountId, String address);
} }

View File

@ -0,0 +1,207 @@
package org.mercury_im.messenger.store;
import org.jivesoftware.smack.roster.packet.RosterPacket;
import org.jivesoftware.smack.roster.rosterstore.RosterStore;
import org.jxmpp.jid.Jid;
import org.jxmpp.jid.impl.JidCreate;
import org.mercury_im.messenger.data.repository.AccountRepository;
import org.mercury_im.messenger.data.repository.PeerRepository;
import org.mercury_im.messenger.entity.Account;
import org.mercury_im.messenger.entity.contact.IPeer;
import org.mercury_im.messenger.entity.contact.Peer;
import org.mercury_im.messenger.entity.contact.SubscriptionDirection;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;
import io.reactivex.disposables.CompositeDisposable;
import io.reactivex.schedulers.Schedulers;
public class MercuryRosterStore implements RosterStore {
private static final Logger LOGGER = Logger.getLogger(RosterStore.class.getName());
private final PeerRepository peerRepository;
private final AccountRepository accountRepository;
private Account account;
private final CompositeDisposable disposable = new CompositeDisposable();
private final Map<String, RosterPacket.Item> itemMap = new HashMap<>();
private String rosterVersion;
public MercuryRosterStore(Account account, PeerRepository rosterRepository, AccountRepository accountRepository) {
this.account = account;
this.peerRepository = rosterRepository;
this.accountRepository = accountRepository;
}
public void subscribe() {
disposable.add(peerRepository.observeAllContactsOfAccount(account)
.observeOn(Schedulers.computation())
.subscribe(contactsList -> {
itemMap.clear();
for (Peer contactModel : contactsList) {
itemMap.put(contactModel.getAddress(), fromEntity(contactModel));
LOGGER.log(Level.INFO, "Populate itemMap with " + contactsList.size() + " items");
}
},
error -> LOGGER.log(Level.WARNING, "An error occurred while updating roster cache", error)));
/*
disposable.add(peerRepository.getRosterVersion(account)
.observeOn(Schedulers.computation())
.subscribe(
result -> setRosterVersion(result),
error -> LOGGER.log(Level.WARNING, "An error occurred updating cached roster version", error)));
*/
}
public void unsubscribe() {
disposable.dispose();
}
private void setRosterVersion(String rosterVersion) {
this.rosterVersion = rosterVersion;
}
@Override
public List<RosterPacket.Item> getEntries() {
return new ArrayList<>(itemMap.values());
}
@Override
public RosterPacket.Item getEntry(Jid bareJid) {
return itemMap.get(bareJid);
}
@Override
public String getRosterVersion() {
return rosterVersion != null ? rosterVersion : "";
}
@Override
public boolean addEntry(RosterPacket.Item item, String version) {
LOGGER.log(Level.INFO, "Add entry " + item.toXML().toString());
// Update database
Peer contact = toEntity(item);
disposable.add(peerRepository.upsertPeer(contact)
.subscribe(
success -> LOGGER.log(Level.FINE, "Upserted contact model " + success + " successfully"),
error -> LOGGER.log(Level.WARNING, "An error occurred upserting contact " + contact, error)
));
/*
disposable.add(peerRepository.updateRosterVersion(account, version)
.subscribe(
success -> LOGGER.log(Level.FINE, "Upserted roster version to " + rosterVersion + " successfully"),
error -> LOGGER.log(Level.WARNING, "An error occurred upserting roster version", error)
));
*/
return true;
}
@Override
public boolean resetEntries(Collection<RosterPacket.Item> items, String version) {
LOGGER.log(Level.INFO, "Reset Entries: " + Arrays.toString(items.toArray()));
// Update database
// TODO: Delete other contacts
for (RosterPacket.Item item : items) {
Peer model = toEntity(item);
disposable.add(peerRepository.upsertPeer(model)
.subscribe(
success -> LOGGER.log(Level.FINE, "Upserted contact model " + success + " successfully"),
error -> LOGGER.log(Level.WARNING, "An error occurred upserting contact " + model, error)
));
}
/*
disposable.add(peerRepository.updateRosterVersion(account, version)
.subscribe(
success -> LOGGER.log(Level.FINE, "Upserted roster version to " + rosterVersion + " successfully"),
error -> LOGGER.log(Level.WARNING, "An error occurred upserting roster version", error)
));
*/
return true;
}
@Override
public boolean removeEntry(Jid bareJid, String version) {
LOGGER.log(Level.INFO, "Remove entry " + bareJid.toString());
disposable.add(peerRepository.deletePeer(account.getId(), bareJid.asEntityBareJidOrThrow().asEntityBareJidString())
.subscribe(
() -> LOGGER.log(Level.FINE, "Deletion of contact " + bareJid.toString() + " successful"),
error -> LOGGER.log(Level.WARNING, "An error occurred deleting contact " + bareJid.toString(), error)
));
/*
disposable.add(peerRepository.updateRosterVersion(account, version)
.subscribe(
success -> LOGGER.log(Level.FINE, "Upserted roster version to " + rosterVersion + " successfully"),
error -> LOGGER.log(Level.WARNING, "An error occurred upserting roster version", error)
));
*/
return true;
}
@Override
public void resetStore() {
LOGGER.log(Level.INFO, "Reset Store");
/*
disposable.add(peerRepository.deleteAllContactsOfAccount(account)
.subscribe(
success -> LOGGER.log(Level.FINE, "Successfully reset store."),
error -> LOGGER.log(Level.WARNING, "An error occurred resetting store", error)
));
disposable.add(peerRepository.updateRosterVersion(account, "")
.subscribe(
success -> LOGGER.log(Level.FINE, "Successfully reset roster version"),
error -> LOGGER.log(Level.WARNING, "An error occurred resetting roster version", error)
));
*/
}
public RosterPacket.Item fromEntity(Peer contactModel) {
RosterPacket.Item item = new RosterPacket.Item(
JidCreate.entityBareFromOrThrowUnchecked(contactModel.getAddress()),
contactModel.getName());
if (contactModel.getSubscriptionDirection() != null) {
item.setItemType(convert(contactModel.getSubscriptionDirection()));
}
item.setApproved(contactModel.isSubscriptionApproved());
item.setSubscriptionPending(contactModel.isSubscriptionPending());
return item;
}
public Peer toEntity(RosterPacket.Item item) {
Peer peer = new IPeer();
peer.setAccount(account);
peer.setAddress(item.getJid().asEntityBareJidOrThrow().asEntityBareJidString());
peer.setName(item.getName());
if (item.getItemType() != null) {
peer.setSubscriptionDirection(convert(item.getItemType()));
}
peer.setSubscriptionApproved(item.isApproved());
peer.setSubscriptionPending(item.isSubscriptionPending());
return peer;
}
public SubscriptionDirection convert(RosterPacket.ItemType type) {
return SubscriptionDirection.valueOf(type.toString());
}
public RosterPacket.ItemType convert(SubscriptionDirection direction) {
return RosterPacket.ItemType.fromString(direction.toString());
}
}

View File

@ -8,7 +8,9 @@ public class IPeer implements Peer {
protected Account account; protected Account account;
protected String address; protected String address;
protected String name; protected String name;
protected SubscriptionMode subscriptionMode; protected SubscriptionDirection subscriptionDirection;
protected boolean pending;
protected boolean approved;
@Override @Override
public long getId() { public long getId() {
@ -51,18 +53,38 @@ public class IPeer implements Peer {
} }
@Override @Override
public SubscriptionMode getSubscriptionMode() { public SubscriptionDirection getSubscriptionDirection() {
return subscriptionMode; return subscriptionDirection;
} }
@Override @Override
public void setSubscriptionMode(SubscriptionMode mode) { public void setSubscriptionDirection(SubscriptionDirection mode) {
this.subscriptionMode = mode; this.subscriptionDirection = mode;
}
@Override
public boolean isSubscriptionPending() {
return pending;
}
@Override
public void setSubscriptionPending(boolean pending) {
this.pending = pending;
}
@Override
public boolean isSubscriptionApproved() {
return approved;
}
@Override
public void setSubscriptionApproved(boolean approved) {
this.approved = approved;
} }
@Override @Override
public boolean isContact() { public boolean isContact() {
return subscriptionMode != SubscriptionMode.NONE return subscriptionDirection != SubscriptionDirection.none
&& subscriptionMode != SubscriptionMode.FROM_THEM_PENDING; && subscriptionDirection != SubscriptionDirection.from;
} }
} }

View File

@ -26,9 +26,17 @@ public interface Peer {
void setName(String name); void setName(String name);
SubscriptionMode getSubscriptionMode(); SubscriptionDirection getSubscriptionDirection();
void setSubscriptionMode(SubscriptionMode mode); void setSubscriptionDirection(SubscriptionDirection mode);
boolean isSubscriptionPending();
void setSubscriptionPending(boolean pending);
boolean isSubscriptionApproved();
void setSubscriptionApproved(boolean approved);
boolean isContact(); boolean isContact();
} }

View File

@ -1,4 +1,4 @@
package org.mercury_im.messenger.data.enums; package org.mercury_im.messenger.entity.contact;
public enum SubscriptionDirection { public enum SubscriptionDirection {
none, none,

View File

@ -1,55 +0,0 @@
package org.mercury_im.messenger.entity.contact;
/**
* Enum describing the relationship between the user and another entity.
*/
public enum SubscriptionMode {
/**
* No subscription between us an them.
*/
NONE,
/**
* No subscription between us and them.
* However, we pre-approved their subscription, should they decide to subscribe to us.
*/
NONE_PREAPPROVED,
/**
* We sent a subscription request, which is now pending to be accepted.
* We do not pre-approve their subscription to us.
*/
TO_THEM_PENDING,
/**
* We sent a subscription request, which is now pending to be accepted.
* Also, we pre-approved their subscription to us.
*/
TO_THEM_PENDING_PREAPPROVED,
/**
* We sent a subscription request and they approved it.
*/
TO_THEM_ACCEPTED,
/**
* We sent a subscription request and they approved it.
* Also, we pre-approved their subscription, should they decide to subscribe back.
*/
TO_THEM_ACCEPTED_PREAPPROVED,
/**
* They sent us a subscription request, but we did not yet accept it.
*/
FROM_THEM_PENDING,
/**
* They sent us a subscription request and we accepted it.
*/
FROM_THEM_ACCEPTED,
/**
* They accepted us and we accepted them.
*/
BIDIRECTIONAL
}

View File

@ -12,6 +12,8 @@ public class IMessage implements Message {
protected List<PayloadContainer> payloads; protected List<PayloadContainer> payloads;
protected MessageDeliveryState deliveryState; protected MessageDeliveryState deliveryState;
protected MessageMetadata metadata; protected MessageMetadata metadata;
protected MessageDirection direction;
@Override @Override
public long getId() { public long getId() {
@ -53,6 +55,16 @@ public class IMessage implements Message {
this.timestamp = timestamp; this.timestamp = timestamp;
} }
@Override
public MessageDirection getDirection() {
return direction;
}
@Override
public void setDirection(MessageDirection direction) {
this.direction = direction;
}
@Override @Override
public List<PayloadContainer> getMessagePayloads() { public List<PayloadContainer> getMessagePayloads() {
return payloads; return payloads;

View File

@ -21,6 +21,14 @@ public interface Message {
void setTimestamp(Date timestamp); void setTimestamp(Date timestamp);
MessageDirection getDirection();
void setDirection(MessageDirection direction);
default boolean isIncoming() {
return getDirection() == MessageDirection.incoming;
}
List<PayloadContainer> getMessagePayloads(); List<PayloadContainer> getMessagePayloads();
void setMessagePayloads(List<PayloadContainer> payloadContainers); void setMessagePayloads(List<PayloadContainer> payloadContainers);

View File

@ -0,0 +1,6 @@
package org.mercury_im.messenger.entity.message;
public enum MessageDirection {
incoming,
outgoing
}