diff --git a/app/src/main/java/org/mercury_im/messenger/android/di/component/AppComponent.java b/app/src/main/java/org/mercury_im/messenger/android/di/component/AppComponent.java index 28947a3..4d946df 100644 --- a/app/src/main/java/org/mercury_im/messenger/android/di/component/AppComponent.java +++ b/app/src/main/java/org/mercury_im/messenger/android/di/component/AppComponent.java @@ -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.contacts.AndroidContactListViewModel; 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.OpenPgpModule; 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.chatlist.AndroidChatListViewModel; 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 javax.inject.Singleton; @@ -93,7 +93,7 @@ public interface AppComponent { void inject(AndroidChatListViewModel chatListViewModel); - void inject(ContactDetailViewModel contactDetailViewModel); + void inject(AndroidContactDetailViewModel contactDetailViewModel); void inject(AccountDetailsViewModel accountDetailsViewModel); diff --git a/app/src/main/java/org/mercury_im/messenger/android/ui/contacts/detail/ContactDetailViewModel.java b/app/src/main/java/org/mercury_im/messenger/android/ui/contacts/detail/AndroidContactDetailViewModel.java similarity index 68% rename from app/src/main/java/org/mercury_im/messenger/android/ui/contacts/detail/ContactDetailViewModel.java rename to app/src/main/java/org/mercury_im/messenger/android/ui/contacts/detail/AndroidContactDetailViewModel.java index 8dc1db9..85f52c3 100644 --- a/app/src/main/java/org/mercury_im/messenger/android/ui/contacts/detail/ContactDetailViewModel.java +++ b/app/src/main/java/org/mercury_im/messenger/android/ui/contacts/detail/AndroidContactDetailViewModel.java @@ -18,12 +18,15 @@ 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.android.ui.base.MercuryAndroidViewModel; 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.contact.ContactDetailViewModel; 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; @@ -41,22 +44,10 @@ import io.reactivex.Completable; import io.reactivex.Single; import io.reactivex.disposables.CompositeDisposable; -public class ContactDetailViewModel extends ViewModel { +public class AndroidContactDetailViewModel extends ViewModel implements MercuryAndroidViewModel { @Inject - PeerRepository peerRepository; - - @Inject - DirectChatRepository directChatRepository; - - @Inject - OpenPgpRepository openPgpRepository; - - @Inject - SchedulersFacade schedulers; - - @Inject - Messenger messenger; + ContactDetailViewModel commonViewModel; private MutableLiveData contactAccountId = new MutableLiveData<>(UUID.randomUUID()); private MutableLiveData contactAddress = new MutableLiveData<>("alice@wonderland.lit"); @@ -66,6 +57,7 @@ public class ContactDetailViewModel extends ViewModel { private MutableLiveData contactName = new MutableLiveData<>("Alice Wonderland"); private MutableLiveData contactAccountAddress = new MutableLiveData<>("mad@hatter.lit"); private MutableLiveData> contactGroups = new MutableLiveData<>(Collections.emptyList()); + private MutableLiveData> ikeyFingerprint = new MutableLiveData<>(new Optional<>()); private MutableLiveData> contactFingerprints = new MutableLiveData<>(Collections.emptyList()); private Roster roster; @@ -73,7 +65,7 @@ public class ContactDetailViewModel extends ViewModel { private CompositeDisposable disposable = new CompositeDisposable(); - public ContactDetailViewModel() { + public AndroidContactDetailViewModel() { super(); MercuryImApplication.getApplication().getAppComponent().inject(this); } @@ -164,64 +156,18 @@ public class ContactDetailViewModel extends ViewModel { return contactGroups; } - public Single 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> 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> getContactIkeyFingerprint() { + return ikeyFingerprint; + } + + @Override + public ContactDetailViewModel getCommonViewModel() { + return commonViewModel; } } diff --git a/app/src/main/java/org/mercury_im/messenger/android/ui/contacts/detail/ContactDetailActivity.java b/app/src/main/java/org/mercury_im/messenger/android/ui/contacts/detail/ContactDetailActivity.java index e38f170..3625e74 100644 --- a/app/src/main/java/org/mercury_im/messenger/android/ui/contacts/detail/ContactDetailActivity.java +++ b/app/src/main/java/org/mercury_im/messenger/android/ui/contacts/detail/ContactDetailActivity.java @@ -19,7 +19,7 @@ import lombok.Value; public class ContactDetailActivity extends AppCompatActivity implements MercuryActivity { public static final String EXTRA_PEER_ID = "PEER_ID"; - private ContactDetailViewModel androidContactDetailViewModel; + private AndroidContactDetailViewModel androidContactDetailViewModel; private UUID peerId; @Override @@ -30,7 +30,7 @@ public class ContactDetailActivity extends AppCompatActivity implements MercuryA Arguments arguments = getArguments(savedInstanceState); peerId = arguments.getPeerId(); - androidContactDetailViewModel = new ViewModelProvider(this).get(ContactDetailViewModel.class); + androidContactDetailViewModel = new ViewModelProvider(this).get(AndroidContactDetailViewModel.class); androidContactDetailViewModel.init(peerId); bindUiComponents(); diff --git a/app/src/main/java/org/mercury_im/messenger/android/ui/contacts/detail/ContactDetailFragment.java b/app/src/main/java/org/mercury_im/messenger/android/ui/contacts/detail/ContactDetailFragment.java index 73df7de..ccecbea 100644 --- a/app/src/main/java/org/mercury_im/messenger/android/ui/contacts/detail/ContactDetailFragment.java +++ b/app/src/main/java/org/mercury_im/messenger/android/ui/contacts/detail/ContactDetailFragment.java @@ -82,13 +82,13 @@ public class ContactDetailFragment extends Fragment { @BindView(R.id.card_contact_fingerprints) MaterialCardView fingerprintsLayout; - private ContactDetailViewModel viewModel; @BindView(R.id.card_ikey_fingerprint) MaterialCardView ikeyLayout; @BindView(R.id.fingerprint) TextView ikeyFingerprint; + private AndroidContactDetailViewModel viewModel; private ToggleableFingerprintsAdapter fingerprintsAdapter; @Nullable @@ -142,7 +142,7 @@ public class ContactDetailFragment extends Fragment { @Override public void onAttach(Context context) { super.onAttach(context); - viewModel = new ViewModelProvider((ViewModelStoreOwner) context).get(ContactDetailViewModel.class); + viewModel = new ViewModelProvider((ViewModelStoreOwner) context).get(AndroidContactDetailViewModel.class); observeViewModel(); } @@ -171,6 +171,7 @@ public class ContactDetailFragment extends Fragment { viewModel.getContactPresenceStatus().observe(this, presenceText -> contactPresence.setText(presenceText)); viewModel.getContactAccountAddress().observe(this, address -> contactAccount.setText(address)); viewModel.getContactGroups().observe(this, this::setRosterGroups); + viewModel.getContactIkeyFingerprint().observe(this, this::setIkeyFingerprint); viewModel.getContactFingerprints().observe(this, this::setFingerprints); } diff --git a/domain/src/main/java/org/mercury_im/messenger/core/viewmodel/contact/ContactDetailViewModel.java b/domain/src/main/java/org/mercury_im/messenger/core/viewmodel/contact/ContactDetailViewModel.java new file mode 100644 index 0000000..89d433e --- /dev/null +++ b/domain/src/main/java/org/mercury_im/messenger/core/viewmodel/contact/ContactDetailViewModel.java @@ -0,0 +1,179 @@ +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.PresenceEventListener; +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.BareJid; +import org.jxmpp.jid.EntityBareJid; +import org.jxmpp.jid.Jid; +import org.jxmpp.jid.impl.JidCreate; +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.CombinedPresenceListener; +import org.mercury_im.messenger.core.util.Optional; +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.pgpainless.key.OpenPgpV4Fingerprint; + +import java.util.ArrayList; +import java.util.Collection; +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; + +public class ContactDetailViewModel implements MercuryViewModel { + + @Inject + SchedulersFacade schedulers; + + @Inject + Messenger messenger; + + @Inject + PeerRepository peerRepository; + + @Inject + IkeyRepository ikeyRepository; + + @Inject + DirectChatRepository directChatRepository; + + @Inject + OpenPgpRepository openPgpRepository; + + private final BehaviorSubject contactPresenceMode = BehaviorSubject.create(); + private final BehaviorSubject contactPresenceStatus = BehaviorSubject.create(); + private final BehaviorSubject> contactFingerprints = BehaviorSubject.createDefault(Collections.emptyList()); + private final BehaviorSubject contactAddress = BehaviorSubject.create(); + private final BehaviorSubject contactAccountId = BehaviorSubject.create(); + private final BehaviorSubject contactAccountAddress = BehaviorSubject.create(); + private final BehaviorSubject contactName = BehaviorSubject.create(); + private final BehaviorSubject> contactGroups = BehaviorSubject.createDefault(Collections.emptyList()); + + private UUID peerId; + private Roster roster; + + public void init(UUID peerId) { + this.peerId = peerId; + + addDisposable(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.onNext(presence.getMode()); + contactPresenceStatus.onNext(presence.getStatus()); + } + + addDisposable(openPgpRepository.observeFingerprints(p.getAccount().getId(), p.getJid()) + .subscribeOn(schedulers.getIoScheduler()) + .observeOn(schedulers.getUiScheduler()) + .subscribe(contactFingerprints::onNext)); + + addDisposable(ikeyRepository.ob); + })); + + addDisposable(peerRepository.observePeer(peerId) + .filter(Optional::isPresent) + .map(Optional::getItem) + .compose(schedulers.executeUiSafeObservable()) + .subscribe(peer -> { + + contactAddress.onNext(peer.getJid()); + contactAccountId.onNext(peer.getAccount().getId()); + contactAccountAddress.onNext(peer.getAccount().getJid()); + contactName.onNext(peer.getDisplayName()); + + RosterEntry entry = roster.getEntry(peer.getJid()); + if (entry != null) { + List groups = entry.getGroups(); + List groupNames = new ArrayList<>(groups.size()); + for (RosterGroup g : groups) { + groupNames.add(g.getName()); + } + contactGroups.onNext(groupNames); + } + })); + } + + public Single getOrCreateChat() { + return peerRepository.getPeer(peerId) + .flatMapSingle(directChatRepository::getOrCreateChatWithPeer) + .map(Chat::getId); + } + + public Observable getContactAddress() { + return contactAddress; + } + + private final PresenceEventListener presenceEventListener = new CombinedPresenceListener() { + @Override + public void presenceReceived(Jid address, Presence presence) { + if (presence.getFrom().asBareJid().equals(getContactAddress().blo)) { + 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 void markFingerprintTrusted(OpenPgpV4Fingerprint fingerprint, boolean checked) { + openPgpRepository.storeTrust(contactAccountId.getValue(), + JidCreate.entityBareFromOrThrowUnchecked(contactAddress.getValue()), + fingerprint, + checked ? OpenPgpTrustStore.Trust.trusted : OpenPgpTrustStore.Trust.untrusted) + .subscribe(); + } + +}