diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 46c12f2..221bfc4 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -35,6 +35,8 @@
+
+
diff --git a/app/src/main/java/org/mercury_im/messenger/di/component/AppComponent.java b/app/src/main/java/org/mercury_im/messenger/di/component/AppComponent.java
index ca69c42..d834ef4 100644
--- a/app/src/main/java/org/mercury_im/messenger/di/component/AppComponent.java
+++ b/app/src/main/java/org/mercury_im/messenger/di/component/AppComponent.java
@@ -16,6 +16,8 @@ import org.mercury_im.messenger.ui.account.AccountsViewModel;
import org.mercury_im.messenger.ui.account.LoginActivity;
import org.mercury_im.messenger.ui.account.LoginViewModel;
import org.mercury_im.messenger.ui.roster.contacts.ContactListViewModel;
+import org.mercury_im.messenger.ui.roster.contacts.detail.ContactDetailActivity;
+import org.mercury_im.messenger.ui.roster.contacts.detail.ContactDetailViewModel;
import javax.inject.Singleton;
@@ -49,7 +51,8 @@ public interface AppComponent {
void inject(ChatInputFragment chatInputFragment);
- void inject(ChatListViewModel chatListViewModel);
+ void inject(ContactDetailActivity contactDetailActivity);
+
// ViewModels
@@ -64,6 +67,10 @@ public interface AppComponent {
void inject(AccountsViewModel accountsViewModel);
+ void inject(ChatListViewModel chatListViewModel);
+
+ void inject(ContactDetailViewModel contactDetailViewModel);
+
// Services
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 a6e2064..1564c01 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
@@ -18,6 +18,7 @@ import org.jxmpp.jid.EntityBareJid;
import org.jxmpp.jid.impl.JidCreate;
import org.mercury_im.messenger.MercuryImApplication;
import org.mercury_im.messenger.R;
+import org.mercury_im.messenger.entity.contact.Peer;
import java.util.UUID;
@@ -114,13 +115,15 @@ public class ChatActivity extends AppCompatActivity
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case R.id.action_debug:
- Log.d("CHATACTIVITY", "Fetch MAM messages!");
- chatViewModel.requestMamMessages()
- .subscribeOn(Schedulers.io())
- .subscribe();
+ Peer peer = chatViewModel.getContact().getValue();
+ Toast.makeText(this, "subscription: " + peer.getSubscriptionDirection().toString() +
+ " isApproved: " + peer.isSubscriptionApproved() + " isPending: " + peer.isSubscriptionPending(), Toast.LENGTH_SHORT).show();
break;
// menu_chat
+ case R.id.action_delete_contact:
+ chatViewModel.deleteContact();
+ break;
case R.id.action_call:
case R.id.action_clear_history:
case R.id.action_notification_settings:
diff --git a/app/src/main/java/org/mercury_im/messenger/ui/chat/ChatViewModel.java b/app/src/main/java/org/mercury_im/messenger/ui/chat/ChatViewModel.java
index 39c2a38..dfeab82 100644
--- a/app/src/main/java/org/mercury_im/messenger/ui/chat/ChatViewModel.java
+++ b/app/src/main/java/org/mercury_im/messenger/ui/chat/ChatViewModel.java
@@ -6,6 +6,7 @@ import androidx.lifecycle.ViewModel;
import org.jxmpp.jid.EntityBareJid;
import org.mercury_im.messenger.MercuryImApplication;
+import org.mercury_im.messenger.Messenger;
import org.mercury_im.messenger.data.repository.DirectChatRepository;
import org.mercury_im.messenger.data.repository.MessageRepository;
import org.mercury_im.messenger.data.repository.PeerRepository;
@@ -24,7 +25,6 @@ import io.reactivex.Completable;
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 ChatViewModel extends ViewModel {
@@ -41,6 +41,9 @@ public class ChatViewModel extends ViewModel {
@Inject
MessageRepository messageRepository;
+ @Inject
+ Messenger messenger;
+
private MutableLiveData contact = new MutableLiveData<>();
private MutableLiveData> messages = new MutableLiveData<>();
private MutableLiveData contactDisplayName = new MutableLiveData<>();
@@ -60,10 +63,15 @@ public class ChatViewModel extends ViewModel {
public void init(DirectChat chat) {
this.chat.setValue(chat);
+ this.contact.setValue(chat.getPeer());
// Subscribe peer
disposable.add(contactRepository.observePeer(chat.getPeer().getId())
- .subscribe(peer -> contactDisplayName.setValue(peer.getItem().getName()),
+ .subscribe(peer -> {
+ if (peer.isPresent()) {
+ contactDisplayName.setValue(peer.getItem().getName());
+ }
+ },
error -> LOGGER.log(Level.SEVERE, "Error subscribing display name to peer", error)));
// Subscribe messages
@@ -112,4 +120,14 @@ public class ChatViewModel extends ViewModel {
*/
return null;
}
+
+ public void deleteContact() {
+ Peer contact = getContact().getValue();
+ disposable.add(messenger.deleteContact(contact)
+ .subscribeOn(Schedulers.io())
+ .observeOn(AndroidSchedulers.mainThread())
+ .subscribe(() -> LOGGER.log(Level.INFO, "Contact deleted."), e -> {
+ LOGGER.log(Level.SEVERE, e.getMessage(), e);
+ }));
+ }
}
diff --git a/app/src/main/java/org/mercury_im/messenger/ui/chatlist/ChatListRecyclerViewAdapter.java b/app/src/main/java/org/mercury_im/messenger/ui/chatlist/ChatListRecyclerViewAdapter.java
index 13a1e1f..87ff7cd 100644
--- a/app/src/main/java/org/mercury_im/messenger/ui/chatlist/ChatListRecyclerViewAdapter.java
+++ b/app/src/main/java/org/mercury_im/messenger/ui/chatlist/ChatListRecyclerViewAdapter.java
@@ -15,10 +15,12 @@ import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.view.ActionMode;
import androidx.recyclerview.widget.RecyclerView;
+import org.jivesoftware.smack.util.Objects;
import org.mercury_im.messenger.R;
import org.mercury_im.messenger.entity.chat.DirectChat;
import org.mercury_im.messenger.ui.avatar.AvatarDrawable;
import org.mercury_im.messenger.ui.chat.ChatActivity;
+import org.mercury_im.messenger.ui.roster.contacts.detail.ContactDetailActivity;
import org.mercury_im.messenger.ui.util.AbstractRecyclerViewAdapter;
import org.mercury_im.messenger.util.ColorUtil;
@@ -102,7 +104,7 @@ public class ChatListRecyclerViewAdapter
@Override
public boolean areContentsTheSame(DirectChat oldItem, DirectChat newItem) {
- return oldItem.getPeer().getName().equals(newItem.getPeer().getName());
+ return Objects.equals(oldItem.getPeer().getName(), newItem.getPeer().getName());
}
}
diff --git a/app/src/main/java/org/mercury_im/messenger/ui/roster/contacts/AddContactDialogFragment.java b/app/src/main/java/org/mercury_im/messenger/ui/roster/contacts/AddContactDialogFragment.java
index c0170dc..1debbbe 100644
--- a/app/src/main/java/org/mercury_im/messenger/ui/roster/contacts/AddContactDialogFragment.java
+++ b/app/src/main/java/org/mercury_im/messenger/ui/roster/contacts/AddContactDialogFragment.java
@@ -2,41 +2,90 @@ package org.mercury_im.messenger.ui.roster.contacts;
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;
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;
+import butterknife.BindView;
+import butterknife.ButterKnife;
+import io.reactivex.android.schedulers.AndroidSchedulers;
+import io.reactivex.disposables.CompositeDisposable;
+import io.reactivex.schedulers.Schedulers;
+
public class AddContactDialogFragment extends AppCompatDialogFragment {
- private final LiveData> accounts;
+ private final List accounts;
+ private final Messenger messenger;
- public AddContactDialogFragment(LiveData> accountList) {
+ @BindView(R.id.account_select_container)
+ LinearLayout accountSelectorContainer;
+
+ @BindView(R.id.spinner)
+ Spinner accountSelector;
+
+ @BindView(R.id.address_layout)
+ TextInputLayout contactAddressLayout;
+
+ @BindView(R.id.address)
+ TextInputEditText contactAddress;
+
+ private final CompositeDisposable disposable = new CompositeDisposable();
+
+ public AddContactDialogFragment(List accountList, Messenger messenger) {
this.accounts = accountList;
+ this.messenger = messenger;
}
@NonNull
@Override
public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) {
- AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
LayoutInflater inflater = requireActivity().getLayoutInflater();
-
View dialogView = inflater.inflate(R.layout.dialog_add_contact, null);
- Spinner spinner = dialogView.findViewById(R.id.spinner);
- spinner.setAdapter(
- new ArrayAdapter<>(requireActivity(), R.layout.support_simple_spinner_dropdown_item, accounts.getValue()));
+ ButterKnife.bind(this, dialogView);
+
+ AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
+
+ // Hide Spinner when only one account.
+ if (accounts == null || accounts.size() <= 1) {
+ accountSelectorContainer.setVisibility(View.GONE);
+ }
+
+ accountSelector.setAdapter(new AccountAdapter(requireActivity(), accounts));
+ accountSelector.setSelection(0);
builder.setMessage("Add Contact")
.setView(dialogView)
@@ -44,7 +93,7 @@ public class AddContactDialogFragment extends AppCompatDialogFragment {
.setPositiveButton("Add", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
-
+ // Later overwrite in onResume.
}
})
.setNegativeButton("Cancel", new DialogInterface.OnClickListener() {
@@ -56,4 +105,69 @@ public class AddContactDialogFragment extends AppCompatDialogFragment {
return builder.create();
}
+
+ @Override
+ public void onResume()
+ {
+ 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(
+ AddContactDialogFragment.this::dismiss,
+ e -> {
+ if (e instanceof SmackException.NotLoggedInException || e instanceof SmackException.NotConnectedException) {
+ contactAddressLayout.setError("Account not connected");
+ } else if (e instanceof ContactAlreadyAddedException) {
+ contactAddressLayout.setError("Contact already added");
+ } else if (e instanceof XmppStringprepException) {
+ contactAddressLayout.setError("Invalid address");
+ } else {
+ contactAddressLayout.setError(e.getClass().getName());
+ }
+ }
+ ));
+ }
+ });
+ }
+ }
+
+ @Override
+ public void onDestroy() {
+ super.onDestroy();
+ disposable.dispose();
+ }
+
+ private static class AccountAdapter extends ArrayAdapter {
+
+ public AccountAdapter(@NonNull Context context, @NonNull List objects) {
+ super(context, R.layout.spinner_item_account, objects);
+ }
+
+ @NonNull
+ @Override
+ public View getView(int position, @Nullable View convertView, @NonNull ViewGroup parent) {
+ if (convertView == null) {
+ convertView = LayoutInflater.from(getContext()).inflate(R.layout.spinner_item_account, parent, false);
+ }
+ TextView textView = convertView.findViewById(R.id.account_address);
+ textView.setText(getItem(position).getAddress());
+ return convertView;
+ }
+
+ @Override
+ public View getDropDownView(int position, @Nullable View convertView, @NonNull ViewGroup parent) {
+ return getView(position, convertView, parent);
+ }
+ }
}
diff --git a/app/src/main/java/org/mercury_im/messenger/ui/roster/contacts/ContactListFragment.java b/app/src/main/java/org/mercury_im/messenger/ui/roster/contacts/ContactListFragment.java
index c503995..e15b6d7 100644
--- a/app/src/main/java/org/mercury_im/messenger/ui/roster/contacts/ContactListFragment.java
+++ b/app/src/main/java/org/mercury_im/messenger/ui/roster/contacts/ContactListFragment.java
@@ -16,6 +16,8 @@ import com.google.android.material.floatingactionbutton.ExtendedFloatingActionBu
import org.mercury_im.messenger.R;
+import javax.inject.Inject;
+
import butterknife.BindView;
import butterknife.ButterKnife;
@@ -52,7 +54,7 @@ public class ContactListFragment extends Fragment {
}
private void displayAddContactDialog() {
- AddContactDialogFragment addContactDialogFragment = new AddContactDialogFragment(contactListViewModel.getAccounts());
+ AddContactDialogFragment addContactDialogFragment = new AddContactDialogFragment(contactListViewModel.getAccounts().getValue(), contactListViewModel.getMessenger());
addContactDialogFragment.show(this.getParentFragmentManager(), "add");
}
diff --git a/app/src/main/java/org/mercury_im/messenger/ui/roster/contacts/ContactListRecyclerViewAdapter.java b/app/src/main/java/org/mercury_im/messenger/ui/roster/contacts/ContactListRecyclerViewAdapter.java
index 584f866..77e6adf 100644
--- a/app/src/main/java/org/mercury_im/messenger/ui/roster/contacts/ContactListRecyclerViewAdapter.java
+++ b/app/src/main/java/org/mercury_im/messenger/ui/roster/contacts/ContactListRecyclerViewAdapter.java
@@ -15,6 +15,7 @@ import org.mercury_im.messenger.R;
import org.mercury_im.messenger.entity.contact.Peer;
import org.mercury_im.messenger.ui.avatar.AvatarDrawable;
import org.mercury_im.messenger.ui.chat.ChatActivity;
+import org.mercury_im.messenger.ui.roster.contacts.detail.ContactDetailActivity;
import org.mercury_im.messenger.ui.util.AbstractRecyclerViewAdapter;
import org.mercury_im.messenger.util.ColorUtil;
@@ -73,9 +74,9 @@ public class ContactListRecyclerViewAdapter
avatarView.setImageDrawable(new AvatarDrawable(name, address));
view.setOnClickListener(view -> {
- Intent intent = new Intent(context, ChatActivity.class);
- intent.putExtra(ChatActivity.EXTRA_JID, address);
- intent.putExtra(ChatActivity.EXTRA_ACCOUNT, contact.getAccount().getId().toString());
+ Intent intent = new Intent(context, ContactDetailActivity.class);
+ intent.putExtra(ContactDetailActivity.EXTRA_JID, address);
+ intent.putExtra(ContactDetailActivity.EXTRA_ACCOUNT, contact.getAccount().getId().toString());
context.startActivity(intent);
});
diff --git a/app/src/main/java/org/mercury_im/messenger/ui/roster/contacts/ContactListViewModel.java b/app/src/main/java/org/mercury_im/messenger/ui/roster/contacts/ContactListViewModel.java
index e807fea..5ddfb8a 100644
--- a/app/src/main/java/org/mercury_im/messenger/ui/roster/contacts/ContactListViewModel.java
+++ b/app/src/main/java/org/mercury_im/messenger/ui/roster/contacts/ContactListViewModel.java
@@ -7,6 +7,7 @@ import androidx.lifecycle.MutableLiveData;
import androidx.lifecycle.ViewModel;
import org.mercury_im.messenger.MercuryImApplication;
+import org.mercury_im.messenger.Messenger;
import org.mercury_im.messenger.data.repository.XmppAccountRepository;
import org.mercury_im.messenger.data.repository.XmppPeerRepository;
import org.mercury_im.messenger.entity.Account;
@@ -17,6 +18,7 @@ import java.util.List;
import javax.inject.Inject;
import io.reactivex.disposables.CompositeDisposable;
+import lombok.Getter;
public class ContactListViewModel extends ViewModel {
@@ -27,6 +29,10 @@ public class ContactListViewModel extends ViewModel {
@Inject
XmppAccountRepository accountRepository;
+ @Inject
+ @Getter
+ Messenger messenger;
+
private final MutableLiveData> rosterEntryList = new MutableLiveData<>();
private final MutableLiveData> accounts = new MutableLiveData<>();
private final CompositeDisposable compositeDisposable = new CompositeDisposable();
diff --git a/app/src/main/java/org/mercury_im/messenger/ui/roster/contacts/detail/ContactDetailActivity.java b/app/src/main/java/org/mercury_im/messenger/ui/roster/contacts/detail/ContactDetailActivity.java
new file mode 100644
index 0000000..29fd5e8
--- /dev/null
+++ b/app/src/main/java/org/mercury_im/messenger/ui/roster/contacts/detail/ContactDetailActivity.java
@@ -0,0 +1,48 @@
+package org.mercury_im.messenger.ui.roster.contacts.detail;
+
+import android.os.Bundle;
+import android.widget.FrameLayout;
+
+import androidx.annotation.Nullable;
+import androidx.appcompat.app.AppCompatActivity;
+import androidx.lifecycle.ViewModelProvider;
+
+import org.mercury_im.messenger.MercuryImApplication;
+import org.mercury_im.messenger.R;
+
+import java.util.UUID;
+
+import butterknife.BindView;
+import butterknife.ButterKnife;
+
+public class ContactDetailActivity extends AppCompatActivity {
+ public static final String EXTRA_JID = "JID";
+ public static final String EXTRA_ACCOUNT = "ACCOUNT";
+
+ @BindView(R.id.fragment)
+ FrameLayout container;
+
+ @Override
+ protected void onCreate(@Nullable Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.activity_fragment_container);
+ ButterKnife.bind(this);
+
+ MercuryImApplication.getApplication().getAppComponent().inject(this);
+
+ if (savedInstanceState == null) {
+ savedInstanceState = getIntent().getExtras();
+ if (savedInstanceState == null) return;
+ }
+
+ String jidString = savedInstanceState.getString(EXTRA_JID);
+ if (jidString != null) {
+ UUID accountId = UUID.fromString(savedInstanceState.getString(EXTRA_ACCOUNT));
+
+ ContactDetailViewModel viewModel = new ViewModelProvider(this).get(ContactDetailViewModel.class);
+ viewModel.bind(accountId, jidString);
+ }
+
+ getSupportFragmentManager().beginTransaction().replace(R.id.fragment, new ContactDetailFragment(), "contact_details").commit();
+ }
+}
diff --git a/app/src/main/java/org/mercury_im/messenger/ui/roster/contacts/detail/ContactDetailFragment.java b/app/src/main/java/org/mercury_im/messenger/ui/roster/contacts/detail/ContactDetailFragment.java
new file mode 100644
index 0000000..1094c47
--- /dev/null
+++ b/app/src/main/java/org/mercury_im/messenger/ui/roster/contacts/detail/ContactDetailFragment.java
@@ -0,0 +1,109 @@
+package org.mercury_im.messenger.ui.roster.contacts.detail;
+
+import android.content.Context;
+import android.content.Intent;
+import android.os.Bundle;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ImageView;
+import android.widget.TextView;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.fragment.app.Fragment;
+import androidx.lifecycle.ViewModelProvider;
+import androidx.lifecycle.ViewModelStoreOwner;
+
+import com.google.android.material.chip.ChipGroup;
+import com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton;
+
+import org.mercury_im.messenger.R;
+import org.mercury_im.messenger.ui.chat.ChatActivity;
+import org.mercury_im.messenger.util.ColorUtil;
+
+import butterknife.BindView;
+import butterknife.ButterKnife;
+
+public class ContactDetailFragment extends Fragment {
+
+ @BindView(R.id.contact_avatar)
+ ImageView contactAvatar;
+
+ @BindView(R.id.contact_status_badge)
+ ImageView contactStatusBadge;
+
+ @BindView(R.id.contact_name)
+ TextView contactName;
+
+ @BindView(R.id.contact_address)
+ TextView contactAddress;
+
+ @BindView(R.id.contact_presence)
+ TextView contactPresence;
+
+ @BindView(R.id.contact_account)
+ TextView contactAccount;
+
+ @BindView(R.id.contact_groups)
+ ChipGroup contactGroups;
+
+ @BindView(R.id.fab)
+ ExtendedFloatingActionButton fab;
+
+ private ContactDetailViewModel viewModel;
+
+ @Nullable
+ @Override
+ public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
+ View view = inflater.inflate(R.layout.fragment_contact_details, container, false);
+ 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);
+ }
+ });
+ }
+
+ return view;
+ }
+
+ @Override
+ public void onAttach(Context context) {
+ super.onAttach(context);
+ viewModel = new ViewModelProvider((ViewModelStoreOwner) context).get(ContactDetailViewModel.class);
+
+ observeViewModel();
+ }
+
+ private void observeViewModel() {
+ viewModel.getContactAvatar().observe(this, drawable -> contactAvatar.setImageDrawable(drawable));
+ viewModel.getContactPresenceMode().observe(this, mode -> {
+ int color = 0;
+ switch (mode) {
+ case chat:
+ case available:
+ color = ColorUtil.rgb(0, 255, 0);
+ break;
+ case away:
+ case xa:
+ color = ColorUtil.rgb(255, 128, 0);
+ break;
+ case dnd:
+ color = ColorUtil.rgb(255, 0, 0);
+ break;
+ }
+ contactStatusBadge.setColorFilter(color);
+ });
+ viewModel.getContactName().observe(this, name -> contactName.setText(name));
+ viewModel.getContactAddress().observe(this, address -> contactAddress.setText(address));
+ viewModel.getContactPresenceStatus().observe(this, presenceText -> contactPresence.setText(presenceText));
+ viewModel.getContactAccountAddress().observe(this, address -> contactAccount.setText(address));
+ }
+}
diff --git a/app/src/main/java/org/mercury_im/messenger/ui/roster/contacts/detail/ContactDetailViewModel.java b/app/src/main/java/org/mercury_im/messenger/ui/roster/contacts/detail/ContactDetailViewModel.java
new file mode 100644
index 0000000..52e2a64
--- /dev/null
+++ b/app/src/main/java/org/mercury_im/messenger/ui/roster/contacts/detail/ContactDetailViewModel.java
@@ -0,0 +1,129 @@
+package org.mercury_im.messenger.ui.roster.contacts.detail;
+
+import android.graphics.drawable.Drawable;
+import android.util.Log;
+
+import androidx.lifecycle.LiveData;
+import androidx.lifecycle.MutableLiveData;
+import androidx.lifecycle.ViewModel;
+
+import org.jivesoftware.smack.PresenceListener;
+import org.jivesoftware.smack.packet.Presence;
+import org.jivesoftware.smack.roster.PresenceEventListener;
+import org.jivesoftware.smack.roster.Roster;
+import org.jxmpp.jid.Jid;
+import org.jxmpp.jid.impl.JidCreate;
+import org.mercury_im.messenger.MercuryImApplication;
+import org.mercury_im.messenger.Messenger;
+import org.mercury_im.messenger.data.repository.PeerRepository;
+import org.mercury_im.messenger.entity.contact.Peer;
+import org.mercury_im.messenger.ui.avatar.AvatarDrawable;
+import org.mercury_im.messenger.util.CombinedPresenceListener;
+
+import java.util.UUID;
+
+import javax.inject.Inject;
+
+import io.reactivex.android.schedulers.AndroidSchedulers;
+import io.reactivex.disposables.CompositeDisposable;
+import io.reactivex.schedulers.Schedulers;
+
+public class ContactDetailViewModel extends ViewModel {
+
+ @Inject
+ PeerRepository peerRepository;
+
+ @Inject
+ Messenger messenger;
+
+ private MutableLiveData contactAccountId = new MutableLiveData<>(UUID.randomUUID());
+ private MutableLiveData contactAddress = new MutableLiveData<>("alice@wonderland.lit");
+ private MutableLiveData contactAvatar = new MutableLiveData<>(new AvatarDrawable("Alice Wonderland", "alice@wonderland.lit"));
+ private MutableLiveData contactPresenceMode = new MutableLiveData<>(Presence.Mode.available);
+ private MutableLiveData contactPresenceStatus = new MutableLiveData<>("Going down the rabbit hole.");
+ private MutableLiveData contactName = new MutableLiveData<>("Alice Wonderland");
+ private MutableLiveData contactAccountAddress = new MutableLiveData<>("mad@hatter.lit");
+
+ private Roster roster;
+
+ private CompositeDisposable disposable = new CompositeDisposable();
+
+ public ContactDetailViewModel() {
+ super();
+ MercuryImApplication.getApplication().getAppComponent().inject(this);
+ }
+
+ public void bind(UUID accountId, String peerAddress) {
+ Log.d("MMMMMM", "Bind!");
+ contactAddress.setValue(peerAddress);
+ contactAccountId.setValue(accountId);
+ disposable.add(peerRepository.observePeerByAddress(accountId, peerAddress)
+ .subscribeOn(Schedulers.io())
+ .observeOn(AndroidSchedulers.mainThread())
+ .subscribe(peerOptional -> {
+ if (!peerOptional.isPresent()) {
+ return;
+ }
+ Peer peer = peerOptional.getItem();
+ contactAccountAddress.setValue(peer.getAccount().getAddress());
+ contactAvatar.setValue(new AvatarDrawable(peer.getDisplayName(), peer.getAddress()));
+ contactName.setValue(peer.getDisplayName());
+ }));
+
+ roster = Roster.getInstanceFor(messenger.getConnectionManager().getConnection(accountId).getConnection());
+ roster.addPresenceEventListener(presenceEventListener);
+
+ Presence presence = roster.getPresence(JidCreate.entityBareFromOrThrowUnchecked(peerAddress));
+ if (presence != null) {
+ contactPresenceMode.postValue(presence.getMode());
+ contactPresenceStatus.postValue(presence.getStatus());
+ }
+ }
+
+ public LiveData getContactAddress() {
+ return contactAddress;
+ }
+
+ @Override
+ protected void onCleared() {
+ super.onCleared();
+ disposable.dispose();
+ if (roster != null) {
+ roster.removePresenceEventListener(presenceEventListener);
+ }
+ }
+
+ public LiveData getAccountId() {
+ return contactAccountId;
+ }
+
+ public LiveData getContactAvatar() {
+ return contactAvatar;
+ }
+
+ public LiveData getContactPresenceMode() {
+ return contactPresenceMode;
+ }
+
+ public LiveData getContactName() {
+ return contactName;
+ }
+
+ public LiveData getContactPresenceStatus() {
+ return contactPresenceStatus;
+ }
+
+ public LiveData getContactAccountAddress() {
+ return contactAccountAddress;
+ }
+
+ private final PresenceEventListener presenceEventListener = new CombinedPresenceListener() {
+ @Override
+ public void presenceReceived(Jid address, Presence presence) {
+ if (presence.getFrom().asBareJid().toString().equals(getContactAddress().getValue())) {
+ contactPresenceMode.postValue(presence.getMode());
+ contactPresenceStatus.postValue(presence.getStatus());
+ }
+ }
+ };
+}
diff --git a/app/src/main/res/layout/activity_fragment_container.xml b/app/src/main/res/layout/activity_fragment_container.xml
new file mode 100644
index 0000000..dd469fa
--- /dev/null
+++ b/app/src/main/res/layout/activity_fragment_container.xml
@@ -0,0 +1,19 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/dialog_add_contact.xml b/app/src/main/res/layout/dialog_add_contact.xml
index 5a8addf..b54de8a 100644
--- a/app/src/main/res/layout/dialog_add_contact.xml
+++ b/app/src/main/res/layout/dialog_add_contact.xml
@@ -20,10 +20,12 @@
+ android:layout_height="56dp"
+ tools:listitem="@layout/spinner_item_account"/>
diff --git a/app/src/main/res/layout/fragment_account_list.xml b/app/src/main/res/layout/fragment_account_list.xml
index bbbbc9f..c49bb15 100644
--- a/app/src/main/res/layout/fragment_account_list.xml
+++ b/app/src/main/res/layout/fragment_account_list.xml
@@ -23,7 +23,6 @@
android:transitionName="fab"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:text="@string/action_add_account"
android:layout_gravity="bottom|end"
android:layout_margin="16dp"
app:icon="@drawable/ic_add_white_24dp"/>
diff --git a/app/src/main/res/layout/fragment_bookmarks_list.xml b/app/src/main/res/layout/fragment_bookmarks_list.xml
index 2e0e5bf..f41ef12 100644
--- a/app/src/main/res/layout/fragment_bookmarks_list.xml
+++ b/app/src/main/res/layout/fragment_bookmarks_list.xml
@@ -21,7 +21,6 @@
android:layout_height="wrap_content"
android:id="@+id/fab"
android:transitionName="fab"
- android:text="@string/action_add_bookmark"
android:layout_margin="16dp"
android:layout_gravity="bottom|end"
app:icon="@drawable/ic_group_add_black_24dp"/>
diff --git a/app/src/main/res/layout/fragment_contact_details.xml b/app/src/main/res/layout/fragment_contact_details.xml
index 3509b84..c59483c 100644
--- a/app/src/main/res/layout/fragment_contact_details.xml
+++ b/app/src/main/res/layout/fragment_contact_details.xml
@@ -1,6 +1,162 @@
-
-
\ No newline at end of file
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/fragment_contact_list.xml b/app/src/main/res/layout/fragment_contact_list.xml
index 0fa9386..ee01cac 100644
--- a/app/src/main/res/layout/fragment_contact_list.xml
+++ b/app/src/main/res/layout/fragment_contact_list.xml
@@ -21,7 +21,6 @@
android:layout_height="wrap_content"
android:id="@+id/fab"
android:transitionName="fab"
- android:text="@string/action_add_contact"
android:layout_margin="16dp"
android:layout_gravity="bottom|end"
app:icon="@drawable/ic_person_add_black_24dp"/>
diff --git a/app/src/main/res/layout/spinner_item_account.xml b/app/src/main/res/layout/spinner_item_account.xml
new file mode 100644
index 0000000..a9b6923
--- /dev/null
+++ b/app/src/main/res/layout/spinner_item_account.xml
@@ -0,0 +1,7 @@
+
+
\ No newline at end of file
diff --git a/app/src/main/res/menu/menu_chat.xml b/app/src/main/res/menu/menu_chat.xml
index 7b59a6d..1e7c05c 100644
--- a/app/src/main/res/menu/menu_chat.xml
+++ b/app/src/main/res/menu/menu_chat.xml
@@ -11,6 +11,12 @@
app:actionViewClass="androidx.appcompat.widget.SearchView"
app:showAsAction="ifRoom" />
+
+
- Add Bookmark
Close Chat
Copy
+ Remove Contact
diff --git a/data/src/main/java/org/mercury_im/messenger/data/repository/XmppPeerRepository.java b/data/src/main/java/org/mercury_im/messenger/data/repository/XmppPeerRepository.java
index 52af54f..0187832 100644
--- a/data/src/main/java/org/mercury_im/messenger/data/repository/XmppPeerRepository.java
+++ b/data/src/main/java/org/mercury_im/messenger/data/repository/XmppPeerRepository.java
@@ -140,7 +140,7 @@ public class XmppPeerRepository
public Observable> observeAllContactsOfAccount(UUID accountId) {
return data().select(PeerModel.class)
.where(PeerModel.ACCOUNT_ID.eq(accountId))
- .and(isContact())
+ //.and(isContact())
.get().observableResult()
.map(ResultDelegate::toList)
.map(this::peerModelsToEntities)
@@ -148,12 +148,6 @@ public class XmppPeerRepository
.observeOn(observerScheduler());
}
- private LogicalCondition extends Expression, ?> isContact() {
- return PeerModel.SUBSCRIPTION_DIRECTION.in(Arrays.asList(
- SubscriptionDirection.both,
- SubscriptionDirection.to));
- }
-
@Override
public Single updatePeer(Peer peer) {
// In order to update, we fetch the model, update it and write it back.
diff --git a/domain/src/main/java/org/mercury_im/messenger/Messenger.java b/domain/src/main/java/org/mercury_im/messenger/Messenger.java
index fc3f5b9..3f8a0b8 100644
--- a/domain/src/main/java/org/mercury_im/messenger/Messenger.java
+++ b/domain/src/main/java/org/mercury_im/messenger/Messenger.java
@@ -1,16 +1,31 @@
package org.mercury_im.messenger;
import org.jivesoftware.smack.SmackConfiguration;
+import org.jivesoftware.smack.SmackException;
+import org.jivesoftware.smack.XMPPException;
+import org.jivesoftware.smack.roster.Roster;
+import org.jivesoftware.smack.roster.RosterEntry;
+import org.jxmpp.jid.BareJid;
+import org.jxmpp.jid.EntityBareJid;
+import org.jxmpp.jid.impl.JidCreate;
+import org.jxmpp.stringprep.XmppStringprepException;
import org.mercury_im.messenger.data.repository.Repositories;
import org.mercury_im.messenger.entity.Account;
+import org.mercury_im.messenger.entity.contact.Peer;
+import org.mercury_im.messenger.exception.ConnectionNotFoundException;
+import org.mercury_im.messenger.exception.ContactAlreadyAddedException;
+import org.mercury_im.messenger.xmpp.MercuryConnection;
import org.mercury_im.messenger.xmpp.MercuryConnectionManager;
+import java.util.UUID;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.inject.Inject;
import javax.inject.Singleton;
+import io.reactivex.Completable;
+import io.reactivex.Single;
import io.reactivex.disposables.CompositeDisposable;
import io.reactivex.schedulers.Schedulers;
@@ -54,4 +69,58 @@ public class Messenger {
return account;
}
+
+ public Completable addContact(UUID accountId, String contactAddress) {
+ return Completable.fromAction(() -> doAddContact(accountId, contactAddress));
+ }
+
+ private void doAddContact(UUID accountId, String contactAddress)
+ throws ConnectionNotFoundException, XmppStringprepException, ContactAlreadyAddedException,
+ SmackException.NotLoggedInException, XMPPException.XMPPErrorException,
+ SmackException.NotConnectedException, InterruptedException, SmackException.NoResponseException {
+ MercuryConnection connection = getConnectionManager().getConnection(accountId);
+ if (connection == null) {
+ throw new ConnectionNotFoundException(accountId);
+ }
+
+ EntityBareJid jid = JidCreate.entityBareFrom(contactAddress);
+ Roster roster = Roster.getInstanceFor(connection.getConnection());
+ if (roster.getEntry(jid) != null) {
+ throw new ContactAlreadyAddedException(jid);
+ }
+
+ if (roster.isSubscriptionPreApprovalSupported()) {
+ LOGGER.log(Level.INFO, "Pre-Approval supported.");
+ try {
+ roster.preApproveAndCreateEntry(jid, null, null);
+ } catch (SmackException.FeatureNotSupportedException e) {
+ throw new AssertionError("pre-approval failed even though the feature is announced.");
+ }
+ } else {
+ roster.createItemAndRequestSubscription(jid, null, null);
+ }
+ }
+
+ public Completable deleteContact(Peer contact) {
+ return Completable.fromAction(() -> doDeleteContact(contact));
+ }
+
+ private void doDeleteContact(Peer contact)
+ throws ConnectionNotFoundException, XmppStringprepException,
+ SmackException.NotLoggedInException, XMPPException.XMPPErrorException,
+ SmackException.NotConnectedException, InterruptedException, SmackException.NoResponseException {
+ MercuryConnection connection = getConnectionManager().getConnection(contact.getAccount().getId());
+ if (connection == null) {
+ throw new ConnectionNotFoundException(contact.getAccount().getId());
+ }
+
+ Roster roster = Roster.getInstanceFor(connection.getConnection());
+ EntityBareJid jid = JidCreate.entityBareFrom(contact.getAddress());
+ RosterEntry entry = roster.getEntry(jid);
+ if (entry != null) {
+ roster.removeEntry(entry);
+ } else {
+ throw new IllegalStateException("Contact " + jid.toString() + " not in roster!");
+ }
+ }
}
diff --git a/domain/src/main/java/org/mercury_im/messenger/exception/ConnectionNotFoundException.java b/domain/src/main/java/org/mercury_im/messenger/exception/ConnectionNotFoundException.java
new file mode 100644
index 0000000..1aee554
--- /dev/null
+++ b/domain/src/main/java/org/mercury_im/messenger/exception/ConnectionNotFoundException.java
@@ -0,0 +1,16 @@
+package org.mercury_im.messenger.exception;
+
+import java.util.UUID;
+
+import lombok.Getter;
+
+public class ConnectionNotFoundException extends Exception {
+
+ @Getter
+ private final UUID accountId;
+
+ public ConnectionNotFoundException(UUID accountId) {
+ super("Connection with ID " + accountId.toString() + " not registered.");
+ this.accountId = accountId;
+ }
+}
diff --git a/domain/src/main/java/org/mercury_im/messenger/exception/ContactAlreadyAddedException.java b/domain/src/main/java/org/mercury_im/messenger/exception/ContactAlreadyAddedException.java
new file mode 100644
index 0000000..2f9b30b
--- /dev/null
+++ b/domain/src/main/java/org/mercury_im/messenger/exception/ContactAlreadyAddedException.java
@@ -0,0 +1,16 @@
+package org.mercury_im.messenger.exception;
+
+import org.jxmpp.jid.Jid;
+
+import lombok.Getter;
+
+public class ContactAlreadyAddedException extends Exception {
+
+ @Getter
+ private final Jid jid;
+
+ public ContactAlreadyAddedException(Jid jid) {
+ super("Contact with address " + jid.toString() + " is already a contact.");
+ this.jid = jid;
+ }
+}
diff --git a/domain/src/main/java/org/mercury_im/messenger/store/MercuryRosterStore.java b/domain/src/main/java/org/mercury_im/messenger/store/MercuryRosterStore.java
index 7a8fdbc..e53fc1e 100644
--- a/domain/src/main/java/org/mercury_im/messenger/store/MercuryRosterStore.java
+++ b/domain/src/main/java/org/mercury_im/messenger/store/MercuryRosterStore.java
@@ -21,8 +21,6 @@ import java.util.UUID;
import java.util.logging.Level;
import java.util.logging.Logger;
-import io.reactivex.Observable;
-import io.reactivex.Scheduler;
import io.reactivex.disposables.CompositeDisposable;
import io.reactivex.schedulers.Schedulers;
import io.reactivex.subjects.BehaviorSubject;
diff --git a/domain/src/main/java/org/mercury_im/messenger/usecase/RosterStoreBinder.java b/domain/src/main/java/org/mercury_im/messenger/usecase/RosterStoreBinder.java
index 6e40009..d826042 100644
--- a/domain/src/main/java/org/mercury_im/messenger/usecase/RosterStoreBinder.java
+++ b/domain/src/main/java/org/mercury_im/messenger/usecase/RosterStoreBinder.java
@@ -1,13 +1,28 @@
package org.mercury_im.messenger.usecase;
+import org.jivesoftware.smack.packet.Presence;
+import org.jivesoftware.smack.roster.PresenceEventListener;
import org.jivesoftware.smack.roster.Roster;
+import org.jivesoftware.smack.roster.RosterEntry;
+import org.jivesoftware.smack.roster.RosterListener;
+import org.jivesoftware.smack.roster.RosterLoadedListener;
+import org.jivesoftware.smack.roster.SubscribeListener;
+import org.jivesoftware.smack.roster.rosterstore.RosterStore;
+import org.jxmpp.jid.BareJid;
+import org.jxmpp.jid.FullJid;
+import org.jxmpp.jid.Jid;
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.store.MercuryRosterStore;
import org.mercury_im.messenger.xmpp.MercuryConnection;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
import java.util.UUID;
+import java.util.logging.Level;
+import java.util.logging.Logger;
import javax.inject.Inject;
@@ -16,6 +31,8 @@ public class RosterStoreBinder {
private final AccountRepository accountRepository;
private final PeerRepository peerRepository;
+ private final Logger LOGGER = Logger.getLogger(RosterStoreBinder.class.getName());
+
@Inject
public RosterStoreBinder(AccountRepository accountRepository, PeerRepository peerRepository) {
this.accountRepository = accountRepository;
@@ -27,6 +44,75 @@ public class RosterStoreBinder {
createRosterStore(connection.getAccount().getId(), accountRepository, peerRepository);
Roster roster = Roster.getInstanceFor(connection.getConnection());
roster.setRosterStore(store);
+ roster.addSubscribeListener(new SubscribeListener() {
+ @Override
+ public SubscribeAnswer processSubscribe(Jid from, Presence subscribeRequest) {
+ RosterEntry entry = roster.getEntry(from.asBareJid());
+ if (entry != null) {
+ return SubscribeAnswer.ApproveAndAlsoRequestIfRequired;
+ }
+ LOGGER.log(Level.INFO, "processSubscribe " + from);
+ return null;
+ }
+ });
+ roster.addPresenceEventListener(new PresenceEventListener() {
+ @Override
+ public void presenceAvailable(FullJid address, Presence availablePresence) {
+ LOGGER.log(Level.INFO, "presenceAvailable " + address.toString());
+ }
+
+ @Override
+ public void presenceUnavailable(FullJid address, Presence presence) {
+ LOGGER.log(Level.INFO, "presenceUnavailable " + address);
+ }
+
+ @Override
+ public void presenceError(Jid address, Presence errorPresence) {
+ LOGGER.log(Level.INFO, "presenceError " + address);
+ }
+
+ @Override
+ public void presenceSubscribed(BareJid address, Presence subscribedPresence) {
+ LOGGER.log(Level.INFO, "presenceSubscribed " + address);
+ }
+
+ @Override
+ public void presenceUnsubscribed(BareJid address, Presence unsubscribedPresence) {
+ LOGGER.log(Level.INFO, "presenceUnsubscribed " + address);
+ }
+ });
+ roster.addRosterListener(new RosterListener() {
+ @Override
+ public void entriesAdded(Collection addresses) {
+ LOGGER.log(Level.INFO, "entriesAdded " + Arrays.toString(addresses.toArray()));
+ }
+
+ @Override
+ public void entriesUpdated(Collection addresses) {
+ LOGGER.log(Level.INFO, "entriesUpdated " + Arrays.toString(addresses.toArray()));
+ }
+
+ @Override
+ public void entriesDeleted(Collection addresses) {
+ LOGGER.log(Level.INFO, "entriesDeleted " + Arrays.toString(addresses.toArray()));
+ }
+
+ @Override
+ public void presenceChanged(Presence presence) {
+ LOGGER.log(Level.INFO, "presenceChanged " + presence.toString());
+ }
+ });
+ roster.addRosterLoadedListener(new RosterLoadedListener() {
+ @Override
+ public void onRosterLoaded(Roster roster) {
+ LOGGER.log(Level.INFO, "onRosterLoaded");
+ }
+
+ @Override
+ public void onRosterLoadingFailed(Exception exception) {
+ LOGGER.log(Level.INFO, "onRosterLoadingFailed");
+ }
+ });
}
private MercuryRosterStore createRosterStore(UUID accountId, AccountRepository accountRepository, PeerRepository peerRepository) {
diff --git a/domain/src/main/java/org/mercury_im/messenger/util/CombinedPresenceListener.java b/domain/src/main/java/org/mercury_im/messenger/util/CombinedPresenceListener.java
new file mode 100644
index 0000000..d5ef8d2
--- /dev/null
+++ b/domain/src/main/java/org/mercury_im/messenger/util/CombinedPresenceListener.java
@@ -0,0 +1,36 @@
+package org.mercury_im.messenger.util;
+
+import org.jivesoftware.smack.packet.Presence;
+import org.jivesoftware.smack.roster.PresenceEventListener;
+import org.jxmpp.jid.BareJid;
+import org.jxmpp.jid.FullJid;
+import org.jxmpp.jid.Jid;
+
+public abstract class CombinedPresenceListener implements PresenceEventListener {
+ @Override
+ public void presenceAvailable(FullJid address, Presence availablePresence) {
+ presenceReceived(address, availablePresence);
+ }
+
+ @Override
+ public void presenceUnavailable(FullJid address, Presence presence) {
+ presenceReceived(address, presence);
+ }
+
+ @Override
+ public void presenceError(Jid address, Presence errorPresence) {
+ presenceReceived(address, errorPresence);
+ }
+
+ @Override
+ public void presenceSubscribed(BareJid address, Presence subscribedPresence) {
+ presenceReceived(address, subscribedPresence);
+ }
+
+ @Override
+ public void presenceUnsubscribed(BareJid address, Presence unsubscribedPresence) {
+ presenceReceived(address, unsubscribedPresence);
+ }
+
+ public abstract void presenceReceived(Jid address, Presence presence);
+}
diff --git a/domain/src/main/java/org/mercury_im/messenger/xmpp/SmackConfig.java b/domain/src/main/java/org/mercury_im/messenger/xmpp/SmackConfig.java
index aef78b8..4027424 100644
--- a/domain/src/main/java/org/mercury_im/messenger/xmpp/SmackConfig.java
+++ b/domain/src/main/java/org/mercury_im/messenger/xmpp/SmackConfig.java
@@ -1,6 +1,7 @@
package org.mercury_im.messenger.xmpp;
import org.jivesoftware.smack.ReconnectionManager;
+import org.jivesoftware.smack.roster.Roster;
import org.jivesoftware.smackx.carbons.CarbonManager;
import org.jivesoftware.smackx.iqversion.VersionManager;
import org.jivesoftware.smackx.mam.MamManager;
@@ -21,5 +22,7 @@ public class SmackConfig {
StableUniqueStanzaIdManager.setEnabledByDefault(true);
CarbonManager.setEnabledByDefault(true);
+
+ Roster.setRosterLoadedAtLoginDefault(true);
}
}