Compare commits
2 Commits
bc94467689
...
c4e672cc21
Author | SHA1 | Date |
---|---|---|
Paul Schaub | c4e672cc21 | |
Paul Schaub | 1e3c98abea |
|
@ -9,6 +9,7 @@ import org.mercury_im.messenger.android.ui.account.login.EnterAccountDetailsFrag
|
||||||
import org.mercury_im.messenger.android.ui.account.login.AndroidIkeySetupViewModel;
|
import org.mercury_im.messenger.android.ui.account.login.AndroidIkeySetupViewModel;
|
||||||
import org.mercury_im.messenger.android.ui.contacts.AndroidContactListViewModel;
|
import org.mercury_im.messenger.android.ui.contacts.AndroidContactListViewModel;
|
||||||
import org.mercury_im.messenger.android.crypto.ikey.AndroidIkeyBackupCreationViewModel;
|
import org.mercury_im.messenger.android.crypto.ikey.AndroidIkeyBackupCreationViewModel;
|
||||||
|
import org.mercury_im.messenger.android.ui.contacts.detail.AndroidContactDetailViewModel;
|
||||||
import org.mercury_im.messenger.core.di.module.IkeyModule;
|
import org.mercury_im.messenger.core.di.module.IkeyModule;
|
||||||
import org.mercury_im.messenger.core.di.module.OpenPgpModule;
|
import org.mercury_im.messenger.core.di.module.OpenPgpModule;
|
||||||
import org.mercury_im.messenger.core.di.module.RxMercuryMessageStoreFactoryModule;
|
import org.mercury_im.messenger.core.di.module.RxMercuryMessageStoreFactoryModule;
|
||||||
|
@ -32,7 +33,6 @@ import org.mercury_im.messenger.android.ui.chat.ChatInputViewModel;
|
||||||
import org.mercury_im.messenger.android.ui.chat.AndroidChatViewModel;
|
import org.mercury_im.messenger.android.ui.chat.AndroidChatViewModel;
|
||||||
import org.mercury_im.messenger.android.ui.chatlist.AndroidChatListViewModel;
|
import org.mercury_im.messenger.android.ui.chatlist.AndroidChatListViewModel;
|
||||||
import org.mercury_im.messenger.android.ui.contacts.detail.ContactDetailActivity;
|
import org.mercury_im.messenger.android.ui.contacts.detail.ContactDetailActivity;
|
||||||
import org.mercury_im.messenger.android.ui.contacts.detail.ContactDetailViewModel;
|
|
||||||
import org.mercury_im.messenger.core.viewmodel.account.LoginViewModel;
|
import org.mercury_im.messenger.core.viewmodel.account.LoginViewModel;
|
||||||
|
|
||||||
import javax.inject.Singleton;
|
import javax.inject.Singleton;
|
||||||
|
@ -93,7 +93,7 @@ public interface AppComponent {
|
||||||
|
|
||||||
void inject(AndroidChatListViewModel chatListViewModel);
|
void inject(AndroidChatListViewModel chatListViewModel);
|
||||||
|
|
||||||
void inject(ContactDetailViewModel contactDetailViewModel);
|
void inject(AndroidContactDetailViewModel contactDetailViewModel);
|
||||||
|
|
||||||
void inject(AccountDetailsViewModel accountDetailsViewModel);
|
void inject(AccountDetailsViewModel accountDetailsViewModel);
|
||||||
|
|
||||||
|
|
|
@ -137,6 +137,9 @@ public class AccountDetailsFragment extends Fragment {
|
||||||
.replace(R.id.fragment, IkeySetupFragment.newInstance(accountId))
|
.replace(R.id.fragment, IkeySetupFragment.newInstance(accountId))
|
||||||
.commit();
|
.commit();
|
||||||
return true;
|
return true;
|
||||||
|
case R.id.action_publish_ikey_element:
|
||||||
|
sendIkeyElement();
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
return super.onOptionsItemSelected(item);
|
return super.onOptionsItemSelected(item);
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,132 @@
|
||||||
|
package org.mercury_im.messenger.android.ui.contacts.detail;
|
||||||
|
|
||||||
|
import android.graphics.drawable.Drawable;
|
||||||
|
|
||||||
|
import androidx.lifecycle.LiveData;
|
||||||
|
import androidx.lifecycle.MutableLiveData;
|
||||||
|
import androidx.lifecycle.ViewModel;
|
||||||
|
|
||||||
|
import org.jivesoftware.smack.SmackException;
|
||||||
|
import org.jivesoftware.smack.XMPPException;
|
||||||
|
import org.jivesoftware.smack.packet.Presence;
|
||||||
|
import org.mercury_im.messenger.android.MercuryImApplication;
|
||||||
|
import org.mercury_im.messenger.android.ui.avatar.AvatarDrawable;
|
||||||
|
import org.mercury_im.messenger.android.ui.base.MercuryAndroidViewModel;
|
||||||
|
import org.mercury_im.messenger.core.util.Optional;
|
||||||
|
import org.mercury_im.messenger.core.viewmodel.contact.ContactDetailViewModel;
|
||||||
|
import org.mercury_im.messenger.core.viewmodel.openpgp.FingerprintViewItem;
|
||||||
|
import org.pgpainless.key.OpenPgpV4Fingerprint;
|
||||||
|
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
import javax.inject.Inject;
|
||||||
|
|
||||||
|
import io.reactivex.Completable;
|
||||||
|
import io.reactivex.Single;
|
||||||
|
|
||||||
|
public class AndroidContactDetailViewModel extends ViewModel implements MercuryAndroidViewModel<ContactDetailViewModel> {
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
ContactDetailViewModel commonViewModel;
|
||||||
|
|
||||||
|
private MutableLiveData<UUID> contactAccountId = new MutableLiveData<>(UUID.randomUUID());
|
||||||
|
private MutableLiveData<String> contactAddress = new MutableLiveData<>("alice@wonderland.lit");
|
||||||
|
private MutableLiveData<Drawable> contactAvatar = new MutableLiveData<>(new AvatarDrawable("Alice Wonderland", "alice@wonderland.lit"));
|
||||||
|
private MutableLiveData<Presence.Mode> contactPresenceMode = new MutableLiveData<>(Presence.Mode.available);
|
||||||
|
private MutableLiveData<String> contactPresenceStatus = new MutableLiveData<>("Going down the rabbit hole.");
|
||||||
|
private MutableLiveData<String> contactName = new MutableLiveData<>("Alice Wonderland");
|
||||||
|
private MutableLiveData<String> contactAccountAddress = new MutableLiveData<>("mad@hatter.lit");
|
||||||
|
private MutableLiveData<List<String>> contactGroups = new MutableLiveData<>(Collections.emptyList());
|
||||||
|
private MutableLiveData<List<FingerprintViewItem>> contactFingerprints = new MutableLiveData<>(Collections.emptyList());
|
||||||
|
private MutableLiveData<Optional<FingerprintViewItem>> contactIkeyFingerprint = new MutableLiveData<>(new Optional<>());
|
||||||
|
|
||||||
|
public AndroidContactDetailViewModel() {
|
||||||
|
super();
|
||||||
|
MercuryImApplication.getApplication().getAppComponent().inject(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Completable init(UUID peerId) {
|
||||||
|
return getCommonViewModel().init(peerId)
|
||||||
|
|
||||||
|
.andThen(Completable.fromAction(() -> {
|
||||||
|
addDisposable(commonViewModel.getContactAccountAddress()
|
||||||
|
.map(Objects::toString)
|
||||||
|
.subscribe(contactAccountAddress::postValue));
|
||||||
|
addDisposable(commonViewModel.getContactAddress()
|
||||||
|
.map(Objects::toString)
|
||||||
|
.subscribe(contactAddress::postValue));
|
||||||
|
addDisposable(commonViewModel.getContactDisplayName().subscribe(contactName::postValue));
|
||||||
|
addDisposable(commonViewModel.getContactPresenceMode().subscribe(contactPresenceMode::postValue));
|
||||||
|
addDisposable(commonViewModel.getContactPresenceStatus().subscribe(contactPresenceStatus::postValue));
|
||||||
|
addDisposable(commonViewModel.getContactGroups().subscribe(contactGroups::postValue));
|
||||||
|
addDisposable(commonViewModel.getContactAvatarBase()
|
||||||
|
.map(tuple -> new AvatarDrawable(tuple.getFirst(), tuple.getSecond().toString()))
|
||||||
|
.subscribe(contactAvatar::postValue));
|
||||||
|
addDisposable(commonViewModel.getContactIdentityFingerprint().subscribe(contactIkeyFingerprint::postValue));
|
||||||
|
addDisposable(commonViewModel.getContactDeviceFingerprints().subscribe(contactFingerprints::postValue));
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
public LiveData<String> getContactAddress() {
|
||||||
|
return contactAddress;
|
||||||
|
}
|
||||||
|
|
||||||
|
public LiveData<UUID> getAccountId() {
|
||||||
|
return contactAccountId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public LiveData<Drawable> getContactAvatar() {
|
||||||
|
return contactAvatar;
|
||||||
|
}
|
||||||
|
|
||||||
|
public LiveData<Presence.Mode> getContactPresenceMode() {
|
||||||
|
return contactPresenceMode;
|
||||||
|
}
|
||||||
|
|
||||||
|
public LiveData<String> getContactName() {
|
||||||
|
return contactName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public LiveData<String> getContactPresenceStatus() {
|
||||||
|
return contactPresenceStatus;
|
||||||
|
}
|
||||||
|
|
||||||
|
public LiveData<String> getContactAccountAddress() {
|
||||||
|
return contactAccountAddress;
|
||||||
|
}
|
||||||
|
|
||||||
|
public LiveData<List<String>> getContactGroups() {
|
||||||
|
return contactGroups;
|
||||||
|
}
|
||||||
|
|
||||||
|
public LiveData<Optional<FingerprintViewItem>> getContactIdentityFingerprint() {
|
||||||
|
return contactIkeyFingerprint;
|
||||||
|
}
|
||||||
|
|
||||||
|
public LiveData<List<FingerprintViewItem>> getContactDeviceFingerprints() {
|
||||||
|
return contactFingerprints;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Single<UUID> getOrCreateChat() {
|
||||||
|
return commonViewModel.getOrCreateChat();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void changeContactName(String newName)
|
||||||
|
throws XMPPException.XMPPErrorException, SmackException.NotConnectedException,
|
||||||
|
InterruptedException, SmackException.NoResponseException {
|
||||||
|
commonViewModel.changeContactName(newName);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public void markDeviceFingerprintTrusted(OpenPgpV4Fingerprint fingerprint, boolean checked) {
|
||||||
|
commonViewModel.markDeviceFingerprintTrusted(fingerprint, checked);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ContactDetailViewModel getCommonViewModel() {
|
||||||
|
return commonViewModel;
|
||||||
|
}
|
||||||
|
}
|
|
@ -8,20 +8,30 @@ import androidx.lifecycle.ViewModelProvider;
|
||||||
|
|
||||||
import org.mercury_im.messenger.android.MercuryImApplication;
|
import org.mercury_im.messenger.android.MercuryImApplication;
|
||||||
import org.mercury_im.messenger.R;
|
import org.mercury_im.messenger.R;
|
||||||
|
import org.mercury_im.messenger.android.di.module.AndroidSchedulersModule;
|
||||||
import org.mercury_im.messenger.android.ui.base.MercuryActivity;
|
import org.mercury_im.messenger.android.ui.base.MercuryActivity;
|
||||||
import org.mercury_im.messenger.android.util.ArgumentUtils;
|
import org.mercury_im.messenger.android.util.ArgumentUtils;
|
||||||
|
import org.mercury_im.messenger.core.data.repository.PeerRepository;
|
||||||
|
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|
||||||
|
import javax.inject.Inject;
|
||||||
|
|
||||||
import butterknife.ButterKnife;
|
import butterknife.ButterKnife;
|
||||||
|
import io.reactivex.Completable;
|
||||||
|
import io.reactivex.android.schedulers.AndroidSchedulers;
|
||||||
|
import io.reactivex.disposables.Disposable;
|
||||||
|
import io.reactivex.schedulers.Schedulers;
|
||||||
import lombok.Value;
|
import lombok.Value;
|
||||||
|
|
||||||
public class ContactDetailActivity extends AppCompatActivity implements MercuryActivity {
|
public class ContactDetailActivity extends AppCompatActivity implements MercuryActivity {
|
||||||
public static final String EXTRA_PEER_ID = "PEER_ID";
|
public static final String EXTRA_PEER_ID = "PEER_ID";
|
||||||
|
|
||||||
private ContactDetailViewModel androidContactDetailViewModel;
|
private AndroidContactDetailViewModel androidContactDetailViewModel;
|
||||||
private UUID peerId;
|
private UUID peerId;
|
||||||
|
|
||||||
|
Disposable disposable;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onCreate(@Nullable Bundle savedInstanceState) {
|
protected void onCreate(@Nullable Bundle savedInstanceState) {
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
|
@ -30,10 +40,13 @@ public class ContactDetailActivity extends AppCompatActivity implements MercuryA
|
||||||
Arguments arguments = getArguments(savedInstanceState);
|
Arguments arguments = getArguments(savedInstanceState);
|
||||||
peerId = arguments.getPeerId();
|
peerId = arguments.getPeerId();
|
||||||
|
|
||||||
androidContactDetailViewModel = new ViewModelProvider(this).get(ContactDetailViewModel.class);
|
androidContactDetailViewModel = new ViewModelProvider(this).get(AndroidContactDetailViewModel.class);
|
||||||
androidContactDetailViewModel.init(peerId);
|
|
||||||
|
|
||||||
bindUiComponents();
|
bindUiComponents();
|
||||||
|
|
||||||
|
disposable = androidContactDetailViewModel.init(peerId)
|
||||||
|
.subscribeOn(Schedulers.io())
|
||||||
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
|
.subscribe();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void bindUiComponents() {
|
private void bindUiComponents() {
|
||||||
|
@ -55,4 +68,10 @@ public class ContactDetailActivity extends AppCompatActivity implements MercuryA
|
||||||
private class Arguments {
|
private class Arguments {
|
||||||
UUID peerId;
|
UUID peerId;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onDestroy() {
|
||||||
|
super.onDestroy();
|
||||||
|
disposable.dispose();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -82,7 +82,6 @@ public class ContactDetailFragment extends Fragment {
|
||||||
@BindView(R.id.card_contact_fingerprints)
|
@BindView(R.id.card_contact_fingerprints)
|
||||||
MaterialCardView fingerprintsLayout;
|
MaterialCardView fingerprintsLayout;
|
||||||
|
|
||||||
private ContactDetailViewModel viewModel;
|
|
||||||
@BindView(R.id.card_ikey_fingerprint)
|
@BindView(R.id.card_ikey_fingerprint)
|
||||||
MaterialCardView ikeyLayout;
|
MaterialCardView ikeyLayout;
|
||||||
|
|
||||||
|
@ -90,6 +89,7 @@ public class ContactDetailFragment extends Fragment {
|
||||||
TextView ikeyFingerprint;
|
TextView ikeyFingerprint;
|
||||||
|
|
||||||
private ToggleableFingerprintsAdapter fingerprintsAdapter;
|
private ToggleableFingerprintsAdapter fingerprintsAdapter;
|
||||||
|
private AndroidContactDetailViewModel viewModel;
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
@Override
|
@Override
|
||||||
|
@ -107,17 +107,9 @@ public class ContactDetailFragment extends Fragment {
|
||||||
|
|
||||||
contactName.setOnClickListener(v -> displayChangeContactNameDialog());
|
contactName.setOnClickListener(v -> displayChangeContactNameDialog());
|
||||||
|
|
||||||
button.setOnClickListener(new View.OnClickListener() {
|
|
||||||
@Override
|
|
||||||
public void onClick(View v) {
|
|
||||||
viewModel.addContactToRosterGroup().subscribeOn(Schedulers.io())
|
|
||||||
.observeOn(AndroidSchedulers.mainThread())
|
|
||||||
.subscribe();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
fingerprintsAdapter = new ToggleableFingerprintsAdapter(
|
fingerprintsAdapter = new ToggleableFingerprintsAdapter(
|
||||||
(fingerprint, checked) -> viewModel.markFingerprintTrusted(fingerprint, checked));
|
(fingerprint, checked) -> viewModel.markDeviceFingerprintTrusted(fingerprint, checked));
|
||||||
fingerprintRecyclerView.setAdapter(fingerprintsAdapter);
|
fingerprintRecyclerView.setAdapter(fingerprintsAdapter);
|
||||||
|
|
||||||
return view;
|
return view;
|
||||||
|
@ -142,7 +134,7 @@ public class ContactDetailFragment extends Fragment {
|
||||||
@Override
|
@Override
|
||||||
public void onAttach(Context context) {
|
public void onAttach(Context context) {
|
||||||
super.onAttach(context);
|
super.onAttach(context);
|
||||||
viewModel = new ViewModelProvider((ViewModelStoreOwner) context).get(ContactDetailViewModel.class);
|
viewModel = new ViewModelProvider((ViewModelStoreOwner) context).get(AndroidContactDetailViewModel.class);
|
||||||
|
|
||||||
observeViewModel();
|
observeViewModel();
|
||||||
}
|
}
|
||||||
|
@ -171,8 +163,8 @@ public class ContactDetailFragment extends Fragment {
|
||||||
viewModel.getContactPresenceStatus().observe(this, presenceText -> contactPresence.setText(presenceText));
|
viewModel.getContactPresenceStatus().observe(this, presenceText -> contactPresence.setText(presenceText));
|
||||||
viewModel.getContactAccountAddress().observe(this, address -> contactAccount.setText(address));
|
viewModel.getContactAccountAddress().observe(this, address -> contactAccount.setText(address));
|
||||||
viewModel.getContactGroups().observe(this, this::setRosterGroups);
|
viewModel.getContactGroups().observe(this, this::setRosterGroups);
|
||||||
viewModel.getContactFingerprints().observe(this, this::setFingerprints);
|
viewModel.getContactDeviceFingerprints().observe(this, this::setFingerprints);
|
||||||
viewModel.getContactIkeyFingerprint().observe(this, this::setIkeyFingerprint);
|
viewModel.getContactIdentityFingerprint().observe(this, this::setIkeyFingerprint);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setRosterGroups(List<String> groups) {
|
private void setRosterGroups(List<String> groups) {
|
||||||
|
@ -180,15 +172,6 @@ public class ContactDetailFragment extends Fragment {
|
||||||
for (String group : groups) {
|
for (String group : groups) {
|
||||||
Chip chip = new Chip(contactGroups.getContext());
|
Chip chip = new Chip(contactGroups.getContext());
|
||||||
chip.setText(group);
|
chip.setText(group);
|
||||||
chip.setOnLongClickListener(new View.OnLongClickListener() {
|
|
||||||
@Override
|
|
||||||
public boolean onLongClick(View v) {
|
|
||||||
viewModel.removeContactFromRosterGroup(group)
|
|
||||||
.subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread())
|
|
||||||
.subscribe();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
contactGroups.addView(chip);
|
contactGroups.addView(chip);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,258 +0,0 @@
|
||||||
package org.mercury_im.messenger.android.ui.contacts.detail;
|
|
||||||
|
|
||||||
import android.graphics.drawable.Drawable;
|
|
||||||
|
|
||||||
import androidx.lifecycle.LiveData;
|
|
||||||
import androidx.lifecycle.MutableLiveData;
|
|
||||||
import androidx.lifecycle.ViewModel;
|
|
||||||
|
|
||||||
import org.jivesoftware.smack.SmackException;
|
|
||||||
import org.jivesoftware.smack.XMPPException;
|
|
||||||
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.RosterGroup;
|
|
||||||
import org.jivesoftware.smackx.ikey.util.IkeyTrust;
|
|
||||||
import org.jivesoftware.smackx.ox.store.definition.OpenPgpTrustStore;
|
|
||||||
import org.jxmpp.jid.BareJid;
|
|
||||||
import org.jxmpp.jid.Jid;
|
|
||||||
import org.jxmpp.jid.impl.JidCreate;
|
|
||||||
import org.mercury_im.messenger.android.MercuryImApplication;
|
|
||||||
import org.mercury_im.messenger.core.Messenger;
|
|
||||||
import org.mercury_im.messenger.core.SchedulersFacade;
|
|
||||||
import org.mercury_im.messenger.core.crypto.ikey.IkeyRepository;
|
|
||||||
import org.mercury_im.messenger.core.data.repository.DirectChatRepository;
|
|
||||||
import org.mercury_im.messenger.core.data.repository.OpenPgpRepository;
|
|
||||||
import org.mercury_im.messenger.core.data.repository.PeerRepository;
|
|
||||||
import org.mercury_im.messenger.core.util.Optional;
|
|
||||||
import org.mercury_im.messenger.core.viewmodel.openpgp.FingerprintViewItem;
|
|
||||||
import org.mercury_im.messenger.entity.chat.Chat;
|
|
||||||
import org.mercury_im.messenger.android.ui.avatar.AvatarDrawable;
|
|
||||||
import org.mercury_im.messenger.core.util.CombinedPresenceListener;
|
|
||||||
import org.pgpainless.key.OpenPgpV4Fingerprint;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.Date;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.UUID;
|
|
||||||
|
|
||||||
import javax.inject.Inject;
|
|
||||||
|
|
||||||
import io.reactivex.Completable;
|
|
||||||
import io.reactivex.Single;
|
|
||||||
import io.reactivex.disposables.CompositeDisposable;
|
|
||||||
|
|
||||||
public class ContactDetailViewModel extends ViewModel {
|
|
||||||
|
|
||||||
@Inject
|
|
||||||
PeerRepository peerRepository;
|
|
||||||
|
|
||||||
@Inject
|
|
||||||
DirectChatRepository directChatRepository;
|
|
||||||
|
|
||||||
@Inject
|
|
||||||
OpenPgpRepository openPgpRepository;
|
|
||||||
|
|
||||||
@Inject
|
|
||||||
IkeyRepository ikeyRepository;
|
|
||||||
|
|
||||||
@Inject
|
|
||||||
SchedulersFacade schedulers;
|
|
||||||
|
|
||||||
@Inject
|
|
||||||
Messenger messenger;
|
|
||||||
|
|
||||||
private MutableLiveData<UUID> contactAccountId = new MutableLiveData<>(UUID.randomUUID());
|
|
||||||
private MutableLiveData<String> contactAddress = new MutableLiveData<>("alice@wonderland.lit");
|
|
||||||
private MutableLiveData<Drawable> contactAvatar = new MutableLiveData<>(new AvatarDrawable("Alice Wonderland", "alice@wonderland.lit"));
|
|
||||||
private MutableLiveData<Presence.Mode> contactPresenceMode = new MutableLiveData<>(Presence.Mode.available);
|
|
||||||
private MutableLiveData<String> contactPresenceStatus = new MutableLiveData<>("Going down the rabbit hole.");
|
|
||||||
private MutableLiveData<String> contactName = new MutableLiveData<>("Alice Wonderland");
|
|
||||||
private MutableLiveData<String> contactAccountAddress = new MutableLiveData<>("mad@hatter.lit");
|
|
||||||
private MutableLiveData<List<String>> contactGroups = new MutableLiveData<>(Collections.emptyList());
|
|
||||||
private MutableLiveData<List<FingerprintViewItem>> contactFingerprints = new MutableLiveData<>(Collections.emptyList());
|
|
||||||
private MutableLiveData<Optional<FingerprintViewItem>> contactIkeyFingerprint = new MutableLiveData<>(new Optional<>());
|
|
||||||
|
|
||||||
private Roster roster;
|
|
||||||
private UUID peerId;
|
|
||||||
|
|
||||||
private CompositeDisposable disposable = new CompositeDisposable();
|
|
||||||
|
|
||||||
public ContactDetailViewModel() {
|
|
||||||
super();
|
|
||||||
MercuryImApplication.getApplication().getAppComponent().inject(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void init(UUID peerId) {
|
|
||||||
this.peerId = peerId;
|
|
||||||
|
|
||||||
disposable.add(peerRepository.getPeer(peerId)
|
|
||||||
.subscribe(p -> {
|
|
||||||
roster = Roster.getInstanceFor(messenger.getConnectionManager().getConnection(p.getAccount()).getConnection());
|
|
||||||
roster.addPresenceEventListener(presenceEventListener);
|
|
||||||
|
|
||||||
Presence presence = roster.getPresence(p.getJid());
|
|
||||||
if (presence != null) {
|
|
||||||
contactPresenceMode.postValue(presence.getMode());
|
|
||||||
contactPresenceStatus.postValue(presence.getStatus());
|
|
||||||
}
|
|
||||||
|
|
||||||
disposable.add(openPgpRepository.observeFingerprints(p.getAccount().getId(), p.getJid())
|
|
||||||
.subscribeOn(schedulers.getIoScheduler())
|
|
||||||
.observeOn(schedulers.getUiScheduler())
|
|
||||||
.subscribe(list -> contactFingerprints.setValue(list)));
|
|
||||||
}));
|
|
||||||
|
|
||||||
disposable.add(peerRepository.observePeer(peerId)
|
|
||||||
.filter(Optional::isPresent)
|
|
||||||
.map(Optional::getItem)
|
|
||||||
.compose(schedulers.executeUiSafeObservable())
|
|
||||||
.subscribe(peer -> {
|
|
||||||
|
|
||||||
|
|
||||||
contactAddress.setValue(peer.getAddress());
|
|
||||||
contactAccountId.setValue(peer.getAccount().getId());
|
|
||||||
contactAccountAddress.setValue(peer.getAccount().getAddress());
|
|
||||||
contactAvatar.setValue(new AvatarDrawable(peer.getDisplayName(), peer.getAddress()));
|
|
||||||
contactName.setValue(peer.getDisplayName());
|
|
||||||
|
|
||||||
RosterEntry entry = roster.getEntry(peer.getJid());
|
|
||||||
if (entry != null) {
|
|
||||||
List<RosterGroup> groups = entry.getGroups();
|
|
||||||
List<String> groupNames = new ArrayList<>(groups.size());
|
|
||||||
for (RosterGroup g : groups) {
|
|
||||||
groupNames.add(g.getName());
|
|
||||||
}
|
|
||||||
contactGroups.postValue(groupNames);
|
|
||||||
}
|
|
||||||
}));
|
|
||||||
|
|
||||||
disposable.add(ikeyRepository.loadRecord(
|
|
||||||
getAccountId().getValue(),
|
|
||||||
JidCreate.entityBareFromOrThrowUnchecked(getContactAccountAddress().getValue()))
|
|
||||||
.compose(schedulers.executeUiSafeObservable())
|
|
||||||
.subscribe(r -> {
|
|
||||||
Optional<IkeyTrust> trustOptional = ikeyRepository.loadSuperordinateTrust(
|
|
||||||
getAccountId().getValue(),
|
|
||||||
JidCreate.entityBareFromOrThrowUnchecked(getContactAccountAddress().getValue()),
|
|
||||||
new OpenPgpV4Fingerprint(r.getSuperordinate())
|
|
||||||
).blockingFirst();
|
|
||||||
contactIkeyFingerprint.postValue(
|
|
||||||
new Optional<>(new FingerprintViewItem(
|
|
||||||
getAccountId().getValue(),
|
|
||||||
JidCreate.entityBareFromOrThrowUnchecked(getContactAccountAddress().getValue()),
|
|
||||||
new OpenPgpV4Fingerprint(r.getSuperordinate()),
|
|
||||||
r.getTimestamp(),
|
|
||||||
new Date(),
|
|
||||||
trustOptional.isPresent() ? trustOptional.getItem().getTrust() : OpenPgpTrustStore.Trust.undecided)));
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
|
|
||||||
public LiveData<String> getContactAddress() {
|
|
||||||
return contactAddress;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onCleared() {
|
|
||||||
super.onCleared();
|
|
||||||
disposable.dispose();
|
|
||||||
if (roster != null) {
|
|
||||||
roster.removePresenceEventListener(presenceEventListener);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public LiveData<UUID> getAccountId() {
|
|
||||||
return contactAccountId;
|
|
||||||
}
|
|
||||||
|
|
||||||
public LiveData<Drawable> getContactAvatar() {
|
|
||||||
return contactAvatar;
|
|
||||||
}
|
|
||||||
|
|
||||||
public LiveData<Presence.Mode> getContactPresenceMode() {
|
|
||||||
return contactPresenceMode;
|
|
||||||
}
|
|
||||||
|
|
||||||
public LiveData<String> getContactName() {
|
|
||||||
return contactName;
|
|
||||||
}
|
|
||||||
|
|
||||||
public LiveData<String> getContactPresenceStatus() {
|
|
||||||
return contactPresenceStatus;
|
|
||||||
}
|
|
||||||
|
|
||||||
public LiveData<String> getContactAccountAddress() {
|
|
||||||
return contactAccountAddress;
|
|
||||||
}
|
|
||||||
|
|
||||||
public LiveData<List<String>> getContactGroups() {
|
|
||||||
return contactGroups;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Single<UUID> getOrCreateChat() {
|
|
||||||
return peerRepository.getPeer(peerId)
|
|
||||||
.flatMapSingle(directChatRepository::getOrCreateChatWithPeer)
|
|
||||||
.map(Chat::getId);
|
|
||||||
}
|
|
||||||
|
|
||||||
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());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
public void changeContactName(String newName)
|
|
||||||
throws XMPPException.XMPPErrorException, SmackException.NotConnectedException,
|
|
||||||
InterruptedException, SmackException.NoResponseException {
|
|
||||||
if (!newName.trim().isEmpty()) {
|
|
||||||
RosterEntry entry = roster.getEntry(JidCreate.entityBareFromOrThrowUnchecked(getContactAddress().getValue()));
|
|
||||||
entry.setName(newName);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public Completable addContactToRosterGroup() {
|
|
||||||
return Completable.fromAction(() -> doAddContactToRosterGroup());
|
|
||||||
}
|
|
||||||
|
|
||||||
private void doAddContactToRosterGroup() throws XMPPException.XMPPErrorException, SmackException.NotConnectedException, InterruptedException, SmackException.NoResponseException {
|
|
||||||
String groupName = "Mercury Seven";
|
|
||||||
RosterGroup group = roster.getGroup(groupName);
|
|
||||||
if (group == null) {
|
|
||||||
group = roster.createGroup(groupName);
|
|
||||||
}
|
|
||||||
BareJid jid = JidCreate.entityBareFromOrThrowUnchecked(getContactAddress().getValue());
|
|
||||||
if (group.contains(jid)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
RosterEntry entry = roster.getEntry(jid);
|
|
||||||
group.addEntry(entry);
|
|
||||||
}
|
|
||||||
|
|
||||||
public Completable removeContactFromRosterGroup(String group) {
|
|
||||||
return Completable.fromAction(() -> roster.getGroup(group).removeEntry(roster.getEntry(JidCreate.entityBareFromOrThrowUnchecked(getContactAddress().getValue()))));
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public LiveData<List<FingerprintViewItem>> getContactFingerprints() {
|
|
||||||
return contactFingerprints;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void markFingerprintTrusted(OpenPgpV4Fingerprint fingerprint, boolean checked) {
|
|
||||||
openPgpRepository.storeTrust(contactAccountId.getValue(),
|
|
||||||
JidCreate.entityBareFromOrThrowUnchecked(contactAddress.getValue()),
|
|
||||||
fingerprint,
|
|
||||||
checked ? OpenPgpTrustStore.Trust.trusted : OpenPgpTrustStore.Trust.untrusted)
|
|
||||||
.subscribe();
|
|
||||||
}
|
|
||||||
|
|
||||||
public LiveData<Optional<FingerprintViewItem>> getContactIkeyFingerprint() {
|
|
||||||
return contactIkeyFingerprint;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -27,4 +27,8 @@
|
||||||
android:id="@+id/action_setup_ikey"
|
android:id="@+id/action_setup_ikey"
|
||||||
android:title="Ikey Setup" />
|
android:title="Ikey Setup" />
|
||||||
|
|
||||||
|
<item
|
||||||
|
android:id="@+id/action_publish_ikey_element"
|
||||||
|
android:title="Publish Ikey Element" />
|
||||||
|
|
||||||
</menu>
|
</menu>
|
|
@ -4,6 +4,7 @@ import org.mercury_im.messenger.cli.MercuryCli;
|
||||||
import org.mercury_im.messenger.cli.di.module.CliDatabaseModule;
|
import org.mercury_im.messenger.cli.di.module.CliDatabaseModule;
|
||||||
import org.mercury_im.messenger.cli.di.module.CliSchedulersModule;
|
import org.mercury_im.messenger.cli.di.module.CliSchedulersModule;
|
||||||
import org.mercury_im.messenger.cli.di.module.X509WorkaroundConnectionFactoryModule;
|
import org.mercury_im.messenger.cli.di.module.X509WorkaroundConnectionFactoryModule;
|
||||||
|
import org.mercury_im.messenger.core.di.module.IkeyModule;
|
||||||
import org.mercury_im.messenger.core.di.module.OpenPgpModule;
|
import org.mercury_im.messenger.core.di.module.OpenPgpModule;
|
||||||
import org.mercury_im.messenger.core.di.module.RxMercuryMessageStoreFactoryModule;
|
import org.mercury_im.messenger.core.di.module.RxMercuryMessageStoreFactoryModule;
|
||||||
import org.mercury_im.messenger.core.di.module.RxMercuryRosterStoreFactoryModule;
|
import org.mercury_im.messenger.core.di.module.RxMercuryRosterStoreFactoryModule;
|
||||||
|
@ -26,7 +27,8 @@ import dagger.Component;
|
||||||
RxMercuryMessageStoreFactoryModule.class,
|
RxMercuryMessageStoreFactoryModule.class,
|
||||||
RxMercuryRosterStoreFactoryModule.class,
|
RxMercuryRosterStoreFactoryModule.class,
|
||||||
OpenPgpModule.class,
|
OpenPgpModule.class,
|
||||||
StanzaIdSourceFactoryModule.class
|
StanzaIdSourceFactoryModule.class,
|
||||||
|
IkeyModule.class
|
||||||
})
|
})
|
||||||
public interface CliComponent {
|
public interface CliComponent {
|
||||||
|
|
||||||
|
|
|
@ -163,11 +163,19 @@ public final class IkeyManager extends Manager {
|
||||||
return new IkeyElement(mechanism.getType(), superordinateElement, signedElement, proofElement);
|
return new IkeyElement(mechanism.getType(), superordinateElement, signedElement, proofElement);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void publishIkeyElement(IkeyElement ikeyElement)
|
public void storeAndPublishElement(IkeyElement ikeyElement)
|
||||||
throws InterruptedException, PubSubException.NotALeafNodeException, XMPPException.XMPPErrorException,
|
throws InterruptedException, PubSubException.NotALeafNodeException, XMPPException.XMPPErrorException,
|
||||||
SmackException.NotConnectedException, SmackException.NoResponseException {
|
SmackException.NotConnectedException, SmackException.NoResponseException, IOException, PGPException {
|
||||||
|
store.storeIkeyRecord(connection().getUser().asEntityBareJid(), elementToRecord(ikeyElement));
|
||||||
|
publishIkeyElement(ikeyElement);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void publishIkeyElement(IkeyElement element)
|
||||||
|
throws InterruptedException, PubSubException.NotALeafNodeException,
|
||||||
|
XMPPException.XMPPErrorException, SmackException.NotConnectedException,
|
||||||
|
SmackException.NoResponseException {
|
||||||
PepManager.getInstanceFor(connection())
|
PepManager.getInstanceFor(connection())
|
||||||
.publish(IkeyConstants.SUBORDINATES_NODE, new PayloadItem<>(ikeyElement));
|
.publish(IkeyConstants.SUBORDINATES_NODE, new PayloadItem<>(element));
|
||||||
}
|
}
|
||||||
|
|
||||||
public IkeyElement fetchIkeyElementOf(EntityBareJid jid)
|
public IkeyElement fetchIkeyElementOf(EntityBareJid jid)
|
||||||
|
@ -201,7 +209,8 @@ public final class IkeyManager extends Manager {
|
||||||
throws IOException, UnsupportedSignatureAlgorithmException, PGPException {
|
throws IOException, UnsupportedSignatureAlgorithmException, PGPException {
|
||||||
IkeyRecord newRecord = elementToRecord(element);
|
IkeyRecord newRecord = elementToRecord(element);
|
||||||
if (isFromTheFuture(newRecord)) {
|
if (isFromTheFuture(newRecord)) {
|
||||||
LOGGER.log(Level.WARNING, "Received ikey element appears to be from the future: " + element.getSignedElement().getChildElement().getTimestamp());
|
LOGGER.log(Level.WARNING, "Received ikey element appears to be from the future: " +
|
||||||
|
newRecord.getTimestamp());
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -307,9 +316,9 @@ public final class IkeyManager extends Manager {
|
||||||
}
|
}
|
||||||
|
|
||||||
private static boolean isFromTheFuture(IkeyRecord record) {
|
private static boolean isFromTheFuture(IkeyRecord record) {
|
||||||
Date elementTimestamp = record.getTimestamp();
|
Date timestamp = record.getTimestamp();
|
||||||
Date now = new Date();
|
Date now = new Date();
|
||||||
return elementTimestamp.after(now);
|
return timestamp.after(now);
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean existsSameOrNewerRecord(IkeyRecord record) throws IOException {
|
private boolean existsSameOrNewerRecord(IkeyRecord record) throws IOException {
|
||||||
|
|
|
@ -0,0 +1,9 @@
|
||||||
|
package org.mercury_im.messenger.core.util;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
public class Tuple<A, B> {
|
||||||
|
private final A first;
|
||||||
|
private final B second;
|
||||||
|
}
|
|
@ -1,6 +1,5 @@
|
||||||
package org.mercury_im.messenger.core.viewmodel.account.detail;
|
package org.mercury_im.messenger.core.viewmodel.account.detail;
|
||||||
|
|
||||||
import org.bouncycastle.openpgp.PGPException;
|
|
||||||
import org.bouncycastle.openpgp.PGPSecretKeyRing;
|
import org.bouncycastle.openpgp.PGPSecretKeyRing;
|
||||||
import org.jivesoftware.smack.XMPPConnection;
|
import org.jivesoftware.smack.XMPPConnection;
|
||||||
import org.jivesoftware.smackx.ikey.IkeyManager;
|
import org.jivesoftware.smackx.ikey.IkeyManager;
|
||||||
|
@ -88,7 +87,7 @@ public class AccountDetailsViewModel implements MercuryViewModel {
|
||||||
IkeyElement ikeyElement = syncCreateIkeyElement(accountId);
|
IkeyElement ikeyElement = syncCreateIkeyElement(accountId);
|
||||||
IkeyManager ikeyManager = IkeyManager.getInstanceFor(connectionManager.getConnection(accountId).getConnection());
|
IkeyManager ikeyManager = IkeyManager.getInstanceFor(connectionManager.getConnection(accountId).getConnection());
|
||||||
|
|
||||||
ikeyManager.publishIkeyElement(ikeyElement);
|
ikeyManager.storeAndPublishElement(ikeyElement);
|
||||||
})
|
})
|
||||||
.compose(schedulers.executeUiSafeCompletable())
|
.compose(schedulers.executeUiSafeCompletable())
|
||||||
.subscribe(
|
.subscribe(
|
||||||
|
@ -96,7 +95,7 @@ public class AccountDetailsViewModel implements MercuryViewModel {
|
||||||
e -> LOGGER.log(Level.SEVERE, "Error publishing ikey element:", e)));
|
e -> LOGGER.log(Level.SEVERE, "Error publishing ikey element:", e)));
|
||||||
}
|
}
|
||||||
|
|
||||||
private IkeyElement syncCreateIkeyElement(UUID accountId) throws URISyntaxException, PGPException, IOException {
|
private IkeyElement syncCreateIkeyElement(UUID accountId) throws URISyntaxException, IOException {
|
||||||
MercuryConnection connection = connectionManager.getConnection(accountId);
|
MercuryConnection connection = connectionManager.getConnection(accountId);
|
||||||
IkeyManager ikeyManager = ikeyInitializer.initFor(connection);
|
IkeyManager ikeyManager = ikeyInitializer.initFor(connection);
|
||||||
|
|
||||||
|
@ -124,8 +123,9 @@ public class AccountDetailsViewModel implements MercuryViewModel {
|
||||||
subordinateElements.add(new SubordinateElement("urn:xmpp:openpgp:0", u, fp.toString()));
|
subordinateElements.add(new SubordinateElement("urn:xmpp:openpgp:0", u, fp.toString()));
|
||||||
}
|
}
|
||||||
|
|
||||||
PGPSecretKeyRing secretKeys = openPgpRepository.loadSecretKeysOf(accountId, account.getJid())
|
PGPSecretKeyRing secretKeys = ikeyRepository.loadSecretKey(accountId)
|
||||||
.blockingGet().getSecretKeyRing(localFp.getKeyId());
|
.blockingFirst(new Optional<>())
|
||||||
|
.getItem();
|
||||||
|
|
||||||
IkeyElement ikeyElement = ikeyManager.createOxIkeyElement(secretKeys,
|
IkeyElement ikeyElement = ikeyManager.createOxIkeyElement(secretKeys,
|
||||||
new UnprotectedKeysProtector(), subordinateElements.toArray(new SubordinateElement[]{}));
|
new UnprotectedKeysProtector(), subordinateElements.toArray(new SubordinateElement[]{}));
|
||||||
|
|
|
@ -0,0 +1,252 @@
|
||||||
|
package org.mercury_im.messenger.core.viewmodel.contact;
|
||||||
|
|
||||||
|
import org.jivesoftware.smack.SmackException;
|
||||||
|
import org.jivesoftware.smack.XMPPException;
|
||||||
|
import org.jivesoftware.smack.packet.Presence;
|
||||||
|
import org.jivesoftware.smack.roster.Roster;
|
||||||
|
import org.jivesoftware.smack.roster.RosterEntry;
|
||||||
|
import org.jivesoftware.smack.roster.RosterGroup;
|
||||||
|
import org.jivesoftware.smackx.ox.store.definition.OpenPgpTrustStore;
|
||||||
|
import org.jxmpp.jid.EntityBareJid;
|
||||||
|
import org.jxmpp.jid.Jid;
|
||||||
|
import org.jxmpp.jid.impl.JidCreate;
|
||||||
|
import org.mercury_im.messenger.core.SchedulersFacade;
|
||||||
|
import org.mercury_im.messenger.core.connection.MercuryConnection;
|
||||||
|
import org.mercury_im.messenger.core.connection.MercuryConnectionManager;
|
||||||
|
import org.mercury_im.messenger.core.crypto.ikey.IkeyRepository;
|
||||||
|
import org.mercury_im.messenger.core.data.repository.DirectChatRepository;
|
||||||
|
import org.mercury_im.messenger.core.data.repository.OpenPgpRepository;
|
||||||
|
import org.mercury_im.messenger.core.data.repository.PeerRepository;
|
||||||
|
import org.mercury_im.messenger.core.util.CombinedPresenceListener;
|
||||||
|
import org.mercury_im.messenger.core.util.Optional;
|
||||||
|
import org.mercury_im.messenger.core.util.Tuple;
|
||||||
|
import org.mercury_im.messenger.core.viewmodel.MercuryViewModel;
|
||||||
|
import org.mercury_im.messenger.core.viewmodel.openpgp.FingerprintViewItem;
|
||||||
|
import org.mercury_im.messenger.entity.chat.Chat;
|
||||||
|
import org.mercury_im.messenger.entity.contact.Peer;
|
||||||
|
import org.pgpainless.key.OpenPgpV4Fingerprint;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
import javax.inject.Inject;
|
||||||
|
|
||||||
|
import io.reactivex.Completable;
|
||||||
|
import io.reactivex.Observable;
|
||||||
|
import io.reactivex.Single;
|
||||||
|
import io.reactivex.subjects.BehaviorSubject;
|
||||||
|
import lombok.Getter;
|
||||||
|
|
||||||
|
public class ContactDetailViewModel implements MercuryViewModel {
|
||||||
|
|
||||||
|
private final MercuryConnectionManager connectionManager;
|
||||||
|
private final PeerRepository peerRepository;
|
||||||
|
private final DirectChatRepository directChatRepository;
|
||||||
|
private final OpenPgpRepository openPgpRepository;
|
||||||
|
private final IkeyRepository ikeyRepository;
|
||||||
|
private final SchedulersFacade schedulers;
|
||||||
|
|
||||||
|
private Roster roster;
|
||||||
|
private FilteredPresenceEventListener presenceEventListener;
|
||||||
|
|
||||||
|
private BehaviorSubject<EntityBareJid> contactAddress = BehaviorSubject.create();
|
||||||
|
private BehaviorSubject<String> contactDisplayName = BehaviorSubject.create();
|
||||||
|
private BehaviorSubject<EntityBareJid> contactAccountAddress = BehaviorSubject.create();
|
||||||
|
private BehaviorSubject<Presence.Mode> contactPresenceMode = BehaviorSubject.create();
|
||||||
|
private BehaviorSubject<String> contactPresenceStatus = BehaviorSubject.create();
|
||||||
|
private BehaviorSubject<List<String>> contactGroups = BehaviorSubject.create();
|
||||||
|
private BehaviorSubject<Optional<FingerprintViewItem>> contactIdentityFingerprint = BehaviorSubject.createDefault(new Optional<>());
|
||||||
|
private BehaviorSubject<List<FingerprintViewItem>> contactDeviceFingerprints = BehaviorSubject.createDefault(Collections.emptyList());
|
||||||
|
private BehaviorSubject<Tuple<String, EntityBareJid>> contactAvatarBase = BehaviorSubject.create();
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
private UUID peerId;
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
private UUID accountId;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
public ContactDetailViewModel(MercuryConnectionManager connectionManager,
|
||||||
|
PeerRepository peerRepository,
|
||||||
|
DirectChatRepository directChatRepository,
|
||||||
|
OpenPgpRepository openPgpRepository,
|
||||||
|
IkeyRepository ikeyRepository,
|
||||||
|
SchedulersFacade schedulers) {
|
||||||
|
this.connectionManager = connectionManager;
|
||||||
|
this.peerRepository = peerRepository;
|
||||||
|
this.directChatRepository = directChatRepository;
|
||||||
|
this.openPgpRepository = openPgpRepository;
|
||||||
|
this.ikeyRepository = ikeyRepository;
|
||||||
|
this.schedulers = schedulers;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Completable init(UUID peerId) {
|
||||||
|
return peerRepository.getPeer(peerId)
|
||||||
|
.flatMapCompletable(this::init);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Completable init(Peer peer) {
|
||||||
|
return Completable.fromAction(() -> {
|
||||||
|
this.peerId = peer.getId();
|
||||||
|
this.accountId = peer.getAccount().getId();
|
||||||
|
|
||||||
|
MercuryConnection connection = connectionManager.getConnection(peer.getAccount());
|
||||||
|
roster = Roster.getInstanceFor(connection.getConnection());
|
||||||
|
setupPresenceEventListener(roster, peer.getJid());
|
||||||
|
|
||||||
|
addDisposable(peerRepository
|
||||||
|
.observePeer(peerId)
|
||||||
|
.filter(Optional::isPresent)
|
||||||
|
.map(Optional::getItem)
|
||||||
|
.compose(schedulers.executeUiSafeObservable())
|
||||||
|
.subscribe(p -> {
|
||||||
|
contactAddress.onNext(peer.getJid());
|
||||||
|
contactDisplayName.onNext(peer.getDisplayName());
|
||||||
|
contactAccountAddress.onNext(peer.getAccount().getJid());
|
||||||
|
contactAvatarBase.onNext(new Tuple<>(peer.getDisplayName(), peer.getJid()));
|
||||||
|
}));
|
||||||
|
|
||||||
|
addDisposable(openPgpRepository
|
||||||
|
.observeFingerprints(peer.getAccount().getId(), peer.getJid())
|
||||||
|
.compose(schedulers.executeUiSafeObservable())
|
||||||
|
.subscribe(fingerprints -> contactDeviceFingerprints.onNext(fingerprints)));
|
||||||
|
|
||||||
|
addDisposable(ikeyRepository
|
||||||
|
.loadRecord(peer.getAccount().getId(), peer.getJid())
|
||||||
|
.compose(schedulers.executeUiSafeObservable())
|
||||||
|
.map(record -> new FingerprintViewItem(
|
||||||
|
peer.getAccount().getId(), record.getJid(),
|
||||||
|
new OpenPgpV4Fingerprint(record.getSuperordinate()),
|
||||||
|
record.getTimestamp(), record.getTimestamp(),
|
||||||
|
OpenPgpTrustStore.Trust.trusted) // TODO
|
||||||
|
)
|
||||||
|
.map(Optional::new)
|
||||||
|
.subscribe(contactIdentityFingerprint::onNext));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setupPresenceEventListener(Roster roster, EntityBareJid jid) {
|
||||||
|
if (presenceEventListener != null) {
|
||||||
|
if (presenceEventListener.getJid().equals(jid)) {
|
||||||
|
// already set up
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// outdated listener from previous contact
|
||||||
|
roster.removePresenceEventListener(presenceEventListener);
|
||||||
|
}
|
||||||
|
|
||||||
|
presenceEventListener = new FilteredPresenceEventListener(jid) {
|
||||||
|
@Override
|
||||||
|
public void presenceReceived(Presence presence) {
|
||||||
|
contactPresenceMode.onNext(presence.getMode());
|
||||||
|
if (presence.getStatus() != null) {
|
||||||
|
contactPresenceStatus.onNext(presence.getStatus());
|
||||||
|
}
|
||||||
|
|
||||||
|
RosterEntry entry = roster.getEntry(jid);
|
||||||
|
if (entry == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
List<RosterGroup> groups = entry.getGroups();
|
||||||
|
List<String> groupNames = new ArrayList<>(groups.size());
|
||||||
|
for (RosterGroup group : groups) {
|
||||||
|
groupNames.add(group.getName());
|
||||||
|
}
|
||||||
|
contactGroups.onNext(groupNames);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
// Trigger once
|
||||||
|
Presence presence = roster.getPresence(jid);
|
||||||
|
if (presence != null) {
|
||||||
|
presenceEventListener.presenceReceived(presence);
|
||||||
|
}
|
||||||
|
|
||||||
|
roster.addPresenceEventListener(presenceEventListener);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Observable<EntityBareJid> getContactAddress() {
|
||||||
|
return contactAddress;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Observable<String> getContactDisplayName() {
|
||||||
|
return contactDisplayName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Observable<EntityBareJid> getContactAccountAddress() {
|
||||||
|
return contactAccountAddress;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Observable<Presence.Mode> getContactPresenceMode() {
|
||||||
|
return contactPresenceMode;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Observable<String> getContactPresenceStatus() {
|
||||||
|
return contactPresenceStatus;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Observable<List<String>> getContactGroups() {
|
||||||
|
return contactGroups;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Observable<Optional<FingerprintViewItem>> getContactIdentityFingerprint() {
|
||||||
|
return contactIdentityFingerprint;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Observable<List<FingerprintViewItem>> getContactDeviceFingerprints() {
|
||||||
|
return contactDeviceFingerprints;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Observable<Tuple<String, EntityBareJid>> getContactAvatarBase() {
|
||||||
|
return contactAvatarBase;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Single<UUID> getOrCreateChat() {
|
||||||
|
return peerRepository.getPeer(peerId)
|
||||||
|
.flatMapSingle(directChatRepository::getOrCreateChatWithPeer)
|
||||||
|
.map(Chat::getId);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void changeContactName(String newName)
|
||||||
|
throws XMPPException.XMPPErrorException, SmackException.NotConnectedException,
|
||||||
|
InterruptedException, SmackException.NoResponseException {
|
||||||
|
if (!newName.trim().isEmpty()) {
|
||||||
|
RosterEntry entry = roster.getEntry(contactAddress.getValue());
|
||||||
|
entry.setName(newName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void markDeviceFingerprintTrusted(OpenPgpV4Fingerprint fingerprint, boolean checked) {
|
||||||
|
openPgpRepository.storeTrust(accountId, contactAddress.getValue(), fingerprint,
|
||||||
|
checked ? OpenPgpTrustStore.Trust.trusted : OpenPgpTrustStore.Trust.untrusted)
|
||||||
|
.subscribe();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static abstract class FilteredPresenceEventListener extends CombinedPresenceListener {
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
private final EntityBareJid jid;
|
||||||
|
|
||||||
|
public FilteredPresenceEventListener(EntityBareJid jid) {
|
||||||
|
this.jid = jid;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void presenceReceived(Jid address, Presence presence) {
|
||||||
|
EntityBareJid entityBareJid = address.asEntityBareJidIfPossible();
|
||||||
|
if (entityBareJid != null && entityBareJid.equals(jid)) {
|
||||||
|
presenceReceived(presence);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public abstract void presenceReceived(Presence presence);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void dispose() {
|
||||||
|
compositeDisposable.dispose();
|
||||||
|
if (presenceEventListener != null) roster.removePresenceEventListener(presenceEventListener);
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue