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.AndroidSchedulersModule;
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.RxMercuryRosterStoreFactoryModule;
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.chat.ChatViewModel;
import org.mercury_im.messenger.data.di.RepositoryModule;
import org.mercury_im.messenger.android.di.module.AppModule;
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.AndroidChatViewModel;
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.ContactDetailViewModel;
import org.mercury_im.messenger.core.viewmodel.accounts.AccountsViewModel;
@ -69,10 +69,12 @@ public interface AppComponent {
// ViewModels
void inject(ContactListViewModel contactListViewModel);
void inject(AndroidContactListViewModel contactListViewModel);
void inject(AndroidChatViewModel androidChatViewModel);
void inject(ChatViewModel chatViewModel);
void inject(ChatInputViewModel chatInputViewModel);
void inject(AndroidLoginViewModel androidLoginViewModel);

View File

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

View File

@ -16,9 +16,11 @@ import androidx.fragment.app.Fragment;
import androidx.lifecycle.ViewModelProvider;
import androidx.recyclerview.widget.RecyclerView;
import com.google.android.material.card.MaterialCardView;
import org.mercury_im.messenger.R;
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.pgpainless.key.OpenPgpV4Fingerprint;
@ -47,7 +49,10 @@ public class AccountDetailsFragment extends Fragment {
TextView localFingerprint;
@BindView(R.id.fingerprint_list)
RecyclerView externalFingerprintList;
RecyclerView externalFingerprintRecyclerView;
@BindView(R.id.other_fingerprints_card)
MaterialCardView otherFingerprintsLayout;
private final UUID accountId;
private ToggleableFingerprintsAdapter adapter;
@ -70,33 +75,30 @@ public class AccountDetailsFragment extends Fragment {
.get(AndroidAccountDetailsViewModel.class);
this.adapter = new ToggleableFingerprintsAdapter(viewModel);
observe();
}
private void observe() {
viewModel.getLocalFingerprint().observe(this,
f -> localFingerprint.setText(OpenPgpFingerprintColorizer.formatOpenPgpV4Fingerprint(f)));
viewModel.getLocalFingerprint().observe(getViewLifecycleOwner(),
f -> localFingerprint.setText(OpenPgpV4FingerprintFormatter.formatOpenPgpV4Fingerprint(f)));
viewModel.getRemoteFingerprints().observe(this, adapter::setItems);
//viewModel.getJid().observe(this, jid::setText);
/*
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.getRemoteFingerprints().observe(getViewLifecycleOwner(), items -> {
otherFingerprintsLayout.setVisibility(items.isEmpty() ? View.GONE : View.VISIBLE);
adapter.setItems(items);
});
*/
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
@ -105,7 +107,9 @@ public class AccountDetailsFragment extends Fragment {
View view = inflater.inflate(R.layout.fragment_account_details, container, false);
ButterKnife.bind(this, view);
externalFingerprintList.setAdapter(adapter);
externalFingerprintRecyclerView.setAdapter(adapter);
observe();
return view;
}
@ -142,12 +146,13 @@ public class AccountDetailsFragment extends Fragment {
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.trustSwitch.setChecked(f.isTrusted());
holder.trustSwitch.setOnCheckedChangeListener(
(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 TextView fingerprintTimestamp;
private final TextView fingerprint;
private final View divider;
public ViewHolder(@NonNull View itemView) {
super(itemView);
this.fingerprint = itemView.findViewById(R.id.fingerprint);
this.trustSwitch = itemView.findViewById(R.id.fingerprint_toggle);
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.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.entity.Account;
import org.mercury_im.messenger.android.ui.avatar.AvatarDrawable;

View File

@ -10,6 +10,7 @@ import androidx.lifecycle.ViewModel;
import androidx.lifecycle.ViewModelProvider;
import org.jxmpp.jid.EntityBareJid;
import org.jxmpp.jid.impl.JidCreate;
import org.mercury_im.messenger.android.MercuryImApplication;
import org.mercury_im.messenger.android.ui.MercuryAndroidViewModel;
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 java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.UUID;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.inject.Inject;
public class AndroidAccountDetailsViewModel extends AndroidViewModel implements MercuryAndroidViewModel<AccountDetailsViewModel> {
private static final Logger LOGGER = Logger.getLogger(AndroidAccountDetailsViewModel.class.getName());
@Inject
SchedulersFacade schedulers;
@ -33,27 +39,33 @@ public class AndroidAccountDetailsViewModel extends AndroidViewModel implements
AccountDetailsViewModel commonViewModel;
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<EntityBareJid> jid;
private MutableLiveData<EntityBareJid> jid = new MutableLiveData<>(JidCreate.entityBareFromOrThrowUnchecked("placeholder@place.holder"));
public AndroidAccountDetailsViewModel(@NonNull Application application, UUID accountId) {
super(application);
this.accountId = accountId;
LOGGER.log(Level.INFO, "Creating AndroidAccountDetailsViewModel");
((MercuryImApplication) application).getAppComponent().inject(this);
addDisposable(getCommonViewModel().observeLocalFingerprint(accountId)
.compose(schedulers.executeUiSafeObservable())
.filter(Optional::isPresent)
.map(Optional::getItem)
.subscribe(localFingerprint::setValue));
.subscribe(localFingerprint::postValue));
addDisposable(getCommonViewModel().observeRemoteFingerprints(accountId)
.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

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

View File

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

View File

@ -1,15 +1,16 @@
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.MutableLiveData;
import androidx.lifecycle.ViewModel;
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.data.repository.RxAccountRepository;
import org.mercury_im.messenger.data.repository.RxPeerRepository;
import org.mercury_im.messenger.core.viewmodel.roster.ContactListViewModel;
import org.mercury_im.messenger.entity.Account;
import org.mercury_im.messenger.entity.contact.Peer;
@ -21,13 +22,11 @@ import io.reactivex.disposables.CompositeDisposable;
import lombok.Getter;
public class ContactListViewModel extends ViewModel {
public class AndroidContactListViewModel extends AndroidViewModel
implements MercuryAndroidViewModel<ContactListViewModel> {
@Inject
RxPeerRepository xmppContactRepository;
@Inject
RxAccountRepository accountRepository;
ContactListViewModel commonViewModel;
@Inject
@Getter
@ -37,14 +36,13 @@ public class ContactListViewModel extends ViewModel {
private final MutableLiveData<List<Account>> accounts = new MutableLiveData<>();
private final CompositeDisposable compositeDisposable = new CompositeDisposable();
public ContactListViewModel() {
super();
MercuryImApplication.getApplication().getAppComponent().inject(this);
Log.d("ContactListViewModel", "Start observing database");
public AndroidContactListViewModel(@NonNull Application application) {
super(application);
((MercuryImApplication) application).getAppComponent().inject(this);
// 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));
compositeDisposable.add(accountRepository.observeAllAccounts()
compositeDisposable.add(getCommonViewModel().getAccounts()
.subscribe(accounts::postValue));
}
@ -61,4 +59,13 @@ public class ContactListViewModel extends ViewModel {
public LiveData<List<Account>> getAccounts() {
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.ViewGroup;
import androidx.appcompat.widget.SearchView;
import androidx.fragment.app.Fragment;
import androidx.lifecycle.ViewModelProvider;
import androidx.recyclerview.widget.RecyclerView;
@ -23,9 +24,9 @@ import static androidx.constraintlayout.widget.Constraints.TAG;
/**
* 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)
RecyclerView recyclerView;
@ -51,18 +52,18 @@ public class ContactListFragment extends Fragment {
}
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");
}
@Override
public void onAttach(Context context) {
super.onAttach(context);
contactListViewModel = new ViewModelProvider(this).get(ContactListViewModel.class);
observeViewModel(contactListViewModel);
viewModel = new ViewModelProvider(this).get(AndroidContactListViewModel.class);
observeViewModel(viewModel);
}
private void observeViewModel(ContactListViewModel viewModel) {
private void observeViewModel(AndroidContactListViewModel viewModel) {
viewModel.getRosterEntryList().observe(this, rosterEntries -> {
if (rosterEntries == null) {
Log.d(TAG, "Displaying null roster entries");
@ -72,4 +73,15 @@ public class ContactListFragment extends Fragment {
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.floatingactionbutton.ExtendedFloatingActionButton;
import org.jivesoftware.smackx.colors.ConsistentColor;
import org.mercury_im.messenger.android.util.OpenPgpFingerprintColorizer;
import org.mercury_im.messenger.android.util.OpenPgpV4FingerprintFormatter;
import org.mercury_im.messenger.core.Messenger;
import org.mercury_im.messenger.R;
import org.mercury_im.messenger.android.ui.chat.ChatActivity;
@ -176,6 +175,6 @@ public class ContactDetailFragment extends Fragment {
if (fingerprints.isEmpty()) {
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.pgpainless.key.OpenPgpV4Fingerprint;
public class OpenPgpFingerprintColorizer {
public class OpenPgpV4FingerprintFormatter {
/**
* Split an OpenPGP fingerprint into 10 blocks of length 4.

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -9,6 +9,7 @@ import org.mercury_im.messenger.entity.Account;
import org.mercury_im.messenger.entity.contact.Peer;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.UUID;
@ -174,4 +175,25 @@ public class RxPeerRepository
.where(PeerModel.ACCOUNT_ID.eq(accountId))
.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.jxmpp.jid.EntityBareJid;
import org.mercury_im.messenger.core.util.Optional;
import org.mercury_im.messenger.entity.Account;
import org.pgpainless.key.OpenPgpV4Fingerprint;
import java.util.Date;

View File

@ -61,4 +61,8 @@ public interface PeerRepository {
}
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;
import org.mercury_im.messenger.core.Messenger;
import org.mercury_im.messenger.core.SchedulersFacade;
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.Repositories;
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.xmpp.MercuryConnectionManager;
import javax.inject.Singleton;
@ -41,6 +44,11 @@ public class ViewModelModule {
return new AccountDetailsViewModel(openPgpRepository, accountRepository, schedulers);
}
@Provides
@Singleton
static ChatViewModel provideChatViewModel(Messenger messenger, Repositories repositories, SchedulersFacade schedulers) {
return new ChatViewModel(messenger, repositories, schedulers);
}
/*
@Provides
@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.setSender(from.asEntityBareJidString());
message.setRecipient(smackMessage.getTo().asBareJid().toString());
message.setXml(smackMessage.toXML().toString());
if (smackMessage.getBody() != null) {
message.setBody(smackMessage.getBody());
}
@ -86,6 +87,7 @@ public class MercuryMessageStore implements IncomingChatMessageListener, Outgoin
message.setTimestamp(new Date());
message.setSender(account.getAddress());
message.setRecipient(to.asBareJid().toString());
message.setXml(smackMessage.build().toXML().toString());
if (smackMessage.getBody() != null) {
message.setBody(smackMessage.getBody());
}
@ -111,6 +113,7 @@ public class MercuryMessageStore implements IncomingChatMessageListener, Outgoin
message.setTimestamp(delayInformation != null ? delayInformation.getStamp() : new Date());
message.setSender(contact.getJid().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.NAMESPACE);
if (body != null) {

View File

@ -6,9 +6,11 @@ import java.util.Date;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.ToString;
@Data
@AllArgsConstructor
@ToString
public class AccountFingerprintViewItem {
OpenPgpV4Fingerprint fingerprint;
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.SchedulersFacade;
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.entity.chat.DirectChat;
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 io.reactivex.Observable;
import io.reactivex.subjects.BehaviorSubject;
import lombok.Getter;
public class ChatViewModel implements MercuryViewModel {
@ -18,6 +20,7 @@ public class ChatViewModel implements MercuryViewModel {
private final Messenger messenger;
private final Repositories repositories;
private final SchedulersFacade schedulers;
private DirectChat chat;
@Getter
private Observable<Peer> peer;
@ -28,23 +31,35 @@ public class ChatViewModel implements MercuryViewModel {
@Getter
private Observable<String> contactDisplayName;
private final BehaviorSubject<Observable<List<Message>>> messageQueryObservable = BehaviorSubject.create();
public ChatViewModel(Messenger messenger,
Repositories repositories,
SchedulersFacade schedulers,
DirectChat chat) {
SchedulersFacade schedulers) {
this.messenger = messenger;
this.repositories = repositories;
this.schedulers = schedulers;
}
public void init(DirectChat directChat) {
this.chat = directChat;
//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())
.map(optional -> optional.isPresent() ? optional.getItem().getDisplayName() : "DELETED");
}
public void sendMessage(String body) {
//addDisposable(messenger.sendMessage());
public void onQueryTextChanged(String query) {
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.util.UUID;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.logging.Level;
import java.util.logging.Logger;
@ -27,6 +28,7 @@ import lombok.Getter;
public class MercuryConnection {
private static final Logger LOGGER = Logger.getLogger(MercuryConnection.class.getName());
private AtomicBoolean connecting = new AtomicBoolean(false);
@Getter
private XMPPConnection connection;
@ -59,20 +61,25 @@ public class MercuryConnection {
}
private synchronized void doConnect() throws ServerUnreachableException {
AbstractXMPPConnection connection = (AbstractXMPPConnection) getConnection();
if (connection.isConnected()) {
return;
if (connecting.compareAndSet(false, true)) {
AbstractXMPPConnection connection = (AbstractXMPPConnection) getConnection();
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() {
@ -109,6 +116,7 @@ public class MercuryConnection {
private final ConnectionListener connectionListener = new ConnectionListener() {
@Override
public void connected(XMPPConnection connection) {
connecting.set(false);
stateObservable.onNext(stateObservable.getValue()
.withConnectivity(ConnectivityState.connected)
.withAuthenticated(false));
@ -127,6 +135,7 @@ public class MercuryConnection {
@Override
public void connectionClosed() {
connecting.set(false);
stateObservable.onNext(stateObservable.getValue()
.withConnectivity(ConnectivityState.disconnected)
.withAuthenticated(false));
@ -134,6 +143,7 @@ public class MercuryConnection {
@Override
public void connectionClosedOnError(Exception e) {
connecting.set(false);
stateObservable.onNext(stateObservable.getValue()
.withConnectivity(ConnectivityState.disconnected)
.withAuthenticated(false));

View File

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