mirror of
https://github.com/oxen-io/session-android.git
synced 2025-12-03 05:22:23 +00:00
Implemented conversation search.
You can now search for messages within a specific conversation.
This commit is contained in:
@@ -49,6 +49,7 @@ import android.support.v4.view.MenuItemCompat;
|
||||
import android.support.v4.view.WindowCompat;
|
||||
import android.support.v7.app.ActionBar;
|
||||
import android.support.v7.app.AlertDialog;
|
||||
import android.support.v7.widget.SearchView;
|
||||
import android.text.Editable;
|
||||
import android.text.TextUtils;
|
||||
import android.text.TextWatcher;
|
||||
@@ -97,6 +98,7 @@ import org.thoughtcrime.securesms.color.MaterialColor;
|
||||
import org.thoughtcrime.securesms.components.AnimatingToggle;
|
||||
import org.thoughtcrime.securesms.components.AttachmentTypeSelector;
|
||||
import org.thoughtcrime.securesms.components.ComposeText;
|
||||
import org.thoughtcrime.securesms.components.ConversationSearchBottomBar;
|
||||
import org.thoughtcrime.securesms.components.HidingLinearLayout;
|
||||
import org.thoughtcrime.securesms.components.InputAwareLayout;
|
||||
import org.thoughtcrime.securesms.components.InputPanel;
|
||||
@@ -179,6 +181,7 @@ import org.thoughtcrime.securesms.recipients.Recipient;
|
||||
import org.thoughtcrime.securesms.recipients.RecipientFormattingException;
|
||||
import org.thoughtcrime.securesms.recipients.RecipientModifiedListener;
|
||||
import org.thoughtcrime.securesms.scribbles.ScribbleActivity;
|
||||
import org.thoughtcrime.securesms.search.model.MessageResult;
|
||||
import org.thoughtcrime.securesms.service.KeyCachingService;
|
||||
import org.thoughtcrime.securesms.sms.MessageSender;
|
||||
import org.thoughtcrime.securesms.sms.OutgoingEncryptedMessage;
|
||||
@@ -235,7 +238,8 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
||||
OnKeyboardShownListener,
|
||||
AttachmentDrawerListener,
|
||||
InputPanel.Listener,
|
||||
InputPanel.MediaListener
|
||||
InputPanel.MediaListener,
|
||||
ConversationSearchBottomBar.EventListener
|
||||
{
|
||||
private static final String TAG = ConversationActivity.class.getSimpleName();
|
||||
|
||||
@@ -280,6 +284,8 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
||||
private Stub<UnverifiedBannerView> unverifiedBannerView;
|
||||
private Stub<GroupShareProfileView> groupShareProfileView;
|
||||
private TypingStatusTextWatcher typingTextWatcher;
|
||||
private ConversationSearchBottomBar searchNav;
|
||||
private MenuItem searchViewItem;
|
||||
|
||||
private AttachmentTypeSelector attachmentTypeSelector;
|
||||
private AttachmentManager attachmentManager;
|
||||
@@ -290,7 +296,9 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
||||
protected HidingLinearLayout inlineAttachmentToggle;
|
||||
private QuickAttachmentDrawer quickAttachmentDrawer;
|
||||
private InputPanel inputPanel;
|
||||
private LinkPreviewViewModel linkPreviewViewModel;
|
||||
|
||||
private LinkPreviewViewModel linkPreviewViewModel;
|
||||
private ConversationSearchViewModel searchViewModel;
|
||||
|
||||
private Recipient recipient;
|
||||
private long threadId;
|
||||
@@ -331,6 +339,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
||||
initializeViews();
|
||||
initializeResources();
|
||||
initializeLinkPreviewObserver();
|
||||
initializeSearchObserver();
|
||||
initializeSecurity(false, isDefaultSms).addListener(new AssertedSuccessListener<Boolean>() {
|
||||
@Override
|
||||
public void onSuccess(Boolean result) {
|
||||
@@ -643,6 +652,56 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
||||
inflater.inflate(R.menu.conversation_add_to_contacts, menu);
|
||||
}
|
||||
|
||||
searchViewItem = menu.findItem(R.id.menu_search);
|
||||
|
||||
SearchView searchView = (SearchView) searchViewItem.getActionView();
|
||||
SearchView.OnQueryTextListener queryListener = new SearchView.OnQueryTextListener() {
|
||||
@Override
|
||||
public boolean onQueryTextSubmit(String query) {
|
||||
searchViewModel.onQueryUpdated(query, threadId);
|
||||
searchNav.showLoading();
|
||||
fragment.onSearchQueryUpdated(query);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onQueryTextChange(String query) {
|
||||
searchViewModel.onQueryUpdated(query, threadId);
|
||||
searchNav.showLoading();
|
||||
fragment.onSearchQueryUpdated(query);
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
searchViewItem.setOnActionExpandListener(new MenuItem.OnActionExpandListener() {
|
||||
@Override
|
||||
public boolean onMenuItemActionExpand(MenuItem item) {
|
||||
searchView.setOnQueryTextListener(queryListener);
|
||||
searchViewModel.onSearchOpened();
|
||||
searchNav.setVisibility(View.VISIBLE);
|
||||
searchNav.setData(0, 0);
|
||||
inputPanel.setVisibility(View.GONE);
|
||||
|
||||
for (int i = 0; i < menu.size(); i++) {
|
||||
if (!menu.getItem(i).equals(searchViewItem)) {
|
||||
menu.getItem(i).setVisible(false);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onMenuItemActionCollapse(MenuItem item) {
|
||||
searchView.setOnQueryTextListener(null);
|
||||
searchViewModel.onSearchClosed();
|
||||
searchNav.setVisibility(View.GONE);
|
||||
inputPanel.setVisibility(View.VISIBLE);
|
||||
fragment.onSearchQueryUpdated(null);
|
||||
invalidateOptionsMenu();
|
||||
return true;
|
||||
}
|
||||
});
|
||||
|
||||
super.onPrepareOptionsMenu(menu);
|
||||
return true;
|
||||
}
|
||||
@@ -655,6 +714,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
||||
case R.id.menu_call_insecure: handleDial(getRecipient(), false); return true;
|
||||
case R.id.menu_view_media: handleViewMedia(); return true;
|
||||
case R.id.menu_add_shortcut: handleAddShortcut(); return true;
|
||||
case R.id.menu_search: handleSearch(); return true;
|
||||
case R.id.menu_add_to_contacts: handleAddToContacts(); return true;
|
||||
case R.id.menu_reset_secure_session: handleResetSecureSession(); return true;
|
||||
case R.id.menu_group_recipients: handleDisplayGroupRecipients(); return true;
|
||||
@@ -920,6 +980,10 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
||||
}.execute();
|
||||
}
|
||||
|
||||
private void handleSearch() {
|
||||
searchViewModel.onSearchOpened();
|
||||
}
|
||||
|
||||
private void handleLeavePushGroup() {
|
||||
if (getRecipient() == null) {
|
||||
Toast.makeText(this, getString(R.string.ConversationActivity_invalid_recipient),
|
||||
@@ -1438,6 +1502,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
||||
quickAttachmentToggle = ViewUtil.findById(this, R.id.quick_attachment_toggle);
|
||||
inlineAttachmentToggle = ViewUtil.findById(this, R.id.inline_attachment_container);
|
||||
inputPanel = ViewUtil.findById(this, R.id.bottom_panel);
|
||||
searchNav = ViewUtil.findById(this, R.id.conversation_search_nav);
|
||||
|
||||
ImageButton quickCameraToggle = ViewUtil.findById(this, R.id.quick_camera_toggle);
|
||||
ImageButton inlineAttachmentButton = ViewUtil.findById(this, R.id.inline_attachment_button);
|
||||
@@ -1489,6 +1554,8 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
||||
quickCameraToggle.setEnabled(false);
|
||||
}
|
||||
|
||||
searchNav.setEventListener(this);
|
||||
|
||||
inlineAttachmentButton.setOnClickListener(v -> handleAddAttachment());
|
||||
}
|
||||
|
||||
@@ -1544,6 +1611,30 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
||||
});
|
||||
}
|
||||
|
||||
private void initializeSearchObserver() {
|
||||
searchViewModel = ViewModelProviders.of(this).get(ConversationSearchViewModel.class);
|
||||
|
||||
searchViewModel.getSearchResults().observe(this, result -> {
|
||||
if (result == null) return;
|
||||
|
||||
if (!result.getResults().isEmpty()) {
|
||||
MessageResult messageResult = result.getResults().get(result.getPosition());
|
||||
fragment.jumpToMessage(messageResult.messageRecipient.getAddress(), messageResult.receivedTimestampMs, searchViewModel::onMissingResult);
|
||||
}
|
||||
|
||||
searchNav.setData(result.getPosition(), result.getResults().size());
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSearchMoveUpPressed() {
|
||||
searchViewModel.onMoveUp();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSearchMoveDownPressed() {
|
||||
searchViewModel.onMoveDown();
|
||||
}
|
||||
|
||||
private void initializeProfiles() {
|
||||
if (!isSecureText) {
|
||||
@@ -1569,7 +1660,10 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
||||
updateReminders(recipient.hasSeenInviteReminder());
|
||||
updateDefaultSubscriptionId(recipient.getDefaultSubscriptionId());
|
||||
initializeSecurity(isSecureText, isDefaultSms);
|
||||
invalidateOptionsMenu();
|
||||
|
||||
if (!searchViewItem.isActionViewExpanded()) {
|
||||
invalidateOptionsMenu();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -2445,6 +2539,11 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onMessageActionToolbarOpened() {
|
||||
searchViewItem.collapseActionView();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAttachmentChanged() {
|
||||
handleSecurityChange(isSecureText, isDefaultSms);
|
||||
|
||||
@@ -105,6 +105,7 @@ public class ConversationAdapter <V extends View & BindableConversationItem>
|
||||
private final @NonNull MessageDigest digest;
|
||||
|
||||
private MessageRecord recordToPulseHighlight;
|
||||
private String searchQuery;
|
||||
|
||||
protected static class ViewHolder extends RecyclerView.ViewHolder {
|
||||
public <V extends View & BindableConversationItem> ViewHolder(final @NonNull V itemView) {
|
||||
@@ -205,6 +206,7 @@ public class ConversationAdapter <V extends View & BindableConversationItem>
|
||||
locale,
|
||||
batchSelected,
|
||||
recipient,
|
||||
searchQuery,
|
||||
messageRecord == recordToPulseHighlight);
|
||||
|
||||
if (messageRecord == recordToPulseHighlight) {
|
||||
@@ -363,6 +365,11 @@ public class ConversationAdapter <V extends View & BindableConversationItem>
|
||||
}
|
||||
}
|
||||
|
||||
public void onSearchQueryUpdated(@Nullable String query) {
|
||||
this.searchQuery = query;
|
||||
notifyDataSetChanged();
|
||||
}
|
||||
|
||||
private boolean hasAudio(MessageRecord messageRecord) {
|
||||
return messageRecord.isMms() && ((MmsMessageRecord)messageRecord).getSlideDeck().getAudioSlide() != null;
|
||||
}
|
||||
|
||||
@@ -27,6 +27,7 @@ import android.os.AsyncTask;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.v4.app.ActivityCompat;
|
||||
import android.support.v4.app.ActivityOptionsCompat;
|
||||
import android.support.v4.app.Fragment;
|
||||
@@ -49,6 +50,7 @@ import org.thoughtcrime.securesms.ShareActivity;
|
||||
import org.thoughtcrime.securesms.attachments.Attachment;
|
||||
import org.thoughtcrime.securesms.components.ConversationTypingView;
|
||||
import org.thoughtcrime.securesms.components.recyclerview.SmoothScrollingLinearLayoutManager;
|
||||
import org.thoughtcrime.securesms.database.Address;
|
||||
import org.thoughtcrime.securesms.linkpreview.LinkPreview;
|
||||
import org.thoughtcrime.securesms.logging.Log;
|
||||
|
||||
@@ -94,6 +96,7 @@ import org.thoughtcrime.securesms.util.StickyHeaderDecoration;
|
||||
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
||||
import org.thoughtcrime.securesms.util.Util;
|
||||
import org.thoughtcrime.securesms.util.ViewUtil;
|
||||
import org.thoughtcrime.securesms.util.concurrent.SimpleTask;
|
||||
import org.thoughtcrime.securesms.util.task.ProgressDialogAsyncTask;
|
||||
import org.whispersystems.libsignal.util.guava.Optional;
|
||||
|
||||
@@ -126,6 +129,7 @@ public class ConversationFragment extends Fragment
|
||||
private long lastSeen;
|
||||
private int startingPosition;
|
||||
private int previousOffset;
|
||||
private int activeOffset;
|
||||
private boolean firstLoad;
|
||||
private long loaderStartTime;
|
||||
private ActionMode actionMode;
|
||||
@@ -631,9 +635,14 @@ public class ConversationFragment extends Fragment
|
||||
|
||||
if (loader.hasOffset()) {
|
||||
adapter.setHeaderView(bottomLoadMoreView);
|
||||
}
|
||||
|
||||
if (firstLoad || loader.hasOffset()) {
|
||||
previousOffset = loader.getOffset();
|
||||
}
|
||||
|
||||
activeOffset = loader.getOffset();
|
||||
|
||||
adapter.changeCursor(cursor);
|
||||
|
||||
int lastSeenPosition = adapter.findLastSeenPosition(lastSeen);
|
||||
@@ -734,9 +743,42 @@ public class ConversationFragment extends Fragment
|
||||
return firstVisiblePosition == 0 && list.getChildAt(0).getBottom() <= list.getHeight();
|
||||
}
|
||||
|
||||
public void onSearchQueryUpdated(@Nullable String query) {
|
||||
getListAdapter().onSearchQueryUpdated(query);
|
||||
}
|
||||
|
||||
public void jumpToMessage(@NonNull Address author, long timestamp, @Nullable Runnable onMessageNotFound) {
|
||||
SimpleTask.run(getLifecycle(), () -> {
|
||||
return DatabaseFactory.getMmsSmsDatabase(getContext())
|
||||
.getMessagePositionInConversation(threadId, timestamp, author);
|
||||
}, p -> moveToMessagePosition(p, onMessageNotFound));
|
||||
}
|
||||
|
||||
private void moveToMessagePosition(int position, @Nullable Runnable onMessageNotFound) {
|
||||
Log.d(TAG, "Moving to message position: " + position + " activeOffset: " + activeOffset + " cursorCount: " + getListAdapter().getCursorCount());
|
||||
|
||||
if (position >= activeOffset && position >= 0 && position < getListAdapter().getCursorCount()) {
|
||||
int offset = activeOffset > 0 ? activeOffset - 1 : 0;
|
||||
list.scrollToPosition(position - offset);
|
||||
getListAdapter().pulseHighlightItem(position - offset);
|
||||
} else if (position < 0) {
|
||||
Log.w(TAG, "Tried to navigate to message, but it wasn't found.");
|
||||
if (onMessageNotFound != null) {
|
||||
onMessageNotFound.run();
|
||||
}
|
||||
} else {
|
||||
Log.i(TAG, "Message was outside of the loaded range. Need to restart the loader.");
|
||||
|
||||
firstLoad = true;
|
||||
startingPosition = position;
|
||||
getLoaderManager().restartLoader(0, Bundle.EMPTY, ConversationFragment.this);
|
||||
}
|
||||
}
|
||||
|
||||
public interface ConversationFragmentListener {
|
||||
void setThreadId(long threadId);
|
||||
void handleReplyMessage(MessageRecord messageRecord);
|
||||
void onMessageActionToolbarOpened();
|
||||
}
|
||||
|
||||
private class ConversationScrollListener extends OnScrollListener {
|
||||
@@ -848,41 +890,14 @@ public class ConversationFragment extends Fragment
|
||||
return;
|
||||
}
|
||||
|
||||
new AsyncTask<Void, Void, Integer>() {
|
||||
@Override
|
||||
protected Integer doInBackground(Void... voids) {
|
||||
if (getActivity() == null || getActivity().isFinishing()) {
|
||||
Log.w(TAG, "Task to retrieve quote position started after the fragment was detached.");
|
||||
return 0;
|
||||
}
|
||||
return DatabaseFactory.getMmsSmsDatabase(getContext())
|
||||
.getQuotedMessagePosition(threadId,
|
||||
messageRecord.getQuote().getId(),
|
||||
messageRecord.getQuote().getAuthor());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPostExecute(Integer position) {
|
||||
if (getActivity() == null || getActivity().isFinishing()) {
|
||||
Log.w(TAG, "Task to retrieve quote position finished after the fragment was detached.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (position >= 0 && position < getListAdapter().getItemCount()) {
|
||||
list.scrollToPosition(position);
|
||||
getListAdapter().pulseHighlightItem(position);
|
||||
} else if (position < 0) {
|
||||
Log.w(TAG, "Tried to navigate to quoted message, but it was deleted.");
|
||||
Toast.makeText(getContext(), R.string.ConversationFragment_quoted_message_no_longer_available, Toast.LENGTH_SHORT).show();
|
||||
} else {
|
||||
Log.i(TAG, "Quoted message was outside of the loaded range. Need to restart the loader.");
|
||||
|
||||
firstLoad = true;
|
||||
startingPosition = position;
|
||||
getLoaderManager().restartLoader(0, Bundle.EMPTY, ConversationFragment.this);
|
||||
}
|
||||
}
|
||||
}.execute();
|
||||
SimpleTask.run(getLifecycle(), () -> {
|
||||
return DatabaseFactory.getMmsSmsDatabase(getContext())
|
||||
.getQuotedMessagePosition(threadId,
|
||||
messageRecord.getQuote().getId(),
|
||||
messageRecord.getQuote().getAuthor());
|
||||
}, p -> moveToMessagePosition(p, () -> {
|
||||
Toast.makeText(getContext(), R.string.ConversationFragment_quoted_message_no_longer_available, Toast.LENGTH_SHORT).show();
|
||||
}));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -965,6 +980,7 @@ public class ConversationFragment extends Fragment
|
||||
}
|
||||
|
||||
setCorrectMenuVisibility(menu);
|
||||
listener.onMessageActionToolbarOpened();
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -28,9 +28,12 @@ import android.support.annotation.DimenRes;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.v7.app.AlertDialog;
|
||||
import android.text.Spannable;
|
||||
import android.text.SpannableString;
|
||||
import android.text.Spanned;
|
||||
import android.text.TextUtils;
|
||||
import android.text.style.BackgroundColorSpan;
|
||||
import android.text.style.ForegroundColorSpan;
|
||||
import android.text.style.URLSpan;
|
||||
import android.text.util.Linkify;
|
||||
import android.util.AttributeSet;
|
||||
@@ -87,6 +90,7 @@ import org.thoughtcrime.securesms.util.DateUtils;
|
||||
import org.thoughtcrime.securesms.util.DynamicTheme;
|
||||
import org.thoughtcrime.securesms.util.LongClickCopySpan;
|
||||
import org.thoughtcrime.securesms.util.LongClickMovementMethod;
|
||||
import org.thoughtcrime.securesms.util.SearchUtil;
|
||||
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
||||
import org.thoughtcrime.securesms.util.Util;
|
||||
import org.thoughtcrime.securesms.util.ViewUtil;
|
||||
@@ -206,6 +210,7 @@ public class ConversationItem extends LinearLayout
|
||||
@NonNull Locale locale,
|
||||
@NonNull Set<MessageRecord> batchSelected,
|
||||
@NonNull Recipient conversationRecipient,
|
||||
@Nullable String searchQuery,
|
||||
boolean pulseHighlight)
|
||||
{
|
||||
this.messageRecord = messageRecord;
|
||||
@@ -223,7 +228,7 @@ public class ConversationItem extends LinearLayout
|
||||
setMessageShape(messageRecord, previousMessageRecord, nextMessageRecord, groupThread);
|
||||
setMediaAttributes(messageRecord, previousMessageRecord, nextMessageRecord, conversationRecipient, groupThread);
|
||||
setInteractionState(messageRecord, pulseHighlight);
|
||||
setBodyText(messageRecord);
|
||||
setBodyText(messageRecord, searchQuery);
|
||||
setBubbleState(messageRecord);
|
||||
setStatusIcons(messageRecord);
|
||||
setContactPhoto(recipient);
|
||||
@@ -401,7 +406,7 @@ public class ConversationItem extends LinearLayout
|
||||
return messageRecord.isMms() && !((MmsMessageRecord)messageRecord).getLinkPreviews().isEmpty();
|
||||
}
|
||||
|
||||
private void setBodyText(MessageRecord messageRecord) {
|
||||
private void setBodyText(MessageRecord messageRecord, @Nullable String searchQuery) {
|
||||
bodyText.setClickable(false);
|
||||
bodyText.setFocusable(false);
|
||||
bodyText.setTextSize(TypedValue.COMPLEX_UNIT_SP, TextSecurePreferences.getMessageBodyTextSize(context));
|
||||
@@ -409,7 +414,11 @@ public class ConversationItem extends LinearLayout
|
||||
if (isCaptionlessMms(messageRecord)) {
|
||||
bodyText.setVisibility(View.GONE);
|
||||
} else {
|
||||
bodyText.setText(linkifyMessageBody(messageRecord.getDisplayBody(), batchSelected.isEmpty()));
|
||||
Spannable styledText = linkifyMessageBody(messageRecord.getDisplayBody(), batchSelected.isEmpty());
|
||||
styledText = SearchUtil.getHighlightedSpan(locale, () -> new BackgroundColorSpan(Color.YELLOW), styledText, searchQuery);
|
||||
styledText = SearchUtil.getHighlightedSpan(locale, () -> new ForegroundColorSpan(Color.BLACK), styledText, searchQuery);
|
||||
|
||||
bodyText.setText(styledText);
|
||||
bodyText.setVisibility(View.VISIBLE);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,147 @@
|
||||
package org.thoughtcrime.securesms.conversation;
|
||||
|
||||
import android.app.Application;
|
||||
import android.arch.lifecycle.AndroidViewModel;
|
||||
import android.arch.lifecycle.LiveData;
|
||||
import android.content.Context;
|
||||
import android.os.AsyncTask;
|
||||
import android.support.annotation.NonNull;
|
||||
|
||||
import org.thoughtcrime.securesms.contacts.ContactAccessor;
|
||||
import org.thoughtcrime.securesms.database.CursorList;
|
||||
import org.thoughtcrime.securesms.database.DatabaseFactory;
|
||||
import org.thoughtcrime.securesms.search.SearchRepository;
|
||||
import org.thoughtcrime.securesms.search.model.MessageResult;
|
||||
import org.thoughtcrime.securesms.util.CloseableLiveData;
|
||||
import org.thoughtcrime.securesms.util.Debouncer;
|
||||
import org.thoughtcrime.securesms.util.Util;
|
||||
|
||||
import java.io.Closeable;
|
||||
import java.util.List;
|
||||
|
||||
public class ConversationSearchViewModel extends AndroidViewModel {
|
||||
|
||||
private final SearchRepository searchRepository;
|
||||
private final CloseableLiveData<SearchResult> result;
|
||||
private final Debouncer debouncer;
|
||||
|
||||
private boolean firstSearch;
|
||||
private boolean searchOpen;
|
||||
private String activeQuery;
|
||||
private long activeThreadId;
|
||||
|
||||
public ConversationSearchViewModel(@NonNull Application application) {
|
||||
super(application);
|
||||
Context context = application.getApplicationContext();
|
||||
result = new CloseableLiveData<>();
|
||||
debouncer = new Debouncer(500);
|
||||
searchRepository = new SearchRepository(context,
|
||||
DatabaseFactory.getSearchDatabase(context),
|
||||
DatabaseFactory.getContactsDatabase(context),
|
||||
DatabaseFactory.getThreadDatabase(context),
|
||||
ContactAccessor.getInstance(),
|
||||
AsyncTask.THREAD_POOL_EXECUTOR);
|
||||
}
|
||||
|
||||
LiveData<SearchResult> getSearchResults() {
|
||||
return result;
|
||||
}
|
||||
|
||||
void onQueryUpdated(@NonNull String query, long threadId) {
|
||||
if (firstSearch && query.length() < 2) {
|
||||
result.postValue(new SearchResult(CursorList.emptyList(), 0));
|
||||
return;
|
||||
}
|
||||
|
||||
if (query.equals(activeQuery)) {
|
||||
return;
|
||||
}
|
||||
|
||||
updateQuery(query, threadId);
|
||||
}
|
||||
|
||||
void onMissingResult() {
|
||||
if (activeQuery != null) {
|
||||
updateQuery(activeQuery, activeThreadId);
|
||||
}
|
||||
}
|
||||
|
||||
void onMoveUp() {
|
||||
debouncer.clear();
|
||||
|
||||
CursorList<MessageResult> messages = (CursorList<MessageResult>) result.getValue().getResults();
|
||||
int position = Math.min(result.getValue().getPosition() + 1, messages.size() - 1);
|
||||
|
||||
result.setValue(new SearchResult(messages, position), false);
|
||||
}
|
||||
|
||||
void onMoveDown() {
|
||||
debouncer.clear();
|
||||
|
||||
CursorList<MessageResult> messages = (CursorList<MessageResult>) result.getValue().getResults();
|
||||
int position = Math.max(result.getValue().getPosition() - 1, 0);
|
||||
|
||||
result.setValue(new SearchResult(messages, position), false);
|
||||
}
|
||||
|
||||
|
||||
void onSearchOpened() {
|
||||
searchOpen = true;
|
||||
firstSearch = true;
|
||||
}
|
||||
|
||||
void onSearchClosed() {
|
||||
searchOpen = false;
|
||||
debouncer.clear();
|
||||
result.close();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onCleared() {
|
||||
super.onCleared();
|
||||
result.close();
|
||||
}
|
||||
|
||||
private void updateQuery(@NonNull String query, long threadId) {
|
||||
activeQuery = query;
|
||||
activeThreadId = threadId;
|
||||
|
||||
debouncer.publish(() -> {
|
||||
firstSearch = false;
|
||||
|
||||
searchRepository.query(query, threadId, messages -> {
|
||||
Util.runOnMain(() -> {
|
||||
if (searchOpen && query.equals(activeQuery)) {
|
||||
result.setValue(new SearchResult(messages, 0));
|
||||
} else {
|
||||
messages.close();
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
static class SearchResult implements Closeable {
|
||||
|
||||
private final CursorList<MessageResult> results;
|
||||
private final int position;
|
||||
|
||||
SearchResult(CursorList<MessageResult> results, int position) {
|
||||
this.results = results;
|
||||
this.position = position;
|
||||
}
|
||||
|
||||
public List<MessageResult> getResults() {
|
||||
return results;
|
||||
}
|
||||
|
||||
public int getPosition() {
|
||||
return position;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
results.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -79,6 +79,7 @@ public class ConversationUpdateItem extends LinearLayout
|
||||
@NonNull Locale locale,
|
||||
@NonNull Set<MessageRecord> batchSelected,
|
||||
@NonNull Recipient conversationRecipient,
|
||||
@Nullable String searchQuery,
|
||||
boolean pulseUpdate)
|
||||
{
|
||||
this.batchSelected = batchSelected;
|
||||
|
||||
Reference in New Issue
Block a user