Skip to content

Commit

Permalink
Improve emoji sticker suggestions.
Browse files Browse the repository at this point in the history
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
  • Loading branch information
greyson-signal committed Mar 26, 2020
1 parent 1e2a27f commit f95a379
Show file tree
Hide file tree
Showing 4 changed files with 104 additions and 26 deletions.
Original file line number Diff line number Diff line change
@@ -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;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,28 +10,29 @@
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;
import org.thoughtcrime.securesms.stickers.StickerSearchRepository;
import org.thoughtcrime.securesms.util.CloseableLiveData;
import org.thoughtcrime.securesms.util.Throttler;

class ConversationStickerViewModel extends ViewModel {
import java.util.List;

private static final int SEARCH_LIMIT = 10;
class ConversationStickerViewModel extends ViewModel {

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()) {
Expand All @@ -44,7 +45,7 @@ public void onChange(boolean selfChange) {
application.getContentResolver().registerContentObserver(DatabaseContentProviders.StickerPack.CONTENT_URI, true, packObserver);
}

@NonNull LiveData<CursorList<StickerRecord>> getStickerResults() {
@NonNull LiveData<List<StickerRecord>> getStickerResults() {
return stickers;
}

Expand All @@ -54,7 +55,7 @@ public void onChange(boolean selfChange) {
}

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);
Expand All @@ -63,7 +64,6 @@ void onInputTextUpdated(@NonNull String text) {

@Override
protected void onCleared() {
stickers.close();
application.getContentResolver().unregisterContentObserver(packObserver);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@
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 {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,20 @@
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;
Expand All @@ -22,15 +28,22 @@ public StickerSearchRepository(@NonNull Context context) {
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);
});
}

Expand All @@ -49,14 +62,7 @@ public void getStickerFeatureAvailability(@NonNull Callback<Boolean> callback) {
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();
}
}

Expand Down

0 comments on commit f95a379

Please sign in to comment.