Show mention picker immediately after @ entered.

master
Cody Henthorne 2020-08-07 15:27:15 -04:00 committed by GitHub
parent d563de4207
commit 1634d7d531
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 55 additions and 23 deletions

View File

@ -102,7 +102,7 @@ public class ComposeText extends EmojiEditText {
if (selectionStart == selectionEnd) { if (selectionStart == selectionEnd) {
doAfterCursorChange(getText()); doAfterCursorChange(getText());
} else { } else {
updateQuery(""); updateQuery(null);
} }
} }
@ -284,7 +284,7 @@ public class ComposeText extends EmojiEditText {
if (enoughToFilter(text)) { if (enoughToFilter(text)) {
performFiltering(text); performFiltering(text);
} else { } else {
updateQuery(""); updateQuery(null);
} }
} }
@ -292,10 +292,10 @@ public class ComposeText extends EmojiEditText {
int end = getSelectionEnd(); int end = getSelectionEnd();
int start = findQueryStart(text, end); int start = findQueryStart(text, end);
CharSequence query = text.subSequence(start, 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) { if (mentionQueryChangedListener != null) {
mentionQueryChangedListener.onQueryChanged(query); mentionQueryChangedListener.onQueryChanged(query);
} }
@ -306,7 +306,7 @@ public class ComposeText extends EmojiEditText {
if (end < 0) { if (end < 0) {
return false; return false;
} }
return end - findQueryStart(text, end) >= 1; return findQueryStart(text, end) != -1;
} }
public void replaceTextWithMention(@NonNull String displayName, @NonNull RecipientId recipientId) { 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) { private int findQueryStart(@NonNull CharSequence text, int inputCursorPosition) {
if (inputCursorPosition == 0) { if (inputCursorPosition == 0) {
return inputCursorPosition; return -1;
} }
int delimiterSearchIndex = inputCursorPosition - 1; int delimiterSearchIndex = inputCursorPosition - 1;
@ -351,7 +351,7 @@ public class ComposeText extends EmojiEditText {
if (delimiterSearchIndex >= 0 && text.charAt(delimiterSearchIndex) == MENTION_STARTER) { if (delimiterSearchIndex >= 0 && text.charAt(delimiterSearchIndex) == MENTION_STARTER) {
return delimiterSearchIndex + 1; return delimiterSearchIndex + 1;
} }
return inputCursorPosition; return -1;
} }
private static class CommitContentListener implements InputConnectionCompat.OnCommitContentListener { private static class CommitContentListener implements InputConnectionCompat.OnCommitContentListener {
@ -391,6 +391,6 @@ public class ComposeText extends EmojiEditText {
} }
public interface MentionQueryChangedListener { public interface MentionQueryChangedListener {
void onQueryChanged(CharSequence query); void onQueryChanged(@Nullable String query);
} }
} }

View File

@ -4,6 +4,7 @@ import android.content.Context;
import android.text.TextUtils; import android.text.TextUtils;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.WorkerThread; import androidx.annotation.WorkerThread;
import com.annimon.stream.Stream; import com.annimon.stream.Stream;
@ -26,8 +27,8 @@ final class MentionsPickerRepository {
} }
@WorkerThread @WorkerThread
@NonNull List<Recipient> search(MentionQuery mentionQuery) { @NonNull List<Recipient> search(@NonNull MentionQuery mentionQuery) {
if (TextUtils.isEmpty(mentionQuery.query)) { if (mentionQuery.query == null) {
return Collections.emptyList(); return Collections.emptyList();
} }
@ -40,10 +41,10 @@ final class MentionsPickerRepository {
} }
static class MentionQuery { static class MentionQuery {
private final String query; @Nullable private final String query;
private final List<GroupMemberEntry.FullMember> members; @NonNull private final List<GroupMemberEntry.FullMember> members;
MentionQuery(@NonNull String query, @NonNull List<GroupMemberEntry.FullMember> members) { MentionQuery(@Nullable String query, @NonNull List<GroupMemberEntry.FullMember> members) {
this.query = query; this.query = query;
this.members = members; this.members = members;
} }

View File

@ -1,6 +1,7 @@
package org.thoughtcrime.securesms.conversation.ui.mentions; package org.thoughtcrime.securesms.conversation.ui.mentions;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.lifecycle.LiveData; import androidx.lifecycle.LiveData;
import androidx.lifecycle.MutableLiveData; import androidx.lifecycle.MutableLiveData;
import androidx.lifecycle.Transformations; import androidx.lifecycle.Transformations;
@ -20,22 +21,23 @@ import org.thoughtcrime.securesms.util.SingleLiveEvent;
import org.thoughtcrime.securesms.util.livedata.LiveDataUtil; import org.thoughtcrime.securesms.util.livedata.LiveDataUtil;
import java.util.List; import java.util.List;
import java.util.Objects;
public class MentionsPickerViewModel extends ViewModel { public class MentionsPickerViewModel extends ViewModel {
private final SingleLiveEvent<Recipient> selectedRecipient; private final SingleLiveEvent<Recipient> selectedRecipient;
private final LiveData<List<MappingModel<?>>> mentionList; private final LiveData<List<MappingModel<?>>> mentionList;
private final MutableLiveData<LiveGroup> group; private final MutableLiveData<LiveGroup> group;
private final MutableLiveData<String> liveQuery; private final MutableLiveData<Query> liveQuery;
MentionsPickerViewModel(@NonNull MentionsPickerRepository mentionsPickerRepository) { MentionsPickerViewModel(@NonNull MentionsPickerRepository mentionsPickerRepository) {
group = new MutableLiveData<>(); group = new MutableLiveData<>();
liveQuery = new MutableLiveData<>(); liveQuery = new MutableLiveData<>(Query.NONE);
selectedRecipient = new SingleLiveEvent<>(); selectedRecipient = new SingleLiveEvent<>();
LiveData<List<FullMember>> fullMembers = Transformations.distinctUntilChanged(Transformations.switchMap(group, LiveGroup::getFullMembers)); LiveData<List<FullMember>> fullMembers = Transformations.distinctUntilChanged(Transformations.switchMap(group, LiveGroup::getFullMembers));
LiveData<String> query = Transformations.distinctUntilChanged(liveQuery); LiveData<Query> query = Transformations.distinctUntilChanged(liveQuery);
LiveData<MentionQuery> mentionQuery = LiveDataUtil.combineLatest(query, fullMembers, MentionQuery::new); LiveData<MentionQuery> mentionQuery = LiveDataUtil.combineLatest(query, fullMembers, (q, m) -> new MentionQuery(q.query, m));
mentionList = LiveDataUtil.mapAsync(mentionQuery, q -> Stream.of(mentionsPickerRepository.search(q)).<MappingModel<?>>map(MentionViewState::new).toList()); mentionList = LiveDataUtil.mapAsync(mentionQuery, q -> Stream.of(mentionsPickerRepository.search(q)).<MappingModel<?>>map(MentionViewState::new).toList());
} }
@ -52,8 +54,8 @@ public class MentionsPickerViewModel extends ViewModel {
return selectedRecipient; return selectedRecipient;
} }
public void onQueryChange(@NonNull CharSequence query) { public void onQueryChange(@Nullable String query) {
liveQuery.setValue(query.toString()); liveQuery.setValue(query == null ? Query.NONE : new Query(query));
} }
public void onRecipientChange(@NonNull Recipient recipient) { 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 { public static final class Factory implements ViewModelProvider.Factory {
@Override @Override
public @NonNull <T extends ViewModel> T create(@NonNull Class<T> modelClass) { public @NonNull <T extends ViewModel> T create(@NonNull Class<T> modelClass) {

View File

@ -1938,10 +1938,6 @@ public class RecipientDatabase extends Database {
} }
public @NonNull List<Recipient> queryRecipientsForMentions(@NonNull String query, @Nullable List<RecipientId> recipientIds) { public @NonNull List<Recipient> queryRecipientsForMentions(@NonNull String query, @Nullable List<RecipientId> recipientIds) {
if (TextUtils.isEmpty(query)) {
return Collections.emptyList();
}
query = buildCaseInsensitiveGlobPattern(query); query = buildCaseInsensitiveGlobPattern(query);
String ids = null; String ids = null;