Improve emoji sticker suggestions.
There was a bug around some emoji being marked as 'obsolete' and therefore not being found. I also made a change so that you can use skin variations of emoji and still find emoji tagged with the default yellow version of it. Fixes #9471
This commit is contained in:
parent
1e2a27f902
commit
f95a37956c
4 changed files with 104 additions and 26 deletions
|
@ -0,0 +1,70 @@
|
|||
package org.thoughtcrime.securesms.components.emoji;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import org.whispersystems.libsignal.util.Pair;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
public final class EmojiUtil {
|
||||
|
||||
private static final Map<String, String> VARIATION_MAP = new HashMap<>();
|
||||
|
||||
static {
|
||||
for (EmojiPageModel page : EmojiPages.DATA_PAGES) {
|
||||
for (Emoji emoji : page.getDisplayEmoji()) {
|
||||
for (String variation : emoji.getVariations()) {
|
||||
VARIATION_MAP.put(variation, emoji.getValue());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static final int MAX_EMOJI_LENGTH;
|
||||
static {
|
||||
int max = 0;
|
||||
for (EmojiPageModel page : EmojiPages.DATA_PAGES) {
|
||||
for (String emoji : page.getEmoji()) {
|
||||
max = Math.max(max, emoji.length());
|
||||
}
|
||||
}
|
||||
MAX_EMOJI_LENGTH = max;
|
||||
}
|
||||
|
||||
private EmojiUtil() {}
|
||||
|
||||
/**
|
||||
* This will return all ways we know of expressing a singular emoji. This is to aid in search,
|
||||
* where some platforms may send an emoji we've locally marked as 'obsolete'.
|
||||
*/
|
||||
public static @NonNull Set<String> getAllRepresentations(@NonNull String emoji) {
|
||||
Set<String> out = new HashSet<>();
|
||||
|
||||
out.add(emoji);
|
||||
|
||||
for (Pair<String, String> pair : EmojiPages.OBSOLETE) {
|
||||
if (pair.first().equals(emoji)) {
|
||||
out.add(pair.second());
|
||||
} else if (pair.second().equals(emoji)) {
|
||||
out.add(pair.first());
|
||||
}
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
/**
|
||||
* When provided an emoji that is a skin variation of another, this will return the default yellow
|
||||
* version. This is to aid in search, so using a variation will still find all emojis tagged with
|
||||
* the default version.
|
||||
*
|
||||
* If the emoji has no skin variations, this function will return the original emoji.
|
||||
*/
|
||||
public static @NonNull String getCanonicalRepresentation(@NonNull String emoji) {
|
||||
String canonical = VARIATION_MAP.get(emoji);
|
||||
return canonical != null ? canonical : emoji;
|
||||
}
|
||||
}
|
|
@ -10,6 +10,7 @@ import android.os.Handler;
|
|||
import androidx.annotation.NonNull;
|
||||
import android.text.TextUtils;
|
||||
|
||||
import org.thoughtcrime.securesms.components.emoji.EmojiUtil;
|
||||
import org.thoughtcrime.securesms.database.CursorList;
|
||||
import org.thoughtcrime.securesms.database.DatabaseContentProviders;
|
||||
import org.thoughtcrime.securesms.database.model.StickerRecord;
|
||||
|
@ -17,21 +18,21 @@ import org.thoughtcrime.securesms.stickers.StickerSearchRepository;
|
|||
import org.thoughtcrime.securesms.util.CloseableLiveData;
|
||||
import org.thoughtcrime.securesms.util.Throttler;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
class ConversationStickerViewModel extends ViewModel {
|
||||
|
||||
private static final int SEARCH_LIMIT = 10;
|
||||
|
||||
private final Application application;
|
||||
private final StickerSearchRepository repository;
|
||||
private final CloseableLiveData<CursorList<StickerRecord>> stickers;
|
||||
private final MutableLiveData<Boolean> stickersAvailable;
|
||||
private final Throttler availabilityThrottler;
|
||||
private final ContentObserver packObserver;
|
||||
private final Application application;
|
||||
private final StickerSearchRepository repository;
|
||||
private final MutableLiveData<List<StickerRecord>> stickers;
|
||||
private final MutableLiveData<Boolean> stickersAvailable;
|
||||
private final Throttler availabilityThrottler;
|
||||
private final ContentObserver packObserver;
|
||||
|
||||
private ConversationStickerViewModel(@NonNull Application application, @NonNull StickerSearchRepository repository) {
|
||||
this.application = application;
|
||||
this.repository = repository;
|
||||
this.stickers = new CloseableLiveData<>();
|
||||
this.stickers = new MutableLiveData<>();
|
||||
this.stickersAvailable = new MutableLiveData<>();
|
||||
this.availabilityThrottler = new Throttler(500);
|
||||
this.packObserver = new ContentObserver(new Handler()) {
|
||||
|
@ -44,7 +45,7 @@ class ConversationStickerViewModel extends ViewModel {
|
|||
application.getContentResolver().registerContentObserver(DatabaseContentProviders.StickerPack.CONTENT_URI, true, packObserver);
|
||||
}
|
||||
|
||||
@NonNull LiveData<CursorList<StickerRecord>> getStickerResults() {
|
||||
@NonNull LiveData<List<StickerRecord>> getStickerResults() {
|
||||
return stickers;
|
||||
}
|
||||
|
||||
|
@ -54,7 +55,7 @@ class ConversationStickerViewModel extends ViewModel {
|
|||
}
|
||||
|
||||
void onInputTextUpdated(@NonNull String text) {
|
||||
if (TextUtils.isEmpty(text) || text.length() > SEARCH_LIMIT) {
|
||||
if (TextUtils.isEmpty(text) || text.length() > EmojiUtil.MAX_EMOJI_LENGTH) {
|
||||
stickers.setValue(CursorList.emptyList());
|
||||
} else {
|
||||
repository.searchByEmoji(text, stickers::postValue);
|
||||
|
@ -63,7 +64,6 @@ class ConversationStickerViewModel extends ViewModel {
|
|||
|
||||
@Override
|
||||
protected void onCleared() {
|
||||
stickers.close();
|
||||
application.getContentResolver().unregisterContentObserver(packObserver);
|
||||
}
|
||||
|
||||
|
|
|
@ -30,6 +30,8 @@ import java.io.File;
|
|||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.net.URLEncoder;
|
||||
import java.util.List;
|
||||
|
||||
public class StickerDatabase extends Database {
|
||||
|
|
|
@ -4,14 +4,20 @@ import android.content.Context;
|
|||
import android.database.Cursor;
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import org.thoughtcrime.securesms.components.emoji.EmojiUtil;
|
||||
import org.thoughtcrime.securesms.database.AttachmentDatabase;
|
||||
import org.thoughtcrime.securesms.database.CursorList;
|
||||
import org.thoughtcrime.securesms.database.DatabaseFactory;
|
||||
import org.thoughtcrime.securesms.database.StickerDatabase;
|
||||
import org.thoughtcrime.securesms.database.StickerDatabase.StickerRecordReader;
|
||||
import org.thoughtcrime.securesms.database.model.StickerPackRecord;
|
||||
import org.thoughtcrime.securesms.database.model.StickerRecord;
|
||||
import org.thoughtcrime.securesms.util.concurrent.SignalExecutors;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
public final class StickerSearchRepository {
|
||||
|
||||
private final StickerDatabase stickerDatabase;
|
||||
|
@ -22,15 +28,22 @@ public final class StickerSearchRepository {
|
|||
this.attachmentDatabase = DatabaseFactory.getAttachmentDatabase(context);
|
||||
}
|
||||
|
||||
public void searchByEmoji(@NonNull String emoji, @NonNull Callback<CursorList<StickerRecord>> callback) {
|
||||
public void searchByEmoji(@NonNull String emoji, @NonNull Callback<List<StickerRecord>> callback) {
|
||||
SignalExecutors.BOUNDED.execute(() -> {
|
||||
Cursor cursor = stickerDatabase.getStickersByEmoji(emoji);
|
||||
String searchEmoji = EmojiUtil.getCanonicalRepresentation(emoji);
|
||||
List<StickerRecord> out = new ArrayList<>();
|
||||
Set<String> possible = EmojiUtil.getAllRepresentations(searchEmoji);
|
||||
|
||||
if (cursor != null) {
|
||||
callback.onResult(new CursorList<>(cursor, new StickerModelBuilder()));
|
||||
} else {
|
||||
callback.onResult(CursorList.emptyList());
|
||||
for (String candidate : possible) {
|
||||
try (StickerRecordReader reader = new StickerRecordReader(stickerDatabase.getStickersByEmoji(candidate))) {
|
||||
StickerRecord record = null;
|
||||
while ((record = reader.getNext()) != null) {
|
||||
out.add(record);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
callback.onResult(out);
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -49,14 +62,7 @@ public final class StickerSearchRepository {
|
|||
private static class StickerModelBuilder implements CursorList.ModelBuilder<StickerRecord> {
|
||||
@Override
|
||||
public StickerRecord build(@NonNull Cursor cursor) {
|
||||
return new StickerDatabase.StickerRecordReader(cursor).getCurrent();
|
||||
}
|
||||
}
|
||||
|
||||
private static class StickerPackModelBuilder implements CursorList.ModelBuilder<StickerPackRecord> {
|
||||
@Override
|
||||
public StickerPackRecord build(@NonNull Cursor cursor) {
|
||||
return new StickerDatabase.StickerPackRecordReader(cursor).getCurrent();
|
||||
return new StickerRecordReader(cursor).getCurrent();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Add table
Reference in a new issue