From 1634d7d531482f46bbbcaa0059e8fe1b62f22fe1 Mon Sep 17 00:00:00 2001 From: Cody Henthorne Date: Fri, 7 Aug 2020 15:27:15 -0400 Subject: [PATCH] Show mention picker immediately after @ entered. --- .../securesms/components/ComposeText.java | 16 +++---- .../ui/mentions/MentionsPickerRepository.java | 11 +++-- .../ui/mentions/MentionsPickerViewModel.java | 47 ++++++++++++++++--- .../securesms/database/RecipientDatabase.java | 4 -- 4 files changed, 55 insertions(+), 23 deletions(-) diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/ComposeText.java b/app/src/main/java/org/thoughtcrime/securesms/components/ComposeText.java index e2c6dde27..2f4bf8846 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/ComposeText.java +++ b/app/src/main/java/org/thoughtcrime/securesms/components/ComposeText.java @@ -102,7 +102,7 @@ public class ComposeText extends EmojiEditText { if (selectionStart == selectionEnd) { doAfterCursorChange(getText()); } else { - updateQuery(""); + updateQuery(null); } } @@ -284,7 +284,7 @@ public class ComposeText extends EmojiEditText { if (enoughToFilter(text)) { performFiltering(text); } else { - updateQuery(""); + updateQuery(null); } } @@ -292,10 +292,10 @@ public class ComposeText extends EmojiEditText { int end = getSelectionEnd(); int start = findQueryStart(text, end); CharSequence query = text.subSequence(start, end); - updateQuery(query); + updateQuery(query.toString()); } - private void updateQuery(@NonNull CharSequence query) { + private void updateQuery(@Nullable String query) { if (mentionQueryChangedListener != null) { mentionQueryChangedListener.onQueryChanged(query); } @@ -306,7 +306,7 @@ public class ComposeText extends EmojiEditText { if (end < 0) { return false; } - return end - findQueryStart(text, end) >= 1; + return findQueryStart(text, end) != -1; } public void replaceTextWithMention(@NonNull String displayName, @NonNull RecipientId recipientId) { @@ -340,7 +340,7 @@ public class ComposeText extends EmojiEditText { private int findQueryStart(@NonNull CharSequence text, int inputCursorPosition) { if (inputCursorPosition == 0) { - return inputCursorPosition; + return -1; } int delimiterSearchIndex = inputCursorPosition - 1; @@ -351,7 +351,7 @@ public class ComposeText extends EmojiEditText { if (delimiterSearchIndex >= 0 && text.charAt(delimiterSearchIndex) == MENTION_STARTER) { return delimiterSearchIndex + 1; } - return inputCursorPosition; + return -1; } private static class CommitContentListener implements InputConnectionCompat.OnCommitContentListener { @@ -391,6 +391,6 @@ public class ComposeText extends EmojiEditText { } public interface MentionQueryChangedListener { - void onQueryChanged(CharSequence query); + void onQueryChanged(@Nullable String query); } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/ui/mentions/MentionsPickerRepository.java b/app/src/main/java/org/thoughtcrime/securesms/conversation/ui/mentions/MentionsPickerRepository.java index ab2c23f1a..016537925 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/ui/mentions/MentionsPickerRepository.java +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/ui/mentions/MentionsPickerRepository.java @@ -4,6 +4,7 @@ import android.content.Context; import android.text.TextUtils; import androidx.annotation.NonNull; +import androidx.annotation.Nullable; import androidx.annotation.WorkerThread; import com.annimon.stream.Stream; @@ -26,8 +27,8 @@ final class MentionsPickerRepository { } @WorkerThread - @NonNull List search(MentionQuery mentionQuery) { - if (TextUtils.isEmpty(mentionQuery.query)) { + @NonNull List search(@NonNull MentionQuery mentionQuery) { + if (mentionQuery.query == null) { return Collections.emptyList(); } @@ -40,10 +41,10 @@ final class MentionsPickerRepository { } static class MentionQuery { - private final String query; - private final List members; + @Nullable private final String query; + @NonNull private final List members; - MentionQuery(@NonNull String query, @NonNull List members) { + MentionQuery(@Nullable String query, @NonNull List members) { this.query = query; this.members = members; } diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/ui/mentions/MentionsPickerViewModel.java b/app/src/main/java/org/thoughtcrime/securesms/conversation/ui/mentions/MentionsPickerViewModel.java index 26df4c787..cc6627067 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/ui/mentions/MentionsPickerViewModel.java +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/ui/mentions/MentionsPickerViewModel.java @@ -1,6 +1,7 @@ package org.thoughtcrime.securesms.conversation.ui.mentions; import androidx.annotation.NonNull; +import androidx.annotation.Nullable; import androidx.lifecycle.LiveData; import androidx.lifecycle.MutableLiveData; import androidx.lifecycle.Transformations; @@ -20,22 +21,23 @@ import org.thoughtcrime.securesms.util.SingleLiveEvent; import org.thoughtcrime.securesms.util.livedata.LiveDataUtil; import java.util.List; +import java.util.Objects; public class MentionsPickerViewModel extends ViewModel { private final SingleLiveEvent selectedRecipient; private final LiveData>> mentionList; private final MutableLiveData group; - private final MutableLiveData liveQuery; + private final MutableLiveData liveQuery; MentionsPickerViewModel(@NonNull MentionsPickerRepository mentionsPickerRepository) { group = new MutableLiveData<>(); - liveQuery = new MutableLiveData<>(); + liveQuery = new MutableLiveData<>(Query.NONE); selectedRecipient = new SingleLiveEvent<>(); LiveData> fullMembers = Transformations.distinctUntilChanged(Transformations.switchMap(group, LiveGroup::getFullMembers)); - LiveData query = Transformations.distinctUntilChanged(liveQuery); - LiveData mentionQuery = LiveDataUtil.combineLatest(query, fullMembers, MentionQuery::new); + LiveData query = Transformations.distinctUntilChanged(liveQuery); + LiveData mentionQuery = LiveDataUtil.combineLatest(query, fullMembers, (q, m) -> new MentionQuery(q.query, m)); mentionList = LiveDataUtil.mapAsync(mentionQuery, q -> Stream.of(mentionsPickerRepository.search(q)).>map(MentionViewState::new).toList()); } @@ -52,8 +54,8 @@ public class MentionsPickerViewModel extends ViewModel { return selectedRecipient; } - public void onQueryChange(@NonNull CharSequence query) { - liveQuery.setValue(query.toString()); + public void onQueryChange(@Nullable String query) { + liveQuery.setValue(query == null ? Query.NONE : new Query(query)); } public void onRecipientChange(@NonNull Recipient recipient) { @@ -64,6 +66,39 @@ public class MentionsPickerViewModel extends ViewModel { } } + /** + * Wraps a nullable query string so it can be properly propagated through + * {@link LiveDataUtil#combineLatest(LiveData, LiveData, LiveDataUtil.Combine)}. + */ + private static class Query { + static final Query NONE = new Query(null); + + @Nullable private final String query; + + Query(@Nullable String query) { + this.query = query; + } + + @Override + public boolean equals(Object object) { + if (this == object) { + return true; + } + + if (object == null || getClass() != object.getClass()) { + return false; + } + + Query other = (Query) object; + return Objects.equals(query, other.query); + } + + @Override + public int hashCode() { + return Objects.hash(query); + } + } + public static final class Factory implements ViewModelProvider.Factory { @Override public @NonNull T create(@NonNull Class modelClass) { diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/RecipientDatabase.java b/app/src/main/java/org/thoughtcrime/securesms/database/RecipientDatabase.java index 316b7e3f8..b30521f7b 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/RecipientDatabase.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/RecipientDatabase.java @@ -1938,10 +1938,6 @@ public class RecipientDatabase extends Database { } public @NonNull List queryRecipientsForMentions(@NonNull String query, @Nullable List recipientIds) { - if (TextUtils.isEmpty(query)) { - return Collections.emptyList(); - } - query = buildCaseInsensitiveGlobPattern(query); String ids = null;