Wip: Implement message store and display

This commit is contained in:
Paul Schaub 2020-06-05 16:35:16 +02:00
parent 206fd1c8da
commit 91084ad2a7
Signed by: vanitasvitae
GPG Key ID: 62BEE9264BF17311
17 changed files with 174 additions and 99 deletions

View File

@ -76,13 +76,17 @@ public class ChatViewModel extends ViewModel {
// Subscribe messages
disposable.add(messageRepository.observeMessages(chat)
.subscribe(ChatViewModel.this.messages::setValue,
.subscribe(messageList -> {
LOGGER.log(Level.INFO, "NEW MESSAGES.");
ChatViewModel.this.messages.setValue(messageList);
},
error -> LOGGER.log(Level.SEVERE, "Error subscribing to messages", error)));
}
@Override
protected void onCleared() {
super.onCleared();
LOGGER.log(Level.INFO, "CLEAR");
disposable.clear();
}
@ -133,6 +137,6 @@ public class ChatViewModel extends ViewModel {
public void sendMessage(String body) {
disposable.add(messenger.sendMessage(getContact().getValue(), body)
.subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread()).subscribe());
.subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread()).subscribe());
}
}

View File

@ -55,7 +55,7 @@ public class MessagesRecyclerViewAdapter extends RecyclerView.Adapter<MessagesRe
} else {
holder.body.setBackgroundResource(background.getOutgoingDrawable());
}
holder.body.setText(((TextPayload) message.getMessagePayloads().get(0).getMessageContents().get(0)).getBody());
holder.body.setText(message.getBody());
holder.body.requestLayout();
}

View File

@ -4,10 +4,7 @@ import android.app.AlertDialog;
import android.app.Dialog;
import android.content.Context;
import android.content.DialogInterface;
import android.database.DataSetObserver;
import android.opengl.Visibility;
import android.os.Bundle;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
@ -15,27 +12,20 @@ import android.widget.ArrayAdapter;
import android.widget.Button;
import android.widget.LinearLayout;
import android.widget.Spinner;
import android.widget.SpinnerAdapter;
import android.widget.TextView;
import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatDialogFragment;
import androidx.lifecycle.LiveData;
import com.google.android.material.textfield.TextInputEditText;
import com.google.android.material.textfield.TextInputLayout;
import org.jivesoftware.smack.SmackException;
import org.jivesoftware.smack.XMPPException;
import org.jivesoftware.smack.util.Async;
import org.jxmpp.jid.impl.JidCreate;
import org.jxmpp.stringprep.XmppStringprepException;
import org.mercury_im.messenger.Messenger;
import org.mercury_im.messenger.R;
import org.mercury_im.messenger.entity.Account;
import org.mercury_im.messenger.exception.ConnectionNotFoundException;
import org.mercury_im.messenger.exception.ContactAlreadyAddedException;
import java.util.List;
@ -87,16 +77,16 @@ public class AddContactDialogFragment extends AppCompatDialogFragment {
accountSelector.setAdapter(new AccountAdapter(requireActivity(), accounts));
accountSelector.setSelection(0);
builder.setMessage("Add Contact")
builder.setTitle(R.string.dialog_title_add_contact)
.setView(dialogView)
.setCancelable(false)
.setPositiveButton("Add", new DialogInterface.OnClickListener() {
.setPositiveButton(R.string.button_add, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
// Later overwrite in onResume.
}
})
.setNegativeButton("Cancel", new DialogInterface.OnClickListener() {
.setNegativeButton(R.string.button_cancel, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
AddContactDialogFragment.this.onCancel(dialog);
@ -111,35 +101,31 @@ public class AddContactDialogFragment extends AppCompatDialogFragment {
{
super.onResume();
final AlertDialog d = (AlertDialog)getDialog();
if(d != null)
{
Button positiveButton = d.getButton(Dialog.BUTTON_POSITIVE);
positiveButton.setOnClickListener(new View.OnClickListener()
{
@Override
public void onClick(View v)
{
Account account = accounts.get(accountSelector.getSelectedItemPosition());
String address = contactAddress.getText() != null ? contactAddress.getText().toString() : "";
disposable.add(messenger.addContact(account.getId(), address)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread()).subscribe(
if(d == null) {
return;
}
Button positiveButton = d.getButton(Dialog.BUTTON_POSITIVE);
positiveButton.setOnClickListener(v -> {
Account account = accounts.get(accountSelector.getSelectedItemPosition());
String address = contactAddress.getText() != null ? contactAddress.getText().toString() : "";
disposable.add(messenger.addContact(account.getId(), address)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
AddContactDialogFragment.this::dismiss,
e -> {
if (e instanceof SmackException.NotLoggedInException || e instanceof SmackException.NotConnectedException) {
contactAddressLayout.setError("Account not connected");
contactAddressLayout.setError(getString(R.string.error_account_not_connected));
} else if (e instanceof ContactAlreadyAddedException) {
contactAddressLayout.setError("Contact already added");
contactAddressLayout.setError(getString(R.string.error_contact_already_added));
} else if (e instanceof XmppStringprepException) {
contactAddressLayout.setError("Invalid address");
contactAddressLayout.setError(getString(R.string.error_invalid_address));
} else {
contactAddressLayout.setError(e.getClass().getName());
}
}
));
}
});
}
});
}
@Override

View File

@ -74,14 +74,11 @@ public class ContactDetailFragment extends Fragment {
ButterKnife.bind(this, view);
if (fab != null) {
fab.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent intent = new Intent(getContext(), ChatActivity.class);
intent.putExtra(ChatActivity.EXTRA_JID, viewModel.getContactAddress().getValue());
intent.putExtra(ChatActivity.EXTRA_ACCOUNT, viewModel.getAccountId().getValue().toString());
startActivity(intent);
}
fab.setOnClickListener(v -> {
Intent intent = new Intent(getContext(), ChatActivity.class);
intent.putExtra(ChatActivity.EXTRA_JID, viewModel.getContactAddress().getValue());
intent.putExtra(ChatActivity.EXTRA_ACCOUNT, viewModel.getAccountId().getValue().toString());
startActivity(intent);
});
}

View File

@ -26,7 +26,7 @@
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/address_layout"
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox"
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox.Dense"
android:layout_width="match_parent"
android:layout_height="wrap_content">

View File

@ -132,4 +132,10 @@
<string name="action_close_chat">Close Chat</string>
<string name="action_copy">Copy</string>
<string name="action_remove_contact">Remove Contact</string>
<string name="dialog_title_add_contact">Add Contact</string>
<string name="button_add">Add</string>
<string name="button_cancel">Cancel</string>
<string name="error_account_not_connected">Account not connected</string>
<string name="error_contact_already_added">Contact already added</string>
<string name="error_invalid_address">Invalid address</string>
</resources>

View File

@ -31,17 +31,15 @@ public class MessageMapping extends AbstractMapping<Message, MessageModel> {
@Override
public MessageModel mapToModel(Message entity, MessageModel model) {
model.setId(entity.getId());
model.setSender(entity.getSender());
model.setRecipient(entity.getRecipient());
model.setTimestamp(entity.getTimestamp());
model.setDirection(entity.getDirection());
model.getPayloads().clear();
for (PayloadContainer payload : entity.getMessagePayloads()) {
MessagePayloadContainerModel payloadModel = messagePayloadContainerMapping.toModel(payload, new MessagePayloadContainerModel());
payloadModel.setMessage(model);
model.getPayloads().add(payloadModel);
}
model.setBody(entity.getBody());
model.setStanzaId(entity.getStanzaId());
model.setOriginId(entity.getOriginId());
model.setLegacyId(entity.getLegacyStanzaId());
return model;
}
@ -53,12 +51,10 @@ public class MessageMapping extends AbstractMapping<Message, MessageModel> {
entity.setRecipient(model.getRecipient());
entity.setTimestamp(model.getTimestamp());
entity.setDirection(model.getDirection());
List<PayloadContainer> payloadContainers = new ArrayList<>(entity.getMessagePayloads().size());
for (MessagePayloadContainerModel containerModel : model.getPayloads()) {
payloadContainers.add(messagePayloadContainerMapping.toEntity(containerModel));
}
entity.setMessagePayloads(payloadContainers);
entity.setBody(model.getBody());
entity.setStanzaId(model.getStanzaId());
entity.setOriginId(model.getOriginId());
entity.setLegacyStanzaId(model.getLegacyId());
return entity;
}
}

View File

@ -38,8 +38,8 @@ public abstract class AbstractMessageModel implements Persistable {
@Convert(MessageDirectionConverter.class)
MessageDirection direction;
@OneToMany
Set<MessagePayloadContainerModel> payloads;
@Column
String body;
@Column
String legacyId;

View File

@ -8,8 +8,6 @@ import org.mercury_im.messenger.data.model.DirectMessagesRelation;
import org.mercury_im.messenger.data.model.GroupChatModel;
import org.mercury_im.messenger.data.model.GroupMessagesRelation;
import org.mercury_im.messenger.data.model.MessageModel;
import org.mercury_im.messenger.data.model.MessagePayloadContainerModel;
import org.mercury_im.messenger.data.model.MessagePayloadModel;
import org.mercury_im.messenger.data.repository.dao.DirectChatDao;
import org.mercury_im.messenger.data.repository.dao.GroupChatDao;
import org.mercury_im.messenger.data.repository.dao.MessageDao;
@ -92,6 +90,7 @@ public class XmppMessageRepository
.from(MessageModel.class)
.join(DirectMessagesRelation.class)
.on(DirectMessagesRelation.MESSAGE_ID.eq(MessageModel.ID))
.where(DirectMessagesRelation.CHAT_ID.eq(chat.getId()))
.get().observableResult()
.map(ResultDelegate::toList)
.map(this::messageModelsToEntities)
@ -117,11 +116,7 @@ public class XmppMessageRepository
public Observable<List<Message>> findMessagesWithBody(String body) {
return data().select(MessageModel.class)
.from(MessageModel.class)
.join(MessagePayloadContainerModel.class)
.on(MessagePayloadContainerModel.MESSAGE_ID.eq(MessageModel.ID))
.join(MessagePayloadModel.class)
.on(MessagePayloadModel.PAYLOAD_CONTAINER_ID.eq(MessagePayloadContainerModel.ID))
.where(MessagePayloadModel.BODY.eq(body))
.where(MessageModel.BODY.eq(body))
.get().observableResult()
.map(ResultDelegate::toList)
.map(this::messageModelsToEntities)
@ -136,12 +131,8 @@ public class XmppMessageRepository
.from(MessageModel.class)
.join(DirectMessagesRelation.class)
.on(DirectMessagesRelation.MESSAGE_ID.eq(MessageModel.ID))
.join(MessagePayloadContainerModel.class)
.on(MessagePayloadContainerModel.MESSAGE_ID.eq(MessageModel.ID))
.join(MessagePayloadModel.class)
.on(MessagePayloadModel.PAYLOAD_CONTAINER_ID.eq(MessagePayloadContainerModel.ID))
.where(MessagePayloadModel.BODY.eq(body))
.where(MessageModel.BODY.eq(body))
.and(DirectMessagesRelation.CHAT_ID.eq(chat.getId()))
.get().observableResult()
.map(ResultDelegate::toList)
@ -157,12 +148,8 @@ public class XmppMessageRepository
.from(MessageModel.class)
.join(GroupMessagesRelation.class)
.on(GroupMessagesRelation.MESSAGE_ID.eq(MessageModel.ID))
.join(MessagePayloadContainerModel.class)
.on(MessagePayloadContainerModel.MESSAGE_ID.eq(MessageModel.ID))
.join(MessagePayloadModel.class)
.on(MessagePayloadModel.PAYLOAD_CONTAINER_ID.eq(MessagePayloadContainerModel.ID))
.where(MessagePayloadModel.BODY.eq(body))
.where(MessageModel.BODY.eq(body))
.and(GroupMessagesRelation.CHAT_ID.eq(chat.getId()))
.get().observableResult()
.map(ResultDelegate::toList)
@ -174,15 +161,6 @@ public class XmppMessageRepository
@Override
public Single<Message> upsertMessage(DirectChat chat, Message message) {
return null;
/*
return data().select(DirectMessagesRelation.class)
.where(DirectMessagesRelation.CHAT_ID.eq(chat.getId()))
.and(DirectMessagesRelation.MESSAGE_ID.eq(message.getId()))
.get().maybe()
.switchIfEmpty(Single.just(message)
.map(messageMapping::toEntity)
.flatMap(messageModel -> )
*/
}
@Override

View File

@ -0,0 +1,81 @@
package org.mercury_im.messenger.store;
import org.jivesoftware.smack.chat2.IncomingChatMessageListener;
import org.jivesoftware.smack.chat2.OutgoingChatMessageListener;
import org.jivesoftware.smack.packet.MessageBuilder;
import org.jivesoftware.smackx.delay.packet.DelayInformation;
import org.jxmpp.jid.EntityBareJid;
import org.mercury_im.messenger.data.repository.DirectChatRepository;
import org.mercury_im.messenger.data.repository.MessageRepository;
import org.mercury_im.messenger.data.repository.PeerRepository;
import org.mercury_im.messenger.entity.Account;
import org.mercury_im.messenger.entity.message.Message;
import org.mercury_im.messenger.entity.message.MessageDirection;
import java.util.Date;
import java.util.logging.Level;
import java.util.logging.Logger;
import io.reactivex.disposables.CompositeDisposable;
import io.reactivex.schedulers.Schedulers;
public class MercuryMessageStore implements IncomingChatMessageListener, OutgoingChatMessageListener {
private static final Logger LOGGER = Logger.getLogger(MercuryMessageStore.class.getName());
private final MessageRepository messageRepository;
private final DirectChatRepository directChatRepository;
private final PeerRepository peerRepository;
private Account account;
private CompositeDisposable disposable = new CompositeDisposable();
public MercuryMessageStore(Account account,
PeerRepository peerRepository,
DirectChatRepository directChatRepository,
MessageRepository messageRepository) {
this.account = account;
this.peerRepository = peerRepository;
this.directChatRepository = directChatRepository;
this.messageRepository = messageRepository;
}
@Override
public void newIncomingMessage(EntityBareJid from,
org.jivesoftware.smack.packet.Message smackMessage,
org.jivesoftware.smack.chat2.Chat smackChat) {
Message message = new Message();
message.setDirection(MessageDirection.incoming);
DelayInformation delayInformation = DelayInformation.from(smackMessage);
message.setTimestamp(delayInformation != null ? delayInformation.getStamp() : new Date());
message.setSender(from.asEntityBareJidString());
message.setRecipient(smackMessage.getTo().asBareJid().toString());
if (smackMessage.getBody() != null) {
message.setBody(smackMessage.getBody());
}
disposable.add(peerRepository.getOrCreatePeer(account, from.asEntityBareJidString())
.flatMap(directChatRepository::getOrCreateChatWithPeer)
.flatMap(chat -> messageRepository.insertMessage(chat, message))
.subscribeOn(Schedulers.io())
.subscribe(m -> LOGGER.log(Level.INFO, "Message written"), e -> LOGGER.log(Level.SEVERE, "Error: ", e)));
}
@Override
public void newOutgoingMessage(EntityBareJid to,
MessageBuilder smackMessage,
org.jivesoftware.smack.chat2.Chat smackChat) {
Message message = new Message();
message.setDirection(MessageDirection.outgoing);
message.setTimestamp(new Date());
message.setSender(account.getAddress());
message.setRecipient(to.asBareJid().toString());
if (smackMessage.getBody() != null) {
message.setBody(smackMessage.getBody().getMessage());
}
disposable.add(peerRepository.getOrCreatePeer(account, to.asEntityBareJidString())
.flatMap(directChatRepository::getOrCreateChatWithPeer)
.flatMap(chat -> messageRepository.insertMessage(chat, message))
.subscribe(m -> LOGGER.log(Level.INFO, "Message written"), e -> LOGGER.log(Level.SEVERE, "Error: ", e)));
}
}

View File

@ -198,8 +198,11 @@ public class MercuryRosterStore implements RosterStore {
}
item.setApproved(contactModel.isSubscriptionApproved());
item.setSubscriptionPending(contactModel.isSubscriptionPending());
for (String groupName : contactModel.getGroupNames()) {
item.addGroupName(groupName);
List<String> groupNames = contactModel.getGroupNames();
if (groupNames != null) {
for (String groupName : groupNames) {
item.addGroupName(groupName);
}
}
return item;
}

View File

@ -1,10 +1,15 @@
package org.mercury_im.messenger.xmpp;
import org.jivesoftware.smack.chat2.ChatManager;
import org.jivesoftware.smackx.caps.EntityCapsManager;
import org.mercury_im.messenger.data.repository.AccountRepository;
import org.mercury_im.messenger.data.repository.DirectChatRepository;
import org.mercury_im.messenger.data.repository.MessageRepository;
import org.mercury_im.messenger.data.repository.PeerRepository;
import org.mercury_im.messenger.data.repository.Repositories;
import org.mercury_im.messenger.entity.Account;
import org.mercury_im.messenger.store.MercuryEntityCapsStore;
import org.mercury_im.messenger.store.MercuryMessageStore;
import org.mercury_im.messenger.usecase.RosterStoreBinder;
import org.mercury_im.messenger.util.Optional;
import org.mercury_im.messenger.xmpp.state.ConnectionPoolState;
@ -36,6 +41,9 @@ public class MercuryConnectionManager {
private final AccountRepository accountRepository;
private final RosterStoreBinder rosterStoreBinder;
private final MercuryEntityCapsStore entityCapsStore;
private final PeerRepository peerRepository;
private final DirectChatRepository directChatRepository;
private final MessageRepository messageRepository;
private final Map<UUID, MercuryConnection> connectionsMap = new ConcurrentHashMap<>();
private final Map<UUID, Disposable> connectionDisposables = new ConcurrentHashMap<>();
@ -55,6 +63,9 @@ public class MercuryConnectionManager {
this.accountRepository = repositories.getAccountRepository();
this.rosterStoreBinder = rosterStoreBinder;
this.entityCapsStore = entityCapsStore;
this.peerRepository = repositories.getPeerRepository();
this.directChatRepository = repositories.getDirectChatRepository();
this.messageRepository = repositories.getMessageRepository();
EntityCapsManager.setPersistentCache(entityCapsStore);
start();
@ -121,6 +132,15 @@ public class MercuryConnectionManager {
public void bindConnection(MercuryConnection connection) {
rosterStoreBinder.setRosterStoreOn(connection);
accountRepository.getAccount(connection.getAccountId())
.subscribeOn(Schedulers.io())
.subscribe(account -> {
MercuryMessageStore mercuryMessageStore = new MercuryMessageStore(account, peerRepository, directChatRepository, messageRepository);
ChatManager chatManager = ChatManager.getInstanceFor(connection.getConnection());
chatManager.addIncomingListener(mercuryMessageStore);
chatManager.addOutgoingListener(mercuryMessageStore);
});
}
private void handleOptionalAccountChangedEvent(MercuryConnection connection, Optional<Account> event) {

View File

@ -81,12 +81,8 @@ public class XmppDirectMessageCenter
String originId = OriginIdElement.addTo(messageBuilder).getId();
String legacyStanzaId = messageBuilder.getStanzaId();
MessageMetadata metadata = new MessageMetadata();
metadata.setLegacyStanzaId(legacyStanzaId);
metadata.setOriginId(originId);
message.setMetadata(metadata);
message.setLegacyStanzaId(legacyStanzaId);
message.setOriginId(originId);
Chat smackChat = chatManager.chatWith(peerAddress);
return messageRepository.insertMessage(chat, message)

View File

@ -12,4 +12,8 @@ import lombok.EqualsAndHashCode;
@EqualsAndHashCode(callSuper = true)
public class DirectChat extends Chat {
Peer peer;
public DirectChat() {
super();
}
}

View File

@ -13,11 +13,17 @@ public class Message {
String recipient;
Date timestamp;
MessageDirection direction;
List<PayloadContainer> messagePayloads;
String body;
MessageDeliveryState deliveryState;
MessageMetadata metadata;
String legacyStanzaId;
String originId;
String stanzaId;
public boolean isIncoming() {
return getDirection() == MessageDirection.incoming;
}
public Message() {
this.id = UUID.randomUUID();
}
}

View File

@ -9,7 +9,5 @@ import lombok.Data;
@Data
public class MessageMetadata {
private long id;
private String legacyStanzaId;
private String originId;
private String stanzaId;
}

@ -1 +1 @@
Subproject commit e2a196fa52222a6816dd850ee8380335a90f7850
Subproject commit 219efa564c222b7add06870bb51ba85dfe353cc7