Wip: Implement local contact search

This commit is contained in:
Paul Schaub 2020-07-13 01:39:26 +02:00
parent 4e1585a4bb
commit 47ab78ba60
Signed by: vanitasvitae
GPG Key ID: 62BEE9264BF17311
31 changed files with 327 additions and 173 deletions

View File

@ -4,11 +4,12 @@ import org.mercury_im.messenger.android.MercuryImApplication;
import org.mercury_im.messenger.android.di.module.AndroidDatabaseModule; import org.mercury_im.messenger.android.di.module.AndroidDatabaseModule;
import org.mercury_im.messenger.android.di.module.AndroidSchedulersModule; import org.mercury_im.messenger.android.di.module.AndroidSchedulersModule;
import org.mercury_im.messenger.android.ui.account.AndroidAccountDetailsViewModel; import org.mercury_im.messenger.android.ui.account.AndroidAccountDetailsViewModel;
import org.mercury_im.messenger.android.ui.ox.AndroidOxSecretKeyBackupRestoreViewModel; import org.mercury_im.messenger.android.ui.roster.contacts.AndroidContactListViewModel;
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;
import org.mercury_im.messenger.core.di.module.XmppTcpConnectionFactoryModule; import org.mercury_im.messenger.core.di.module.XmppTcpConnectionFactoryModule;
import org.mercury_im.messenger.core.viewmodel.accounts.AccountDetailsViewModel; import org.mercury_im.messenger.core.viewmodel.accounts.AccountDetailsViewModel;
import org.mercury_im.messenger.core.viewmodel.chat.ChatViewModel;
import org.mercury_im.messenger.data.di.RepositoryModule; import org.mercury_im.messenger.data.di.RepositoryModule;
import org.mercury_im.messenger.android.di.module.AppModule; import org.mercury_im.messenger.android.di.module.AppModule;
import org.mercury_im.messenger.core.di.module.ViewModelModule; import org.mercury_im.messenger.core.di.module.ViewModelModule;
@ -22,7 +23,6 @@ import org.mercury_im.messenger.android.ui.chat.ChatInputFragment;
import org.mercury_im.messenger.android.ui.chat.ChatInputViewModel; 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.ChatListViewModel; import org.mercury_im.messenger.android.ui.chatlist.ChatListViewModel;
import org.mercury_im.messenger.android.ui.roster.contacts.ContactListViewModel;
import org.mercury_im.messenger.android.ui.roster.contacts.detail.ContactDetailActivity; import org.mercury_im.messenger.android.ui.roster.contacts.detail.ContactDetailActivity;
import org.mercury_im.messenger.android.ui.roster.contacts.detail.ContactDetailViewModel; import org.mercury_im.messenger.android.ui.roster.contacts.detail.ContactDetailViewModel;
import org.mercury_im.messenger.core.viewmodel.accounts.AccountsViewModel; import org.mercury_im.messenger.core.viewmodel.accounts.AccountsViewModel;
@ -69,10 +69,12 @@ public interface AppComponent {
// ViewModels // ViewModels
void inject(ContactListViewModel contactListViewModel); void inject(AndroidContactListViewModel contactListViewModel);
void inject(AndroidChatViewModel androidChatViewModel); void inject(AndroidChatViewModel androidChatViewModel);
void inject(ChatViewModel chatViewModel);
void inject(ChatInputViewModel chatInputViewModel); void inject(ChatInputViewModel chatInputViewModel);
void inject(AndroidLoginViewModel androidLoginViewModel); void inject(AndroidLoginViewModel androidLoginViewModel);

View File

@ -7,6 +7,7 @@ import android.view.MenuItem;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity; import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.widget.SearchView;
import androidx.appcompat.widget.Toolbar; import androidx.appcompat.widget.Toolbar;
import androidx.fragment.app.FragmentTransaction; import androidx.fragment.app.FragmentTransaction;
@ -40,6 +41,8 @@ public class MainActivity extends AppCompatActivity
@BindView(R.id.bottom_navigation) @BindView(R.id.bottom_navigation)
BottomNavigationView bottomNavigationView; BottomNavigationView bottomNavigationView;
private SearchView searchView;
private ChatListFragment chatListFragment = new ChatListFragment(); private ChatListFragment chatListFragment = new ChatListFragment();
private RosterFragment rosterFragment = new RosterFragment(); private RosterFragment rosterFragment = new RosterFragment();
private AccountsFragment accountsFragment = new AccountsFragment(); private AccountsFragment accountsFragment = new AccountsFragment();
@ -63,6 +66,10 @@ public class MainActivity extends AppCompatActivity
public boolean onCreateOptionsMenu(Menu menu) { public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present. // Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.menu_main, menu); getMenuInflater().inflate(R.menu.menu_main, menu);
final MenuItem searchItem = menu.findItem(R.id.action_search);
searchView = (SearchView) searchItem.getActionView();
return true; return true;
} }
@ -91,6 +98,7 @@ public class MainActivity extends AppCompatActivity
case R.id.entry_contacts: case R.id.entry_contacts:
transaction.replace(R.id.fragment, rosterFragment).commit(); transaction.replace(R.id.fragment, rosterFragment).commit();
searchView.setOnQueryTextListener(rosterFragment);
return true; return true;
case R.id.entry_accounts: case R.id.entry_accounts:
@ -102,7 +110,8 @@ public class MainActivity extends AppCompatActivity
@Override @Override
public void onAccountListItemClick(Account item) { public void onAccountListItemClick(Account item) {
getSupportFragmentManager().beginTransaction().replace(R.id.fragment, new AccountDetailsFragment(item.getId())).commit(); getSupportFragmentManager().beginTransaction().addToBackStack("Test")
.replace(R.id.fragment, new AccountDetailsFragment(item.getId())).commit();
} }
@Override @Override

View File

@ -16,9 +16,11 @@ import androidx.fragment.app.Fragment;
import androidx.lifecycle.ViewModelProvider; import androidx.lifecycle.ViewModelProvider;
import androidx.recyclerview.widget.RecyclerView; import androidx.recyclerview.widget.RecyclerView;
import com.google.android.material.card.MaterialCardView;
import org.mercury_im.messenger.R; import org.mercury_im.messenger.R;
import org.mercury_im.messenger.android.MercuryImApplication; import org.mercury_im.messenger.android.MercuryImApplication;
import org.mercury_im.messenger.android.util.OpenPgpFingerprintColorizer; import org.mercury_im.messenger.android.util.OpenPgpV4FingerprintFormatter;
import org.mercury_im.messenger.core.viewmodel.accounts.AccountFingerprintViewItem; import org.mercury_im.messenger.core.viewmodel.accounts.AccountFingerprintViewItem;
import org.pgpainless.key.OpenPgpV4Fingerprint; import org.pgpainless.key.OpenPgpV4Fingerprint;
@ -47,7 +49,10 @@ public class AccountDetailsFragment extends Fragment {
TextView localFingerprint; TextView localFingerprint;
@BindView(R.id.fingerprint_list) @BindView(R.id.fingerprint_list)
RecyclerView externalFingerprintList; RecyclerView externalFingerprintRecyclerView;
@BindView(R.id.other_fingerprints_card)
MaterialCardView otherFingerprintsLayout;
private final UUID accountId; private final UUID accountId;
private ToggleableFingerprintsAdapter adapter; private ToggleableFingerprintsAdapter adapter;
@ -70,33 +75,30 @@ public class AccountDetailsFragment extends Fragment {
.get(AndroidAccountDetailsViewModel.class); .get(AndroidAccountDetailsViewModel.class);
this.adapter = new ToggleableFingerprintsAdapter(viewModel); this.adapter = new ToggleableFingerprintsAdapter(viewModel);
observe();
} }
private void observe() { private void observe() {
viewModel.getLocalFingerprint().observe(this, viewModel.getLocalFingerprint().observe(getViewLifecycleOwner(),
f -> localFingerprint.setText(OpenPgpFingerprintColorizer.formatOpenPgpV4Fingerprint(f))); f -> localFingerprint.setText(OpenPgpV4FingerprintFormatter.formatOpenPgpV4Fingerprint(f)));
viewModel.getRemoteFingerprints().observe(this, adapter::setItems); viewModel.getRemoteFingerprints().observe(getViewLifecycleOwner(), items -> {
otherFingerprintsLayout.setVisibility(items.isEmpty() ? View.GONE : View.VISIBLE);
//viewModel.getJid().observe(this, jid::setText); adapter.setItems(items);
/*
localFingerprintShareButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent sendIntent = new Intent();
sendIntent.setAction(Intent.ACTION_SEND);
sendIntent.putExtra(Intent.EXTRA_TEXT, viewModel.getLocalFingerprint().getValue());
sendIntent.setType("text/plain");
Intent shareIntent = Intent.createChooser(sendIntent, null);
startActivity(shareIntent);
}
}); });
*/ viewModel.getJid().observe(getViewLifecycleOwner(), accountJid -> jid.setText(accountJid.toString()));
localFingerprintShareButton.setOnClickListener(v -> {
OpenPgpV4Fingerprint fingerprint = viewModel.getLocalFingerprint().getValue();
Intent sendIntent = new Intent();
sendIntent.setAction(Intent.ACTION_SEND);
sendIntent.putExtra(Intent.EXTRA_TEXT, "openpgp4fpr:" + fingerprint);
sendIntent.setType("text/plain");
Intent shareIntent = Intent.createChooser(sendIntent, "Share OpenPGP Fingerprint");
startActivity(shareIntent);
});
} }
@Nullable @Nullable
@ -105,7 +107,9 @@ public class AccountDetailsFragment extends Fragment {
View view = inflater.inflate(R.layout.fragment_account_details, container, false); View view = inflater.inflate(R.layout.fragment_account_details, container, false);
ButterKnife.bind(this, view); ButterKnife.bind(this, view);
externalFingerprintList.setAdapter(adapter); externalFingerprintRecyclerView.setAdapter(adapter);
observe();
return view; return view;
} }
@ -142,12 +146,13 @@ public class AccountDetailsFragment extends Fragment {
final OpenPgpV4Fingerprint fingerprint = f.getFingerprint(); final OpenPgpV4Fingerprint fingerprint = f.getFingerprint();
holder.fingerprint.setText(OpenPgpFingerprintColorizer.formatOpenPgpV4Fingerprint(fingerprint)); holder.fingerprint.setText(OpenPgpV4FingerprintFormatter.formatOpenPgpV4Fingerprint(fingerprint));
holder.fingerprintTimestamp.setText(dateFormat.format(f.getAnnouncementDate())); holder.fingerprintTimestamp.setText(dateFormat.format(f.getAnnouncementDate()));
holder.trustSwitch.setChecked(f.isTrusted()); holder.trustSwitch.setChecked(f.isTrusted());
holder.trustSwitch.setOnCheckedChangeListener( holder.trustSwitch.setOnCheckedChangeListener(
(buttonView, isChecked) -> viewModel.markFingerprintTrusted(fingerprint, isChecked)); (buttonView, isChecked) -> viewModel.markFingerprintTrusted(fingerprint, isChecked));
holder.divider.setVisibility(position == fingerprints.size() - 1 ? View.GONE : View.VISIBLE);
} }
} }
@ -161,12 +166,14 @@ public class AccountDetailsFragment extends Fragment {
private final Switch trustSwitch; private final Switch trustSwitch;
private final TextView fingerprintTimestamp; private final TextView fingerprintTimestamp;
private final TextView fingerprint; private final TextView fingerprint;
private final View divider;
public ViewHolder(@NonNull View itemView) { public ViewHolder(@NonNull View itemView) {
super(itemView); super(itemView);
this.fingerprint = itemView.findViewById(R.id.fingerprint); this.fingerprint = itemView.findViewById(R.id.fingerprint);
this.trustSwitch = itemView.findViewById(R.id.fingerprint_toggle); this.trustSwitch = itemView.findViewById(R.id.fingerprint_toggle);
this.fingerprintTimestamp = itemView.findViewById(R.id.fingerprint_timestamp); this.fingerprintTimestamp = itemView.findViewById(R.id.fingerprint_timestamp);
this.divider = itemView.findViewById(R.id.divider);
} }
} }
} }

View File

@ -12,7 +12,6 @@ import androidx.recyclerview.widget.RecyclerView;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import org.mercury_im.messenger.R; import org.mercury_im.messenger.R;
import org.mercury_im.messenger.android.util.OpenPgpFingerprintColorizer;
import org.mercury_im.messenger.core.viewmodel.accounts.AccountViewItem; import org.mercury_im.messenger.core.viewmodel.accounts.AccountViewItem;
import org.mercury_im.messenger.entity.Account; import org.mercury_im.messenger.entity.Account;
import org.mercury_im.messenger.android.ui.avatar.AvatarDrawable; import org.mercury_im.messenger.android.ui.avatar.AvatarDrawable;

View File

@ -10,6 +10,7 @@ import androidx.lifecycle.ViewModel;
import androidx.lifecycle.ViewModelProvider; import androidx.lifecycle.ViewModelProvider;
import org.jxmpp.jid.EntityBareJid; import org.jxmpp.jid.EntityBareJid;
import org.jxmpp.jid.impl.JidCreate;
import org.mercury_im.messenger.android.MercuryImApplication; import org.mercury_im.messenger.android.MercuryImApplication;
import org.mercury_im.messenger.android.ui.MercuryAndroidViewModel; import org.mercury_im.messenger.android.ui.MercuryAndroidViewModel;
import org.mercury_im.messenger.core.SchedulersFacade; import org.mercury_im.messenger.core.SchedulersFacade;
@ -19,13 +20,18 @@ import org.mercury_im.messenger.core.viewmodel.accounts.AccountFingerprintViewIt
import org.pgpainless.key.OpenPgpV4Fingerprint; import org.pgpainless.key.OpenPgpV4Fingerprint;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays;
import java.util.List; import java.util.List;
import java.util.UUID; import java.util.UUID;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.inject.Inject; import javax.inject.Inject;
public class AndroidAccountDetailsViewModel extends AndroidViewModel implements MercuryAndroidViewModel<AccountDetailsViewModel> { public class AndroidAccountDetailsViewModel extends AndroidViewModel implements MercuryAndroidViewModel<AccountDetailsViewModel> {
private static final Logger LOGGER = Logger.getLogger(AndroidAccountDetailsViewModel.class.getName());
@Inject @Inject
SchedulersFacade schedulers; SchedulersFacade schedulers;
@ -33,27 +39,33 @@ public class AndroidAccountDetailsViewModel extends AndroidViewModel implements
AccountDetailsViewModel commonViewModel; AccountDetailsViewModel commonViewModel;
private final UUID accountId; private final UUID accountId;
private MutableLiveData<OpenPgpV4Fingerprint> localFingerprint = new MutableLiveData<>(); private MutableLiveData<OpenPgpV4Fingerprint> localFingerprint = new MutableLiveData<>(new OpenPgpV4Fingerprint("09858F60046289311743B90F3152226EB43287C5"));
private MutableLiveData<List<AccountFingerprintViewItem>> remoteFingerprints = new MutableLiveData<>(new ArrayList<>()); private MutableLiveData<List<AccountFingerprintViewItem>> remoteFingerprints = new MutableLiveData<>(new ArrayList<>());
private MutableLiveData<EntityBareJid> jid; private MutableLiveData<EntityBareJid> jid = new MutableLiveData<>(JidCreate.entityBareFromOrThrowUnchecked("placeholder@place.holder"));
public AndroidAccountDetailsViewModel(@NonNull Application application, UUID accountId) { public AndroidAccountDetailsViewModel(@NonNull Application application, UUID accountId) {
super(application); super(application);
this.accountId = accountId; this.accountId = accountId;
LOGGER.log(Level.INFO, "Creating AndroidAccountDetailsViewModel");
((MercuryImApplication) application).getAppComponent().inject(this); ((MercuryImApplication) application).getAppComponent().inject(this);
addDisposable(getCommonViewModel().observeLocalFingerprint(accountId) addDisposable(getCommonViewModel().observeLocalFingerprint(accountId)
.compose(schedulers.executeUiSafeObservable()) .compose(schedulers.executeUiSafeObservable())
.filter(Optional::isPresent) .filter(Optional::isPresent)
.map(Optional::getItem) .map(Optional::getItem)
.subscribe(localFingerprint::setValue)); .subscribe(localFingerprint::postValue));
addDisposable(getCommonViewModel().observeRemoteFingerprints(accountId) addDisposable(getCommonViewModel().observeRemoteFingerprints(accountId)
.compose(schedulers.executeUiSafeObservable()) .compose(schedulers.executeUiSafeObservable())
.subscribe(list -> remoteFingerprints.setValue(list))); .subscribe(list -> {
LOGGER.log(Level.INFO, "Set remote fingerprints to list: " + Arrays.toString(list.toArray()));
remoteFingerprints.postValue(list);
},
e -> LOGGER.log(Level.SEVERE, "Error observing remote fingerprints.", e),
() -> LOGGER.log(Level.INFO, "observing remote fingerprint onComplete.")));
//addDisposable(getCommonViewModel().getJid(accountId).subscribe(jid::setValue)); addDisposable(getCommonViewModel().getJid(accountId).subscribe(jid::postValue));
} }
@Override @Override

View File

@ -12,6 +12,7 @@ import org.mercury_im.messenger.core.SchedulersFacade;
import org.mercury_im.messenger.core.data.repository.DirectChatRepository; import org.mercury_im.messenger.core.data.repository.DirectChatRepository;
import org.mercury_im.messenger.core.data.repository.MessageRepository; import org.mercury_im.messenger.core.data.repository.MessageRepository;
import org.mercury_im.messenger.core.data.repository.PeerRepository; import org.mercury_im.messenger.core.data.repository.PeerRepository;
import org.mercury_im.messenger.core.util.Optional;
import org.mercury_im.messenger.core.viewmodel.chat.ChatViewModel; import org.mercury_im.messenger.core.viewmodel.chat.ChatViewModel;
import org.mercury_im.messenger.data.repository.RxMessageRepository; import org.mercury_im.messenger.data.repository.RxMessageRepository;
import org.mercury_im.messenger.entity.chat.DirectChat; import org.mercury_im.messenger.entity.chat.DirectChat;
@ -29,6 +30,7 @@ import io.reactivex.Completable;
import io.reactivex.Observable; import io.reactivex.Observable;
import io.reactivex.android.schedulers.AndroidSchedulers; import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.disposables.CompositeDisposable; import io.reactivex.disposables.CompositeDisposable;
import io.reactivex.disposables.Disposable;
import io.reactivex.schedulers.Schedulers; import io.reactivex.schedulers.Schedulers;
public class AndroidChatViewModel extends ViewModel implements MercuryAndroidViewModel<ChatViewModel> { public class AndroidChatViewModel extends ViewModel implements MercuryAndroidViewModel<ChatViewModel> {
@ -36,7 +38,7 @@ public class AndroidChatViewModel extends ViewModel implements MercuryAndroidVie
private final CompositeDisposable disposable = new CompositeDisposable(); private final CompositeDisposable disposable = new CompositeDisposable();
private static final Logger LOGGER = Logger.getLogger(AndroidChatViewModel.class.getName()); private static final Logger LOGGER = Logger.getLogger(AndroidChatViewModel.class.getName());
//@Inject @Inject
ChatViewModel commonViewModel; ChatViewModel commonViewModel;
@Inject @Inject
@ -74,30 +76,22 @@ public class AndroidChatViewModel extends ViewModel implements MercuryAndroidVie
public void init(DirectChat chat) { public void init(DirectChat chat) {
this.chat.setValue(chat); this.chat.setValue(chat);
this.contact.setValue(chat.getPeer()); this.contact.setValue(chat.getPeer());
this.commonViewModel.init(chat);
// Subscribe peer // Subscribe peer
disposable.add(contactRepository.observePeer(chat.getPeer().getId()) disposable.add(commonViewModel.getPeer()
.subscribe(peer -> { .subscribe(peer -> contactDisplayName.setValue(peer.getName()),
if (peer.isPresent()) {
contactDisplayName.setValue(peer.getItem().getName());
}
},
error -> LOGGER.log(Level.SEVERE, "Error subscribing display name to peer", error))); error -> LOGGER.log(Level.SEVERE, "Error subscribing display name to peer", error)));
// Subscribe messages // Subscribe messages
disposable.add(messageRepository.observeMessages(chat) addDisposable(commonViewModel.getMessages()
.doOnNext(m -> LOGGER.log(Level.INFO, "NEW MESSAGES.")) .subscribe(messageList -> AndroidChatViewModel.this.messages.postValue(messageList),
.subscribe(messageList -> {
AndroidChatViewModel.this.messages.postValue(messageList);
},
error -> LOGGER.log(Level.SEVERE, "Error subscribing to messages", error))); error -> LOGGER.log(Level.SEVERE, "Error subscribing to messages", error)));
} }
@Override @Override
protected void onCleared() { protected void onCleared() {
super.onCleared(); super.onCleared();
LOGGER.log(Level.INFO, "CLEAR");
disposable.clear(); disposable.clear();
} }
@ -114,12 +108,7 @@ public class AndroidChatViewModel extends ViewModel implements MercuryAndroidVie
} }
public void queryTextChanged(String query) { public void queryTextChanged(String query) {
Observable<List<Message>> observable = query.isEmpty() ? commonViewModel.onQueryTextChanged(query);
messageRepository.observeMessages(chat.getValue()) :
messageRepository.findMessagesWithBody(chat.getValue(), query);
disposable.add(observable.subscribe(messages ->
AndroidChatViewModel.this.messages.setValue(messages)));
} }
public void deleteContact() { public void deleteContact() {
@ -127,9 +116,9 @@ public class AndroidChatViewModel extends ViewModel implements MercuryAndroidVie
disposable.add(messenger.deleteContact(contact) disposable.add(messenger.deleteContact(contact)
.subscribeOn(schedulers.getIoScheduler()) .subscribeOn(schedulers.getIoScheduler())
.observeOn(schedulers.getUiScheduler()) .observeOn(schedulers.getUiScheduler())
.subscribe(() -> LOGGER.log(Level.INFO, "Contact deleted."), e -> { .subscribe(
LOGGER.log(Level.SEVERE, e.getMessage(), e); () -> LOGGER.log(Level.INFO, "Contact deleted."),
})); e -> LOGGER.log(Level.SEVERE, e.getMessage(), e)));
} }
public void sendMessage(String body) { public void sendMessage(String body) {

View File

@ -7,6 +7,7 @@ import android.view.ViewGroup;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.appcompat.widget.SearchView;
import androidx.fragment.app.Fragment; import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentManager; import androidx.fragment.app.FragmentManager;
import androidx.fragment.app.FragmentPagerAdapter; import androidx.fragment.app.FragmentPagerAdapter;
@ -21,7 +22,7 @@ import org.mercury_im.messenger.android.ui.roster.contacts.ContactListFragment;
import butterknife.BindView; import butterknife.BindView;
import butterknife.ButterKnife; import butterknife.ButterKnife;
public class RosterFragment extends Fragment { public class RosterFragment extends Fragment implements SearchView.OnQueryTextListener {
@BindView(R.id.tab_layout) @BindView(R.id.tab_layout)
TabLayout tabLayout; TabLayout tabLayout;
@ -29,6 +30,9 @@ public class RosterFragment extends Fragment {
@BindView(R.id.viewpager) @BindView(R.id.viewpager)
ViewPager viewPager; ViewPager viewPager;
private ContactListFragment contactListFragment = new ContactListFragment();
private BookmarkListFragment bookmarkListFragment = new BookmarkListFragment();
@Nullable @Nullable
@Override @Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, public View onCreateView(LayoutInflater inflater, ViewGroup container,
@ -44,6 +48,17 @@ public class RosterFragment extends Fragment {
return view; return view;
} }
@Override
public boolean onQueryTextSubmit(String query) {
return true;
}
@Override
public boolean onQueryTextChange(String newText) {
contactListFragment.onQueryTextChange(newText);
return false;
}
private class RosterFragmentPagerAdapter extends FragmentPagerAdapter { private class RosterFragmentPagerAdapter extends FragmentPagerAdapter {
final int PAGE_COUNT = 2; final int PAGE_COUNT = 2;
@ -52,8 +67,8 @@ public class RosterFragment extends Fragment {
getString(R.string.tab_bookmarks) getString(R.string.tab_bookmarks)
}; };
final Fragment[] PAGES = new Fragment[] { final Fragment[] PAGES = new Fragment[] {
new ContactListFragment(), contactListFragment,
new BookmarkListFragment() bookmarkListFragment
}; };
public RosterFragmentPagerAdapter(@NonNull FragmentManager fm, int behavior) { public RosterFragmentPagerAdapter(@NonNull FragmentManager fm, int behavior) {

View File

@ -1,15 +1,16 @@
package org.mercury_im.messenger.android.ui.roster.contacts; package org.mercury_im.messenger.android.ui.roster.contacts;
import android.util.Log; import android.app.Application;
import androidx.annotation.NonNull;
import androidx.lifecycle.AndroidViewModel;
import androidx.lifecycle.LiveData; import androidx.lifecycle.LiveData;
import androidx.lifecycle.MutableLiveData; import androidx.lifecycle.MutableLiveData;
import androidx.lifecycle.ViewModel;
import org.mercury_im.messenger.android.MercuryImApplication; import org.mercury_im.messenger.android.MercuryImApplication;
import org.mercury_im.messenger.android.ui.MercuryAndroidViewModel;
import org.mercury_im.messenger.core.Messenger; import org.mercury_im.messenger.core.Messenger;
import org.mercury_im.messenger.data.repository.RxAccountRepository; import org.mercury_im.messenger.core.viewmodel.roster.ContactListViewModel;
import org.mercury_im.messenger.data.repository.RxPeerRepository;
import org.mercury_im.messenger.entity.Account; import org.mercury_im.messenger.entity.Account;
import org.mercury_im.messenger.entity.contact.Peer; import org.mercury_im.messenger.entity.contact.Peer;
@ -21,13 +22,11 @@ import io.reactivex.disposables.CompositeDisposable;
import lombok.Getter; import lombok.Getter;
public class ContactListViewModel extends ViewModel { public class AndroidContactListViewModel extends AndroidViewModel
implements MercuryAndroidViewModel<ContactListViewModel> {
@Inject @Inject
RxPeerRepository xmppContactRepository; ContactListViewModel commonViewModel;
@Inject
RxAccountRepository accountRepository;
@Inject @Inject
@Getter @Getter
@ -37,14 +36,13 @@ public class ContactListViewModel extends ViewModel {
private final MutableLiveData<List<Account>> accounts = new MutableLiveData<>(); private final MutableLiveData<List<Account>> accounts = new MutableLiveData<>();
private final CompositeDisposable compositeDisposable = new CompositeDisposable(); private final CompositeDisposable compositeDisposable = new CompositeDisposable();
public ContactListViewModel() { public AndroidContactListViewModel(@NonNull Application application) {
super(); super(application);
MercuryImApplication.getApplication().getAppComponent().inject(this); ((MercuryImApplication) application).getAppComponent().inject(this);
Log.d("ContactListViewModel", "Start observing database");
// Subscribe to changes to the contacts table and update the LiveData object for the UI. // Subscribe to changes to the contacts table and update the LiveData object for the UI.
compositeDisposable.add(xmppContactRepository.observeAllPeers() compositeDisposable.add(getCommonViewModel().getContacts()
.subscribe(rosterEntryList::postValue)); .subscribe(rosterEntryList::postValue));
compositeDisposable.add(accountRepository.observeAllAccounts() compositeDisposable.add(getCommonViewModel().getAccounts()
.subscribe(accounts::postValue)); .subscribe(accounts::postValue));
} }
@ -61,4 +59,13 @@ public class ContactListViewModel extends ViewModel {
public LiveData<List<Account>> getAccounts() { public LiveData<List<Account>> getAccounts() {
return accounts; return accounts;
} }
public void onContactSearchQueryTextChanged(String query) {
getCommonViewModel().onContactSearchQueryChanged(query);
}
@Override
public ContactListViewModel getCommonViewModel() {
return commonViewModel;
}
} }

View File

@ -7,6 +7,7 @@ import android.view.LayoutInflater;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import androidx.appcompat.widget.SearchView;
import androidx.fragment.app.Fragment; import androidx.fragment.app.Fragment;
import androidx.lifecycle.ViewModelProvider; import androidx.lifecycle.ViewModelProvider;
import androidx.recyclerview.widget.RecyclerView; import androidx.recyclerview.widget.RecyclerView;
@ -23,9 +24,9 @@ import static androidx.constraintlayout.widget.Constraints.TAG;
/** /**
* A placeholder fragment containing a simple view. * A placeholder fragment containing a simple view.
*/ */
public class ContactListFragment extends Fragment { public class ContactListFragment extends Fragment implements SearchView.OnQueryTextListener {
private ContactListViewModel contactListViewModel; private AndroidContactListViewModel viewModel;
@BindView(R.id.roster_entry_list__recycler_view) @BindView(R.id.roster_entry_list__recycler_view)
RecyclerView recyclerView; RecyclerView recyclerView;
@ -51,18 +52,18 @@ public class ContactListFragment extends Fragment {
} }
private void displayAddContactDialog() { private void displayAddContactDialog() {
AddContactDialogFragment addContactDialogFragment = new AddContactDialogFragment(contactListViewModel.getAccounts().getValue(), contactListViewModel.getMessenger()); AddContactDialogFragment addContactDialogFragment = new AddContactDialogFragment(viewModel.getAccounts().getValue(), viewModel.getMessenger());
addContactDialogFragment.show(this.getParentFragmentManager(), "add"); addContactDialogFragment.show(this.getParentFragmentManager(), "add");
} }
@Override @Override
public void onAttach(Context context) { public void onAttach(Context context) {
super.onAttach(context); super.onAttach(context);
contactListViewModel = new ViewModelProvider(this).get(ContactListViewModel.class); viewModel = new ViewModelProvider(this).get(AndroidContactListViewModel.class);
observeViewModel(contactListViewModel); observeViewModel(viewModel);
} }
private void observeViewModel(ContactListViewModel viewModel) { private void observeViewModel(AndroidContactListViewModel viewModel) {
viewModel.getRosterEntryList().observe(this, rosterEntries -> { viewModel.getRosterEntryList().observe(this, rosterEntries -> {
if (rosterEntries == null) { if (rosterEntries == null) {
Log.d(TAG, "Displaying null roster entries"); Log.d(TAG, "Displaying null roster entries");
@ -72,4 +73,15 @@ public class ContactListFragment extends Fragment {
Log.d(TAG, "Displaying " + rosterEntries.size() + " roster entries"); Log.d(TAG, "Displaying " + rosterEntries.size() + " roster entries");
}); });
} }
@Override
public boolean onQueryTextSubmit(String query) {
return false;
}
@Override
public boolean onQueryTextChange(String newText) {
viewModel.onContactSearchQueryTextChanged(newText);
return true;
}
} }

View File

@ -23,8 +23,7 @@ import com.google.android.material.chip.Chip;
import com.google.android.material.chip.ChipGroup; import com.google.android.material.chip.ChipGroup;
import com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton; import com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton;
import org.jivesoftware.smackx.colors.ConsistentColor; import org.mercury_im.messenger.android.util.OpenPgpV4FingerprintFormatter;
import org.mercury_im.messenger.android.util.OpenPgpFingerprintColorizer;
import org.mercury_im.messenger.core.Messenger; import org.mercury_im.messenger.core.Messenger;
import org.mercury_im.messenger.R; import org.mercury_im.messenger.R;
import org.mercury_im.messenger.android.ui.chat.ChatActivity; import org.mercury_im.messenger.android.ui.chat.ChatActivity;
@ -176,6 +175,6 @@ public class ContactDetailFragment extends Fragment {
if (fingerprints.isEmpty()) { if (fingerprints.isEmpty()) {
return; return;
} }
fingerprint.setText(OpenPgpFingerprintColorizer.formatOpenPgpV4Fingerprint(fingerprints.get(0))); fingerprint.setText(OpenPgpV4FingerprintFormatter.formatOpenPgpV4Fingerprint(fingerprints.get(0)));
} }
} }

View File

@ -7,7 +7,7 @@ import android.text.style.ForegroundColorSpan;
import org.jivesoftware.smackx.colors.ConsistentColor; import org.jivesoftware.smackx.colors.ConsistentColor;
import org.pgpainless.key.OpenPgpV4Fingerprint; import org.pgpainless.key.OpenPgpV4Fingerprint;
public class OpenPgpFingerprintColorizer { public class OpenPgpV4FingerprintFormatter {
/** /**
* Split an OpenPGP fingerprint into 10 blocks of length 4. * Split an OpenPGP fingerprint into 10 blocks of length 4.

View File

@ -1,16 +1,20 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" <ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
xmlns:tools="http://schemas.android.com/tools"
android:orientation="vertical"> android:orientation="vertical">
<de.hdodenhof.circleimageview.CircleImageView <de.hdodenhof.circleimageview.CircleImageView
android:id="@+id/avatar" android:id="@+id/avatar"
android:layout_width="196dp" android:layout_width="196dp"
android:layout_height="196dp" android:layout_height="196dp"
android:layout_marginTop="60dp" android:paddingTop="12dp"
android:src="@drawable/aldrin" android:src="@drawable/aldrin"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
@ -52,6 +56,7 @@
android:layout_marginBottom="8dp"/> android:layout_marginBottom="8dp"/>
<include layout="@layout/view_fingerprints_card_toggleable" <include layout="@layout/view_fingerprints_card_toggleable"
android:id="@+id/other_fingerprints_card"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_marginTop="8dp" android:layout_marginTop="8dp"
@ -60,4 +65,5 @@
</LinearLayout> </LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout> </androidx.constraintlayout.widget.ConstraintLayout>
</ScrollView>

View File

@ -40,7 +40,7 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="1dp" android:layout_height="1dp"
android:background="?android:attr/listDivider" android:background="?android:attr/listDivider"
app:layout_constraintTop_toBottomOf="@id/fingerprint" /> app:layout_constraintTop_toBottomOf="@id/fingerprint_timestamp" />
</androidx.constraintlayout.widget.ConstraintLayout> </androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -14,7 +14,8 @@
android:orientation="vertical" android:orientation="vertical"
android:paddingStart="12dp" android:paddingStart="12dp"
android:paddingEnd="12dp" android:paddingEnd="12dp"
android:paddingTop="12dp"> android:paddingTop="12dp"
android:paddingBottom="12dp">
<TextView <TextView
android:id="@+id/title" android:id="@+id/title"
@ -31,6 +32,7 @@
tools:listitem="@layout/view_fingerprint_toggleable" tools:listitem="@layout/view_fingerprint_toggleable"
android:layout_centerHorizontal="true" android:layout_centerHorizontal="true"
android:layout_marginTop="8dp" android:layout_marginTop="8dp"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.5" app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"

View File

@ -34,6 +34,7 @@ public class MessageMapping extends AbstractMapping<Message, MessageModel> {
model.setStanzaId(entity.getStanzaId()); model.setStanzaId(entity.getStanzaId());
model.setOriginId(entity.getOriginId()); model.setOriginId(entity.getOriginId());
model.setLegacyId(entity.getLegacyStanzaId()); model.setLegacyId(entity.getLegacyStanzaId());
model.setXml(entity.getXml());
return model; return model;
} }
@ -49,6 +50,7 @@ public class MessageMapping extends AbstractMapping<Message, MessageModel> {
entity.setStanzaId(model.getStanzaId()); entity.setStanzaId(model.getStanzaId());
entity.setOriginId(model.getOriginId()); entity.setOriginId(model.getOriginId());
entity.setLegacyStanzaId(model.getLegacyId()); entity.setLegacyStanzaId(model.getLegacyId());
entity.setXml(model.getXml());
return entity; return entity;
} }
} }

View File

@ -42,6 +42,9 @@ public abstract class AbstractMessageModel implements Persistable {
@Column(length = 65536) @Column(length = 65536)
String body; String body;
@Column(length = 65536)
String xml;
@Column @Column
String legacyId; String legacyId;

View File

@ -110,7 +110,7 @@ public class RxMessageRepository
public Observable<List<Message>> findMessagesWithBody(String body) { public Observable<List<Message>> findMessagesWithBody(String body) {
return data().select(MessageModel.class) return data().select(MessageModel.class)
.from(MessageModel.class) .from(MessageModel.class)
.where(MessageModel.BODY.eq(body)) .where(MessageModel.BODY.like("%" + body + "%"))
.get().observableResult() .get().observableResult()
.map(ResultDelegate::toList) .map(ResultDelegate::toList)
.map(this::messageModelsToEntities); .map(this::messageModelsToEntities);
@ -121,7 +121,7 @@ public class RxMessageRepository
return data().select(MessageModel.class) return data().select(MessageModel.class)
.from(MessageModel.class) .from(MessageModel.class)
.where(MessageModel.BODY.eq(body) .where(MessageModel.BODY.like("%" + body + "%")
.and(MessageModel.CHAT_ID.eq(chat.getId()))) .and(MessageModel.CHAT_ID.eq(chat.getId())))
.get().observableResult() .get().observableResult()
.map(ResultDelegate::toList) .map(ResultDelegate::toList)
@ -133,7 +133,7 @@ public class RxMessageRepository
return data().select(MessageModel.class) return data().select(MessageModel.class)
.from(MessageModel.class) .from(MessageModel.class)
.where(MessageModel.BODY.eq(body) .where(MessageModel.BODY.like("%" + body + "%")
.and(MessageModel.CHAT_ID.eq(chat.getId()))) .and(MessageModel.CHAT_ID.eq(chat.getId())))
.get().observableResult() .get().observableResult()
.map(ResultDelegate::toList) .map(ResultDelegate::toList)

View File

@ -170,10 +170,11 @@ public class RxOpenPgpRepository implements OpenPgpRepository {
.where(AnnouncedOpenPgpContactKey.ACCOUNT_ID.eq(accountId) .where(AnnouncedOpenPgpContactKey.ACCOUNT_ID.eq(accountId)
.and(AnnouncedOpenPgpContactKey.OWNER.eq(owner)) .and(AnnouncedOpenPgpContactKey.OWNER.eq(owner))
.and(AnnouncedOpenPgpContactKey.FINGERPRINT.eq(fingerprint))) .and(AnnouncedOpenPgpContactKey.FINGERPRINT.eq(fingerprint)))
.get().observableResult() .limit(1)
.map(ResultDelegate::first) .get()
.map(AnnouncedOpenPgpContactKey::getModificationDate) .maybe()
.singleOrError(); .toSingle()
.map(AnnouncedOpenPgpContactKey::getModificationDate);
} }
@Override @Override
@ -256,27 +257,18 @@ public class RxOpenPgpRepository implements OpenPgpRepository {
@Override @Override
public Observable<List<OpenPgpV4Fingerprint>> observeRemoteFingerprintsOfAccount(UUID accountId) { public Observable<List<OpenPgpV4Fingerprint>> observeRemoteFingerprintsOfAccount(UUID accountId) {
return observeLocalFingerprintOf(accountId) return accountRepository.getAccount(accountId).toSingle()
.flatMap(localFingerprint -> data.select(OpenPgpPublicKeyRing.class) .flatMapObservable(account -> data.select(AnnouncedOpenPgpContactKey.class)
.where(OpenPgpPublicKeyRing.ACCOUNT_ID.eq(accountId)) .where(AnnouncedOpenPgpContactKey.ACCOUNT_ID.eq(accountId)
.and(AnnouncedOpenPgpContactKey.OWNER.eq(account.getJid())))
.get().observableResult() .get().observableResult()
.map(result -> { .map(ResultDelegate::toList)
OpenPgpPublicKeyRing ring = new ResultDelegate<>(result).firstOrNull(); .map(list -> {
if (ring == null) { ArrayList<OpenPgpV4Fingerprint> fingerprints = new ArrayList<>();
return Collections.emptyList(); for (AnnouncedOpenPgpContactKey key : list) {
} else { fingerprints.add(key.getFingerprint());
Iterator<PGPPublicKeyRing> iterator = PGPainless.readKeyRing().publicKeyRingCollection(ring.getBytes()).iterator();
List<OpenPgpV4Fingerprint> fingerprints = new ArrayList<>();
while (iterator.hasNext()) {
PGPPublicKeyRing r = iterator.next();
OpenPgpV4Fingerprint f = new OpenPgpV4Fingerprint(r);
fingerprints.add(f);
}
if (localFingerprint.isPresent()) {
fingerprints.remove(localFingerprint.getItem());
}
return fingerprints;
} }
return fingerprints;
})); }));
} }

View File

@ -9,6 +9,7 @@ import org.mercury_im.messenger.entity.Account;
import org.mercury_im.messenger.entity.contact.Peer; import org.mercury_im.messenger.entity.contact.Peer;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.UUID; import java.util.UUID;
@ -174,4 +175,25 @@ public class RxPeerRepository
.where(PeerModel.ACCOUNT_ID.eq(accountId)) .where(PeerModel.ACCOUNT_ID.eq(accountId))
.get().single().ignoreElement(); .get().single().ignoreElement();
} }
@Override
public Observable<List<Peer>> findPeers(String query) {
return data().select(PeerModel.class)
.where(PeerModel.ADDRESS.like("%" + query + "%")
.or(PeerModel.NAME.like("%" + query + "%")))
.get().observableResult()
.map(ResultDelegate::toList)
.map(this::peerModelsToEntities);
}
@Override
public Observable<List<Peer>> findPeers(UUID accountId, String query) {
return data().select(PeerModel.class)
.where(PeerModel.ACCOUNT_ID.eq(accountId)
.and(PeerModel.ADDRESS.like("%" + query + "%")
.or(PeerModel.NAME.like("%" + query + "%"))))
.get().observableResult()
.map(ResultDelegate::toList)
.map(this::peerModelsToEntities);
}
} }

View File

@ -5,7 +5,6 @@ import org.bouncycastle.openpgp.PGPSecretKeyRingCollection;
import org.jivesoftware.smackx.ox.store.definition.OpenPgpTrustStore; import org.jivesoftware.smackx.ox.store.definition.OpenPgpTrustStore;
import org.jxmpp.jid.EntityBareJid; import org.jxmpp.jid.EntityBareJid;
import org.mercury_im.messenger.core.util.Optional; import org.mercury_im.messenger.core.util.Optional;
import org.mercury_im.messenger.entity.Account;
import org.pgpainless.key.OpenPgpV4Fingerprint; import org.pgpainless.key.OpenPgpV4Fingerprint;
import java.util.Date; import java.util.Date;

View File

@ -61,4 +61,8 @@ public interface PeerRepository {
} }
Completable deleteAllPeers(UUID accountId); Completable deleteAllPeers(UUID accountId);
Observable<List<Peer>> findPeers(String query);
Observable<List<Peer>> findPeers(UUID accountId, String query);
} }

View File

@ -1,23 +0,0 @@
package org.mercury_im.messenger.core.di.component;
import org.mercury_im.messenger.core.di.scope.AccountScope;
import java.util.UUID;
import dagger.BindsInstance;
import dagger.Component;
@Component
public interface ConnectionComponent {
ConnectionComponent getComponent();
@Component.Builder
interface Builder {
@BindsInstance Builder withAccount(@AccountScope UUID accountId);
ConnectionComponent build();
}
}

View File

@ -0,0 +1,23 @@
package org.mercury_im.messenger.core.di.component;
import org.mercury_im.messenger.core.viewmodel.accounts.AccountDetailsViewModel;
import org.mercury_im.messenger.core.viewmodel.accounts.AccountsViewModel;
import org.mercury_im.messenger.core.viewmodel.accounts.LoginViewModel;
import org.mercury_im.messenger.core.viewmodel.chat.ChatViewModel;
import org.mercury_im.messenger.core.viewmodel.ox.OxSecretKeyBackupRestoreViewModel;
import dagger.Component;
@Component
public interface CoreComponent {
void inject(LoginViewModel viewModel);
void inject(AccountsViewModel viewModel);
void inject(AccountDetailsViewModel viewModel);
void inject(ChatViewModel viewModel);
void inject(OxSecretKeyBackupRestoreViewModel viewModel);
}

View File

@ -1,11 +1,14 @@
package org.mercury_im.messenger.core.di.module; package org.mercury_im.messenger.core.di.module;
import org.mercury_im.messenger.core.Messenger;
import org.mercury_im.messenger.core.SchedulersFacade; import org.mercury_im.messenger.core.SchedulersFacade;
import org.mercury_im.messenger.core.data.repository.AccountRepository; import org.mercury_im.messenger.core.data.repository.AccountRepository;
import org.mercury_im.messenger.core.data.repository.OpenPgpRepository; import org.mercury_im.messenger.core.data.repository.OpenPgpRepository;
import org.mercury_im.messenger.core.data.repository.Repositories;
import org.mercury_im.messenger.core.viewmodel.accounts.AccountDetailsViewModel; import org.mercury_im.messenger.core.viewmodel.accounts.AccountDetailsViewModel;
import org.mercury_im.messenger.core.viewmodel.accounts.AccountsViewModel; import org.mercury_im.messenger.core.viewmodel.accounts.AccountsViewModel;
import org.mercury_im.messenger.core.viewmodel.accounts.LoginViewModel; import org.mercury_im.messenger.core.viewmodel.accounts.LoginViewModel;
import org.mercury_im.messenger.core.viewmodel.chat.ChatViewModel;
import org.mercury_im.messenger.core.xmpp.MercuryConnectionManager; import org.mercury_im.messenger.core.xmpp.MercuryConnectionManager;
import javax.inject.Singleton; import javax.inject.Singleton;
@ -41,6 +44,11 @@ public class ViewModelModule {
return new AccountDetailsViewModel(openPgpRepository, accountRepository, schedulers); return new AccountDetailsViewModel(openPgpRepository, accountRepository, schedulers);
} }
@Provides
@Singleton
static ChatViewModel provideChatViewModel(Messenger messenger, Repositories repositories, SchedulersFacade schedulers) {
return new ChatViewModel(messenger, repositories, schedulers);
}
/* /*
@Provides @Provides
@Singleton @Singleton

View File

@ -1,13 +0,0 @@
package org.mercury_im.messenger.core.di.scope;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import javax.inject.Scope;
@Scope
@Documented
@Retention(value = RetentionPolicy.RUNTIME)
public @interface AccountScope {
}

View File

@ -68,6 +68,7 @@ public class MercuryMessageStore implements IncomingChatMessageListener, Outgoin
message.setTimestamp(delayInformation != null ? delayInformation.getStamp() : new Date()); message.setTimestamp(delayInformation != null ? delayInformation.getStamp() : new Date());
message.setSender(from.asEntityBareJidString()); message.setSender(from.asEntityBareJidString());
message.setRecipient(smackMessage.getTo().asBareJid().toString()); message.setRecipient(smackMessage.getTo().asBareJid().toString());
message.setXml(smackMessage.toXML().toString());
if (smackMessage.getBody() != null) { if (smackMessage.getBody() != null) {
message.setBody(smackMessage.getBody()); message.setBody(smackMessage.getBody());
} }
@ -86,6 +87,7 @@ public class MercuryMessageStore implements IncomingChatMessageListener, Outgoin
message.setTimestamp(new Date()); message.setTimestamp(new Date());
message.setSender(account.getAddress()); message.setSender(account.getAddress());
message.setRecipient(to.asBareJid().toString()); message.setRecipient(to.asBareJid().toString());
message.setXml(smackMessage.build().toXML().toString());
if (smackMessage.getBody() != null) { if (smackMessage.getBody() != null) {
message.setBody(smackMessage.getBody()); message.setBody(smackMessage.getBody());
} }
@ -111,6 +113,7 @@ public class MercuryMessageStore implements IncomingChatMessageListener, Outgoin
message.setTimestamp(delayInformation != null ? delayInformation.getStamp() : new Date()); message.setTimestamp(delayInformation != null ? delayInformation.getStamp() : new Date());
message.setSender(contact.getJid().toString()); message.setSender(contact.getJid().toString());
message.setRecipient(smackMessage.getTo().asBareJid().toString()); message.setRecipient(smackMessage.getTo().asBareJid().toString());
message.setXml(smackMessage.toXML().toString());
org.jivesoftware.smack.packet.Message.Body body = decryptedPayload.getExtension(org.jivesoftware.smack.packet.Message.Body.ELEMENT, org.jivesoftware.smack.packet.Message.Body body = decryptedPayload.getExtension(org.jivesoftware.smack.packet.Message.Body.ELEMENT,
org.jivesoftware.smack.packet.Message.Body.NAMESPACE); org.jivesoftware.smack.packet.Message.Body.NAMESPACE);
if (body != null) { if (body != null) {

View File

@ -6,9 +6,11 @@ import java.util.Date;
import lombok.AllArgsConstructor; import lombok.AllArgsConstructor;
import lombok.Data; import lombok.Data;
import lombok.ToString;
@Data @Data
@AllArgsConstructor @AllArgsConstructor
@ToString
public class AccountFingerprintViewItem { public class AccountFingerprintViewItem {
OpenPgpV4Fingerprint fingerprint; OpenPgpV4Fingerprint fingerprint;
Date announcementDate; Date announcementDate;

View File

@ -3,6 +3,7 @@ package org.mercury_im.messenger.core.viewmodel.chat;
import org.mercury_im.messenger.core.Messenger; import org.mercury_im.messenger.core.Messenger;
import org.mercury_im.messenger.core.SchedulersFacade; import org.mercury_im.messenger.core.SchedulersFacade;
import org.mercury_im.messenger.core.data.repository.Repositories; import org.mercury_im.messenger.core.data.repository.Repositories;
import org.mercury_im.messenger.core.util.Optional;
import org.mercury_im.messenger.core.viewmodel.MercuryViewModel; import org.mercury_im.messenger.core.viewmodel.MercuryViewModel;
import org.mercury_im.messenger.entity.chat.DirectChat; import org.mercury_im.messenger.entity.chat.DirectChat;
import org.mercury_im.messenger.entity.contact.Peer; import org.mercury_im.messenger.entity.contact.Peer;
@ -11,6 +12,7 @@ import org.mercury_im.messenger.entity.message.Message;
import java.util.List; import java.util.List;
import io.reactivex.Observable; import io.reactivex.Observable;
import io.reactivex.subjects.BehaviorSubject;
import lombok.Getter; import lombok.Getter;
public class ChatViewModel implements MercuryViewModel { public class ChatViewModel implements MercuryViewModel {
@ -18,6 +20,7 @@ public class ChatViewModel implements MercuryViewModel {
private final Messenger messenger; private final Messenger messenger;
private final Repositories repositories; private final Repositories repositories;
private final SchedulersFacade schedulers; private final SchedulersFacade schedulers;
private DirectChat chat;
@Getter @Getter
private Observable<Peer> peer; private Observable<Peer> peer;
@ -28,23 +31,35 @@ public class ChatViewModel implements MercuryViewModel {
@Getter @Getter
private Observable<String> contactDisplayName; private Observable<String> contactDisplayName;
private final BehaviorSubject<Observable<List<Message>>> messageQueryObservable = BehaviorSubject.create();
public ChatViewModel(Messenger messenger, public ChatViewModel(Messenger messenger,
Repositories repositories, Repositories repositories,
SchedulersFacade schedulers, SchedulersFacade schedulers) {
DirectChat chat) {
this.messenger = messenger; this.messenger = messenger;
this.repositories = repositories; this.repositories = repositories;
this.schedulers = schedulers; this.schedulers = schedulers;
}
public void init(DirectChat directChat) {
this.chat = directChat;
//peer = repositories.getPeerRepository().observePeer(chat.getPeer()); //peer = repositories.getPeerRepository().observePeer(chat.getPeer());
messages = repositories.getMessageRepository().observeMessages(chat); messageQueryObservable.onNext(repositories.getMessageRepository().observeMessages(chat));
messages = Observable.switchOnNext(messageQueryObservable);
peer = repositories.getPeerRepository().observePeer(chat.getPeer().getId())
.filter(Optional::isPresent).map(Optional::getItem);
contactDisplayName = repositories.getPeerRepository().observePeer(chat.getPeer()) contactDisplayName = repositories.getPeerRepository().observePeer(chat.getPeer())
.map(optional -> optional.isPresent() ? optional.getItem().getDisplayName() : "DELETED"); .map(optional -> optional.isPresent() ? optional.getItem().getDisplayName() : "DELETED");
} }
public void sendMessage(String body) { public void onQueryTextChanged(String query) {
//addDisposable(messenger.sendMessage()); if (query.trim().isEmpty()) {
messageQueryObservable.onNext(repositories.getMessageRepository().observeMessages(chat));
} else {
messageQueryObservable.onNext(repositories.getMessageRepository().findMessagesWithBody(chat, query));
}
} }
} }

View File

@ -0,0 +1,47 @@
package org.mercury_im.messenger.core.viewmodel.roster;
import org.mercury_im.messenger.core.data.repository.AccountRepository;
import org.mercury_im.messenger.core.data.repository.PeerRepository;
import org.mercury_im.messenger.core.viewmodel.MercuryViewModel;
import org.mercury_im.messenger.entity.Account;
import org.mercury_im.messenger.entity.contact.Peer;
import java.util.List;
import javax.inject.Inject;
import io.reactivex.Observable;
import io.reactivex.subjects.BehaviorSubject;
import lombok.Getter;
public class ContactListViewModel implements MercuryViewModel {
private final PeerRepository peerRepository;
private final AccountRepository accountRepository;
@Getter
private Observable<List<Peer>> contacts;
@Getter
private Observable<List<Account>> accounts;
private BehaviorSubject<Observable<List<Peer>>> queryResultObservables = BehaviorSubject.create();
@Inject
public ContactListViewModel(PeerRepository peerRepository, AccountRepository accountRepository) {
this.peerRepository = peerRepository;
this.accountRepository = accountRepository;
queryResultObservables.onNext(peerRepository.observeAllPeers());
this.contacts = Observable.switchOnNext(queryResultObservables);
this.accounts = accountRepository.observeAllAccounts();
}
public void onContactSearchQueryChanged(String query) {
if (query.trim().isEmpty()) {
queryResultObservables.onNext(peerRepository.observeAllPeers());
} else {
queryResultObservables.onNext(peerRepository.findPeers(query));
}
}
}

View File

@ -16,6 +16,7 @@ import org.mercury_im.messenger.core.xmpp.state.ConnectionState;
import java.io.IOException; import java.io.IOException;
import java.util.UUID; import java.util.UUID;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.logging.Level; import java.util.logging.Level;
import java.util.logging.Logger; import java.util.logging.Logger;
@ -27,6 +28,7 @@ import lombok.Getter;
public class MercuryConnection { public class MercuryConnection {
private static final Logger LOGGER = Logger.getLogger(MercuryConnection.class.getName()); private static final Logger LOGGER = Logger.getLogger(MercuryConnection.class.getName());
private AtomicBoolean connecting = new AtomicBoolean(false);
@Getter @Getter
private XMPPConnection connection; private XMPPConnection connection;
@ -59,20 +61,25 @@ public class MercuryConnection {
} }
private synchronized void doConnect() throws ServerUnreachableException { private synchronized void doConnect() throws ServerUnreachableException {
AbstractXMPPConnection connection = (AbstractXMPPConnection) getConnection(); if (connecting.compareAndSet(false, true)) {
if (connection.isConnected()) { AbstractXMPPConnection connection = (AbstractXMPPConnection) getConnection();
return; if (connection.isConnected()) {
return;
}
try {
connection.connect();
connecting.set(false);
} catch (SmackException.EndpointConnectionException e) {
connection.disconnect();
throw new ServerUnreachableException("Cannot connect to server " + connection.getXMPPServiceDomain().asUnescapedString(), e);
} catch (IOException | InterruptedException | XMPPException | SmackException e) {
throw new AssertionError("Unexpected exception.", e);
}
stateObservable.onNext(stateObservable.getValue().withConnectivity(ConnectivityState.connecting));
LOGGER.log(Level.INFO, "Connected!");
} else {
LOGGER.log(Level.INFO, "Already connecting.");
} }
try {
connection.connect();
} catch (SmackException.EndpointConnectionException e) {
connection.disconnect();
throw new ServerUnreachableException("Cannot connect to server " + connection.getXMPPServiceDomain().asUnescapedString(), e);
} catch (IOException | InterruptedException | XMPPException | SmackException e) {
throw new AssertionError("Unexpected exception.", e);
}
stateObservable.onNext(stateObservable.getValue().withConnectivity(ConnectivityState.connecting));
LOGGER.log(Level.INFO, "Connected!");
} }
public Completable login() { public Completable login() {
@ -109,6 +116,7 @@ public class MercuryConnection {
private final ConnectionListener connectionListener = new ConnectionListener() { private final ConnectionListener connectionListener = new ConnectionListener() {
@Override @Override
public void connected(XMPPConnection connection) { public void connected(XMPPConnection connection) {
connecting.set(false);
stateObservable.onNext(stateObservable.getValue() stateObservable.onNext(stateObservable.getValue()
.withConnectivity(ConnectivityState.connected) .withConnectivity(ConnectivityState.connected)
.withAuthenticated(false)); .withAuthenticated(false));
@ -127,6 +135,7 @@ public class MercuryConnection {
@Override @Override
public void connectionClosed() { public void connectionClosed() {
connecting.set(false);
stateObservable.onNext(stateObservable.getValue() stateObservable.onNext(stateObservable.getValue()
.withConnectivity(ConnectivityState.disconnected) .withConnectivity(ConnectivityState.disconnected)
.withAuthenticated(false)); .withAuthenticated(false));
@ -134,6 +143,7 @@ public class MercuryConnection {
@Override @Override
public void connectionClosedOnError(Exception e) { public void connectionClosedOnError(Exception e) {
connecting.set(false);
stateObservable.onNext(stateObservable.getValue() stateObservable.onNext(stateObservable.getValue()
.withConnectivity(ConnectivityState.disconnected) .withConnectivity(ConnectivityState.disconnected)
.withAuthenticated(false)); .withAuthenticated(false));

View File

@ -18,6 +18,7 @@ public class Message {
String legacyStanzaId; String legacyStanzaId;
String originId; String originId;
String stanzaId; String stanzaId;
String xml;
boolean encrypted; boolean encrypted;
boolean received; boolean received;
boolean read; boolean read;