Add support for OTA emoji download.
No files matched your search
@@ -458,6 +458,7 @@ dependencies {
|
||||
androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
|
||||
|
||||
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
|
||||
implementation "com.fasterxml.jackson.module:jackson-module-kotlin:2.12.0"
|
||||
}
|
||||
|
||||
dependencyVerification {
|
||||
|
||||
|
Before Width: | Height: | Size: 240 KiB After Width: | Height: | Size: 115 KiB |
|
Before Width: | Height: | Size: 421 KiB After Width: | Height: | Size: 141 KiB |
|
Before Width: | Height: | Size: 24 KiB After Width: | Height: | Size: 8.3 KiB |
|
Before Width: | Height: | Size: 365 KiB After Width: | Height: | Size: 162 KiB |
|
Before Width: | Height: | Size: 434 KiB After Width: | Height: | Size: 209 KiB |
|
Before Width: | Height: | Size: 664 KiB After Width: | Height: | Size: 303 KiB |
|
Before Width: | Height: | Size: 608 KiB After Width: | Height: | Size: 239 KiB |
|
Before Width: | Height: | Size: 552 KiB After Width: | Height: | Size: 182 KiB |
|
Before Width: | Height: | Size: 631 KiB After Width: | Height: | Size: 225 KiB |
|
Before Width: | Height: | Size: 653 KiB After Width: | Height: | Size: 242 KiB |
|
Before Width: | Height: | Size: 652 KiB After Width: | Height: | Size: 240 KiB |
|
Before Width: | Height: | Size: 531 KiB After Width: | Height: | Size: 218 KiB |
|
Before Width: | Height: | Size: 685 KiB After Width: | Height: | Size: 270 KiB |
|
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 6.2 KiB |
|
Before Width: | Height: | Size: 603 KiB After Width: | Height: | Size: 264 KiB |
|
Before Width: | Height: | Size: 391 KiB After Width: | Height: | Size: 138 KiB |
@@ -0,0 +1 @@
|
||||
{"emoji":{"Places":[["d83cdf0d"],["d83cdf0e"],["d83cdf0f"],["d83cdf10"],["d83dddfafe0f"],["d83dddfe"],["d83edded"],["d83cdfd4fe0f"],["26f0fe0f"],["d83cdf0b"],["d83dddfb"],["d83cdfd5fe0f"],["d83cdfd6fe0f"],["d83cdfdcfe0f"],["d83cdfddfe0f"],["d83cdfdefe0f"],["d83cdfdffe0f"],["d83cdfdbfe0f"],["d83cdfd7fe0f"],["d83eddf1"],["d83edea8"],["d83edeb5"],["d83dded6"],["d83cdfd8fe0f"],["d83cdfdafe0f"],["d83cdfe0"],["d83cdfe1"],["d83cdfe2"],["d83cdfe3"],["d83cdfe4"],["d83cdfe5"],["d83cdfe6"],["d83cdfe8"],["d83cdfe9"],["d83cdfea"],["d83cdfeb"],["d83cdfec"],["d83cdfed"],["d83cdfef"],["d83cdff0"],["d83ddc92"],["d83dddfc"],["d83dddfd"],["26ea"],["d83ddd4c"],["d83dded5"],["d83ddd4d"],["26e9fe0f"],["d83ddd4b"],["26f2"],["26fa"],["d83cdf01"],["d83cdf03"],["d83cdfd9fe0f"],["d83cdf04"],["d83cdf05"],["d83cdf06"],["d83cdf07"],["d83cdf09"],["2668fe0f"],["d83cdfa0"],["d83cdfa1"],["d83cdfa2"],["d83ddc88"],["d83cdfaa"],["d83dde82"],["d83dde83"],["d83dde84"],["d83dde85"],["d83dde86"],["d83dde87"],["d83dde88"],["d83dde89"],["d83dde8a"],["d83dde9d"],["d83dde9e"],["d83dde8b"],["d83dde8c"],["d83dde8d"],["d83dde8e"],["d83dde90"],["d83dde91"],["d83dde92"],["d83dde93"],["d83dde94"],["d83dde95"],["d83dde96"],["d83dde97"],["d83dde98"],["d83dde99"],["d83ddefb"],["d83dde9a"],["d83dde9b"],["d83dde9c"],["d83cdfcefe0f"],["d83cdfcdfe0f"],["d83ddef5"],["d83eddbd"],["d83eddbc"],["d83ddefa"],["d83ddeb2"],["d83ddef4"],["d83ddef9"],["d83ddefc"],["d83dde8f"],["d83ddee3fe0f"],["d83ddee4fe0f"],["d83ddee2fe0f"],["26fd"],["d83ddea8"],["d83ddea5"],["d83ddea6"],["d83dded1"],["d83ddea7"],["2693"],["26f5"],["d83ddef6"],["d83ddea4"],["d83ddef3fe0f"],["26f4fe0f"],["d83ddee5fe0f"],["d83ddea2"],["2708fe0f"],["d83ddee9fe0f"],["d83ddeeb"],["d83ddeec"],["d83ede82"],["d83ddcba"],["d83dde81"],["d83dde9f"],["d83ddea0"],["d83ddea1"],["d83ddef0fe0f"],["d83dde80"],["d83ddef8"],["d83ddecefe0f"],["d83eddf3"],["231b"],["23f3"],["231a"],["23f0"],["23f1fe0f"],["23f2fe0f"],["d83ddd70fe0f"],["d83ddd5b"],["d83ddd67"],["d83ddd50"],["d83ddd5c"],["d83ddd51"],["d83ddd5d"],["d83ddd52"],["d83ddd5e"],["d83ddd53"],["d83ddd5f"],["d83ddd54"],["d83ddd60"],["d83ddd55"],["d83ddd61"],["d83ddd56"],["d83ddd62"],["d83ddd57"],["d83ddd63"],["d83ddd58"],["d83ddd64"],["d83ddd59"],["d83ddd65"],["d83ddd5a"],["d83ddd66"],["d83cdf11"],["d83cdf12"],["d83cdf13"],["d83cdf14"],["d83cdf15"],["d83cdf16"],["d83cdf17"],["d83cdf18"],["d83cdf19"],["d83cdf1a"],["d83cdf1b"],["d83cdf1c"],["d83cdf21fe0f"],["2600fe0f"],["d83cdf1d"],["d83cdf1e"],["d83ede90"],["2b50"],["d83cdf1f"],["d83cdf20"],["d83cdf0c"],["2601fe0f"],["26c5"],["26c8fe0f"],["d83cdf24fe0f"],["d83cdf25fe0f"],["d83cdf26fe0f"],["d83cdf27fe0f"],["d83cdf28fe0f"],["d83cdf29fe0f"],["d83cdf2afe0f"],["d83cdf2bfe0f"],["d83cdf2cfe0f"],["d83cdf00"],["d83cdf08"],["d83cdf02"],["2602fe0f"],["2614"],["26f1fe0f"],["26a1"],["2744fe0f"],["2603fe0f"],["26c4"],["2604fe0f"],["d83ddd25"],["d83ddca7"],["d83cdf0a"]],"Foods":[["d83cdf47"],["d83cdf48"],["d83cdf49"],["d83cdf4a"],["d83cdf4b"],["d83cdf4c"],["d83cdf4d"],["d83edd6d"],["d83cdf4e"],["d83cdf4f"],["d83cdf50"],["d83cdf51"],["d83cdf52"],["d83cdf53"],["d83eded0"],["d83edd5d"],["d83cdf45"],["d83eded2"],["d83edd65"],["d83edd51"],["d83cdf46"],["d83edd54"],["d83edd55"],["d83cdf3d"],["d83cdf36fe0f"],["d83eded1"],["d83edd52"],["d83edd6c"],["d83edd66"],["d83eddc4"],["d83eddc5"],["d83cdf44"],["d83edd5c"],["d83cdf30"],["d83cdf5e"],["d83edd50"],["d83edd56"],["d83eded3"],["d83edd68"],["d83edd6f"],["d83edd5e"],["d83eddc7"],["d83eddc0"],["d83cdf56"],["d83cdf57"],["d83edd69"],["d83edd53"],["d83cdf54"],["d83cdf5f"],["d83cdf55"],["d83cdf2d"],["d83edd6a"],["d83cdf2e"],["d83cdf2f"],["d83eded4"],["d83edd59"],["d83eddc6"],["d83edd5a"],["d83cdf73"],["d83edd58"],["d83cdf72"],["d83eded5"],["d83edd63"],["d83edd57"],["d83cdf7f"],["d83eddc8"],["d83eddc2"],["d83edd6b"],["d83cdf71"],["d83cdf58"],["d83cdf59"],["d83cdf5a"],["d83cdf5b"],["d83cdf5c"],["d83cdf5d"],["d83cdf60"],["d83cdf62"],["d83cdf63"],["d83cdf64"],["d83cdf65"],["d83edd6e"],["d83cdf61"],["d83edd5f"],["d83edd60"],["d83edd61"],["d83edd80"],["d83edd9e"],["d83edd90"],["d83edd91"],["d83eddaa"],["d83cdf66"],["d83cdf67"],["d83cdf68"],["d83cdf69"],["d83cdf6a"],["d83cdf82"],["d83cdf70"],["d83eddc1"],["d83edd67"],["d83cdf6b"],["d83cdf6c"],["d83cdf6d"],["d83cdf6e"],["d83cdf6f"],["d83cdf7c"],["d83edd5b"],["2615"],["d83eded6"],["d83cdf75"],["d83cdf76"],["d83cdf7e"],["d83cdf77"],["d83cdf78"],["d83cdf79"],["d83cdf7a"],["d83cdf7b"],["d83edd42"],["d83edd43"],["d83edd64"],["d83eddcb"],["d83eddc3"],["d83eddc9"],["d83eddca"],["d83edd62"],["d83cdf7dfe0f"],["d83cdf74"],["d83edd44"],["d83ddd2a"],["d83cdffa"]],"Activity":[["d83cdf83"],["d83cdf84"],["d83cdf86"],["d83cdf87"],["d83edde8"],["2728"],["d83cdf88"],["d83cdf89"],["d83cdf8a"],["d83cdf8b"],["d83cdf8d"],["d83cdf8e"],["d83cdf8f"],["d83cdf90"],["d83cdf91"],["d83edde7"],["d83cdf80"],["d83cdf81"],["d83cdf97fe0f"],["d83cdf9ffe0f"],["d83cdfab"],["d83cdf96fe0f"],["d83cdfc6"],["d83cdfc5"],["d83edd47"],["d83edd48"],["d83edd49"],["26bd"],["26be"],["d83edd4e"],["d83cdfc0"],[Line truncated
|
||||
@@ -40,8 +40,10 @@ import org.thoughtcrime.securesms.database.DatabaseFactory;
|
||||
import org.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper;
|
||||
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
|
||||
import org.thoughtcrime.securesms.dependencies.ApplicationDependencyProvider;
|
||||
import org.thoughtcrime.securesms.emoji.EmojiSource;
|
||||
import org.thoughtcrime.securesms.gcm.FcmJobService;
|
||||
import org.thoughtcrime.securesms.jobs.CreateSignedPreKeyJob;
|
||||
import org.thoughtcrime.securesms.jobs.DownloadLatestEmojiDataJob;
|
||||
import org.thoughtcrime.securesms.jobs.FcmRefreshJob;
|
||||
import org.thoughtcrime.securesms.jobs.GroupV1MigrationJob;
|
||||
import org.thoughtcrime.securesms.jobs.MultiDeviceContactUpdateJob;
|
||||
@@ -57,10 +59,8 @@ import org.thoughtcrime.securesms.notifications.NotificationChannels;
|
||||
import org.thoughtcrime.securesms.providers.BlobProvider;
|
||||
import org.thoughtcrime.securesms.push.SignalServiceNetworkAccess;
|
||||
import org.thoughtcrime.securesms.registration.RegistrationUtil;
|
||||
import org.thoughtcrime.securesms.revealable.ViewOnceMessageManager;
|
||||
import org.thoughtcrime.securesms.ringrtc.RingRtcLogger;
|
||||
import org.thoughtcrime.securesms.service.DirectoryRefreshListener;
|
||||
import org.thoughtcrime.securesms.service.ExpiringMessageManager;
|
||||
import org.thoughtcrime.securesms.service.KeyCachingService;
|
||||
import org.thoughtcrime.securesms.service.LocalBackupListener;
|
||||
import org.thoughtcrime.securesms.service.RotateSenderCertificateListener;
|
||||
@@ -154,6 +154,8 @@ public class ApplicationContext extends MultiDexApplication implements AppForegr
|
||||
.addNonBlocking(RefreshPreKeysJob::scheduleIfNecessary)
|
||||
.addNonBlocking(StorageSyncHelper::scheduleRoutineSync)
|
||||
.addNonBlocking(() -> ApplicationDependencies.getJobManager().beginJobLoop())
|
||||
.addNonBlocking(EmojiSource::refresh)
|
||||
.addNonBlocking(DownloadLatestEmojiDataJob::scheduleIfNecessary)
|
||||
.addPostRender(this::initializeExpiringMessageManager)
|
||||
.execute();
|
||||
|
||||
|
||||
@@ -1,17 +1,20 @@
|
||||
package org.thoughtcrime.securesms.components.emoji;
|
||||
|
||||
import android.net.Uri;
|
||||
|
||||
import androidx.annotation.AttrRes;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
public class CompositeEmojiPageModel implements EmojiPageModel {
|
||||
@AttrRes private final int iconAttr;
|
||||
@NonNull private final EmojiPageModel[] models;
|
||||
@AttrRes private final int iconAttr;
|
||||
@NonNull private final List<EmojiPageModel> models;
|
||||
|
||||
public CompositeEmojiPageModel(@AttrRes int iconAttr, @NonNull EmojiPageModel... models) {
|
||||
public CompositeEmojiPageModel(@AttrRes int iconAttr, @NonNull List<EmojiPageModel> models) {
|
||||
this.iconAttr = iconAttr;
|
||||
this.models = models;
|
||||
}
|
||||
@@ -39,12 +42,7 @@ public class CompositeEmojiPageModel implements EmojiPageModel {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasSpriteMap() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable String getSprite() {
|
||||
public @Nullable Uri getSpriteUri() {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
@@ -11,6 +11,10 @@ public class Emoji {
|
||||
this.variations = Arrays.asList(variations);
|
||||
}
|
||||
|
||||
public Emoji(List<String> variations) {
|
||||
this.variations = variations;
|
||||
}
|
||||
|
||||
public String getValue() {
|
||||
return variations.get(0);
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@ import androidx.viewpager.widget.PagerAdapter;
|
||||
|
||||
import org.thoughtcrime.securesms.R;
|
||||
import org.thoughtcrime.securesms.components.emoji.EmojiPageViewGridAdapter.VariationSelectorListener;
|
||||
import org.thoughtcrime.securesms.emoji.EmojiSource;
|
||||
import org.thoughtcrime.securesms.keyvalue.SignalStore;
|
||||
import org.thoughtcrime.securesms.mms.GlideRequests;
|
||||
import org.thoughtcrime.securesms.util.ResUtil;
|
||||
@@ -66,7 +67,7 @@ public class EmojiKeyboardProvider implements MediaKeyboardProvider,
|
||||
}, this);
|
||||
|
||||
models.add(recentModel);
|
||||
models.addAll(EmojiPages.DISPLAY_PAGES);
|
||||
models.addAll(EmojiSource.getLatest().getDisplayPages());
|
||||
|
||||
currentPosition = recentModel.getEmoji().size() > 0 ? 0 : 1;
|
||||
}
|
||||
|
||||
@@ -1,12 +1,15 @@
|
||||
package org.thoughtcrime.securesms.components.emoji;
|
||||
|
||||
import android.net.Uri;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public interface EmojiPageModel {
|
||||
int getIconAttr();
|
||||
List<String> getEmoji();
|
||||
List<Emoji> getDisplayEmoji();
|
||||
boolean hasSpriteMap();
|
||||
String getSprite();
|
||||
@Nullable Uri getSpriteUri();
|
||||
boolean isDynamic();
|
||||
}
|
||||
@@ -1,310 +0,0 @@
|
||||
package org.thoughtcrime.securesms.components.emoji;
|
||||
|
||||
import org.thoughtcrime.securesms.R;
|
||||
import org.whispersystems.libsignal.util.Pair;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* IMPORTANT: The code in this class is generated by a script! Do not edit by hand!
|
||||
*/
|
||||
class EmojiPages {
|
||||
private static final EmojiPageModel PAGE_PLACES = new StaticEmojiPageModel(R.attr.emoji_category_places, new Emoji[] {
|
||||
new Emoji("\ud83c\udf0d"), new Emoji("\ud83c\udf0e"), new Emoji("\ud83c\udf0f"), new Emoji("\ud83c\udf10"), new Emoji("\ud83d\uddfa\ufe0f"), new Emoji("\ud83d\uddfe"), new Emoji("\ud83e\udded"), new Emoji("\ud83c\udfd4\ufe0f"), new Emoji("\u26f0\ufe0f"), new Emoji("\ud83c\udf0b"), new Emoji("\ud83d\uddfb"), new Emoji("\ud83c\udfd5\ufe0f"), new Emoji("\ud83c\udfd6\ufe0f"), new Emoji("\ud83c\udfdc\ufe0f"), new Emoji("\ud83c\udfdd\ufe0f"), new Emoji("\ud83c\udfde\ufe0f"), new Emoji("\ud83c\udfdf\ufe0f"), new Emoji("\ud83c\udfdb\ufe0f"), new Emoji("\ud83c\udfd7\ufe0f"), new Emoji("\ud83e\uddf1"), new Emoji("\ud83e\udea8"), new Emoji("\ud83e\udeb5"), new Emoji("\ud83d\uded6"), new Emoji("\ud83c\udfd8\ufe0f"), new Emoji("\ud83c\udfda\ufe0f"), new Emoji("\ud83c\udfe0"), new Emoji("\ud83c\udfe1"), new Emoji("\ud83c\udfe2"), new Emoji("\ud83c\udfe3"), new Emoji("\ud83c\udfe4"), new Emoji("\ud83c\udfe5"), new Emoji("\ud83c\udfe6"), new Emoji("\ud83c\udfe8"), new Emoji("\ud83c\udfe9"), new Emoji("\ud83c\udfea"), new Emoji("\ud83c\udfeb"), new Emoji("\ud83c\udfec"), new Emoji("\ud83c\udfed"), new Emoji("\ud83c\udfef"), new Emoji("\ud83c\udff0"), new Emoji("\ud83d\udc92"), new Emoji("\ud83d\uddfc"), new Emoji("\ud83d\uddfd"), new Emoji("\u26ea"), new Emoji("\ud83d\udd4c"), new Emoji("\ud83d\uded5"), new Emoji("\ud83d\udd4d"), new Emoji("\u26e9\ufe0f"), new Emoji("\ud83d\udd4b"), new Emoji("\u26f2"), new Emoji("\u26fa"), new Emoji("\ud83c\udf01"), new Emoji("\ud83c\udf03"), new Emoji("\ud83c\udfd9\ufe0f"), new Emoji("\ud83c\udf04"), new Emoji("\ud83c\udf05"), new Emoji("\ud83c\udf06"), new Emoji("\ud83c\udf07"), new Emoji("\ud83c\udf09"), new Emoji("\u2668\ufe0f"), new Emoji("\ud83c\udfa0"), new Emoji("\ud83c\udfa1"), new Emoji("\ud83c\udfa2"), new Emoji("\ud83d\udc88"), new Emoji("\ud83c\udfaa"), new Emoji("\ud83d\ude82"), new Emoji("\ud83d\ude83"), new Emoji("\ud83d\ude84"), new Emoji("\ud83d\ude85"), new Emoji("\ud83d\ude86"), new Emoji("\ud83d\ude87"), new Emoji("\ud83d\ude88"), new Emoji("\ud83d\ude89"), new Emoji("\ud83d\ude8a"), new Emoji("\ud83d\ude9d"), new Emoji("\ud83d\ude9e"), new Emoji("\ud83d\ude8b"), new Emoji("\ud83d\ude8c"), new Emoji("\ud83d\ude8d"), new Emoji("\ud83d\ude8e"), new Emoji("\ud83d\ude90"), new Emoji("\ud83d\ude91"), new Emoji("\ud83d\ude92"), new Emoji("\ud83d\ude93"), new Emoji("\ud83d\ude94"), new Emoji("\ud83d\ude95"), new Emoji("\ud83d\ude96"), new Emoji("\ud83d\ude97"), new Emoji("\ud83d\ude98"), new Emoji("\ud83d\ude99"), new Emoji("\ud83d\udefb"), new Emoji("\ud83d\ude9a"), new Emoji("\ud83d\ude9b"), new Emoji("\ud83d\ude9c"), new Emoji("\ud83c\udfce\ufe0f"), new Emoji("\ud83c\udfcd\ufe0f"), new Emoji("\ud83d\udef5"), new Emoji("\ud83e\uddbd"), new Emoji("\ud83e\uddbc"), new Emoji("\ud83d\udefa"), new Emoji("\ud83d\udeb2"), new Emoji("\ud83d\udef4"), new Emoji("\ud83d\udef9"), new Emoji("\ud83d\udefc"), new Emoji("\ud83d\ude8f"), new Emoji("\ud83d\udee3\ufe0f"), new Emoji("\ud83d\udee4\ufe0f"), new Emoji("\ud83d\udee2\ufe0f"), new Emoji("\u26fd"), new Emoji("\ud83d\udea8"), new Emoji("\ud83d\udea5"), new Emoji("\ud83d\udea6"), new Emoji("\ud83d\uded1"), new Emoji("\ud83d\udea7"), new Emoji("\u2693"), new Emoji("\u26f5"), new Emoji("\ud83d\udef6"), new Emoji("\ud83d\udea4"), new Emoji("\ud83d\udef3\ufe0f"), new Emoji("\u26f4\ufe0f"), new Emoji("\ud83d\udee5\ufe0f"), new Emoji("\ud83d\udea2"), new Emoji("\u2708\ufe0f"), new Emoji("\ud83d\udee9\ufe0f"), new Emoji("\ud83d\udeeb"), new Emoji("\ud83d\udeec"), new Emoji("\ud83e\ude82"), new Emoji("\ud83d\udcba"), new Emoji("\ud83d\ude81"), new Emoji("\ud83d\ude9f"), new Emoji("\ud83d\udea0"), new Emoji("\ud83d\udea1"), new Emoji("\ud83d\udef0\ufe0f"), new Emoji("\ud83d\ude80"), new Emoji("\ud83d\udef8"), new Emoji("\ud83d\udece\ufe0f"), new Emoji("\ud83e\uddf3"), new Emoji("\u231b"), new Emoji("\u23f3"), new Emoji("\u231a"), new Emoji("\u23f0"), new Emoji("\u23f1\ufe0f"), new Emoji("\u23f2\ufe0f"), new Emoji("\ud83d\udd70\ufe0f"), new Emoji("\ud83d\udd5b"), new Emoji("\ud83d\udd67"), new Emoji("\ud83d\udd50"), new Emoji("\ud83d\udd5c"), new Emoji("\ud83d\udd51"), new Emoji("\ud83d\udd5d"), new Emoji("\ud83d\udd52"), new Emoji("\ud83d\udd5e"), new Emoji("\ud83d\udd53"), new Emoji("\ud83d\udd5f"), new Emoji("\ud83d\udd54"), new Emoji("\ud83d\udd60"), new Emoji("\ud83d\udd55"), new Emoji("\ud83d\udd61"), new Emoji("\ud83d\udd56"), new Emoji("\ud83d\udd62"), new Emoji("\ud83d\udd57"), new Emoji("\ud83d\udd63"), new Emoji("\ud83d\udd58"), new Emoji("\ud83d\udd64"), new Emoji("\ud83d\udd59"), new Emoji("\ud83d\udd65"), new Emoji("\ud83d\udd5a"), new Emoji("\ud83d\udd66"), new Emoji("\ud83c\udf11"), new EmojLine truncated
|
||||
}, "emoji/Places.webp");
|
||||
|
||||
private static final EmojiPageModel PAGE_FOODS = new StaticEmojiPageModel(R.attr.emoji_category_foods, new Emoji[] {
|
||||
new Emoji("\ud83c\udf47"), new Emoji("\ud83c\udf48"), new Emoji("\ud83c\udf49"), new Emoji("\ud83c\udf4a"), new Emoji("\ud83c\udf4b"), new Emoji("\ud83c\udf4c"), new Emoji("\ud83c\udf4d"), new Emoji("\ud83e\udd6d"), new Emoji("\ud83c\udf4e"), new Emoji("\ud83c\udf4f"), new Emoji("\ud83c\udf50"), new Emoji("\ud83c\udf51"), new Emoji("\ud83c\udf52"), new Emoji("\ud83c\udf53"), new Emoji("\ud83e\uded0"), new Emoji("\ud83e\udd5d"), new Emoji("\ud83c\udf45"), new Emoji("\ud83e\uded2"), new Emoji("\ud83e\udd65"), new Emoji("\ud83e\udd51"), new Emoji("\ud83c\udf46"), new Emoji("\ud83e\udd54"), new Emoji("\ud83e\udd55"), new Emoji("\ud83c\udf3d"), new Emoji("\ud83c\udf36\ufe0f"), new Emoji("\ud83e\uded1"), new Emoji("\ud83e\udd52"), new Emoji("\ud83e\udd6c"), new Emoji("\ud83e\udd66"), new Emoji("\ud83e\uddc4"), new Emoji("\ud83e\uddc5"), new Emoji("\ud83c\udf44"), new Emoji("\ud83e\udd5c"), new Emoji("\ud83c\udf30"), new Emoji("\ud83c\udf5e"), new Emoji("\ud83e\udd50"), new Emoji("\ud83e\udd56"), new Emoji("\ud83e\uded3"), new Emoji("\ud83e\udd68"), new Emoji("\ud83e\udd6f"), new Emoji("\ud83e\udd5e"), new Emoji("\ud83e\uddc7"), new Emoji("\ud83e\uddc0"), new Emoji("\ud83c\udf56"), new Emoji("\ud83c\udf57"), new Emoji("\ud83e\udd69"), new Emoji("\ud83e\udd53"), new Emoji("\ud83c\udf54"), new Emoji("\ud83c\udf5f"), new Emoji("\ud83c\udf55"), new Emoji("\ud83c\udf2d"), new Emoji("\ud83e\udd6a"), new Emoji("\ud83c\udf2e"), new Emoji("\ud83c\udf2f"), new Emoji("\ud83e\uded4"), new Emoji("\ud83e\udd59"), new Emoji("\ud83e\uddc6"), new Emoji("\ud83e\udd5a"), new Emoji("\ud83c\udf73"), new Emoji("\ud83e\udd58"), new Emoji("\ud83c\udf72"), new Emoji("\ud83e\uded5"), new Emoji("\ud83e\udd63"), new Emoji("\ud83e\udd57"), new Emoji("\ud83c\udf7f"), new Emoji("\ud83e\uddc8"), new Emoji("\ud83e\uddc2"), new Emoji("\ud83e\udd6b"), new Emoji("\ud83c\udf71"), new Emoji("\ud83c\udf58"), new Emoji("\ud83c\udf59"), new Emoji("\ud83c\udf5a"), new Emoji("\ud83c\udf5b"), new Emoji("\ud83c\udf5c"), new Emoji("\ud83c\udf5d"), new Emoji("\ud83c\udf60"), new Emoji("\ud83c\udf62"), new Emoji("\ud83c\udf63"), new Emoji("\ud83c\udf64"), new Emoji("\ud83c\udf65"), new Emoji("\ud83e\udd6e"), new Emoji("\ud83c\udf61"), new Emoji("\ud83e\udd5f"), new Emoji("\ud83e\udd60"), new Emoji("\ud83e\udd61"), new Emoji("\ud83e\udd80"), new Emoji("\ud83e\udd9e"), new Emoji("\ud83e\udd90"), new Emoji("\ud83e\udd91"), new Emoji("\ud83e\uddaa"), new Emoji("\ud83c\udf66"), new Emoji("\ud83c\udf67"), new Emoji("\ud83c\udf68"), new Emoji("\ud83c\udf69"), new Emoji("\ud83c\udf6a"), new Emoji("\ud83c\udf82"), new Emoji("\ud83c\udf70"), new Emoji("\ud83e\uddc1"), new Emoji("\ud83e\udd67"), new Emoji("\ud83c\udf6b"), new Emoji("\ud83c\udf6c"), new Emoji("\ud83c\udf6d"), new Emoji("\ud83c\udf6e"), new Emoji("\ud83c\udf6f"), new Emoji("\ud83c\udf7c"), new Emoji("\ud83e\udd5b"), new Emoji("\u2615"), new Emoji("\ud83e\uded6"), new Emoji("\ud83c\udf75"), new Emoji("\ud83c\udf76"), new Emoji("\ud83c\udf7e"), new Emoji("\ud83c\udf77"), new Emoji("\ud83c\udf78"), new Emoji("\ud83c\udf79"), new Emoji("\ud83c\udf7a"), new Emoji("\ud83c\udf7b"), new Emoji("\ud83e\udd42"), new Emoji("\ud83e\udd43"), new Emoji("\ud83e\udd64"), new Emoji("\ud83e\uddcb"), new Emoji("\ud83e\uddc3"), new Emoji("\ud83e\uddc9"), new Emoji("\ud83e\uddca"), new Emoji("\ud83e\udd62"), new Emoji("\ud83c\udf7d\ufe0f"), new Emoji("\ud83c\udf74"), new Emoji("\ud83e\udd44"), new Emoji("\ud83d\udd2a"), new Emoji("\ud83c\udffa"),
|
||||
}, "emoji/Foods.webp");
|
||||
|
||||
private static final EmojiPageModel PAGE_ACTIVITY = new StaticEmojiPageModel(R.attr.emoji_category_activity, new Emoji[] {
|
||||
new Emoji("\ud83c\udf83"), new Emoji("\ud83c\udf84"), new Emoji("\ud83c\udf86"), new Emoji("\ud83c\udf87"), new Emoji("\ud83e\udde8"), new Emoji("\u2728"), new Emoji("\ud83c\udf88"), new Emoji("\ud83c\udf89"), new Emoji("\ud83c\udf8a"), new Emoji("\ud83c\udf8b"), new Emoji("\ud83c\udf8d"), new Emoji("\ud83c\udf8e"), new Emoji("\ud83c\udf8f"), new Emoji("\ud83c\udf90"), new Emoji("\ud83c\udf91"), new Emoji("\ud83e\udde7"), new Emoji("\ud83c\udf80"), new Emoji("\ud83c\udf81"), new Emoji("\ud83c\udf97\ufe0f"), new Emoji("\ud83c\udf9f\ufe0f"), new Emoji("\ud83c\udfab"), new Emoji("\ud83c\udf96\ufe0f"), new Emoji("\ud83c\udfc6"), new Emoji("\ud83c\udfc5"), new Emoji("\ud83e\udd47"), new Emoji("\ud83e\udd48"), new Emoji("\ud83e\udd49"), new Emoji("\u26bd"), new Emoji("\u26be"), new Emoji("\ud83e\udd4e"), new Emoji("\ud83c\udfc0"), new Emoji("\ud83c\udfd0"), new Emoji("\ud83c\udfc8"), new Emoji("\ud83c\udfc9"), new Emoji("\ud83c\udfbe"), new Emoji("\ud83e\udd4f"), new Emoji("\ud83c\udfb3"), new Emoji("\ud83c\udfcf"), new Emoji("\ud83c\udfd1"), new Emoji("\ud83c\udfd2"), new Emoji("\ud83e\udd4d"), new Emoji("\ud83c\udfd3"), new Emoji("\ud83c\udff8"), new Emoji("\ud83e\udd4a"), new Emoji("\ud83e\udd4b"), new Emoji("\ud83e\udd45"), new Emoji("\u26f3"), new Emoji("\u26f8\ufe0f"), new Emoji("\ud83c\udfa3"), new Emoji("\ud83e\udd3f"), new Emoji("\ud83c\udfbd"), new Emoji("\ud83c\udfbf"), new Emoji("\ud83d\udef7"), new Emoji("\ud83e\udd4c"), new Emoji("\ud83c\udfaf"), new Emoji("\ud83e\ude80"), new Emoji("\ud83e\ude81"), new Emoji("\ud83c\udfb1"), new Emoji("\ud83d\udd2e"), new Emoji("\ud83e\ude84"), new Emoji("\ud83e\uddff"), new Emoji("\ud83c\udfae"), new Emoji("\ud83d\udd79\ufe0f"), new Emoji("\ud83c\udfb0"), new Emoji("\ud83c\udfb2"), new Emoji("\ud83e\udde9"), new Emoji("\ud83e\uddf8"), new Emoji("\ud83e\ude85"), new Emoji("\ud83e\ude86"), new Emoji("\u2660\ufe0f"), new Emoji("\u2665\ufe0f"), new Emoji("\u2666\ufe0f"), new Emoji("\u2663\ufe0f"), new Emoji("\u265f\ufe0f"), new Emoji("\ud83c\udccf"), new Emoji("\ud83c\udc04"), new Emoji("\ud83c\udfb4"), new Emoji("\ud83c\udfad"), new Emoji("\ud83d\uddbc\ufe0f"), new Emoji("\ud83c\udfa8"), new Emoji("\ud83e\uddf5"), new Emoji("\ud83e\udea1"), new Emoji("\ud83e\uddf6"), new Emoji("\ud83e\udea2"),
|
||||
}, "emoji/Activity.webp");
|
||||
|
||||
private static final EmojiPageModel PAGE_FLAGS_0 = new StaticEmojiPageModel(R.attr.emoji_category_flags, new Emoji[] {
|
||||
new Emoji("\ud83c\udfc1"), new Emoji("\ud83d\udea9"), new Emoji("\ud83c\udf8c"), new Emoji("\ud83c\udff4"), new Emoji("\ud83c\udff3\ufe0f"), new Emoji("\ud83c\udff3\ufe0f\u200d\ud83c\udf08"), new Emoji("\ud83c\udff3\ufe0f\u200d\u26a7\ufe0f"), new Emoji("\ud83c\udff4\u200d\u2620\ufe0f"), new Emoji("\ud83c\udde6\ud83c\udde8"), new Emoji("\ud83c\udde6\ud83c\udde9"), new Emoji("\ud83c\udde6\ud83c\uddea"), new Emoji("\ud83c\udde6\ud83c\uddeb"), new Emoji("\ud83c\udde6\ud83c\uddec"), new Emoji("\ud83c\udde6\ud83c\uddee"), new Emoji("\ud83c\udde6\ud83c\uddf1"), new Emoji("\ud83c\udde6\ud83c\uddf2"), new Emoji("\ud83c\udde6\ud83c\uddf4"), new Emoji("\ud83c\udde6\ud83c\uddf6"), new Emoji("\ud83c\udde6\ud83c\uddf7"), new Emoji("\ud83c\udde6\ud83c\uddf8"), new Emoji("\ud83c\udde6\ud83c\uddf9"), new Emoji("\ud83c\udde6\ud83c\uddfa"), new Emoji("\ud83c\udde6\ud83c\uddfc"), new Emoji("\ud83c\udde6\ud83c\uddfd"), new Emoji("\ud83c\udde6\ud83c\uddff"), new Emoji("\ud83c\udde7\ud83c\udde6"), new Emoji("\ud83c\udde7\ud83c\udde7"), new Emoji("\ud83c\udde7\ud83c\udde9"), new Emoji("\ud83c\udde7\ud83c\uddea"), new Emoji("\ud83c\udde7\ud83c\uddeb"), new Emoji("\ud83c\udde7\ud83c\uddec"), new Emoji("\ud83c\udde7\ud83c\udded"), new Emoji("\ud83c\udde7\ud83c\uddee"), new Emoji("\ud83c\udde7\ud83c\uddef"), new Emoji("\ud83c\udde7\ud83c\uddf1"), new Emoji("\ud83c\udde7\ud83c\uddf2"), new Emoji("\ud83c\udde7\ud83c\uddf3"), new Emoji("\ud83c\udde7\ud83c\uddf4"), new Emoji("\ud83c\udde7\ud83c\uddf6"), new Emoji("\ud83c\udde7\ud83c\uddf7"), new Emoji("\ud83c\udde7\ud83c\uddf8"), new Emoji("\ud83c\udde7\ud83c\uddf9"), new Emoji("\ud83c\udde7\ud83c\uddfb"), new Emoji("\ud83c\udde7\ud83c\uddfc"), new Emoji("\ud83c\udde7\ud83c\uddfe"), new Emoji("\ud83c\udde7\ud83c\uddff"), new Emoji("\ud83c\udde8\ud83c\udde6"), new Emoji("\ud83c\udde8\ud83c\udde8"), new Emoji("\ud83c\udde8\ud83c\udde9"), new Emoji("\ud83c\udde8\ud83c\uddeb"), new Emoji("\ud83c\udde8\ud83c\uddec"), new Emoji("\ud83c\udde8\ud83c\udded"), new Emoji("\ud83c\udde8\ud83c\uddee"), new Emoji("\ud83c\udde8\ud83c\uddf0"), new Emoji("\ud83c\udde8\ud83c\uddf1"), new Emoji("\ud83c\udde8\ud83c\uddf2"), new Emoji("\ud83c\udde8\ud83c\uddf3"), new Emoji("\ud83c\udde8\ud83c\uddf4"), new Emoji("\ud83c\udde8\ud83c\uddf5"), new Emoji("\ud83c\udde8\ud83c\uddf7"), new Emoji("\ud83c\udde8\ud83c\uddfa"), new Emoji("\ud83c\udde8\ud83c\uddfb"), new Emoji("\ud83c\udde8\ud83c\uddfc"), new Emoji("\ud83c\udde8\ud83c\uddfd"), new Emoji("\ud83c\udde8\ud83c\uddfe"), new Emoji("\ud83c\udde8\ud83c\uddff"), new Emoji("\ud83c\udde9\ud83c\uddea"), new Emoji("\ud83c\udde9\ud83c\uddec"), new Emoji("\ud83c\udde9\ud83c\uddef"), new Emoji("\ud83c\udde9\ud83c\uddf0"), new Emoji("\ud83c\udde9\ud83c\uddf2"), new Emoji("\ud83c\udde9\ud83c\uddf4"), new Emoji("\ud83c\udde9\ud83c\uddff"), new Emoji("\ud83c\uddea\ud83c\udde6"), new Emoji("\ud83c\uddea\ud83c\udde8"), new Emoji("\ud83c\uddea\ud83c\uddea"), new Emoji("\ud83c\uddea\ud83c\uddec"), new Emoji("\ud83c\uddea\ud83c\udded"), new Emoji("\ud83c\uddea\ud83c\uddf7"), new Emoji("\ud83c\uddea\ud83c\uddf8"), new Emoji("\ud83c\uddea\ud83c\uddf9"), new Emoji("\ud83c\uddea\ud83c\uddfa"), new Emoji("\ud83c\uddeb\ud83c\uddee"), new Emoji("\ud83c\uddeb\ud83c\uddef"), new Emoji("\ud83c\uddeb\ud83c\uddf0"), new Emoji("\ud83c\uddeb\ud83c\uddf2"), new Emoji("\ud83c\uddeb\ud83c\uddf4"), new Emoji("\ud83c\uddeb\ud83c\uddf7"), new Emoji("\ud83c\uddec\ud83c\udde6"), new Emoji("\ud83c\uddec\ud83c\udde7"), new Emoji("\ud83c\uddec\ud83c\udde9"), new Emoji("\ud83c\uddec\ud83c\uddea"), new Emoji("\ud83c\uddec\ud83c\uddeb"), new Emoji("\ud83c\uddec\ud83c\uddec"), new Emoji("\ud83c\uddec\ud83c\udded"), new Emoji("\ud83c\uddec\ud83c\uddee"), new Emoji("\ud83c\uddec\ud83c\uddf1"), new Emoji("\ud83c\uddec\ud83c\uddf2"), new Emoji("\ud83c\uddec\ud83c\uddf3"), new Emoji("\ud83c\uddec\ud83c\uddf5"), new Emoji("\ud83c\uddec\ud83c\uddf6"), new Emoji("\ud83c\uddec\ud83c\uddf7"), new Emoji("\ud83c\uddec\ud83c\uddf8"), new Emoji("\ud83c\uddec\ud83c\uddf9"), new Emoji("\ud83c\uddec\ud83c\uddfa"), new Emoji("\ud83c\uddec\ud83c\uddfc"), new Emoji("\ud83c\uddec\ud83c\uddfe"), new Emoji("\ud83c\udded\ud83c\uddf0"), new Emoji("\ud83c\udded\ud83c\uddf2"), new Emoji("\ud83c\udded\ud83c\uddf3"), new Emoji("\ud83c\udded\ud83c\uddf7"), new Emoji("\ud83c\udded\ud83c\uddf9"), new Emoji("\ud83c\udded\ud83c\uddfa"), new Emoji("\ud83c\uddee\ud83c\udde8"), new Emoji("\ud83c\uddee\ud83c\udde9"), new Emoji("\ud83c\uddee\ud83c\uddea"), new Emoji("\ud83c\uddee\ud83c\uddf1"), new Emoji("\ud83c\uddee\ud83c\uddf2"), new Emoji("\ud83c\uddee\ud83c\uddf3"), new Emoji("\ud83c\uddee\ud83c\uddf4"), new Emoji("\ud83c\uddee\ud83c\uddf6"), new Emoji("\ud83c\uddee\ud83c\uddf7"), new Emoji("\ud8Line truncated
|
||||
}, "emoji/Flags_0.webp");
|
||||
|
||||
private static final EmojiPageModel PAGE_FLAGS_1 = new StaticEmojiPageModel(R.attr.emoji_category_flags, new Emoji[] {
|
||||
new Emoji("\ud83c\uddfb\ud83c\uddf3"), new Emoji("\ud83c\uddfb\ud83c\uddfa"), new Emoji("\ud83c\uddfc\ud83c\uddeb"), new Emoji("\ud83c\uddfc\ud83c\uddf8"), new Emoji("\ud83c\uddfd\ud83c\uddf0"), new Emoji("\ud83c\uddfe\ud83c\uddea"), new Emoji("\ud83c\uddfe\ud83c\uddf9"), new Emoji("\ud83c\uddff\ud83c\udde6"), new Emoji("\ud83c\uddff\ud83c\uddf2"), new Emoji("\ud83c\uddff\ud83c\uddfc"), new Emoji("\ud83c\udff4\udb40\udc67\udb40\udc62\udb40\udc65\udb40\udc6e\udb40\udc67\udb40\udc7f"), new Emoji("\ud83c\udff4\udb40\udc67\udb40\udc62\udb40\udc73\udb40\udc63\udb40\udc74\udb40\udc7f"), new Emoji("\ud83c\udff4\udb40\udc67\udb40\udc62\udb40\udc77\udb40\udc6c\udb40\udc73\udb40\udc7f"),
|
||||
}, "emoji/Flags_1.webp");
|
||||
|
||||
private static final EmojiPageModel PAGE_SYMBOLS = new StaticEmojiPageModel(R.attr.emoji_category_symbols, new Emoji[] {
|
||||
new Emoji("\ud83c\udfe7"), new Emoji("\ud83d\udeae"), new Emoji("\ud83d\udeb0"), new Emoji("\u267f"), new Emoji("\ud83d\udeb9"), new Emoji("\ud83d\udeba"), new Emoji("\ud83d\udebb"), new Emoji("\ud83d\udebc"), new Emoji("\ud83d\udebe"), new Emoji("\ud83d\udec2"), new Emoji("\ud83d\udec3"), new Emoji("\ud83d\udec4"), new Emoji("\ud83d\udec5"), new Emoji("\u26a0\ufe0f"), new Emoji("\ud83d\udeb8"), new Emoji("\u26d4"), new Emoji("\ud83d\udeab"), new Emoji("\ud83d\udeb3"), new Emoji("\ud83d\udead"), new Emoji("\ud83d\udeaf"), new Emoji("\ud83d\udeb1"), new Emoji("\ud83d\udeb7"), new Emoji("\ud83d\udcf5"), new Emoji("\ud83d\udd1e"), new Emoji("\u2622\ufe0f"), new Emoji("\u2623\ufe0f"), new Emoji("\u2b06\ufe0f"), new Emoji("\u2197\ufe0f"), new Emoji("\u27a1\ufe0f"), new Emoji("\u2198\ufe0f"), new Emoji("\u2b07\ufe0f"), new Emoji("\u2199\ufe0f"), new Emoji("\u2b05\ufe0f"), new Emoji("\u2196\ufe0f"), new Emoji("\u2195\ufe0f"), new Emoji("\u2194\ufe0f"), new Emoji("\u21a9\ufe0f"), new Emoji("\u21aa\ufe0f"), new Emoji("\u2934\ufe0f"), new Emoji("\u2935\ufe0f"), new Emoji("\ud83d\udd03"), new Emoji("\ud83d\udd04"), new Emoji("\ud83d\udd19"), new Emoji("\ud83d\udd1a"), new Emoji("\ud83d\udd1b"), new Emoji("\ud83d\udd1c"), new Emoji("\ud83d\udd1d"), new Emoji("\ud83d\uded0"), new Emoji("\u269b\ufe0f"), new Emoji("\ud83d\udd49\ufe0f"), new Emoji("\u2721\ufe0f"), new Emoji("\u2638\ufe0f"), new Emoji("\u262f\ufe0f"), new Emoji("\u271d\ufe0f"), new Emoji("\u2626\ufe0f"), new Emoji("\u262a\ufe0f"), new Emoji("\u262e\ufe0f"), new Emoji("\ud83d\udd4e"), new Emoji("\ud83d\udd2f"), new Emoji("\u2648"), new Emoji("\u2649"), new Emoji("\u264a"), new Emoji("\u264b"), new Emoji("\u264c"), new Emoji("\u264d"), new Emoji("\u264e"), new Emoji("\u264f"), new Emoji("\u2650"), new Emoji("\u2651"), new Emoji("\u2652"), new Emoji("\u2653"), new Emoji("\u26ce"), new Emoji("\ud83d\udd00"), new Emoji("\ud83d\udd01"), new Emoji("\ud83d\udd02"), new Emoji("\u25b6\ufe0f"), new Emoji("\u23e9"), new Emoji("\u23ed\ufe0f"), new Emoji("\u23ef\ufe0f"), new Emoji("\u25c0\ufe0f"), new Emoji("\u23ea"), new Emoji("\u23ee\ufe0f"), new Emoji("\ud83d\udd3c"), new Emoji("\u23eb"), new Emoji("\ud83d\udd3d"), new Emoji("\u23ec"), new Emoji("\u23f8\ufe0f"), new Emoji("\u23f9\ufe0f"), new Emoji("\u23fa\ufe0f"), new Emoji("\u23cf\ufe0f"), new Emoji("\ud83c\udfa6"), new Emoji("\ud83d\udd05"), new Emoji("\ud83d\udd06"), new Emoji("\ud83d\udcf6"), new Emoji("\ud83d\udcf3"), new Emoji("\ud83d\udcf4"), new Emoji("\u26a7\ufe0f"), new Emoji("\u2716\ufe0f"), new Emoji("\u2795"), new Emoji("\u2796"), new Emoji("\u2797"), new Emoji("\u267e\ufe0f"), new Emoji("\u203c\ufe0f"), new Emoji("\u2049\ufe0f"), new Emoji("\u2753"), new Emoji("\u2754"), new Emoji("\u2755"), new Emoji("\u2757"), new Emoji("\u3030\ufe0f"), new Emoji("\ud83d\udcb1"), new Emoji("\ud83d\udcb2"), new Emoji("\u267b\ufe0f"), new Emoji("\u269c\ufe0f"), new Emoji("\ud83d\udd31"), new Emoji("\ud83d\udcdb"), new Emoji("\ud83d\udd30"), new Emoji("\u2b55"), new Emoji("\u2705"), new Emoji("\u2611\ufe0f"), new Emoji("\u2714\ufe0f"), new Emoji("\u274c"), new Emoji("\u274e"), new Emoji("\u27b0"), new Emoji("\u27bf"), new Emoji("\u303d\ufe0f"), new Emoji("\u2733\ufe0f"), new Emoji("\u2734\ufe0f"), new Emoji("\u2747\ufe0f"), new Emoji("\u00a9\ufe0f"), new Emoji("\u00ae\ufe0f"), new Emoji("\u2122\ufe0f"), new Emoji("\u0023\ufe0f\u20e3"), new Emoji("\u002a\ufe0f\u20e3"), new Emoji("\u0030\ufe0f\u20e3"), new Emoji("\u0031\ufe0f\u20e3"), new Emoji("\u0032\ufe0f\u20e3"), new Emoji("\u0033\ufe0f\u20e3"), new Emoji("\u0034\ufe0f\u20e3"), new Emoji("\u0035\ufe0f\u20e3"), new Emoji("\u0036\ufe0f\u20e3"), new Emoji("\u0037\ufe0f\u20e3"), new Emoji("\u0038\ufe0f\u20e3"), new Emoji("\u0039\ufe0f\u20e3"), new Emoji("\ud83d\udd1f"), new Emoji("\ud83d\udd20"), new Emoji("\ud83d\udd21"), new Emoji("\ud83d\udd22"), new Emoji("\ud83d\udd23"), new Emoji("\ud83d\udd24"), new Emoji("\ud83c\udd70\ufe0f"), new Emoji("\ud83c\udd8e"), new Emoji("\ud83c\udd71\ufe0f"), new Emoji("\ud83c\udd91"), new Emoji("\ud83c\udd92"), new Emoji("\ud83c\udd93"), new Emoji("\u2139\ufe0f"), new Emoji("\ud83c\udd94"), new Emoji("\u24c2\ufe0f"), new Emoji("\ud83c\udd95"), new Emoji("\ud83c\udd96"), new Emoji("\ud83c\udd7e\ufe0f"), new Emoji("\ud83c\udd97"), new Emoji("\ud83c\udd7f\ufe0f"), new Emoji("\ud83c\udd98"), new Emoji("\ud83c\udd99"), new Emoji("\ud83c\udd9a"), new Emoji("\ud83c\ude01"), new Emoji("\ud83c\ude02\ufe0f"), new Emoji("\ud83c\ude37\ufe0f"), new Emoji("\ud83c\ude36"), new Emoji("\ud83c\ude2f"), new Emoji("\ud83c\ude50"), new Emoji("\ud83c\ude39"), new Emoji("\ud83c\ude1a"), new Emoji("\ud83c\ude32")Line truncated
|
||||
}, "emoji/Symbols.webp");
|
||||
|
||||
private static final EmojiPageModel PAGE_NATURE = new StaticEmojiPageModel(R.attr.emoji_category_nature, new Emoji[] {
|
||||
new Emoji("\ud83d\udc35"), new Emoji("\ud83d\udc12"), new Emoji("\ud83e\udd8d"), new Emoji("\ud83e\udda7"), new Emoji("\ud83d\udc36"), new Emoji("\ud83d\udc15"), new Emoji("\ud83e\uddae"), new Emoji("\ud83d\udc15\u200d\ud83e\uddba"), new Emoji("\ud83d\udc29"), new Emoji("\ud83d\udc3a"), new Emoji("\ud83e\udd8a"), new Emoji("\ud83e\udd9d"), new Emoji("\ud83d\udc31"), new Emoji("\ud83d\udc08"), new Emoji("\ud83d\udc08\u200d\u2b1b"), new Emoji("\ud83e\udd81"), new Emoji("\ud83d\udc2f"), new Emoji("\ud83d\udc05"), new Emoji("\ud83d\udc06"), new Emoji("\ud83d\udc34"), new Emoji("\ud83d\udc0e"), new Emoji("\ud83e\udd84"), new Emoji("\ud83e\udd93"), new Emoji("\ud83e\udd8c"), new Emoji("\ud83e\uddac"), new Emoji("\ud83d\udc2e"), new Emoji("\ud83d\udc02"), new Emoji("\ud83d\udc03"), new Emoji("\ud83d\udc04"), new Emoji("\ud83d\udc37"), new Emoji("\ud83d\udc16"), new Emoji("\ud83d\udc17"), new Emoji("\ud83d\udc3d"), new Emoji("\ud83d\udc0f"), new Emoji("\ud83d\udc11"), new Emoji("\ud83d\udc10"), new Emoji("\ud83d\udc2a"), new Emoji("\ud83d\udc2b"), new Emoji("\ud83e\udd99"), new Emoji("\ud83e\udd92"), new Emoji("\ud83d\udc18"), new Emoji("\ud83e\udda3"), new Emoji("\ud83e\udd8f"), new Emoji("\ud83e\udd9b"), new Emoji("\ud83d\udc2d"), new Emoji("\ud83d\udc01"), new Emoji("\ud83d\udc00"), new Emoji("\ud83d\udc39"), new Emoji("\ud83d\udc30"), new Emoji("\ud83d\udc07"), new Emoji("\ud83d\udc3f\ufe0f"), new Emoji("\ud83e\uddab"), new Emoji("\ud83e\udd94"), new Emoji("\ud83e\udd87"), new Emoji("\ud83d\udc3b"), new Emoji("\ud83d\udc3b\u200d\u2744\ufe0f"), new Emoji("\ud83d\udc28"), new Emoji("\ud83d\udc3c"), new Emoji("\ud83e\udda5"), new Emoji("\ud83e\udda6"), new Emoji("\ud83e\udda8"), new Emoji("\ud83e\udd98"), new Emoji("\ud83e\udda1"), new Emoji("\ud83d\udc3e"), new Emoji("\ud83e\udd83"), new Emoji("\ud83d\udc14"), new Emoji("\ud83d\udc13"), new Emoji("\ud83d\udc23"), new Emoji("\ud83d\udc24"), new Emoji("\ud83d\udc25"), new Emoji("\ud83d\udc26"), new Emoji("\ud83d\udc27"), new Emoji("\ud83d\udd4a\ufe0f"), new Emoji("\ud83e\udd85"), new Emoji("\ud83e\udd86"), new Emoji("\ud83e\udda2"), new Emoji("\ud83e\udd89"), new Emoji("\ud83e\udda4"), new Emoji("\ud83e\udeb6"), new Emoji("\ud83e\udda9"), new Emoji("\ud83e\udd9a"), new Emoji("\ud83e\udd9c"), new Emoji("\ud83d\udc38"), new Emoji("\ud83d\udc0a"), new Emoji("\ud83d\udc22"), new Emoji("\ud83e\udd8e"), new Emoji("\ud83d\udc0d"), new Emoji("\ud83d\udc32"), new Emoji("\ud83d\udc09"), new Emoji("\ud83e\udd95"), new Emoji("\ud83e\udd96"), new Emoji("\ud83d\udc33"), new Emoji("\ud83d\udc0b"), new Emoji("\ud83d\udc2c"), new Emoji("\ud83e\uddad"), new Emoji("\ud83d\udc1f"), new Emoji("\ud83d\udc20"), new Emoji("\ud83d\udc21"), new Emoji("\ud83e\udd88"), new Emoji("\ud83d\udc19"), new Emoji("\ud83d\udc1a"), new Emoji("\ud83d\udc0c"), new Emoji("\ud83e\udd8b"), new Emoji("\ud83d\udc1b"), new Emoji("\ud83d\udc1c"), new Emoji("\ud83d\udc1d"), new Emoji("\ud83e\udeb2"), new Emoji("\ud83e\udd97"), new Emoji("\ud83e\udeb3"), new Emoji("\ud83d\udd77\ufe0f"), new Emoji("\ud83d\udd78\ufe0f"), new Emoji("\ud83e\udd82"), new Emoji("\ud83e\udd9f"), new Emoji("\ud83e\udeb0"), new Emoji("\ud83e\udeb1"), new Emoji("\ud83e\udda0"), new Emoji("\ud83d\udc90"), new Emoji("\ud83c\udf38"), new Emoji("\ud83d\udcae"), new Emoji("\ud83c\udff5\ufe0f"), new Emoji("\ud83c\udf39"), new Emoji("\ud83e\udd40"), new Emoji("\ud83c\udf3a"), new Emoji("\ud83c\udf3b"), new Emoji("\ud83c\udf3c"), new Emoji("\ud83c\udf37"), new Emoji("\ud83c\udf31"), new Emoji("\ud83e\udeb4"), new Emoji("\ud83c\udf32"), new Emoji("\ud83c\udf33"), new Emoji("\ud83c\udf34"), new Emoji("\ud83c\udf35"), new Emoji("\ud83c\udf3e"), new Emoji("\ud83c\udf3f"), new Emoji("\u2618\ufe0f"), new Emoji("\ud83c\udf40"), new Emoji("\ud83c\udf41"), new Emoji("\ud83c\udf42"), new Emoji("\ud83c\udf43"), new Emoji("\ud83d\udc1e"),
|
||||
}, "emoji/Nature.webp");
|
||||
|
||||
private static final EmojiPageModel PAGE_PEOPLE_0 = new StaticEmojiPageModel(R.attr.emoji_category_people, new Emoji[] {
|
||||
new Emoji("\ud83d\ude00"), new Emoji("\ud83d\ude03"), new Emoji("\ud83d\ude04"), new Emoji("\ud83d\ude01"), new Emoji("\ud83d\ude06"), new Emoji("\ud83d\ude05"), new Emoji("\ud83e\udd23"), new Emoji("\ud83d\ude02"), new Emoji("\ud83d\ude42"), new Emoji("\ud83d\ude43"), new Emoji("\ud83d\ude09"), new Emoji("\ud83d\ude0a"), new Emoji("\ud83d\ude07"), new Emoji("\ud83e\udd70"), new Emoji("\ud83d\ude0d"), new Emoji("\ud83e\udd29"), new Emoji("\ud83d\ude18"), new Emoji("\ud83d\ude17"), new Emoji("\u263a\ufe0f"), new Emoji("\ud83d\ude1a"), new Emoji("\ud83d\ude19"), new Emoji("\ud83e\udd72"), new Emoji("\ud83d\ude0b"), new Emoji("\ud83d\ude1b"), new Emoji("\ud83d\ude1c"), new Emoji("\ud83e\udd2a"), new Emoji("\ud83d\ude1d"), new Emoji("\ud83e\udd11"), new Emoji("\ud83e\udd17"), new Emoji("\ud83e\udd2d"), new Emoji("\ud83e\udd2b"), new Emoji("\ud83e\udd14"), new Emoji("\ud83e\udd10"), new Emoji("\ud83e\udd28"), new Emoji("\ud83d\ude10"), new Emoji("\ud83d\ude11"), new Emoji("\ud83d\ude36"), new Emoji("\ud83d\ude0f"), new Emoji("\ud83d\ude12"), new Emoji("\ud83d\ude44"), new Emoji("\ud83d\ude2c"), new Emoji("\ud83e\udd25"), new Emoji("\ud83d\ude0c"), new Emoji("\ud83d\ude14"), new Emoji("\ud83d\ude2a"), new Emoji("\ud83e\udd24"), new Emoji("\ud83d\ude34"), new Emoji("\ud83d\ude37"), new Emoji("\ud83e\udd12"), new Emoji("\ud83e\udd15"), new Emoji("\ud83e\udd22"), new Emoji("\ud83e\udd2e"), new Emoji("\ud83e\udd27"), new Emoji("\ud83e\udd75"), new Emoji("\ud83e\udd76"), new Emoji("\ud83e\udd74"), new Emoji("\ud83d\ude35"), new Emoji("\ud83e\udd2f"), new Emoji("\ud83e\udd20"), new Emoji("\ud83e\udd73"), new Emoji("\ud83e\udd78"), new Emoji("\ud83d\ude0e"), new Emoji("\ud83e\udd13"), new Emoji("\ud83e\uddd0"), new Emoji("\ud83d\ude15"), new Emoji("\ud83d\ude1f"), new Emoji("\ud83d\ude41"), new Emoji("\u2639\ufe0f"), new Emoji("\ud83d\ude2e"), new Emoji("\ud83d\ude2f"), new Emoji("\ud83d\ude32"), new Emoji("\ud83d\ude33"), new Emoji("\ud83e\udd7a"), new Emoji("\ud83d\ude26"), new Emoji("\ud83d\ude27"), new Emoji("\ud83d\ude28"), new Emoji("\ud83d\ude30"), new Emoji("\ud83d\ude25"), new Emoji("\ud83d\ude22"), new Emoji("\ud83d\ude2d"), new Emoji("\ud83d\ude31"), new Emoji("\ud83d\ude16"), new Emoji("\ud83d\ude23"), new Emoji("\ud83d\ude1e"), new Emoji("\ud83d\ude13"), new Emoji("\ud83d\ude29"), new Emoji("\ud83d\ude2b"), new Emoji("\ud83e\udd71"), new Emoji("\ud83d\ude24"), new Emoji("\ud83d\ude21"), new Emoji("\ud83d\ude20"), new Emoji("\ud83e\udd2c"), new Emoji("\ud83d\ude08"), new Emoji("\ud83d\udc7f"), new Emoji("\ud83d\udc80"), new Emoji("\u2620\ufe0f"), new Emoji("\ud83d\udca9"), new Emoji("\ud83e\udd21"), new Emoji("\ud83d\udc79"), new Emoji("\ud83d\udc7a"), new Emoji("\ud83d\udc7b"), new Emoji("\ud83d\udc7d"), new Emoji("\ud83d\udc7e"), new Emoji("\ud83e\udd16"), new Emoji("\ud83d\ude3a"), new Emoji("\ud83d\ude38"), new Emoji("\ud83d\ude39"), new Emoji("\ud83d\ude3b"), new Emoji("\ud83d\ude3c"), new Emoji("\ud83d\ude3d"), new Emoji("\ud83d\ude40"), new Emoji("\ud83d\ude3f"), new Emoji("\ud83d\ude3e"), new Emoji("\ud83d\ude48"), new Emoji("\ud83d\ude49"), new Emoji("\ud83d\ude4a"), new Emoji("\ud83d\udc8b"), new Emoji("\ud83d\udc8c"), new Emoji("\ud83d\udc98"), new Emoji("\ud83d\udc9d"), new Emoji("\ud83d\udc96"), new Emoji("\ud83d\udc97"), new Emoji("\ud83d\udc93"), new Emoji("\ud83d\udc9e"), new Emoji("\ud83d\udc95"), new Emoji("\ud83d\udc9f"), new Emoji("\u2763\ufe0f"), new Emoji("\ud83d\udc94"), new Emoji("\u2764\ufe0f"), new Emoji("\ud83e\udde1"), new Emoji("\ud83d\udc9b"), new Emoji("\ud83d\udc9a"), new Emoji("\ud83d\udc99"), new Emoji("\ud83d\udc9c"), new Emoji("\ud83e\udd0e"), new Emoji("\ud83d\udda4"), new Emoji("\ud83e\udd0d"), new Emoji("\ud83d\udcaf"), new Emoji("\ud83d\udca2"), new Emoji("\ud83d\udca5"), new Emoji("\ud83d\udcab"), new Emoji("\ud83d\udca6"), new Emoji("\ud83d\udca8"), new Emoji("\ud83d\udd73\ufe0f"), new Emoji("\ud83d\udca3"), new Emoji("\ud83d\udcac"), new Emoji("\ud83d\udc41\ufe0f\u200d\ud83d\udde8\ufe0f"), new Emoji("\ud83d\udde8\ufe0f"), new Emoji("\ud83d\uddef\ufe0f"), new Emoji("\ud83d\udcad"), new Emoji("\ud83d\udca4"), new Emoji("\ud83d\udc4b", "\ud83d\udc4b\ud83c\udffb", "\ud83d\udc4b\ud83c\udffc", "\ud83d\udc4b\ud83c\udffd", "\ud83d\udc4b\ud83c\udffe", "\ud83d\udc4b\ud83c\udfff"), new Emoji("\ud83e\udd1a", "\ud83e\udd1a\ud83c\udffb", "\ud83e\udd1a\ud83c\udffc", "\ud83e\udd1a\ud83c\udffd", "\ud83e\udd1a\ud83c\udffe", "\ud83e\udd1a\ud83c\udfff"), new Emoji("\ud83d\udd90\ufe0f", "\ud83d\udd90\ud83c\udffb", "\ud83d\udd90\ud83c\udffc", "\ud83d\udd90\ud83c\udffd", "\ud83d\udd90\ud83c\udffe", "\ud83d\udd90\ud83c\udfff"), new Emoji("\u270b", "\u270b\ud83c\udffb", "\u270b\ud8Line truncated
|
||||
}, "emoji/People_0.webp");
|
||||
|
||||
private static final EmojiPageModel PAGE_PEOPLE_1 = new StaticEmojiPageModel(R.attr.emoji_category_people, new Emoji[] {
|
||||
new Emoji("\ud83d\udc47", "\ud83d\udc47\ud83c\udffb", "\ud83d\udc47\ud83c\udffc", "\ud83d\udc47\ud83c\udffd", "\ud83d\udc47\ud83c\udffe", "\ud83d\udc47\ud83c\udfff"), new Emoji("\u261d\ufe0f", "\u261d\ud83c\udffb", "\u261d\ud83c\udffc", "\u261d\ud83c\udffd", "\u261d\ud83c\udffe", "\u261d\ud83c\udfff"), new Emoji("\ud83d\udc4d", "\ud83d\udc4d\ud83c\udffb", "\ud83d\udc4d\ud83c\udffc", "\ud83d\udc4d\ud83c\udffd", "\ud83d\udc4d\ud83c\udffe", "\ud83d\udc4d\ud83c\udfff"), new Emoji("\ud83d\udc4e", "\ud83d\udc4e\ud83c\udffb", "\ud83d\udc4e\ud83c\udffc", "\ud83d\udc4e\ud83c\udffd", "\ud83d\udc4e\ud83c\udffe", "\ud83d\udc4e\ud83c\udfff"), new Emoji("\u270a", "\u270a\ud83c\udffb", "\u270a\ud83c\udffc", "\u270a\ud83c\udffd", "\u270a\ud83c\udffe", "\u270a\ud83c\udfff"), new Emoji("\ud83d\udc4a", "\ud83d\udc4a\ud83c\udffb", "\ud83d\udc4a\ud83c\udffc", "\ud83d\udc4a\ud83c\udffd", "\ud83d\udc4a\ud83c\udffe", "\ud83d\udc4a\ud83c\udfff"), new Emoji("\ud83e\udd1b", "\ud83e\udd1b\ud83c\udffb", "\ud83e\udd1b\ud83c\udffc", "\ud83e\udd1b\ud83c\udffd", "\ud83e\udd1b\ud83c\udffe", "\ud83e\udd1b\ud83c\udfff"), new Emoji("\ud83e\udd1c", "\ud83e\udd1c\ud83c\udffb", "\ud83e\udd1c\ud83c\udffc", "\ud83e\udd1c\ud83c\udffd", "\ud83e\udd1c\ud83c\udffe", "\ud83e\udd1c\ud83c\udfff"), new Emoji("\ud83d\udc4f", "\ud83d\udc4f\ud83c\udffb", "\ud83d\udc4f\ud83c\udffc", "\ud83d\udc4f\ud83c\udffd", "\ud83d\udc4f\ud83c\udffe", "\ud83d\udc4f\ud83c\udfff"), new Emoji("\ud83d\ude4c", "\ud83d\ude4c\ud83c\udffb", "\ud83d\ude4c\ud83c\udffc", "\ud83d\ude4c\ud83c\udffd", "\ud83d\ude4c\ud83c\udffe", "\ud83d\ude4c\ud83c\udfff"), new Emoji("\ud83d\udc50", "\ud83d\udc50\ud83c\udffb", "\ud83d\udc50\ud83c\udffc", "\ud83d\udc50\ud83c\udffd", "\ud83d\udc50\ud83c\udffe", "\ud83d\udc50\ud83c\udfff"), new Emoji("\ud83e\udd32", "\ud83e\udd32\ud83c\udffb", "\ud83e\udd32\ud83c\udffc", "\ud83e\udd32\ud83c\udffd", "\ud83e\udd32\ud83c\udffe", "\ud83e\udd32\ud83c\udfff"), new Emoji("\ud83e\udd1d"), new Emoji("\ud83d\ude4f", "\ud83d\ude4f\ud83c\udffb", "\ud83d\ude4f\ud83c\udffc", "\ud83d\ude4f\ud83c\udffd", "\ud83d\ude4f\ud83c\udffe", "\ud83d\ude4f\ud83c\udfff"), new Emoji("\u270d\ufe0f", "\u270d\ud83c\udffb", "\u270d\ud83c\udffc", "\u270d\ud83c\udffd", "\u270d\ud83c\udffe", "\u270d\ud83c\udfff"), new Emoji("\ud83d\udc85", "\ud83d\udc85\ud83c\udffb", "\ud83d\udc85\ud83c\udffc", "\ud83d\udc85\ud83c\udffd", "\ud83d\udc85\ud83c\udffe", "\ud83d\udc85\ud83c\udfff"), new Emoji("\ud83e\udd33", "\ud83e\udd33\ud83c\udffb", "\ud83e\udd33\ud83c\udffc", "\ud83e\udd33\ud83c\udffd", "\ud83e\udd33\ud83c\udffe", "\ud83e\udd33\ud83c\udfff"), new Emoji("\ud83d\udcaa", "\ud83d\udcaa\ud83c\udffb", "\ud83d\udcaa\ud83c\udffc", "\ud83d\udcaa\ud83c\udffd", "\ud83d\udcaa\ud83c\udffe", "\ud83d\udcaa\ud83c\udfff"), new Emoji("\ud83e\uddbe"), new Emoji("\ud83e\uddbf"), new Emoji("\ud83e\uddb5", "\ud83e\uddb5\ud83c\udffb", "\ud83e\uddb5\ud83c\udffc", "\ud83e\uddb5\ud83c\udffd", "\ud83e\uddb5\ud83c\udffe", "\ud83e\uddb5\ud83c\udfff"), new Emoji("\ud83e\uddb6", "\ud83e\uddb6\ud83c\udffb", "\ud83e\uddb6\ud83c\udffc", "\ud83e\uddb6\ud83c\udffd", "\ud83e\uddb6\ud83c\udffe", "\ud83e\uddb6\ud83c\udfff"), new Emoji("\ud83d\udc42", "\ud83d\udc42\ud83c\udffb", "\ud83d\udc42\ud83c\udffc", "\ud83d\udc42\ud83c\udffd", "\ud83d\udc42\ud83c\udffe", "\ud83d\udc42\ud83c\udfff"), new Emoji("\ud83e\uddbb", "\ud83e\uddbb\ud83c\udffb", "\ud83e\uddbb\ud83c\udffc", "\ud83e\uddbb\ud83c\udffd", "\ud83e\uddbb\ud83c\udffe", "\ud83e\uddbb\ud83c\udfff"), new Emoji("\ud83d\udc43", "\ud83d\udc43\ud83c\udffb", "\ud83d\udc43\ud83c\udffc", "\ud83d\udc43\ud83c\udffd", "\ud83d\udc43\ud83c\udffe", "\ud83d\udc43\ud83c\udfff"), new Emoji("\ud83e\udde0"), new Emoji("\ud83e\udec0"), new Emoji("\ud83e\udec1"), new Emoji("\ud83e\uddb7"), new Emoji("\ud83e\uddb4"), new Emoji("\ud83d\udc40"), new Emoji("\ud83d\udc41\ufe0f"), new Emoji("\ud83d\udc45"), new Emoji("\ud83d\udc44"), new Emoji("\ud83d\udc76", "\ud83d\udc76\ud83c\udffb", "\ud83d\udc76\ud83c\udffc", "\ud83d\udc76\ud83c\udffd", "\ud83d\udc76\ud83c\udffe", "\ud83d\udc76\ud83c\udfff"), new Emoji("\ud83e\uddd2", "\ud83e\uddd2\ud83c\udffb", "\ud83e\uddd2\ud83c\udffc", "\ud83e\uddd2\ud83c\udffd", "\ud83e\uddd2\ud83c\udffe", "\ud83e\uddd2\ud83c\udfff"), new Emoji("\ud83d\udc66", "\ud83d\udc66\ud83c\udffb", "\ud83d\udc66\ud83c\udffc", "\ud83d\udc66\ud83c\udffd", "\ud83d\udc66\ud83c\udffe", "\ud83d\udc66\ud83c\udfff"), new Emoji("\ud83d\udc67", "\ud83d\udc67\ud83c\udffb", "\ud83d\udc67\ud83c\udffc", "\ud83d\udc67\ud83c\udffd", "\ud83d\udc67\ud83c\udffe", "\ud83d\udc67\ud83c\udfff"), new Emoji("\ud83e\uddd1", "\ud83e\uddd1\ud83c\udffb", "\ud83e\uddd1\ud83c\udffc", "\ud83e\uddd1\ud83c\udffd", "\ud83e\uddd1\ud83c\udffe", "\ud83e\uddd1\ud83c\udfff"), new Emoji("\ud83d\udc68", "\ud83d\udc68\ud83c\udffb", "\ud83d\udc68\ud83c\udffc", "\ud83d\udc68\ud83c\udffd", "\ud83d\udc68\ud83c\udffe", "\ud83d\udc68\ud83c\udfff"), newLine truncated
|
||||
}, "emoji/People_1.webp");
|
||||
|
||||
private static final EmojiPageModel PAGE_PEOPLE_2 = new StaticEmojiPageModel(R.attr.emoji_category_people, new Emoji[] {
|
||||
new Emoji("\ud83d\udc69\u200d\ud83e\uddb2", "\ud83d\udc69\ud83c\udffb\u200d\ud83e\uddb2", "\ud83d\udc69\ud83c\udffc\u200d\ud83e\uddb2", "\ud83d\udc69\ud83c\udffd\u200d\ud83e\uddb2", "\ud83d\udc69\ud83c\udffe\u200d\ud83e\uddb2", "\ud83d\udc69\ud83c\udfff\u200d\ud83e\uddb2"), new Emoji("\ud83e\uddd1\u200d\ud83e\uddb2", "\ud83e\uddd1\ud83c\udffb\u200d\ud83e\uddb2", "\ud83e\uddd1\ud83c\udffc\u200d\ud83e\uddb2", "\ud83e\uddd1\ud83c\udffd\u200d\ud83e\uddb2", "\ud83e\uddd1\ud83c\udffe\u200d\ud83e\uddb2", "\ud83e\uddd1\ud83c\udfff\u200d\ud83e\uddb2"), new Emoji("\ud83d\udc71\u200d\u2640\ufe0f", "\ud83d\udc71\ud83c\udffb\u200d\u2640\ufe0f", "\ud83d\udc71\ud83c\udffc\u200d\u2640\ufe0f", "\ud83d\udc71\ud83c\udffd\u200d\u2640\ufe0f", "\ud83d\udc71\ud83c\udffe\u200d\u2640\ufe0f", "\ud83d\udc71\ud83c\udfff\u200d\u2640\ufe0f"), new Emoji("\ud83d\udc71\u200d\u2642\ufe0f", "\ud83d\udc71\ud83c\udffb\u200d\u2642\ufe0f", "\ud83d\udc71\ud83c\udffc\u200d\u2642\ufe0f", "\ud83d\udc71\ud83c\udffd\u200d\u2642\ufe0f", "\ud83d\udc71\ud83c\udffe\u200d\u2642\ufe0f", "\ud83d\udc71\ud83c\udfff\u200d\u2642\ufe0f"), new Emoji("\ud83e\uddd3", "\ud83e\uddd3\ud83c\udffb", "\ud83e\uddd3\ud83c\udffc", "\ud83e\uddd3\ud83c\udffd", "\ud83e\uddd3\ud83c\udffe", "\ud83e\uddd3\ud83c\udfff"), new Emoji("\ud83d\udc74", "\ud83d\udc74\ud83c\udffb", "\ud83d\udc74\ud83c\udffc", "\ud83d\udc74\ud83c\udffd", "\ud83d\udc74\ud83c\udffe", "\ud83d\udc74\ud83c\udfff"), new Emoji("\ud83d\udc75", "\ud83d\udc75\ud83c\udffb", "\ud83d\udc75\ud83c\udffc", "\ud83d\udc75\ud83c\udffd", "\ud83d\udc75\ud83c\udffe", "\ud83d\udc75\ud83c\udfff"), new Emoji("\ud83d\ude4d\u200d\u2642\ufe0f", "\ud83d\ude4d\ud83c\udffb\u200d\u2642\ufe0f", "\ud83d\ude4d\ud83c\udffc\u200d\u2642\ufe0f", "\ud83d\ude4d\ud83c\udffd\u200d\u2642\ufe0f", "\ud83d\ude4d\ud83c\udffe\u200d\u2642\ufe0f", "\ud83d\ude4d\ud83c\udfff\u200d\u2642\ufe0f"), new Emoji("\ud83d\ude4d\u200d\u2640\ufe0f", "\ud83d\ude4d\ud83c\udffb\u200d\u2640\ufe0f", "\ud83d\ude4d\ud83c\udffc\u200d\u2640\ufe0f", "\ud83d\ude4d\ud83c\udffd\u200d\u2640\ufe0f", "\ud83d\ude4d\ud83c\udffe\u200d\u2640\ufe0f", "\ud83d\ude4d\ud83c\udfff\u200d\u2640\ufe0f"), new Emoji("\ud83d\ude4e\u200d\u2642\ufe0f", "\ud83d\ude4e\ud83c\udffb\u200d\u2642\ufe0f", "\ud83d\ude4e\ud83c\udffc\u200d\u2642\ufe0f", "\ud83d\ude4e\ud83c\udffd\u200d\u2642\ufe0f", "\ud83d\ude4e\ud83c\udffe\u200d\u2642\ufe0f", "\ud83d\ude4e\ud83c\udfff\u200d\u2642\ufe0f"), new Emoji("\ud83d\ude4e\u200d\u2640\ufe0f", "\ud83d\ude4e\ud83c\udffb\u200d\u2640\ufe0f", "\ud83d\ude4e\ud83c\udffc\u200d\u2640\ufe0f", "\ud83d\ude4e\ud83c\udffd\u200d\u2640\ufe0f", "\ud83d\ude4e\ud83c\udffe\u200d\u2640\ufe0f", "\ud83d\ude4e\ud83c\udfff\u200d\u2640\ufe0f"), new Emoji("\ud83d\ude45\u200d\u2642\ufe0f", "\ud83d\ude45\ud83c\udffb\u200d\u2642\ufe0f", "\ud83d\ude45\ud83c\udffc\u200d\u2642\ufe0f", "\ud83d\ude45\ud83c\udffd\u200d\u2642\ufe0f", "\ud83d\ude45\ud83c\udffe\u200d\u2642\ufe0f", "\ud83d\ude45\ud83c\udfff\u200d\u2642\ufe0f"), new Emoji("\ud83d\ude45\u200d\u2640\ufe0f", "\ud83d\ude45\ud83c\udffb\u200d\u2640\ufe0f", "\ud83d\ude45\ud83c\udffc\u200d\u2640\ufe0f", "\ud83d\ude45\ud83c\udffd\u200d\u2640\ufe0f", "\ud83d\ude45\ud83c\udffe\u200d\u2640\ufe0f", "\ud83d\ude45\ud83c\udfff\u200d\u2640\ufe0f"), new Emoji("\ud83d\ude46\u200d\u2642\ufe0f", "\ud83d\ude46\ud83c\udffb\u200d\u2642\ufe0f", "\ud83d\ude46\ud83c\udffc\u200d\u2642\ufe0f", "\ud83d\ude46\ud83c\udffd\u200d\u2642\ufe0f", "\ud83d\ude46\ud83c\udffe\u200d\u2642\ufe0f", "\ud83d\ude46\ud83c\udfff\u200d\u2642\ufe0f"), new Emoji("\ud83d\ude46\u200d\u2640\ufe0f", "\ud83d\ude46\ud83c\udffb\u200d\u2640\ufe0f", "\ud83d\ude46\ud83c\udffc\u200d\u2640\ufe0f", "\ud83d\ude46\ud83c\udffd\u200d\u2640\ufe0f", "\ud83d\ude46\ud83c\udffe\u200d\u2640\ufe0f", "\ud83d\ude46\ud83c\udfff\u200d\u2640\ufe0f"), new Emoji("\ud83d\udc81\u200d\u2642\ufe0f", "\ud83d\udc81\ud83c\udffb\u200d\u2642\ufe0f", "\ud83d\udc81\ud83c\udffc\u200d\u2642\ufe0f", "\ud83d\udc81\ud83c\udffd\u200d\u2642\ufe0f", "\ud83d\udc81\ud83c\udffe\u200d\u2642\ufe0f", "\ud83d\udc81\ud83c\udfff\u200d\u2642\ufe0f"), new Emoji("\ud83d\udc81\u200d\u2640\ufe0f", "\ud83d\udc81\ud83c\udffb\u200d\u2640\ufe0f", "\ud83d\udc81\ud83c\udffc\u200d\u2640\ufe0f", "\ud83d\udc81\ud83c\udffd\u200d\u2640\ufe0f", "\ud83d\udc81\ud83c\udffe\u200d\u2640\ufe0f", "\ud83d\udc81\ud83c\udfff\u200d\u2640\ufe0f"), new Emoji("\ud83d\ude4b\u200d\u2642\ufe0f", "\ud83d\ude4b\ud83c\udffb\u200d\u2642\ufe0f", "\ud83d\ude4b\ud83c\udffc\u200d\u2642\ufe0f", "\ud83d\ude4b\ud83c\udffd\u200d\u2642\ufe0f", "\ud83d\ude4b\ud83c\udffe\u200d\u2642\ufe0f", "\ud83d\ude4b\ud83c\udfff\u200d\u2642\ufe0f"), new Emoji("\ud83d\ude4b\u200d\u2640\ufe0f", "\ud83d\ude4b\ud83c\udffb\u200d\u2640\ufe0f", "\ud83d\ude4b\ud83c\udffc\u200d\u2640\ufe0f", "\ud83d\ude4b\ud83c\udffd\u200d\u2640\ufe0f", "\ud83d\ude4b\ud83c\udffe\u200d\u2640\ufe0f", "\ud83d\ude4b\ud83c\udfff\u200d\u2640\ufe0f"), new Emoji("\ud83e\uddcf", "\ud83e\uddcf\ud83c\udffb", Line truncated
|
||||
}, "emoji/People_2.webp");
|
||||
|
||||
private static final EmojiPageModel PAGE_PEOPLE_3 = new StaticEmojiPageModel(R.attr.emoji_category_people, new Emoji[] {
|
||||
new Emoji("\ud83e\uddd1\u200d\ud83c\udf3e", "\ud83e\uddd1\ud83c\udffb\u200d\ud83c\udf3e", "\ud83e\uddd1\ud83c\udffc\u200d\ud83c\udf3e", "\ud83e\uddd1\ud83c\udffd\u200d\ud83c\udf3e", "\ud83e\uddd1\ud83c\udffe\u200d\ud83c\udf3e", "\ud83e\uddd1\ud83c\udfff\u200d\ud83c\udf3e"), new Emoji("\ud83d\udc68\u200d\ud83c\udf3e", "\ud83d\udc68\ud83c\udffb\u200d\ud83c\udf3e", "\ud83d\udc68\ud83c\udffc\u200d\ud83c\udf3e", "\ud83d\udc68\ud83c\udffd\u200d\ud83c\udf3e", "\ud83d\udc68\ud83c\udffe\u200d\ud83c\udf3e", "\ud83d\udc68\ud83c\udfff\u200d\ud83c\udf3e"), new Emoji("\ud83d\udc69\u200d\ud83c\udf3e", "\ud83d\udc69\ud83c\udffb\u200d\ud83c\udf3e", "\ud83d\udc69\ud83c\udffc\u200d\ud83c\udf3e", "\ud83d\udc69\ud83c\udffd\u200d\ud83c\udf3e", "\ud83d\udc69\ud83c\udffe\u200d\ud83c\udf3e", "\ud83d\udc69\ud83c\udfff\u200d\ud83c\udf3e"), new Emoji("\ud83e\uddd1\u200d\ud83c\udf73", "\ud83e\uddd1\ud83c\udffb\u200d\ud83c\udf73", "\ud83e\uddd1\ud83c\udffc\u200d\ud83c\udf73", "\ud83e\uddd1\ud83c\udffd\u200d\ud83c\udf73", "\ud83e\uddd1\ud83c\udffe\u200d\ud83c\udf73", "\ud83e\uddd1\ud83c\udfff\u200d\ud83c\udf73"), new Emoji("\ud83d\udc68\u200d\ud83c\udf73", "\ud83d\udc68\ud83c\udffb\u200d\ud83c\udf73", "\ud83d\udc68\ud83c\udffc\u200d\ud83c\udf73", "\ud83d\udc68\ud83c\udffd\u200d\ud83c\udf73", "\ud83d\udc68\ud83c\udffe\u200d\ud83c\udf73", "\ud83d\udc68\ud83c\udfff\u200d\ud83c\udf73"), new Emoji("\ud83d\udc69\u200d\ud83c\udf73", "\ud83d\udc69\ud83c\udffb\u200d\ud83c\udf73", "\ud83d\udc69\ud83c\udffc\u200d\ud83c\udf73", "\ud83d\udc69\ud83c\udffd\u200d\ud83c\udf73", "\ud83d\udc69\ud83c\udffe\u200d\ud83c\udf73", "\ud83d\udc69\ud83c\udfff\u200d\ud83c\udf73"), new Emoji("\ud83e\uddd1\u200d\ud83d\udd27", "\ud83e\uddd1\ud83c\udffb\u200d\ud83d\udd27", "\ud83e\uddd1\ud83c\udffc\u200d\ud83d\udd27", "\ud83e\uddd1\ud83c\udffd\u200d\ud83d\udd27", "\ud83e\uddd1\ud83c\udffe\u200d\ud83d\udd27", "\ud83e\uddd1\ud83c\udfff\u200d\ud83d\udd27"), new Emoji("\ud83d\udc68\u200d\ud83d\udd27", "\ud83d\udc68\ud83c\udffb\u200d\ud83d\udd27", "\ud83d\udc68\ud83c\udffc\u200d\ud83d\udd27", "\ud83d\udc68\ud83c\udffd\u200d\ud83d\udd27", "\ud83d\udc68\ud83c\udffe\u200d\ud83d\udd27", "\ud83d\udc68\ud83c\udfff\u200d\ud83d\udd27"), new Emoji("\ud83d\udc69\u200d\ud83d\udd27", "\ud83d\udc69\ud83c\udffb\u200d\ud83d\udd27", "\ud83d\udc69\ud83c\udffc\u200d\ud83d\udd27", "\ud83d\udc69\ud83c\udffd\u200d\ud83d\udd27", "\ud83d\udc69\ud83c\udffe\u200d\ud83d\udd27", "\ud83d\udc69\ud83c\udfff\u200d\ud83d\udd27"), new Emoji("\ud83e\uddd1\u200d\ud83c\udfed", "\ud83e\uddd1\ud83c\udffb\u200d\ud83c\udfed", "\ud83e\uddd1\ud83c\udffc\u200d\ud83c\udfed", "\ud83e\uddd1\ud83c\udffd\u200d\ud83c\udfed", "\ud83e\uddd1\ud83c\udffe\u200d\ud83c\udfed", "\ud83e\uddd1\ud83c\udfff\u200d\ud83c\udfed"), new Emoji("\ud83d\udc68\u200d\ud83c\udfed", "\ud83d\udc68\ud83c\udffb\u200d\ud83c\udfed", "\ud83d\udc68\ud83c\udffc\u200d\ud83c\udfed", "\ud83d\udc68\ud83c\udffd\u200d\ud83c\udfed", "\ud83d\udc68\ud83c\udffe\u200d\ud83c\udfed", "\ud83d\udc68\ud83c\udfff\u200d\ud83c\udfed"), new Emoji("\ud83d\udc69\u200d\ud83c\udfed", "\ud83d\udc69\ud83c\udffb\u200d\ud83c\udfed", "\ud83d\udc69\ud83c\udffc\u200d\ud83c\udfed", "\ud83d\udc69\ud83c\udffd\u200d\ud83c\udfed", "\ud83d\udc69\ud83c\udffe\u200d\ud83c\udfed", "\ud83d\udc69\ud83c\udfff\u200d\ud83c\udfed"), new Emoji("\ud83e\uddd1\u200d\ud83d\udcbc", "\ud83e\uddd1\ud83c\udffb\u200d\ud83d\udcbc", "\ud83e\uddd1\ud83c\udffc\u200d\ud83d\udcbc", "\ud83e\uddd1\ud83c\udffd\u200d\ud83d\udcbc", "\ud83e\uddd1\ud83c\udffe\u200d\ud83d\udcbc", "\ud83e\uddd1\ud83c\udfff\u200d\ud83d\udcbc"), new Emoji("\ud83d\udc68\u200d\ud83d\udcbc", "\ud83d\udc68\ud83c\udffb\u200d\ud83d\udcbc", "\ud83d\udc68\ud83c\udffc\u200d\ud83d\udcbc", "\ud83d\udc68\ud83c\udffd\u200d\ud83d\udcbc", "\ud83d\udc68\ud83c\udffe\u200d\ud83d\udcbc", "\ud83d\udc68\ud83c\udfff\u200d\ud83d\udcbc"), new Emoji("\ud83d\udc69\u200d\ud83d\udcbc", "\ud83d\udc69\ud83c\udffb\u200d\ud83d\udcbc", "\ud83d\udc69\ud83c\udffc\u200d\ud83d\udcbc", "\ud83d\udc69\ud83c\udffd\u200d\ud83d\udcbc", "\ud83d\udc69\ud83c\udffe\u200d\ud83d\udcbc", "\ud83d\udc69\ud83c\udfff\u200d\ud83d\udcbc"), new Emoji("\ud83e\uddd1\u200d\ud83d\udd2c", "\ud83e\uddd1\ud83c\udffb\u200d\ud83d\udd2c", "\ud83e\uddd1\ud83c\udffc\u200d\ud83d\udd2c", "\ud83e\uddd1\ud83c\udffd\u200d\ud83d\udd2c", "\ud83e\uddd1\ud83c\udffe\u200d\ud83d\udd2c", "\ud83e\uddd1\ud83c\udfff\u200d\ud83d\udd2c"), new Emoji("\ud83d\udc68\u200d\ud83d\udd2c", "\ud83d\udc68\ud83c\udffb\u200d\ud83d\udd2c", "\ud83d\udc68\ud83c\udffc\u200d\ud83d\udd2c", "\ud83d\udc68\ud83c\udffd\u200d\ud83d\udd2c", "\ud83d\udc68\ud83c\udffe\u200d\ud83d\udd2c", "\ud83d\udc68\ud83c\udfff\u200d\ud83d\udd2c"), new Emoji("\ud83d\udc69\u200d\ud83d\udd2c", "\ud83d\udc69\ud83c\udffb\u200d\ud83d\udd2c", "\ud83d\udc69\ud83c\udffc\u200d\ud83d\udd2c", "\ud83d\udc69\ud83c\udffd\u200d\ud83d\udd2c", "\ud83d\udc69\ud83c\udffe\u200d\ud83d\udd2c", "\ud83d\udc69\ud83c\udfff\u200d\ud83d\udd2c"), new EmoLine truncated
|
||||
}, "emoji/People_3.webp");
|
||||
|
||||
private static final EmojiPageModel PAGE_PEOPLE_4 = new StaticEmojiPageModel(R.attr.emoji_category_people, new Emoji[] {
|
||||
new Emoji("\ud83e\udd77", "\ud83e\udd77\ud83c\udffb", "\ud83e\udd77\ud83c\udffc", "\ud83e\udd77\ud83c\udffd", "\ud83e\udd77\ud83c\udffe", "\ud83e\udd77\ud83c\udfff"), new Emoji("\ud83d\udc77\u200d\u2642\ufe0f", "\ud83d\udc77\ud83c\udffb\u200d\u2642\ufe0f", "\ud83d\udc77\ud83c\udffc\u200d\u2642\ufe0f", "\ud83d\udc77\ud83c\udffd\u200d\u2642\ufe0f", "\ud83d\udc77\ud83c\udffe\u200d\u2642\ufe0f", "\ud83d\udc77\ud83c\udfff\u200d\u2642\ufe0f"), new Emoji("\ud83d\udc77\u200d\u2640\ufe0f", "\ud83d\udc77\ud83c\udffb\u200d\u2640\ufe0f", "\ud83d\udc77\ud83c\udffc\u200d\u2640\ufe0f", "\ud83d\udc77\ud83c\udffd\u200d\u2640\ufe0f", "\ud83d\udc77\ud83c\udffe\u200d\u2640\ufe0f", "\ud83d\udc77\ud83c\udfff\u200d\u2640\ufe0f"), new Emoji("\ud83e\udd34", "\ud83e\udd34\ud83c\udffb", "\ud83e\udd34\ud83c\udffc", "\ud83e\udd34\ud83c\udffd", "\ud83e\udd34\ud83c\udffe", "\ud83e\udd34\ud83c\udfff"), new Emoji("\ud83d\udc78", "\ud83d\udc78\ud83c\udffb", "\ud83d\udc78\ud83c\udffc", "\ud83d\udc78\ud83c\udffd", "\ud83d\udc78\ud83c\udffe", "\ud83d\udc78\ud83c\udfff"), new Emoji("\ud83d\udc73\u200d\u2642\ufe0f", "\ud83d\udc73\ud83c\udffb\u200d\u2642\ufe0f", "\ud83d\udc73\ud83c\udffc\u200d\u2642\ufe0f", "\ud83d\udc73\ud83c\udffd\u200d\u2642\ufe0f", "\ud83d\udc73\ud83c\udffe\u200d\u2642\ufe0f", "\ud83d\udc73\ud83c\udfff\u200d\u2642\ufe0f"), new Emoji("\ud83d\udc73\u200d\u2640\ufe0f", "\ud83d\udc73\ud83c\udffb\u200d\u2640\ufe0f", "\ud83d\udc73\ud83c\udffc\u200d\u2640\ufe0f", "\ud83d\udc73\ud83c\udffd\u200d\u2640\ufe0f", "\ud83d\udc73\ud83c\udffe\u200d\u2640\ufe0f", "\ud83d\udc73\ud83c\udfff\u200d\u2640\ufe0f"), new Emoji("\ud83d\udc72", "\ud83d\udc72\ud83c\udffb", "\ud83d\udc72\ud83c\udffc", "\ud83d\udc72\ud83c\udffd", "\ud83d\udc72\ud83c\udffe", "\ud83d\udc72\ud83c\udfff"), new Emoji("\ud83e\uddd5", "\ud83e\uddd5\ud83c\udffb", "\ud83e\uddd5\ud83c\udffc", "\ud83e\uddd5\ud83c\udffd", "\ud83e\uddd5\ud83c\udffe", "\ud83e\uddd5\ud83c\udfff"), new Emoji("\ud83e\udd35\u200d\u2642\ufe0f", "\ud83e\udd35\ud83c\udffb\u200d\u2642\ufe0f", "\ud83e\udd35\ud83c\udffc\u200d\u2642\ufe0f", "\ud83e\udd35\ud83c\udffd\u200d\u2642\ufe0f", "\ud83e\udd35\ud83c\udffe\u200d\u2642\ufe0f", "\ud83e\udd35\ud83c\udfff\u200d\u2642\ufe0f"), new Emoji("\ud83e\udd35\u200d\u2640\ufe0f", "\ud83e\udd35\ud83c\udffb\u200d\u2640\ufe0f", "\ud83e\udd35\ud83c\udffc\u200d\u2640\ufe0f", "\ud83e\udd35\ud83c\udffd\u200d\u2640\ufe0f", "\ud83e\udd35\ud83c\udffe\u200d\u2640\ufe0f", "\ud83e\udd35\ud83c\udfff\u200d\u2640\ufe0f"), new Emoji("\ud83d\udc70", "\ud83d\udc70\ud83c\udffb", "\ud83d\udc70\ud83c\udffc", "\ud83d\udc70\ud83c\udffd", "\ud83d\udc70\ud83c\udffe", "\ud83d\udc70\ud83c\udfff"), new Emoji("\ud83d\udc70\u200d\u2642\ufe0f", "\ud83d\udc70\ud83c\udffb\u200d\u2642\ufe0f", "\ud83d\udc70\ud83c\udffc\u200d\u2642\ufe0f", "\ud83d\udc70\ud83c\udffd\u200d\u2642\ufe0f", "\ud83d\udc70\ud83c\udffe\u200d\u2642\ufe0f", "\ud83d\udc70\ud83c\udfff\u200d\u2642\ufe0f"), new Emoji("\ud83d\udc70\u200d\u2640\ufe0f", "\ud83d\udc70\ud83c\udffb\u200d\u2640\ufe0f", "\ud83d\udc70\ud83c\udffc\u200d\u2640\ufe0f", "\ud83d\udc70\ud83c\udffd\u200d\u2640\ufe0f", "\ud83d\udc70\ud83c\udffe\u200d\u2640\ufe0f", "\ud83d\udc70\ud83c\udfff\u200d\u2640\ufe0f"), new Emoji("\ud83e\udd30", "\ud83e\udd30\ud83c\udffb", "\ud83e\udd30\ud83c\udffc", "\ud83e\udd30\ud83c\udffd", "\ud83e\udd30\ud83c\udffe", "\ud83e\udd30\ud83c\udfff"), new Emoji("\ud83e\udd31", "\ud83e\udd31\ud83c\udffb", "\ud83e\udd31\ud83c\udffc", "\ud83e\udd31\ud83c\udffd", "\ud83e\udd31\ud83c\udffe", "\ud83e\udd31\ud83c\udfff"), new Emoji("\ud83d\udc69\u200d\ud83c\udf7c", "\ud83d\udc69\ud83c\udffb\u200d\ud83c\udf7c", "\ud83d\udc69\ud83c\udffc\u200d\ud83c\udf7c", "\ud83d\udc69\ud83c\udffd\u200d\ud83c\udf7c", "\ud83d\udc69\ud83c\udffe\u200d\ud83c\udf7c", "\ud83d\udc69\ud83c\udfff\u200d\ud83c\udf7c"), new Emoji("\ud83d\udc68\u200d\ud83c\udf7c", "\ud83d\udc68\ud83c\udffb\u200d\ud83c\udf7c", "\ud83d\udc68\ud83c\udffc\u200d\ud83c\udf7c", "\ud83d\udc68\ud83c\udffd\u200d\ud83c\udf7c", "\ud83d\udc68\ud83c\udffe\u200d\ud83c\udf7c", "\ud83d\udc68\ud83c\udfff\u200d\ud83c\udf7c"), new Emoji("\ud83e\uddd1\u200d\ud83c\udf7c", "\ud83e\uddd1\ud83c\udffb\u200d\ud83c\udf7c", "\ud83e\uddd1\ud83c\udffc\u200d\ud83c\udf7c", "\ud83e\uddd1\ud83c\udffd\u200d\ud83c\udf7c", "\ud83e\uddd1\ud83c\udffe\u200d\ud83c\udf7c", "\ud83e\uddd1\ud83c\udfff\u200d\ud83c\udf7c"), new Emoji("\ud83d\udc7c", "\ud83d\udc7c\ud83c\udffb", "\ud83d\udc7c\ud83c\udffc", "\ud83d\udc7c\ud83c\udffd", "\ud83d\udc7c\ud83c\udffe", "\ud83d\udc7c\ud83c\udfff"), new Emoji("\ud83c\udf85", "\ud83c\udf85\ud83c\udffb", "\ud83c\udf85\ud83c\udffc", "\ud83c\udf85\ud83c\udffd", "\ud83c\udf85\ud83c\udffe", "\ud83c\udf85\ud83c\udfff"), new Emoji("\ud83e\udd36", "\ud83e\udd36\ud83c\udffb", "\ud83e\udd36\ud83c\udffc", "\ud83e\udd36\ud83c\udffd", "\ud83e\udd36\ud83c\udffe", "\ud83e\udd36\ud83c\udfff"), new Emoji("\ud83e\uddd1\u200d\ud83c\udf84", "\ud83e\uddd1\ud83c\udffb\u200d\ud83c\udf84Line truncated
|
||||
}, "emoji/People_4.webp");
|
||||
|
||||
private static final EmojiPageModel PAGE_PEOPLE_5 = new StaticEmojiPageModel(R.attr.emoji_category_people, new Emoji[] {
|
||||
new Emoji("\ud83d\udc87\u200d\u2640\ufe0f", "\ud83d\udc87\ud83c\udffb\u200d\u2640\ufe0f", "\ud83d\udc87\ud83c\udffc\u200d\u2640\ufe0f", "\ud83d\udc87\ud83c\udffd\u200d\u2640\ufe0f", "\ud83d\udc87\ud83c\udffe\u200d\u2640\ufe0f", "\ud83d\udc87\ud83c\udfff\u200d\u2640\ufe0f"), new Emoji("\ud83d\udeb6\u200d\u2642\ufe0f", "\ud83d\udeb6\ud83c\udffb\u200d\u2642\ufe0f", "\ud83d\udeb6\ud83c\udffc\u200d\u2642\ufe0f", "\ud83d\udeb6\ud83c\udffd\u200d\u2642\ufe0f", "\ud83d\udeb6\ud83c\udffe\u200d\u2642\ufe0f", "\ud83d\udeb6\ud83c\udfff\u200d\u2642\ufe0f"), new Emoji("\ud83d\udeb6\u200d\u2640\ufe0f", "\ud83d\udeb6\ud83c\udffb\u200d\u2640\ufe0f", "\ud83d\udeb6\ud83c\udffc\u200d\u2640\ufe0f", "\ud83d\udeb6\ud83c\udffd\u200d\u2640\ufe0f", "\ud83d\udeb6\ud83c\udffe\u200d\u2640\ufe0f", "\ud83d\udeb6\ud83c\udfff\u200d\u2640\ufe0f"), new Emoji("\ud83e\uddcd", "\ud83e\uddcd\ud83c\udffb", "\ud83e\uddcd\ud83c\udffc", "\ud83e\uddcd\ud83c\udffd", "\ud83e\uddcd\ud83c\udffe", "\ud83e\uddcd\ud83c\udfff"), new Emoji("\ud83e\uddcd\u200d\u2642\ufe0f", "\ud83e\uddcd\ud83c\udffb\u200d\u2642\ufe0f", "\ud83e\uddcd\ud83c\udffc\u200d\u2642\ufe0f", "\ud83e\uddcd\ud83c\udffd\u200d\u2642\ufe0f", "\ud83e\uddcd\ud83c\udffe\u200d\u2642\ufe0f", "\ud83e\uddcd\ud83c\udfff\u200d\u2642\ufe0f"), new Emoji("\ud83e\uddcd\u200d\u2640\ufe0f", "\ud83e\uddcd\ud83c\udffb\u200d\u2640\ufe0f", "\ud83e\uddcd\ud83c\udffc\u200d\u2640\ufe0f", "\ud83e\uddcd\ud83c\udffd\u200d\u2640\ufe0f", "\ud83e\uddcd\ud83c\udffe\u200d\u2640\ufe0f", "\ud83e\uddcd\ud83c\udfff\u200d\u2640\ufe0f"), new Emoji("\ud83e\uddce", "\ud83e\uddce\ud83c\udffb", "\ud83e\uddce\ud83c\udffc", "\ud83e\uddce\ud83c\udffd", "\ud83e\uddce\ud83c\udffe", "\ud83e\uddce\ud83c\udfff"), new Emoji("\ud83e\uddce\u200d\u2642\ufe0f", "\ud83e\uddce\ud83c\udffb\u200d\u2642\ufe0f", "\ud83e\uddce\ud83c\udffc\u200d\u2642\ufe0f", "\ud83e\uddce\ud83c\udffd\u200d\u2642\ufe0f", "\ud83e\uddce\ud83c\udffe\u200d\u2642\ufe0f", "\ud83e\uddce\ud83c\udfff\u200d\u2642\ufe0f"), new Emoji("\ud83e\uddce\u200d\u2640\ufe0f", "\ud83e\uddce\ud83c\udffb\u200d\u2640\ufe0f", "\ud83e\uddce\ud83c\udffc\u200d\u2640\ufe0f", "\ud83e\uddce\ud83c\udffd\u200d\u2640\ufe0f", "\ud83e\uddce\ud83c\udffe\u200d\u2640\ufe0f", "\ud83e\uddce\ud83c\udfff\u200d\u2640\ufe0f"), new Emoji("\ud83e\uddd1\u200d\ud83e\uddaf", "\ud83e\uddd1\ud83c\udffb\u200d\ud83e\uddaf", "\ud83e\uddd1\ud83c\udffc\u200d\ud83e\uddaf", "\ud83e\uddd1\ud83c\udffd\u200d\ud83e\uddaf", "\ud83e\uddd1\ud83c\udffe\u200d\ud83e\uddaf", "\ud83e\uddd1\ud83c\udfff\u200d\ud83e\uddaf"), new Emoji("\ud83d\udc68\u200d\ud83e\uddaf", "\ud83d\udc68\ud83c\udffb\u200d\ud83e\uddaf", "\ud83d\udc68\ud83c\udffc\u200d\ud83e\uddaf", "\ud83d\udc68\ud83c\udffd\u200d\ud83e\uddaf", "\ud83d\udc68\ud83c\udffe\u200d\ud83e\uddaf", "\ud83d\udc68\ud83c\udfff\u200d\ud83e\uddaf"), new Emoji("\ud83d\udc69\u200d\ud83e\uddaf", "\ud83d\udc69\ud83c\udffb\u200d\ud83e\uddaf", "\ud83d\udc69\ud83c\udffc\u200d\ud83e\uddaf", "\ud83d\udc69\ud83c\udffd\u200d\ud83e\uddaf", "\ud83d\udc69\ud83c\udffe\u200d\ud83e\uddaf", "\ud83d\udc69\ud83c\udfff\u200d\ud83e\uddaf"), new Emoji("\ud83e\uddd1\u200d\ud83e\uddbc", "\ud83e\uddd1\ud83c\udffb\u200d\ud83e\uddbc", "\ud83e\uddd1\ud83c\udffc\u200d\ud83e\uddbc", "\ud83e\uddd1\ud83c\udffd\u200d\ud83e\uddbc", "\ud83e\uddd1\ud83c\udffe\u200d\ud83e\uddbc", "\ud83e\uddd1\ud83c\udfff\u200d\ud83e\uddbc"), new Emoji("\ud83d\udc68\u200d\ud83e\uddbc", "\ud83d\udc68\ud83c\udffb\u200d\ud83e\uddbc", "\ud83d\udc68\ud83c\udffc\u200d\ud83e\uddbc", "\ud83d\udc68\ud83c\udffd\u200d\ud83e\uddbc", "\ud83d\udc68\ud83c\udffe\u200d\ud83e\uddbc", "\ud83d\udc68\ud83c\udfff\u200d\ud83e\uddbc"), new Emoji("\ud83d\udc69\u200d\ud83e\uddbc", "\ud83d\udc69\ud83c\udffb\u200d\ud83e\uddbc", "\ud83d\udc69\ud83c\udffc\u200d\ud83e\uddbc", "\ud83d\udc69\ud83c\udffd\u200d\ud83e\uddbc", "\ud83d\udc69\ud83c\udffe\u200d\ud83e\uddbc", "\ud83d\udc69\ud83c\udfff\u200d\ud83e\uddbc"), new Emoji("\ud83e\uddd1\u200d\ud83e\uddbd", "\ud83e\uddd1\ud83c\udffb\u200d\ud83e\uddbd", "\ud83e\uddd1\ud83c\udffc\u200d\ud83e\uddbd", "\ud83e\uddd1\ud83c\udffd\u200d\ud83e\uddbd", "\ud83e\uddd1\ud83c\udffe\u200d\ud83e\uddbd", "\ud83e\uddd1\ud83c\udfff\u200d\ud83e\uddbd"), new Emoji("\ud83d\udc68\u200d\ud83e\uddbd", "\ud83d\udc68\ud83c\udffb\u200d\ud83e\uddbd", "\ud83d\udc68\ud83c\udffc\u200d\ud83e\uddbd", "\ud83d\udc68\ud83c\udffd\u200d\ud83e\uddbd", "\ud83d\udc68\ud83c\udffe\u200d\ud83e\uddbd", "\ud83d\udc68\ud83c\udfff\u200d\ud83e\uddbd"), new Emoji("\ud83d\udc69\u200d\ud83e\uddbd", "\ud83d\udc69\ud83c\udffb\u200d\ud83e\uddbd", "\ud83d\udc69\ud83c\udffc\u200d\ud83e\uddbd", "\ud83d\udc69\ud83c\udffd\u200d\ud83e\uddbd", "\ud83d\udc69\ud83c\udffe\u200d\ud83e\uddbd", "\ud83d\udc69\ud83c\udfff\u200d\ud83e\uddbd"), new Emoji("\ud83c\udfc3\u200d\u2642\ufe0f", "\ud83c\udfc3\ud83c\udffb\u200d\u2642\ufe0f", "\ud83c\udfc3\ud83c\udffc\u200d\u2642\ufe0f", "\ud83c\udfc3\ud83c\udffd\u200d\u2642\ufe0f", "\ud83c\udfc3\ud83c\udffe\u200d\u2642\ufeLine truncated
|
||||
}, "emoji/People_5.webp");
|
||||
|
||||
private static final EmojiPageModel PAGE_PEOPLE_6 = new StaticEmojiPageModel(R.attr.emoji_category_people, new Emoji[] {
|
||||
new Emoji("\ud83d\udeb4\u200d\u2640\ufe0f", "\ud83d\udeb4\ud83c\udffb\u200d\u2640\ufe0f", "\ud83d\udeb4\ud83c\udffc\u200d\u2640\ufe0f", "\ud83d\udeb4\ud83c\udffd\u200d\u2640\ufe0f", "\ud83d\udeb4\ud83c\udffe\u200d\u2640\ufe0f", "\ud83d\udeb4\ud83c\udfff\u200d\u2640\ufe0f"), new Emoji("\ud83d\udeb5\u200d\u2642\ufe0f", "\ud83d\udeb5\ud83c\udffb\u200d\u2642\ufe0f", "\ud83d\udeb5\ud83c\udffc\u200d\u2642\ufe0f", "\ud83d\udeb5\ud83c\udffd\u200d\u2642\ufe0f", "\ud83d\udeb5\ud83c\udffe\u200d\u2642\ufe0f", "\ud83d\udeb5\ud83c\udfff\u200d\u2642\ufe0f"), new Emoji("\ud83d\udeb5\u200d\u2640\ufe0f", "\ud83d\udeb5\ud83c\udffb\u200d\u2640\ufe0f", "\ud83d\udeb5\ud83c\udffc\u200d\u2640\ufe0f", "\ud83d\udeb5\ud83c\udffd\u200d\u2640\ufe0f", "\ud83d\udeb5\ud83c\udffe\u200d\u2640\ufe0f", "\ud83d\udeb5\ud83c\udfff\u200d\u2640\ufe0f"), new Emoji("\ud83e\udd38", "\ud83e\udd38\ud83c\udffb", "\ud83e\udd38\ud83c\udffc", "\ud83e\udd38\ud83c\udffd", "\ud83e\udd38\ud83c\udffe", "\ud83e\udd38\ud83c\udfff"), new Emoji("\ud83e\udd38\u200d\u2642\ufe0f", "\ud83e\udd38\ud83c\udffb\u200d\u2642\ufe0f", "\ud83e\udd38\ud83c\udffc\u200d\u2642\ufe0f", "\ud83e\udd38\ud83c\udffd\u200d\u2642\ufe0f", "\ud83e\udd38\ud83c\udffe\u200d\u2642\ufe0f", "\ud83e\udd38\ud83c\udfff\u200d\u2642\ufe0f"), new Emoji("\ud83e\udd38\u200d\u2640\ufe0f", "\ud83e\udd38\ud83c\udffb\u200d\u2640\ufe0f", "\ud83e\udd38\ud83c\udffc\u200d\u2640\ufe0f", "\ud83e\udd38\ud83c\udffd\u200d\u2640\ufe0f", "\ud83e\udd38\ud83c\udffe\u200d\u2640\ufe0f", "\ud83e\udd38\ud83c\udfff\u200d\u2640\ufe0f"), new Emoji("\ud83e\udd3c"), new Emoji("\ud83e\udd3c\u200d\u2642\ufe0f"), new Emoji("\ud83e\udd3c\u200d\u2640\ufe0f"), new Emoji("\ud83e\udd3d", "\ud83e\udd3d\ud83c\udffb", "\ud83e\udd3d\ud83c\udffc", "\ud83e\udd3d\ud83c\udffd", "\ud83e\udd3d\ud83c\udffe", "\ud83e\udd3d\ud83c\udfff"), new Emoji("\ud83e\udd3d\u200d\u2642\ufe0f", "\ud83e\udd3d\ud83c\udffb\u200d\u2642\ufe0f", "\ud83e\udd3d\ud83c\udffc\u200d\u2642\ufe0f", "\ud83e\udd3d\ud83c\udffd\u200d\u2642\ufe0f", "\ud83e\udd3d\ud83c\udffe\u200d\u2642\ufe0f", "\ud83e\udd3d\ud83c\udfff\u200d\u2642\ufe0f"), new Emoji("\ud83e\udd3d\u200d\u2640\ufe0f", "\ud83e\udd3d\ud83c\udffb\u200d\u2640\ufe0f", "\ud83e\udd3d\ud83c\udffc\u200d\u2640\ufe0f", "\ud83e\udd3d\ud83c\udffd\u200d\u2640\ufe0f", "\ud83e\udd3d\ud83c\udffe\u200d\u2640\ufe0f", "\ud83e\udd3d\ud83c\udfff\u200d\u2640\ufe0f"), new Emoji("\ud83e\udd3e", "\ud83e\udd3e\ud83c\udffb", "\ud83e\udd3e\ud83c\udffc", "\ud83e\udd3e\ud83c\udffd", "\ud83e\udd3e\ud83c\udffe", "\ud83e\udd3e\ud83c\udfff"), new Emoji("\ud83e\udd3e\u200d\u2642\ufe0f", "\ud83e\udd3e\ud83c\udffb\u200d\u2642\ufe0f", "\ud83e\udd3e\ud83c\udffc\u200d\u2642\ufe0f", "\ud83e\udd3e\ud83c\udffd\u200d\u2642\ufe0f", "\ud83e\udd3e\ud83c\udffe\u200d\u2642\ufe0f", "\ud83e\udd3e\ud83c\udfff\u200d\u2642\ufe0f"), new Emoji("\ud83e\udd3e\u200d\u2640\ufe0f", "\ud83e\udd3e\ud83c\udffb\u200d\u2640\ufe0f", "\ud83e\udd3e\ud83c\udffc\u200d\u2640\ufe0f", "\ud83e\udd3e\ud83c\udffd\u200d\u2640\ufe0f", "\ud83e\udd3e\ud83c\udffe\u200d\u2640\ufe0f", "\ud83e\udd3e\ud83c\udfff\u200d\u2640\ufe0f"), new Emoji("\ud83e\udd39", "\ud83e\udd39\ud83c\udffb", "\ud83e\udd39\ud83c\udffc", "\ud83e\udd39\ud83c\udffd", "\ud83e\udd39\ud83c\udffe", "\ud83e\udd39\ud83c\udfff"), new Emoji("\ud83e\udd39\u200d\u2642\ufe0f", "\ud83e\udd39\ud83c\udffb\u200d\u2642\ufe0f", "\ud83e\udd39\ud83c\udffc\u200d\u2642\ufe0f", "\ud83e\udd39\ud83c\udffd\u200d\u2642\ufe0f", "\ud83e\udd39\ud83c\udffe\u200d\u2642\ufe0f", "\ud83e\udd39\ud83c\udfff\u200d\u2642\ufe0f"), new Emoji("\ud83e\udd39\u200d\u2640\ufe0f", "\ud83e\udd39\ud83c\udffb\u200d\u2640\ufe0f", "\ud83e\udd39\ud83c\udffc\u200d\u2640\ufe0f", "\ud83e\udd39\ud83c\udffd\u200d\u2640\ufe0f", "\ud83e\udd39\ud83c\udffe\u200d\u2640\ufe0f", "\ud83e\udd39\ud83c\udfff\u200d\u2640\ufe0f"), new Emoji("\ud83e\uddd8\u200d\u2642\ufe0f", "\ud83e\uddd8\ud83c\udffb\u200d\u2642\ufe0f", "\ud83e\uddd8\ud83c\udffc\u200d\u2642\ufe0f", "\ud83e\uddd8\ud83c\udffd\u200d\u2642\ufe0f", "\ud83e\uddd8\ud83c\udffe\u200d\u2642\ufe0f", "\ud83e\uddd8\ud83c\udfff\u200d\u2642\ufe0f"), new Emoji("\ud83e\uddd8\u200d\u2640\ufe0f", "\ud83e\uddd8\ud83c\udffb\u200d\u2640\ufe0f", "\ud83e\uddd8\ud83c\udffc\u200d\u2640\ufe0f", "\ud83e\uddd8\ud83c\udffd\u200d\u2640\ufe0f", "\ud83e\uddd8\ud83c\udffe\u200d\u2640\ufe0f", "\ud83e\uddd8\ud83c\udfff\u200d\u2640\ufe0f"), new Emoji("\ud83d\udec0", "\ud83d\udec0\ud83c\udffb", "\ud83d\udec0\ud83c\udffc", "\ud83d\udec0\ud83c\udffd", "\ud83d\udec0\ud83c\udffe", "\ud83d\udec0\ud83c\udfff"), new Emoji("\ud83d\udecc", "\ud83d\udecc\ud83c\udffb", "\ud83d\udecc\ud83c\udffc", "\ud83d\udecc\ud83c\udffd", "\ud83d\udecc\ud83c\udffe", "\ud83d\udecc\ud83c\udfff"), new Emoji("\ud83e\uddd1\u200d\ud83e\udd1d\u200d\ud83e\uddd1", "\ud83e\uddd1\ud83c\udffb\u200d\ud83e\udd1d\u200d\ud83e\uddd1\ud83c\udffb", "\ud83e\uddd1\ud83c\udffb\u200d\ud83e\udd1d\u200d\ud83e\uddd1\ud83c\udffc", "\ud83e\uddd1\ud83c\udffb\u200d\ud83e\uddLine truncated
|
||||
}, "emoji/People_6.webp");
|
||||
|
||||
private static final EmojiPageModel PAGE_PEOPLE_7 = new StaticEmojiPageModel(R.attr.emoji_category_people, new Emoji[] {
|
||||
new Emoji("\ud83d\udc63"), new Emoji("\ud83e\udd35", "\ud83e\udd35\ud83c\udffb", "\ud83e\udd35\ud83c\udffc", "\ud83e\udd35\ud83c\udffd", "\ud83e\udd35\ud83c\udffe", "\ud83e\udd35\ud83c\udfff"),
|
||||
}, "emoji/People_7.webp");
|
||||
|
||||
private static final EmojiPageModel PAGE_OBJECTS = new StaticEmojiPageModel(R.attr.emoji_category_objects, new Emoji[] {
|
||||
new Emoji("\ud83d\udc53"), new Emoji("\ud83d\udd76\ufe0f"), new Emoji("\ud83e\udd7d"), new Emoji("\ud83e\udd7c"), new Emoji("\ud83e\uddba"), new Emoji("\ud83d\udc54"), new Emoji("\ud83d\udc55"), new Emoji("\ud83d\udc56"), new Emoji("\ud83e\udde3"), new Emoji("\ud83e\udde4"), new Emoji("\ud83e\udde5"), new Emoji("\ud83e\udde6"), new Emoji("\ud83d\udc57"), new Emoji("\ud83d\udc58"), new Emoji("\ud83e\udd7b"), new Emoji("\ud83e\ude71"), new Emoji("\ud83e\ude72"), new Emoji("\ud83e\ude73"), new Emoji("\ud83d\udc59"), new Emoji("\ud83d\udc5a"), new Emoji("\ud83d\udc5b"), new Emoji("\ud83d\udc5c"), new Emoji("\ud83d\udc5d"), new Emoji("\ud83d\udecd\ufe0f"), new Emoji("\ud83c\udf92"), new Emoji("\ud83e\ude74"), new Emoji("\ud83d\udc5e"), new Emoji("\ud83d\udc5f"), new Emoji("\ud83e\udd7e"), new Emoji("\ud83e\udd7f"), new Emoji("\ud83d\udc60"), new Emoji("\ud83d\udc61"), new Emoji("\ud83e\ude70"), new Emoji("\ud83d\udc62"), new Emoji("\ud83d\udc51"), new Emoji("\ud83d\udc52"), new Emoji("\ud83c\udfa9"), new Emoji("\ud83c\udf93"), new Emoji("\ud83e\udde2"), new Emoji("\ud83e\ude96"), new Emoji("\u26d1\ufe0f"), new Emoji("\ud83d\udcff"), new Emoji("\ud83d\udc84"), new Emoji("\ud83d\udc8d"), new Emoji("\ud83d\udc8e"), new Emoji("\ud83d\udd07"), new Emoji("\ud83d\udd08"), new Emoji("\ud83d\udd09"), new Emoji("\ud83d\udd0a"), new Emoji("\ud83d\udce2"), new Emoji("\ud83d\udce3"), new Emoji("\ud83d\udcef"), new Emoji("\ud83d\udd14"), new Emoji("\ud83d\udd15"), new Emoji("\ud83c\udfbc"), new Emoji("\ud83c\udfb5"), new Emoji("\ud83c\udfb6"), new Emoji("\ud83c\udf99\ufe0f"), new Emoji("\ud83c\udf9a\ufe0f"), new Emoji("\ud83c\udf9b\ufe0f"), new Emoji("\ud83c\udfa4"), new Emoji("\ud83c\udfa7"), new Emoji("\ud83d\udcfb"), new Emoji("\ud83c\udfb7"), new Emoji("\ud83e\ude97"), new Emoji("\ud83c\udfb8"), new Emoji("\ud83c\udfb9"), new Emoji("\ud83c\udfba"), new Emoji("\ud83c\udfbb"), new Emoji("\ud83e\ude95"), new Emoji("\ud83e\udd41"), new Emoji("\ud83e\ude98"), new Emoji("\ud83d\udcf1"), new Emoji("\ud83d\udcf2"), new Emoji("\u260e\ufe0f"), new Emoji("\ud83d\udcde"), new Emoji("\ud83d\udcdf"), new Emoji("\ud83d\udce0"), new Emoji("\ud83d\udd0b"), new Emoji("\ud83d\udd0c"), new Emoji("\ud83d\udcbb"), new Emoji("\ud83d\udda5\ufe0f"), new Emoji("\ud83d\udda8\ufe0f"), new Emoji("\u2328\ufe0f"), new Emoji("\ud83d\uddb1\ufe0f"), new Emoji("\ud83d\uddb2\ufe0f"), new Emoji("\ud83d\udcbd"), new Emoji("\ud83d\udcbe"), new Emoji("\ud83d\udcbf"), new Emoji("\ud83d\udcc0"), new Emoji("\ud83e\uddee"), new Emoji("\ud83c\udfa5"), new Emoji("\ud83c\udf9e\ufe0f"), new Emoji("\ud83d\udcfd\ufe0f"), new Emoji("\ud83c\udfac"), new Emoji("\ud83d\udcfa"), new Emoji("\ud83d\udcf7"), new Emoji("\ud83d\udcf8"), new Emoji("\ud83d\udcf9"), new Emoji("\ud83d\udcfc"), new Emoji("\ud83d\udd0d"), new Emoji("\ud83d\udd0e"), new Emoji("\ud83d\udd6f\ufe0f"), new Emoji("\ud83d\udca1"), new Emoji("\ud83d\udd26"), new Emoji("\ud83c\udfee"), new Emoji("\ud83e\ude94"), new Emoji("\ud83d\udcd4"), new Emoji("\ud83d\udcd5"), new Emoji("\ud83d\udcd6"), new Emoji("\ud83d\udcd7"), new Emoji("\ud83d\udcd8"), new Emoji("\ud83d\udcd9"), new Emoji("\ud83d\udcda"), new Emoji("\ud83d\udcd3"), new Emoji("\ud83d\udcd2"), new Emoji("\ud83d\udcc3"), new Emoji("\ud83d\udcdc"), new Emoji("\ud83d\udcc4"), new Emoji("\ud83d\udcf0"), new Emoji("\ud83d\uddde\ufe0f"), new Emoji("\ud83d\udcd1"), new Emoji("\ud83d\udd16"), new Emoji("\ud83c\udff7\ufe0f"), new Emoji("\ud83d\udcb0"), new Emoji("\ud83e\ude99"), new Emoji("\ud83d\udcb4"), new Emoji("\ud83d\udcb5"), new Emoji("\ud83d\udcb6"), new Emoji("\ud83d\udcb7"), new Emoji("\ud83d\udcb8"), new Emoji("\ud83d\udcb3"), new Emoji("\ud83e\uddfe"), new Emoji("\ud83d\udcb9"), new Emoji("\u2709\ufe0f"), new Emoji("\ud83d\udce7"), new Emoji("\ud83d\udce8"), new Emoji("\ud83d\udce9"), new Emoji("\ud83d\udce4"), new Emoji("\ud83d\udce5"), new Emoji("\ud83d\udce6"), new Emoji("\ud83d\udceb"), new Emoji("\ud83d\udcea"), new Emoji("\ud83d\udcec"), new Emoji("\ud83d\udced"), new Emoji("\ud83d\udcee"), new Emoji("\ud83d\uddf3\ufe0f"), new Emoji("\u270f\ufe0f"), new Emoji("\u2712\ufe0f"), new Emoji("\ud83d\udd8b\ufe0f"), new Emoji("\ud83d\udd8a\ufe0f"), new Emoji("\ud83d\udd8c\ufe0f"), new Emoji("\ud83d\udd8d\ufe0f"), new Emoji("\ud83d\udcdd"), new Emoji("\ud83d\udcbc"), new Emoji("\ud83d\udcc1"), new Emoji("\ud83d\udcc2"), new Emoji("\ud83d\uddc2\ufe0f"), new Emoji("\ud83d\udcc5"), new Emoji("\ud83d\udcc6"), new Emoji("\ud83d\uddd2\ufe0f"), new Emoji("\ud83d\uddd3\ufe0f"), new Emoji("\ud83d\udcc7"), new Emoji("\ud83d\udcc8"), new Emoji("\ud83d\udcc9"), new Emoji("\ud83d\udcca"), new Emoji("\ud83d\udccb"), new Emoji("\ud83d\Line truncated
|
||||
}, "emoji/Objects.webp");
|
||||
|
||||
private static final EmojiPageModel PAGE_PEOPLE = new CompositeEmojiPageModel(R.attr.emoji_category_people, PAGE_PEOPLE_0, PAGE_PEOPLE_1, PAGE_PEOPLE_2, PAGE_PEOPLE_3, PAGE_PEOPLE_4, PAGE_PEOPLE_5, PAGE_PEOPLE_6, PAGE_PEOPLE_7);
|
||||
|
||||
private static final EmojiPageModel PAGE_FLAGS = new CompositeEmojiPageModel(R.attr.emoji_category_flags, PAGE_FLAGS_0, PAGE_FLAGS_1);
|
||||
|
||||
private static final EmojiPageModel PAGE_EMOTICONS = new StaticEmojiPageModel(R.attr.emoji_category_emoticons, new String[] {
|
||||
":-)", ";-)", "(-:", ":->", ":-D", "\\o/",
|
||||
":-P", "B-)", ":-$", ":-*", "O:-)", "=-O",
|
||||
"O_O", "O_o", "o_O", ":O", ":-!", ":-x",
|
||||
":-|", ":-\\", ":-(", ":'(", ":-[", ">:-(",
|
||||
"^.^", "^_^", "\\(\u02c6\u02da\u02c6)/",
|
||||
"\u30fd(\u00b0\u25c7\u00b0 )\u30ce", "\u00af\\(\u00b0_o)/\u00af",
|
||||
"\u00af\\_(\u30c4)_/\u00af", "(\u00ac_\u00ac)",
|
||||
"(>_<)", "(\u2565\ufe4f\u2565)", "(\u261e\uff9f\u30ee\uff9f)\u261e",
|
||||
"\u261c(\uff9f\u30ee\uff9f\u261c)", "\u261c(\u2312\u25bd\u2312)\u261e",
|
||||
"(\u256f\u00b0\u25a1\u00b0)\u256f\ufe35", "\u253b\u2501\u253b",
|
||||
"\u252c\u2500\u252c", "\u30ce(\u00b0\u2013\u00b0\u30ce)",
|
||||
"(^._.^)\uff89", "\u0e05^\u2022\ufecc\u2022^\u0e05",
|
||||
"\u0295\u2022\u1d25\u2022\u0294", "(\u2022_\u2022)",
|
||||
" \u25a0-\u25a0\u00ac <(\u2022_\u2022) ", "(\u25a0_\u25a0\u00ac)",
|
||||
"\u01aa(\u0693\u05f2)\u200e\u01aa\u200b\u200b"
|
||||
}, null);
|
||||
|
||||
static final List<EmojiPageModel> DISPLAY_PAGES = Arrays.asList(PAGE_PEOPLE, PAGE_NATURE, PAGE_FOODS, PAGE_ACTIVITY, PAGE_PLACES, PAGE_OBJECTS, PAGE_SYMBOLS, PAGE_FLAGS, PAGE_EMOTICONS);
|
||||
|
||||
static final List<EmojiPageModel> DATA_PAGES = Arrays.asList(PAGE_PEOPLE_0, PAGE_PEOPLE_1, PAGE_PEOPLE_2, PAGE_PEOPLE_3, PAGE_PEOPLE_4, PAGE_PEOPLE_5, PAGE_PEOPLE_6, PAGE_PEOPLE_7, PAGE_NATURE, PAGE_FOODS, PAGE_ACTIVITY, PAGE_PLACES, PAGE_OBJECTS, PAGE_SYMBOLS, PAGE_FLAGS_0, PAGE_FLAGS_1, PAGE_EMOTICONS);
|
||||
|
||||
static final List<Pair<String, String>> OBSOLETE = new LinkedList<Pair<String, String>>() {{
|
||||
add(new Pair<>("\ud83d\udc71", "\ud83d\udc71\u200d\u2642\ufe0f"));
|
||||
add(new Pair<>("\ud83d\udc71\ud83c\udffb", "\ud83d\udc71\ud83c\udffb\u200d\u2642\ufe0f"));
|
||||
add(new Pair<>("\ud83d\udc71\ud83c\udffc", "\ud83d\udc71\ud83c\udffc\u200d\u2642\ufe0f"));
|
||||
add(new Pair<>("\ud83d\udc71\ud83c\udffd", "\ud83d\udc71\ud83c\udffd\u200d\u2642\ufe0f"));
|
||||
add(new Pair<>("\ud83d\udc71\ud83c\udffe", "\ud83d\udc71\ud83c\udffe\u200d\u2642\ufe0f"));
|
||||
add(new Pair<>("\ud83d\udc71\ud83c\udfff", "\ud83d\udc71\ud83c\udfff\u200d\u2642\ufe0f"));
|
||||
add(new Pair<>("\ud83d\ude4d", "\ud83d\ude4d\u200d\u2640\ufe0f"));
|
||||
add(new Pair<>("\ud83d\ude4d\ud83c\udffb", "\ud83d\ude4d\ud83c\udffb\u200d\u2640\ufe0f"));
|
||||
add(new Pair<>("\ud83d\ude4d\ud83c\udffc", "\ud83d\ude4d\ud83c\udffc\u200d\u2640\ufe0f"));
|
||||
add(new Pair<>("\ud83d\ude4d\ud83c\udffd", "\ud83d\ude4d\ud83c\udffd\u200d\u2640\ufe0f"));
|
||||
add(new Pair<>("\ud83d\ude4d\ud83c\udffe", "\ud83d\ude4d\ud83c\udffe\u200d\u2640\ufe0f"));
|
||||
add(new Pair<>("\ud83d\ude4d\ud83c\udfff", "\ud83d\ude4d\ud83c\udfff\u200d\u2640\ufe0f"));
|
||||
add(new Pair<>("\ud83d\ude4e", "\ud83d\ude4e\u200d\u2640\ufe0f"));
|
||||
add(new Pair<>("\ud83d\ude4e\ud83c\udffb", "\ud83d\ude4e\ud83c\udffb\u200d\u2640\ufe0f"));
|
||||
add(new Pair<>("\ud83d\ude4e\ud83c\udffc", "\ud83d\ude4e\ud83c\udffc\u200d\u2640\ufe0f"));
|
||||
add(new Pair<>("\ud83d\ude4e\ud83c\udffd", "\ud83d\ude4e\ud83c\udffd\u200d\u2640\ufe0f"));
|
||||
add(new Pair<>("\ud83d\ude4e\ud83c\udffe", "\ud83d\ude4e\ud83c\udffe\u200d\u2640\ufe0f"));
|
||||
add(new Pair<>("\ud83d\ude4e\ud83c\udfff", "\ud83d\ude4e\ud83c\udfff\u200d\u2640\ufe0f"));
|
||||
add(new Pair<>("\ud83d\ude45", "\ud83d\ude45\u200d\u2640\ufe0f"));
|
||||
add(new Pair<>("\ud83d\ude45\ud83c\udffb", "\ud83d\ude45\ud83c\udffb\u200d\u2640\ufe0f"));
|
||||
add(new Pair<>("\ud83d\ude45\ud83c\udffc", "\ud83d\ude45\ud83c\udffc\u200d\u2640\ufe0f"));
|
||||
add(new Pair<>("\ud83d\ude45\ud83c\udffd", "\ud83d\ude45\ud83c\udffd\u200d\u2640\ufe0f"));
|
||||
add(new Pair<>("\ud83d\ude45\ud83c\udffe", "\ud83d\ude45\ud83c\udffe\u200d\u2640\ufe0f"));
|
||||
add(new Pair<>("\ud83d\ude45\ud83c\udfff", "\ud83d\ude45\ud83c\udfff\u200d\u2640\ufe0f"));
|
||||
add(new Pair<>("\ud83d\ude46", "\ud83d\ude46\u200d\u2640\ufe0f"));
|
||||
add(new Pair<>("\ud83d\ude46\ud83c\udffb", "\ud83d\ude46\ud83c\udffb\u200d\u2640\ufe0f"));
|
||||
add(new Pair<>("\ud83d\ude46\ud83c\udffc", "\ud83d\ude46\ud83c\udffc\u200d\u2640\ufe0f"));
|
||||
add(new Pair<>("\ud83d\ude46\ud83c\udffd", "\ud83d\ude46\ud83c\udffd\u200d\u2640\ufe0f"));
|
||||
add(new Pair<>("\ud83d\ude46\ud83c\udffe", "\ud83d\ude46\ud83c\udffe\u200d\u2640\ufe0f"));
|
||||
add(new Pair<>("\ud83d\ude46\ud83c\udfff", "\ud83d\ude46\ud83c\udfff\u200d\u2640\ufe0f"));
|
||||
add(new Pair<>("\ud83d\udc81", "\ud83d\udc81\u200d\u2640\ufe0f"));
|
||||
add(new Pair<>("\ud83d\udc81\ud83c\udffb", "\ud83d\udc81\ud83c\udffb\u200d\u2640\ufe0f"));
|
||||
add(new Pair<>("\ud83d\udc81\ud83c\udffc", "\ud83d\udc81\ud83c\udffc\u200d\u2640\ufe0f"));
|
||||
add(new Pair<>("\ud83d\udc81\ud83c\udffd", "\ud83d\udc81\ud83c\udffd\u200d\u2640\ufe0f"));
|
||||
add(new Pair<>("\ud83d\udc81\ud83c\udffe", "\ud83d\udc81\ud83c\udffe\u200d\u2640\ufe0f"));
|
||||
add(new Pair<>("\ud83d\udc81\ud83c\udfff", "\ud83d\udc81\ud83c\udfff\u200d\u2640\ufe0f"));
|
||||
add(new Pair<>("\ud83d\ude4b", "\ud83d\ude4b\u200d\u2640\ufe0f"));
|
||||
add(new Pair<>("\ud83d\ude4b\ud83c\udffb", "\ud83d\ude4b\ud83c\udffb\u200d\u2640\ufe0f"));
|
||||
add(new Pair<>("\ud83d\ude4b\ud83c\udffc", "\ud83d\ude4b\ud83c\udffc\u200d\u2640\ufe0f"));
|
||||
add(new Pair<>("\ud83d\ude4b\ud83c\udffd", "\ud83d\ude4b\ud83c\udffd\u200d\u2640\ufe0f"));
|
||||
add(new Pair<>("\ud83d\ude4b\ud83c\udffe", "\ud83d\ude4b\ud83c\udffe\u200d\u2640\ufe0f"));
|
||||
add(new Pair<>("\ud83d\ude4b\ud83c\udfff", "\ud83d\ude4b\ud83c\udfff\u200d\u2640\ufe0f"));
|
||||
add(new Pair<>("\ud83d\ude47", "\ud83d\ude47\u200d\u2642\ufe0f"));
|
||||
add(new Pair<>("\ud83d\ude47\ud83c\udffb", "\ud83d\ude47\ud83c\udffb\u200d\u2642\ufe0f"));
|
||||
add(new Pair<>("\ud83d\ude47\ud83c\udffc", "\ud83d\ude47\ud83c\udffc\u200d\u2642\ufe0f"));
|
||||
add(new Pair<>("\ud83d\ude47\ud83c\udffd", "\ud83d\ude47\ud83c\udffd\u200d\u2642\ufe0f"));
|
||||
add(new Pair<>("\ud83d\ude47\ud83c\udffe", "\ud83d\ude47\ud83c\udffe\u200d\u2642\ufe0f"));
|
||||
add(new Pair<>("\ud83d\ude47\ud83c\udfff", "\ud83d\ude47\ud83c\udfff\u200d\u2642\ufe0f"));
|
||||
add(new Pair<>("\ud83d\udc6e", "\ud83d\udc6e\u200d\u2642\ufe0f"));
|
||||
add(new Pair<>("\ud83d\udc6e\ud83c\udffb", "\ud83d\udc6e\ud83c\udffb\u200d\u2642\ufe0f"));
|
||||
add(new Pair<>("\ud83d\udc6e\ud83c\udffc", "\ud83d\udc6e\ud83c\udffc\u200d\u2642\ufe0f"));
|
||||
add(new Pair<>("\ud83d\udc6e\ud83c\udffd", "\ud83d\udc6e\ud83c\udffd\u200d\u2642\ufe0f"));
|
||||
add(new Pair<>("\ud83d\udc6e\ud83c\udffe", "\ud83d\udc6e\ud83c\udffe\u200d\u2642\ufe0f"));
|
||||
add(new Pair<>("\ud83d\udc6e\ud83c\udfff", "\ud83d\udc6e\ud83c\udfff\u200d\u2642\ufe0f"));
|
||||
add(new Pair<>("\ud83d\udd75\ufe0f", "\ud83d\udd75\ufe0f\u200d\u2642\ufe0f"));
|
||||
add(new Pair<>("\ud83d\udd75\ud83c\udffb", "\ud83d\udd75\ud83c\udffb\u200d\u2642\ufe0f"));
|
||||
add(new Pair<>("\ud83d\udd75\ud83c\udffc", "\ud83d\udd75\ud83c\udffc\u200d\u2642\ufe0f"));
|
||||
add(new Pair<>("\ud83d\udd75\ud83c\udffd", "\ud83d\udd75\ud83c\udffd\u200d\u2642\ufe0f"));
|
||||
add(new Pair<>("\ud83d\udd75\ud83c\udffe", "\ud83d\udd75\ud83c\udffe\u200d\u2642\ufe0f"));
|
||||
add(new Pair<>("\ud83d\udd75\ud83c\udfff", "\ud83d\udd75\ud83c\udfff\u200d\u2642\ufe0f"));
|
||||
add(new Pair<>("\ud83d\udc82", "\ud83d\udc82\u200d\u2642\ufe0f"));
|
||||
add(new Pair<>("\ud83d\udc82\ud83c\udffb", "\ud83d\udc82\ud83c\udffb\u200d\u2642\ufe0f"));
|
||||
add(new Pair<>("\ud83d\udc82\ud83c\udffc", "\ud83d\udc82\ud83c\udffc\u200d\u2642\ufe0f"));
|
||||
add(new Pair<>("\ud83d\udc82\ud83c\udffd", "\ud83d\udc82\ud83c\udffd\u200d\u2642\ufe0f"));
|
||||
add(new Pair<>("\ud83d\udc82\ud83c\udffe", "\ud83d\udc82\ud83c\udffe\u200d\u2642\ufe0f"));
|
||||
add(new Pair<>("\ud83d\udc82\ud83c\udfff", "\ud83d\udc82\ud83c\udfff\u200d\u2642\ufe0f"));
|
||||
add(new Pair<>("\ud83d\udc77", "\ud83d\udc77\u200d\u2642\ufe0f"));
|
||||
add(new Pair<>("\ud83d\udc77\ud83c\udffb", "\ud83d\udc77\ud83c\udffb\u200d\u2642\ufe0f"));
|
||||
add(new Pair<>("\ud83d\udc77\ud83c\udffc", "\ud83d\udc77\ud83c\udffc\u200d\u2642\ufe0f"));
|
||||
add(new Pair<>("\ud83d\udc77\ud83c\udffd", "\ud83d\udc77\ud83c\udffd\u200d\u2642\ufe0f"));
|
||||
add(new Pair<>("\ud83d\udc77\ud83c\udffe", "\ud83d\udc77\ud83c\udffe\u200d\u2642\ufe0f"));
|
||||
add(new Pair<>("\ud83d\udc77\ud83c\udfff", "\ud83d\udc77\ud83c\udfff\u200d\u2642\ufe0f"));
|
||||
add(new Pair<>("\ud83d\udc73", "\ud83d\udc73\u200d\u2642\ufe0f"));
|
||||
add(new Pair<>("\ud83d\udc73\ud83c\udffb", "\ud83d\udc73\ud83c\udffb\u200d\u2642\ufe0f"));
|
||||
add(new Pair<>("\ud83d\udc73\ud83c\udffc", "\ud83d\udc73\ud83c\udffc\u200d\u2642\ufe0f"));
|
||||
add(new Pair<>("\ud83d\udc73\ud83c\udffd", "\ud83d\udc73\ud83c\udffd\u200d\u2642\ufe0f"));
|
||||
add(new Pair<>("\ud83d\udc73\ud83c\udffe", "\ud83d\udc73\ud83c\udffe\u200d\u2642\ufe0f"));
|
||||
add(new Pair<>("\ud83d\udc73\ud83c\udfff", "\ud83d\udc73\ud83c\udfff\u200d\u2642\ufe0f"));
|
||||
add(new Pair<>("\ud83e\uddd9", "\ud83e\uddd9\u200d\u2640\ufe0f"));
|
||||
add(new Pair<>("\ud83e\uddd9\ud83c\udffb", "\ud83e\uddd9\ud83c\udffb\u200d\u2640\ufe0f"));
|
||||
add(new Pair<>("\ud83e\uddd9\ud83c\udffc", "\ud83e\uddd9\ud83c\udffc\u200d\u2640\ufe0f"));
|
||||
add(new Pair<>("\ud83e\uddd9\ud83c\udffd", "\ud83e\uddd9\ud83c\udffd\u200d\u2640\ufe0f"));
|
||||
add(new Pair<>("\ud83e\uddd9\ud83c\udffe", "\ud83e\uddd9\ud83c\udffe\u200d\u2640\ufe0f"));
|
||||
add(new Pair<>("\ud83e\uddd9\ud83c\udfff", "\ud83e\uddd9\ud83c\udfff\u200d\u2640\ufe0f"));
|
||||
add(new Pair<>("\ud83e\uddda", "\ud83e\uddda\u200d\u2640\ufe0f"));
|
||||
add(new Pair<>("\ud83e\uddda\ud83c\udffb", "\ud83e\uddda\ud83c\udffb\u200d\u2640\ufe0f"));
|
||||
add(new Pair<>("\ud83e\uddda\ud83c\udffc", "\ud83e\uddda\ud83c\udffc\u200d\u2640\ufe0f"));
|
||||
add(new Pair<>("\ud83e\uddda\ud83c\udffd", "\ud83e\uddda\ud83c\udffd\u200d\u2640\ufe0f"));
|
||||
add(new Pair<>("\ud83e\uddda\ud83c\udffe", "\ud83e\uddda\ud83c\udffe\u200d\u2640\ufe0f"));
|
||||
add(new Pair<>("\ud83e\uddda\ud83c\udfff", "\ud83e\uddda\ud83c\udfff\u200d\u2640\ufe0f"));
|
||||
add(new Pair<>("\ud83e\udddb", "\ud83e\udddb\u200d\u2640\ufe0f"));
|
||||
add(new Pair<>("\ud83e\udddb\ud83c\udffb", "\ud83e\udddb\ud83c\udffb\u200d\u2640\ufe0f"));
|
||||
add(new Pair<>("\ud83e\udddb\ud83c\udffc", "\ud83e\udddb\ud83c\udffc\u200d\u2640\ufe0f"));
|
||||
add(new Pair<>("\ud83e\udddb\ud83c\udffd", "\ud83e\udddb\ud83c\udffd\u200d\u2640\ufe0f"));
|
||||
add(new Pair<>("\ud83e\udddb\ud83c\udffe", "\ud83e\udddb\ud83c\udffe\u200d\u2640\ufe0f"));
|
||||
add(new Pair<>("\ud83e\udddb\ud83c\udfff", "\ud83e\udddb\ud83c\udfff\u200d\u2640\ufe0f"));
|
||||
add(new Pair<>("\ud83e\udddc", "\ud83e\udddc\u200d\u2642\ufe0f"));
|
||||
add(new Pair<>("\ud83e\udddc\ud83c\udffb", "\ud83e\udddc\ud83c\udffb\u200d\u2642\ufe0f"));
|
||||
add(new Pair<>("\ud83e\udddc\ud83c\udffc", "\ud83e\udddc\ud83c\udffc\u200d\u2642\ufe0f"));
|
||||
add(new Pair<>("\ud83e\udddc\ud83c\udffd", "\ud83e\udddc\ud83c\udffd\u200d\u2642\ufe0f"));
|
||||
add(new Pair<>("\ud83e\udddc\ud83c\udffe", "\ud83e\udddc\ud83c\udffe\u200d\u2642\ufe0f"));
|
||||
add(new Pair<>("\ud83e\udddc\ud83c\udfff", "\ud83e\udddc\ud83c\udfff\u200d\u2642\ufe0f"));
|
||||
add(new Pair<>("\ud83e\udddd", "\ud83e\udddd\u200d\u2642\ufe0f"));
|
||||
add(new Pair<>("\ud83e\udddd\ud83c\udffb", "\ud83e\udddd\ud83c\udffb\u200d\u2642\ufe0f"));
|
||||
add(new Pair<>("\ud83e\udddd\ud83c\udffc", "\ud83e\udddd\ud83c\udffc\u200d\u2642\ufe0f"));
|
||||
add(new Pair<>("\ud83e\udddd\ud83c\udffd", "\ud83e\udddd\ud83c\udffd\u200d\u2642\ufe0f"));
|
||||
add(new Pair<>("\ud83e\udddd\ud83c\udffe", "\ud83e\udddd\ud83c\udffe\u200d\u2642\ufe0f"));
|
||||
add(new Pair<>("\ud83e\udddd\ud83c\udfff", "\ud83e\udddd\ud83c\udfff\u200d\u2642\ufe0f"));
|
||||
add(new Pair<>("\ud83e\uddde", "\ud83e\uddde\u200d\u2642\ufe0f"));
|
||||
add(new Pair<>("\ud83e\udddf", "\ud83e\udddf\u200d\u2642\ufe0f"));
|
||||
add(new Pair<>("\ud83d\udc86", "\ud83d\udc86\u200d\u2640\ufe0f"));
|
||||
add(new Pair<>("\ud83d\udc86\ud83c\udffb", "\ud83d\udc86\ud83c\udffb\u200d\u2640\ufe0f"));
|
||||
add(new Pair<>("\ud83d\udc86\ud83c\udffc", "\ud83d\udc86\ud83c\udffc\u200d\u2640\ufe0f"));
|
||||
add(new Pair<>("\ud83d\udc86\ud83c\udffd", "\ud83d\udc86\ud83c\udffd\u200d\u2640\ufe0f"));
|
||||
add(new Pair<>("\ud83d\udc86\ud83c\udffe", "\ud83d\udc86\ud83c\udffe\u200d\u2640\ufe0f"));
|
||||
add(new Pair<>("\ud83d\udc86\ud83c\udfff", "\ud83d\udc86\ud83c\udfff\u200d\u2640\ufe0f"));
|
||||
add(new Pair<>("\ud83d\udc87", "\ud83d\udc87\u200d\u2640\ufe0f"));
|
||||
add(new Pair<>("\ud83d\udc87\ud83c\udffb", "\ud83d\udc87\ud83c\udffb\u200d\u2640\ufe0f"));
|
||||
add(new Pair<>("\ud83d\udc87\ud83c\udffc", "\ud83d\udc87\ud83c\udffc\u200d\u2640\ufe0f"));
|
||||
add(new Pair<>("\ud83d\udc87\ud83c\udffd", "\ud83d\udc87\ud83c\udffd\u200d\u2640\ufe0f"));
|
||||
add(new Pair<>("\ud83d\udc87\ud83c\udffe", "\ud83d\udc87\ud83c\udffe\u200d\u2640\ufe0f"));
|
||||
add(new Pair<>("\ud83d\udc87\ud83c\udfff", "\ud83d\udc87\ud83c\udfff\u200d\u2640\ufe0f"));
|
||||
add(new Pair<>("\ud83d\udeb6", "\ud83d\udeb6\u200d\u2642\ufe0f"));
|
||||
add(new Pair<>("\ud83d\udeb6\ud83c\udffb", "\ud83d\udeb6\ud83c\udffb\u200d\u2642\ufe0f"));
|
||||
add(new Pair<>("\ud83d\udeb6\ud83c\udffc", "\ud83d\udeb6\ud83c\udffc\u200d\u2642\ufe0f"));
|
||||
add(new Pair<>("\ud83d\udeb6\ud83c\udffd", "\ud83d\udeb6\ud83c\udffd\u200d\u2642\ufe0f"));
|
||||
add(new Pair<>("\ud83d\udeb6\ud83c\udffe", "\ud83d\udeb6\ud83c\udffe\u200d\u2642\ufe0f"));
|
||||
add(new Pair<>("\ud83d\udeb6\ud83c\udfff", "\ud83d\udeb6\ud83c\udfff\u200d\u2642\ufe0f"));
|
||||
add(new Pair<>("\ud83c\udfc3", "\ud83c\udfc3\u200d\u2642\ufe0f"));
|
||||
add(new Pair<>("\ud83c\udfc3\ud83c\udffb", "\ud83c\udfc3\ud83c\udffb\u200d\u2642\ufe0f"));
|
||||
add(new Pair<>("\ud83c\udfc3\ud83c\udffc", "\ud83c\udfc3\ud83c\udffc\u200d\u2642\ufe0f"));
|
||||
add(new Pair<>("\ud83c\udfc3\ud83c\udffd", "\ud83c\udfc3\ud83c\udffd\u200d\u2642\ufe0f"));
|
||||
add(new Pair<>("\ud83c\udfc3\ud83c\udffe", "\ud83c\udfc3\ud83c\udffe\u200d\u2642\ufe0f"));
|
||||
add(new Pair<>("\ud83c\udfc3\ud83c\udfff", "\ud83c\udfc3\ud83c\udfff\u200d\u2642\ufe0f"));
|
||||
add(new Pair<>("\ud83d\udc6f", "\ud83d\udc6f\u200d\u2640\ufe0f"));
|
||||
add(new Pair<>("\ud83e\uddd6", "\ud83e\uddd6\u200d\u2642\ufe0f"));
|
||||
add(new Pair<>("\ud83e\uddd6\ud83c\udffb", "\ud83e\uddd6\ud83c\udffb\u200d\u2642\ufe0f"));
|
||||
add(new Pair<>("\ud83e\uddd6\ud83c\udffc", "\ud83e\uddd6\ud83c\udffc\u200d\u2642\ufe0f"));
|
||||
add(new Pair<>("\ud83e\uddd6\ud83c\udffd", "\ud83e\uddd6\ud83c\udffd\u200d\u2642\ufe0f"));
|
||||
add(new Pair<>("\ud83e\uddd6\ud83c\udffe", "\ud83e\uddd6\ud83c\udffe\u200d\u2642\ufe0f"));
|
||||
add(new Pair<>("\ud83e\uddd6\ud83c\udfff", "\ud83e\uddd6\ud83c\udfff\u200d\u2642\ufe0f"));
|
||||
add(new Pair<>("\ud83e\uddd7", "\ud83e\uddd7\u200d\u2640\ufe0f"));
|
||||
add(new Pair<>("\ud83e\uddd7\ud83c\udffb", "\ud83e\uddd7\ud83c\udffb\u200d\u2640\ufe0f"));
|
||||
add(new Pair<>("\ud83e\uddd7\ud83c\udffc", "\ud83e\uddd7\ud83c\udffc\u200d\u2640\ufe0f"));
|
||||
add(new Pair<>("\ud83e\uddd7\ud83c\udffd", "\ud83e\uddd7\ud83c\udffd\u200d\u2640\ufe0f"));
|
||||
add(new Pair<>("\ud83e\uddd7\ud83c\udffe", "\ud83e\uddd7\ud83c\udffe\u200d\u2640\ufe0f"));
|
||||
add(new Pair<>("\ud83e\uddd7\ud83c\udfff", "\ud83e\uddd7\ud83c\udfff\u200d\u2640\ufe0f"));
|
||||
add(new Pair<>("\ud83c\udfcc\ufe0f", "\ud83c\udfcc\ufe0f\u200d\u2642\ufe0f"));
|
||||
add(new Pair<>("\ud83c\udfcc\ud83c\udffb", "\ud83c\udfcc\ud83c\udffb\u200d\u2642\ufe0f"));
|
||||
add(new Pair<>("\ud83c\udfcc\ud83c\udffc", "\ud83c\udfcc\ud83c\udffc\u200d\u2642\ufe0f"));
|
||||
add(new Pair<>("\ud83c\udfcc\ud83c\udffd", "\ud83c\udfcc\ud83c\udffd\u200d\u2642\ufe0f"));
|
||||
add(new Pair<>("\ud83c\udfcc\ud83c\udffe", "\ud83c\udfcc\ud83c\udffe\u200d\u2642\ufe0f"));
|
||||
add(new Pair<>("\ud83c\udfcc\ud83c\udfff", "\ud83c\udfcc\ud83c\udfff\u200d\u2642\ufe0f"));
|
||||
add(new Pair<>("\ud83c\udfc4", "\ud83c\udfc4\u200d\u2642\ufe0f"));
|
||||
add(new Pair<>("\ud83c\udfc4\ud83c\udffb", "\ud83c\udfc4\ud83c\udffb\u200d\u2642\ufe0f"));
|
||||
add(new Pair<>("\ud83c\udfc4\ud83c\udffc", "\ud83c\udfc4\ud83c\udffc\u200d\u2642\ufe0f"));
|
||||
add(new Pair<>("\ud83c\udfc4\ud83c\udffd", "\ud83c\udfc4\ud83c\udffd\u200d\u2642\ufe0f"));
|
||||
add(new Pair<>("\ud83c\udfc4\ud83c\udffe", "\ud83c\udfc4\ud83c\udffe\u200d\u2642\ufe0f"));
|
||||
add(new Pair<>("\ud83c\udfc4\ud83c\udfff", "\ud83c\udfc4\ud83c\udfff\u200d\u2642\ufe0f"));
|
||||
add(new Pair<>("\ud83d\udea3", "\ud83d\udea3\u200d\u2642\ufe0f"));
|
||||
add(new Pair<>("\ud83d\udea3\ud83c\udffb", "\ud83d\udea3\ud83c\udffb\u200d\u2642\ufe0f"));
|
||||
add(new Pair<>("\ud83d\udea3\ud83c\udffc", "\ud83d\udea3\ud83c\udffc\u200d\u2642\ufe0f"));
|
||||
add(new Pair<>("\ud83d\udea3\ud83c\udffd", "\ud83d\udea3\ud83c\udffd\u200d\u2642\ufe0f"));
|
||||
add(new Pair<>("\ud83d\udea3\ud83c\udffe", "\ud83d\udea3\ud83c\udffe\u200d\u2642\ufe0f"));
|
||||
add(new Pair<>("\ud83d\udea3\ud83c\udfff", "\ud83d\udea3\ud83c\udfff\u200d\u2642\ufe0f"));
|
||||
add(new Pair<>("\ud83c\udfca", "\ud83c\udfca\u200d\u2642\ufe0f"));
|
||||
add(new Pair<>("\ud83c\udfca\ud83c\udffb", "\ud83c\udfca\ud83c\udffb\u200d\u2642\ufe0f"));
|
||||
add(new Pair<>("\ud83c\udfca\ud83c\udffc", "\ud83c\udfca\ud83c\udffc\u200d\u2642\ufe0f"));
|
||||
add(new Pair<>("\ud83c\udfca\ud83c\udffd", "\ud83c\udfca\ud83c\udffd\u200d\u2642\ufe0f"));
|
||||
add(new Pair<>("\ud83c\udfca\ud83c\udffe", "\ud83c\udfca\ud83c\udffe\u200d\u2642\ufe0f"));
|
||||
add(new Pair<>("\ud83c\udfca\ud83c\udfff", "\ud83c\udfca\ud83c\udfff\u200d\u2642\ufe0f"));
|
||||
add(new Pair<>("\u26f9\ufe0f", "\u26f9\ufe0f\u200d\u2642\ufe0f"));
|
||||
add(new Pair<>("\u26f9\ud83c\udffb", "\u26f9\ud83c\udffb\u200d\u2642\ufe0f"));
|
||||
add(new Pair<>("\u26f9\ud83c\udffc", "\u26f9\ud83c\udffc\u200d\u2642\ufe0f"));
|
||||
add(new Pair<>("\u26f9\ud83c\udffd", "\u26f9\ud83c\udffd\u200d\u2642\ufe0f"));
|
||||
add(new Pair<>("\u26f9\ud83c\udffe", "\u26f9\ud83c\udffe\u200d\u2642\ufe0f"));
|
||||
add(new Pair<>("\u26f9\ud83c\udfff", "\u26f9\ud83c\udfff\u200d\u2642\ufe0f"));
|
||||
add(new Pair<>("\ud83c\udfcb\ufe0f", "\ud83c\udfcb\ufe0f\u200d\u2642\ufe0f"));
|
||||
add(new Pair<>("\ud83c\udfcb\ud83c\udffb", "\ud83c\udfcb\ud83c\udffb\u200d\u2642\ufe0f"));
|
||||
add(new Pair<>("\ud83c\udfcb\ud83c\udffc", "\ud83c\udfcb\ud83c\udffc\u200d\u2642\ufe0f"));
|
||||
add(new Pair<>("\ud83c\udfcb\ud83c\udffd", "\ud83c\udfcb\ud83c\udffd\u200d\u2642\ufe0f"));
|
||||
add(new Pair<>("\ud83c\udfcb\ud83c\udffe", "\ud83c\udfcb\ud83c\udffe\u200d\u2642\ufe0f"));
|
||||
add(new Pair<>("\ud83c\udfcb\ud83c\udfff", "\ud83c\udfcb\ud83c\udfff\u200d\u2642\ufe0f"));
|
||||
add(new Pair<>("\ud83d\udeb4", "\ud83d\udeb4\u200d\u2642\ufe0f"));
|
||||
add(new Pair<>("\ud83d\udeb4\ud83c\udffb", "\ud83d\udeb4\ud83c\udffb\u200d\u2642\ufe0f"));
|
||||
add(new Pair<>("\ud83d\udeb4\ud83c\udffc", "\ud83d\udeb4\ud83c\udffc\u200d\u2642\ufe0f"));
|
||||
add(new Pair<>("\ud83d\udeb4\ud83c\udffd", "\ud83d\udeb4\ud83c\udffd\u200d\u2642\ufe0f"));
|
||||
add(new Pair<>("\ud83d\udeb4\ud83c\udffe", "\ud83d\udeb4\ud83c\udffe\u200d\u2642\ufe0f"));
|
||||
add(new Pair<>("\ud83d\udeb4\ud83c\udfff", "\ud83d\udeb4\ud83c\udfff\u200d\u2642\ufe0f"));
|
||||
add(new Pair<>("\ud83d\udeb5", "\ud83d\udeb5\u200d\u2642\ufe0f"));
|
||||
add(new Pair<>("\ud83d\udeb5\ud83c\udffb", "\ud83d\udeb5\ud83c\udffb\u200d\u2642\ufe0f"));
|
||||
add(new Pair<>("\ud83d\udeb5\ud83c\udffc", "\ud83d\udeb5\ud83c\udffc\u200d\u2642\ufe0f"));
|
||||
add(new Pair<>("\ud83d\udeb5\ud83c\udffd", "\ud83d\udeb5\ud83c\udffd\u200d\u2642\ufe0f"));
|
||||
add(new Pair<>("\ud83d\udeb5\ud83c\udffe", "\ud83d\udeb5\ud83c\udffe\u200d\u2642\ufe0f"));
|
||||
add(new Pair<>("\ud83d\udeb5\ud83c\udfff", "\ud83d\udeb5\ud83c\udfff\u200d\u2642\ufe0f"));
|
||||
add(new Pair<>("\ud83e\uddd8", "\ud83e\uddd8\u200d\u2640\ufe0f"));
|
||||
add(new Pair<>("\ud83e\uddd8\ud83c\udffb", "\ud83e\uddd8\ud83c\udffb\u200d\u2640\ufe0f"));
|
||||
add(new Pair<>("\ud83e\uddd8\ud83c\udffc", "\ud83e\uddd8\ud83c\udffc\u200d\u2640\ufe0f"));
|
||||
add(new Pair<>("\ud83e\uddd8\ud83c\udffd", "\ud83e\uddd8\ud83c\udffd\u200d\u2640\ufe0f"));
|
||||
add(new Pair<>("\ud83e\uddd8\ud83c\udffe", "\ud83e\uddd8\ud83c\udffe\u200d\u2640\ufe0f"));
|
||||
add(new Pair<>("\ud83e\uddd8\ud83c\udfff", "\ud83e\uddd8\ud83c\udfff\u200d\u2640\ufe0f"));
|
||||
add(new Pair<>("\ud83d\udc8f", "\ud83d\udc69\u200d\u2764\ufe0f\u200d\ud83d\udc8b\u200d\ud83d\udc68"));
|
||||
add(new Pair<>("\ud83d\udc91", "\ud83d\udc69\u200d\u2764\ufe0f\u200d\ud83d\udc68"));
|
||||
add(new Pair<>("\ud83d\udc6a", "\ud83d\udc68\u200d\ud83d\udc69\u200d\ud83d\udc66"));
|
||||
}};
|
||||
}
|
||||
@@ -1,6 +1,5 @@
|
||||
package org.thoughtcrime.securesms.components.emoji;
|
||||
|
||||
import android.annotation.TargetApi;
|
||||
import android.content.Context;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.Canvas;
|
||||
@@ -9,8 +8,6 @@ import android.graphics.Paint;
|
||||
import android.graphics.PixelFormat;
|
||||
import android.graphics.Rect;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.os.Build.VERSION;
|
||||
import android.os.Build.VERSION_CODES;
|
||||
import android.text.Spannable;
|
||||
import android.text.SpannableStringBuilder;
|
||||
import android.widget.TextView;
|
||||
@@ -18,18 +15,18 @@ import android.widget.TextView;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.bumptech.glide.load.DataSource;
|
||||
import com.bumptech.glide.load.engine.DiskCacheStrategy;
|
||||
import com.bumptech.glide.load.engine.GlideException;
|
||||
import com.bumptech.glide.request.RequestListener;
|
||||
import com.bumptech.glide.request.target.Target;
|
||||
|
||||
import org.signal.core.util.ThreadUtil;
|
||||
import org.signal.core.util.logging.Log;
|
||||
import org.thoughtcrime.securesms.R;
|
||||
import org.thoughtcrime.securesms.components.emoji.parsing.EmojiDrawInfo;
|
||||
import org.thoughtcrime.securesms.components.emoji.parsing.EmojiPageBitmap;
|
||||
import org.thoughtcrime.securesms.components.emoji.parsing.EmojiParser;
|
||||
import org.thoughtcrime.securesms.components.emoji.parsing.EmojiTree;
|
||||
import org.thoughtcrime.securesms.util.FutureTaskListener;
|
||||
import org.whispersystems.libsignal.util.Pair;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import org.thoughtcrime.securesms.emoji.EmojiSource;
|
||||
import org.thoughtcrime.securesms.mms.GlideApp;
|
||||
|
||||
class EmojiProvider {
|
||||
|
||||
@@ -37,15 +34,7 @@ class EmojiProvider {
|
||||
private static volatile EmojiProvider instance = null;
|
||||
private static final Paint paint = new Paint(Paint.FILTER_BITMAP_FLAG | Paint.ANTI_ALIAS_FLAG);
|
||||
|
||||
private final EmojiTree emojiTree = new EmojiTree();
|
||||
|
||||
private static final int EMOJI_RAW_HEIGHT = 64;
|
||||
private static final int EMOJI_RAW_WIDTH = 64;
|
||||
private static final int EMOJI_VERT_PAD = 0;
|
||||
private static final int EMOJI_PER_ROW = 16;
|
||||
|
||||
private final float decodeScale;
|
||||
private final float verticalPad;
|
||||
private final Context context;
|
||||
|
||||
public static EmojiProvider getInstance(Context context) {
|
||||
if (instance == null) {
|
||||
@@ -59,28 +48,12 @@ class EmojiProvider {
|
||||
}
|
||||
|
||||
private EmojiProvider(Context context) {
|
||||
this.decodeScale = Math.min(1f, context.getResources().getDimension(R.dimen.emoji_drawer_size) / EMOJI_RAW_HEIGHT);
|
||||
this.verticalPad = EMOJI_VERT_PAD * this.decodeScale;
|
||||
|
||||
for (EmojiPageModel page : EmojiPages.DATA_PAGES) {
|
||||
if (page.hasSpriteMap()) {
|
||||
EmojiPageBitmap pageBitmap = new EmojiPageBitmap(context, page, decodeScale);
|
||||
|
||||
List<String> emojis = page.getEmoji();
|
||||
for (int i = 0; i < emojis.size(); i++) {
|
||||
emojiTree.add(emojis.get(i), new EmojiDrawInfo(pageBitmap, i));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (Pair<String,String> obsolete : EmojiPages.OBSOLETE) {
|
||||
emojiTree.add(obsolete.first(), emojiTree.getEmoji(obsolete.second(), 0, obsolete.second().length()));
|
||||
}
|
||||
this.context = context.getApplicationContext();
|
||||
}
|
||||
|
||||
@Nullable EmojiParser.CandidateList getCandidates(@Nullable CharSequence text) {
|
||||
if (text == null) return null;
|
||||
return new EmojiParser(emojiTree).findCandidates(text);
|
||||
return new EmojiParser(EmojiSource.getLatest().getEmojiTree()).findCandidates(text);
|
||||
}
|
||||
|
||||
@Nullable Spannable emojify(@Nullable CharSequence text, @NonNull TextView tv) {
|
||||
@@ -89,9 +62,10 @@ class EmojiProvider {
|
||||
|
||||
@Nullable Spannable emojify(@Nullable EmojiParser.CandidateList matches,
|
||||
@Nullable CharSequence text,
|
||||
@NonNull TextView tv) {
|
||||
@NonNull TextView tv)
|
||||
{
|
||||
if (matches == null || text == null) return null;
|
||||
SpannableStringBuilder builder = new SpannableStringBuilder(text);
|
||||
SpannableStringBuilder builder = new SpannableStringBuilder(text);
|
||||
|
||||
for (EmojiParser.Candidate candidate : matches) {
|
||||
Drawable drawable = getEmojiDrawable(candidate.getDrawInfo());
|
||||
@@ -106,48 +80,71 @@ class EmojiProvider {
|
||||
}
|
||||
|
||||
@Nullable Drawable getEmojiDrawable(CharSequence emoji) {
|
||||
EmojiDrawInfo drawInfo = emojiTree.getEmoji(emoji, 0, emoji.length());
|
||||
EmojiDrawInfo drawInfo = EmojiSource.getLatest().getEmojiTree().getEmoji(emoji, 0, emoji.length());
|
||||
return getEmojiDrawable(drawInfo);
|
||||
}
|
||||
|
||||
private @Nullable Drawable getEmojiDrawable(@Nullable EmojiDrawInfo drawInfo) {
|
||||
if (drawInfo == null) {
|
||||
if (drawInfo == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
final EmojiDrawable drawable = new EmojiDrawable(drawInfo, decodeScale);
|
||||
drawInfo.getPage().get().addListener(new FutureTaskListener<Bitmap>() {
|
||||
@Override public void onSuccess(final Bitmap result) {
|
||||
ThreadUtil.runOnMain(() -> drawable.setBitmap(result));
|
||||
}
|
||||
final EmojiSource source = EmojiSource.getLatest();
|
||||
final EmojiDrawable drawable = new EmojiDrawable(source, drawInfo);
|
||||
GlideApp.with(context)
|
||||
.asBitmap()
|
||||
.diskCacheStrategy(DiskCacheStrategy.NONE)
|
||||
.load(drawInfo.getPage().getModel())
|
||||
.addListener(new RequestListener<Bitmap>() {
|
||||
@Override
|
||||
public boolean onLoadFailed(@Nullable GlideException e, Object model, Target<Bitmap> target, boolean isFirstResource) {
|
||||
Log.d(TAG, "Failed to load emoji bitmap resource", e);
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onResourceReady(Bitmap resource, Object model, Target<Bitmap> target, DataSource dataSource, boolean isFirstResource) {
|
||||
ThreadUtil.runOnMain(() -> drawable.setBitmap(resource));
|
||||
return true;
|
||||
}
|
||||
})
|
||||
.submit();
|
||||
|
||||
@Override public void onFailure(ExecutionException error) {
|
||||
Log.w(TAG, error);
|
||||
}
|
||||
});
|
||||
return drawable;
|
||||
}
|
||||
|
||||
class EmojiDrawable extends Drawable {
|
||||
private final EmojiDrawInfo info;
|
||||
private Bitmap bmp;
|
||||
private float intrinsicWidth;
|
||||
private float intrinsicHeight;
|
||||
static final class EmojiDrawable extends Drawable {
|
||||
private final float intrinsicWidth;
|
||||
private final float intrinsicHeight;
|
||||
private final Rect emojiBounds;
|
||||
|
||||
private Bitmap bmp;
|
||||
|
||||
@Override
|
||||
public int getIntrinsicWidth() {
|
||||
return (int)intrinsicWidth;
|
||||
return (int) intrinsicWidth;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getIntrinsicHeight() {
|
||||
return (int)intrinsicHeight;
|
||||
return (int) intrinsicHeight;
|
||||
}
|
||||
|
||||
EmojiDrawable(EmojiDrawInfo info, float decodeScale) {
|
||||
this.info = info;
|
||||
this.intrinsicWidth = EMOJI_RAW_WIDTH * decodeScale;
|
||||
this.intrinsicHeight = EMOJI_RAW_HEIGHT * decodeScale;
|
||||
EmojiDrawable(@NonNull EmojiSource source, @NonNull EmojiDrawInfo info) {
|
||||
this.intrinsicWidth = source.getMetrics().getRawWidth() * source.getDecodeScale();
|
||||
this.intrinsicHeight = source.getMetrics().getRawHeight() * source.getDecodeScale();
|
||||
|
||||
final int glyphWidth = (int) (source.getMetrics().getRawWidth() * source.getDecodeScale());
|
||||
final int glyphHeight = (int) (source.getMetrics().getRawHeight() * source.getDecodeScale());
|
||||
final int index = info.getIndex();
|
||||
final int emojiPerRow = source.getMetrics().getPerRow();
|
||||
final int xStart = (index % emojiPerRow) * glyphWidth;
|
||||
final int yStart = (index / emojiPerRow) * glyphHeight;
|
||||
|
||||
this.emojiBounds = new Rect(xStart,
|
||||
yStart,
|
||||
xStart + glyphWidth,
|
||||
yStart + glyphHeight);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -156,22 +153,15 @@ class EmojiProvider {
|
||||
return;
|
||||
}
|
||||
|
||||
final int row = info.getIndex() / EMOJI_PER_ROW;
|
||||
final int row_index = info.getIndex() % EMOJI_PER_ROW;
|
||||
|
||||
canvas.drawBitmap(bmp,
|
||||
new Rect((int)(row_index * intrinsicWidth),
|
||||
(int)(row * intrinsicHeight + row * verticalPad)+1,
|
||||
(int)(((row_index + 1) * intrinsicWidth)-1),
|
||||
(int)((row + 1) * intrinsicHeight + row * verticalPad)-1),
|
||||
emojiBounds,
|
||||
getBounds(),
|
||||
paint);
|
||||
}
|
||||
|
||||
@TargetApi(VERSION_CODES.HONEYCOMB_MR1)
|
||||
public void setBitmap(Bitmap bitmap) {
|
||||
ThreadUtil.assertMainThread();
|
||||
if (VERSION.SDK_INT < VERSION_CODES.HONEYCOMB_MR1 || bmp == null || !bmp.sameAs(bitmap)) {
|
||||
if (bmp == null || !bmp.sameAs(bitmap)) {
|
||||
bmp = bitmap;
|
||||
invalidateSelf();
|
||||
}
|
||||
@@ -188,5 +178,4 @@ class EmojiProvider {
|
||||
@Override
|
||||
public void setColorFilter(ColorFilter cf) { }
|
||||
}
|
||||
|
||||
}
|
||||
@@ -20,7 +20,6 @@ import androidx.core.content.ContextCompat;
|
||||
import androidx.core.widget.TextViewCompat;
|
||||
|
||||
import org.thoughtcrime.securesms.R;
|
||||
import org.thoughtcrime.securesms.components.emoji.EmojiProvider.EmojiDrawable;
|
||||
import org.thoughtcrime.securesms.components.emoji.parsing.EmojiParser;
|
||||
import org.thoughtcrime.securesms.components.mention.MentionAnnotation;
|
||||
import org.thoughtcrime.securesms.components.mention.MentionRendererDelegate;
|
||||
@@ -233,8 +232,8 @@ public class EmojiTextView extends AppCompatTextView {
|
||||
|
||||
@Override
|
||||
public void invalidateDrawable(@NonNull Drawable drawable) {
|
||||
if (drawable instanceof EmojiDrawable) invalidate();
|
||||
else super.invalidateDrawable(drawable);
|
||||
if (drawable instanceof EmojiProvider.EmojiDrawable) invalidate();
|
||||
else super.invalidateDrawable(drawable);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -7,9 +7,10 @@ import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import org.thoughtcrime.securesms.components.emoji.parsing.EmojiParser;
|
||||
import org.thoughtcrime.securesms.emoji.EmojiSource;
|
||||
import org.thoughtcrime.securesms.emoji.ObsoleteEmoji;
|
||||
import org.thoughtcrime.securesms.util.StringUtil;
|
||||
import org.thoughtcrime.securesms.util.Util;
|
||||
import org.whispersystems.libsignal.util.Pair;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
@@ -19,38 +20,10 @@ import java.util.Set;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
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 static final Pattern EMOJI_PATTERN = Pattern.compile("^(?:(?:[\u00a9\u00ae\u203c\u2049\u2122\u2139\u2194-\u2199\u21a9-\u21aa\u231a-\u231b\u2328\u23cf\u23e9-\u23f3\u23f8-\u23fa\u24c2\u25aa-\u25ab\u25b6\u25c0\u25fb-\u25fe\u2600-\u2604\u260e\u2611\u2614-\u2615\u2618\u261d\u2620\u2622-\u2623\u2626\u262a\u262e-\u262f\u2638-\u263a\u2648-\u2653\u2660\u2663\u2665-\u2666\u2668\u267b\u267f\u2692-\u2694\u2696-\u2697\u2699\u269b-\u269c\u26a0-\u26a1\u26aa-\u26ab\u26b0-\u26b1\u26bd-\u26be\u26c4-\u26c5\u26c8\u26ce-\u26cf\u26d1\u26d3-\u26d4\u26e9-\u26ea\u26f0-\u26f5\u26f7-\u26fa\u26fd\u2702\u2705\u2708-\u270d\u270f\u2712\u2714\u2716\u271d\u2721\u2728\u2733-\u2734\u2744\u2747\u274c\u274e\u2753-\u2755\u2757\u2763-\u2764\u2795-\u2797\u27a1\u27b0\u27bf\u2934-\u2935\u2b05-\u2b07\u2b1b-\u2b1c\u2b50\u2b55\u3030\u303d\u3297\u3299\ud83c\udc04\ud83c\udccf\ud83c\udd70-\ud83c\udd71\ud83c\udd7e-\ud83c\udd7f\ud83c\udd8e\ud83c\udd91-\ud83c\udd9a\ud83c\ude01-\ud83c\ude02\ud83c\ude1a\ud83c\ude2f\ud83c\ude32-\ud83c\ude3a\ud83c\ude50-\ud83c\ude51\u200d\ud83c\udf00-\ud83d\uddff\ud83d\ude00-\ud83d\ude4f\ud83d\ude80-\ud83d\udeff\ud83e\udd00-\ud83e\uddff\udb40\udc20-\udb40\udc7f]|\u200d[\u2640\u2642]|[\ud83c\udde6-\ud83c\uddff]{2}|.[\u20e0\u20e3\ufe0f]+)+)+$");
|
||||
|
||||
private EmojiUtil() {}
|
||||
|
||||
public static List<EmojiPageModel> getDisplayPages() {
|
||||
return EmojiPages.DISPLAY_PAGES;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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'.
|
||||
@@ -60,11 +33,11 @@ public final class EmojiUtil {
|
||||
|
||||
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());
|
||||
for (ObsoleteEmoji obsoleteEmoji : EmojiSource.getLatest().getObsolete()) {
|
||||
if (obsoleteEmoji.getObsolete().equals(emoji)) {
|
||||
out.add(obsoleteEmoji.getReplaceWith());
|
||||
} else if (obsoleteEmoji.getReplaceWith().equals(emoji)) {
|
||||
out.add(obsoleteEmoji.getObsolete());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -79,7 +52,7 @@ public final class EmojiUtil {
|
||||
* 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);
|
||||
String canonical = EmojiSource.getLatest().getVariationMap().get(emoji);
|
||||
return canonical != null ? canonical : emoji;
|
||||
}
|
||||
|
||||
|
||||
@@ -2,10 +2,12 @@ package org.thoughtcrime.securesms.components.emoji;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import android.net.Uri;
|
||||
import android.preference.PreferenceManager;
|
||||
|
||||
import androidx.annotation.MainThread;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.annimon.stream.Stream;
|
||||
import com.fasterxml.jackson.databind.type.CollectionType;
|
||||
@@ -63,11 +65,7 @@ public class RecentEmojiPageModel implements EmojiPageModel {
|
||||
return Stream.of(getEmoji()).map(Emoji::new).toList();
|
||||
}
|
||||
|
||||
@Override public boolean hasSpriteMap() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override public String getSprite() {
|
||||
@Override public @Nullable Uri getSpriteUri() {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,23 +1,25 @@
|
||||
package org.thoughtcrime.securesms.components.emoji;
|
||||
|
||||
import android.net.Uri;
|
||||
|
||||
import androidx.annotation.AttrRes;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
public class StaticEmojiPageModel implements EmojiPageModel {
|
||||
@AttrRes private final int iconAttr;
|
||||
@NonNull private final List<Emoji> emoji;
|
||||
@Nullable private final String sprite;
|
||||
@Nullable private final Uri sprite;
|
||||
|
||||
public StaticEmojiPageModel(@AttrRes int iconAttr, @NonNull String[] strings, @Nullable String sprite) {
|
||||
public StaticEmojiPageModel(@AttrRes int iconAttr, @NonNull String[] strings, @Nullable Uri sprite) {
|
||||
List<Emoji> emoji = new ArrayList<>(strings.length);
|
||||
for (String s : strings) {
|
||||
emoji.add(new Emoji(s));
|
||||
emoji.add(new Emoji(Collections.singletonList(s)));
|
||||
}
|
||||
|
||||
this.iconAttr = iconAttr;
|
||||
@@ -25,9 +27,9 @@ public class StaticEmojiPageModel implements EmojiPageModel {
|
||||
this.sprite = sprite;
|
||||
}
|
||||
|
||||
public StaticEmojiPageModel(@AttrRes int iconAttr, @NonNull Emoji[] emoji, @Nullable String sprite) {
|
||||
public StaticEmojiPageModel(@AttrRes int iconAttr, @NonNull List<Emoji> emoji, @Nullable Uri sprite) {
|
||||
this.iconAttr = iconAttr;
|
||||
this.emoji = Arrays.asList(emoji);
|
||||
this.emoji = Collections.unmodifiableList(emoji);
|
||||
this.sprite = sprite;
|
||||
}
|
||||
|
||||
@@ -50,12 +52,7 @@ public class StaticEmojiPageModel implements EmojiPageModel {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasSpriteMap() {
|
||||
return sprite != null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable String getSprite() {
|
||||
public @Nullable Uri getSpriteUri() {
|
||||
return sprite;
|
||||
}
|
||||
|
||||
|
||||
@@ -3,17 +3,19 @@ package org.thoughtcrime.securesms.components.emoji.parsing;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import org.thoughtcrime.securesms.emoji.EmojiPageReference;
|
||||
|
||||
public class EmojiDrawInfo {
|
||||
|
||||
private final EmojiPageBitmap page;
|
||||
private final int index;
|
||||
private final EmojiPageReference page;
|
||||
private final int index;
|
||||
|
||||
public EmojiDrawInfo(final @NonNull EmojiPageBitmap page, final int index) {
|
||||
public EmojiDrawInfo(final @NonNull EmojiPageReference page, final int index) {
|
||||
this.page = page;
|
||||
this.index = index;
|
||||
}
|
||||
|
||||
public @NonNull EmojiPageBitmap getPage() {
|
||||
public @NonNull EmojiPageReference getPage() {
|
||||
return page;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,103 +0,0 @@
|
||||
package org.thoughtcrime.securesms.components.emoji.parsing;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.content.Context;
|
||||
import android.content.res.AssetManager;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.BitmapFactory;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import org.signal.core.util.ThreadUtil;
|
||||
import org.signal.core.util.logging.Log;
|
||||
import org.thoughtcrime.securesms.components.emoji.EmojiPageModel;
|
||||
import org.thoughtcrime.securesms.util.ListenableFutureTask;
|
||||
import org.thoughtcrime.securesms.util.Stopwatch;
|
||||
import org.thoughtcrime.securesms.util.Util;
|
||||
import org.thoughtcrime.securesms.util.concurrent.SimpleTask;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.lang.ref.SoftReference;
|
||||
import java.util.concurrent.Callable;
|
||||
|
||||
public class EmojiPageBitmap {
|
||||
|
||||
private static final String TAG = Log.tag(EmojiPageBitmap.class);
|
||||
|
||||
private final Context context;
|
||||
private final EmojiPageModel model;
|
||||
private final float decodeScale;
|
||||
|
||||
private SoftReference<Bitmap> bitmapReference;
|
||||
private ListenableFutureTask<Bitmap> task;
|
||||
|
||||
public EmojiPageBitmap(@NonNull Context context, @NonNull EmojiPageModel model, float decodeScale) {
|
||||
this.context = context.getApplicationContext();
|
||||
this.model = model;
|
||||
this.decodeScale = decodeScale;
|
||||
}
|
||||
|
||||
@SuppressLint("StaticFieldLeak")
|
||||
public ListenableFutureTask<Bitmap> get() {
|
||||
ThreadUtil.assertMainThread();
|
||||
|
||||
if (bitmapReference != null && bitmapReference.get() != null) {
|
||||
return new ListenableFutureTask<>(bitmapReference.get());
|
||||
} else if (task != null) {
|
||||
return task;
|
||||
} else {
|
||||
Callable<Bitmap> callable = () -> {
|
||||
try {
|
||||
Log.i(TAG, "loading page " + model.getSprite());
|
||||
return loadPage();
|
||||
} catch (IOException ioe) {
|
||||
Log.w(TAG, ioe);
|
||||
}
|
||||
return null;
|
||||
};
|
||||
task = new ListenableFutureTask<>(callable);
|
||||
SimpleTask.run(() -> {
|
||||
task.run();
|
||||
return null;
|
||||
},
|
||||
unused -> task = null);
|
||||
}
|
||||
return task;
|
||||
}
|
||||
|
||||
private Bitmap loadPage() throws IOException {
|
||||
if (bitmapReference != null && bitmapReference.get() != null) return bitmapReference.get();
|
||||
|
||||
|
||||
float scale = decodeScale;
|
||||
AssetManager assetManager = context.getAssets();
|
||||
InputStream assetStream = assetManager.open(model.getSprite());
|
||||
BitmapFactory.Options options = new BitmapFactory.Options();
|
||||
|
||||
if (Util.isLowMemory(context)) {
|
||||
Log.i(TAG, "Low memory detected. Changing sample size.");
|
||||
options.inSampleSize = 2;
|
||||
scale = decodeScale * 2;
|
||||
}
|
||||
|
||||
Stopwatch stopwatch = new Stopwatch(model.getSprite());
|
||||
Bitmap bitmap = BitmapFactory.decodeStream(assetStream, null, options);
|
||||
stopwatch.split("decode");
|
||||
|
||||
Bitmap scaledBitmap = Bitmap.createScaledBitmap(bitmap, (int)(bitmap.getWidth() * scale), (int)(bitmap.getHeight() * scale), true);
|
||||
stopwatch.split("scale");
|
||||
stopwatch.stop(TAG);
|
||||
|
||||
bitmapReference = new SoftReference<>(scaledBitmap);
|
||||
Log.i(TAG, "onPageLoaded(" + model.getSprite() + ") originalByteCount: " + bitmap.getByteCount()
|
||||
+ " scaledByteCount: " + scaledBitmap.getByteCount()
|
||||
+ " scaledSize: " + scaledBitmap.getWidth() + "x" + scaledBitmap.getHeight());
|
||||
return scaledBitmap;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NonNull String toString() {
|
||||
return model.getSprite();
|
||||
}
|
||||
}
|
||||
@@ -16,6 +16,7 @@ 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.emoji.EmojiSource;
|
||||
import org.thoughtcrime.securesms.stickers.StickerSearchRepository;
|
||||
import org.thoughtcrime.securesms.util.Throttler;
|
||||
|
||||
@@ -56,7 +57,7 @@ class ConversationStickerViewModel extends ViewModel {
|
||||
}
|
||||
|
||||
void onInputTextUpdated(@NonNull String text) {
|
||||
if (TextUtils.isEmpty(text) || text.length() > EmojiUtil.MAX_EMOJI_LENGTH) {
|
||||
if (TextUtils.isEmpty(text) || text.length() > EmojiSource.getLatest().getMaxEmojiLength()) {
|
||||
stickers.setValue(CursorList.emptyList());
|
||||
} else {
|
||||
repository.searchByEmoji(text, stickers::postValue);
|
||||
|
||||
@@ -84,8 +84,8 @@ public class ApplicationDependencies {
|
||||
private static volatile ViewOnceMessageManager viewOnceMessageManager;
|
||||
private static volatile ExpiringMessageManager expiringMessageManager;
|
||||
private static volatile Payments payments;
|
||||
private static volatile ShakeToReport shakeToReport;
|
||||
private static volatile SignalCallManager signalCallManager;
|
||||
private static volatile ShakeToReport shakeToReport;
|
||||
private static volatile OkHttpClient okHttpClient;
|
||||
|
||||
@MainThread
|
||||
@@ -451,7 +451,6 @@ public class ApplicationDependencies {
|
||||
synchronized (LOCK) {
|
||||
if (okHttpClient == null) {
|
||||
okHttpClient = new OkHttpClient.Builder()
|
||||
.proxySelector(new ContentProxySelector())
|
||||
.addInterceptor(new StandardUserAgentInterceptor())
|
||||
.dns(SignalServiceNetworkAccess.DNS)
|
||||
.build();
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
package org.thoughtcrime.securesms.emoji
|
||||
|
||||
import androidx.annotation.AttrRes
|
||||
import org.thoughtcrime.securesms.R
|
||||
|
||||
/**
|
||||
* All the different Emoji categories the app is aware of in the order we want to display them.
|
||||
*/
|
||||
enum class EmojiCategory(val priority: Int, val key: String, @AttrRes val icon: Int) {
|
||||
PEOPLE(0, "People", R.attr.emoji_category_people),
|
||||
NATURE(1, "Nature", R.attr.emoji_category_nature),
|
||||
FOODS(2, "Foods", R.attr.emoji_category_foods),
|
||||
ACTIVITY(3, "Activity", R.attr.emoji_category_activity),
|
||||
PLACES(4, "Places", R.attr.emoji_category_places),
|
||||
OBJECTS(5, "Objects", R.attr.emoji_category_objects),
|
||||
SYMBOLS(6, "Symbols", R.attr.emoji_category_symbols),
|
||||
FLAGS(7, "Flags", R.attr.emoji_category_flags),
|
||||
EMOTICONS(8, "Emoticons", R.attr.emoji_category_emoticons);
|
||||
|
||||
companion object {
|
||||
fun forKey(key: String) = values().first { it.key == key }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
package org.thoughtcrime.securesms.emoji
|
||||
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies
|
||||
import org.thoughtcrime.securesms.jobs.DownloadLatestEmojiDataJob
|
||||
import org.thoughtcrime.securesms.keyvalue.SignalStore
|
||||
import org.thoughtcrime.securesms.service.PersistentAlarmManagerListener
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
private val INTERVAL_WITHOUT_REMOTE_DOWNLOAD = TimeUnit.DAYS.toMillis(1)
|
||||
private val INTERVAL_WITH_REMOTE_DOWNLOAD = TimeUnit.DAYS.toMillis(7)
|
||||
|
||||
class EmojiDownloadListener : PersistentAlarmManagerListener() {
|
||||
|
||||
override fun getNextScheduledExecutionTime(context: Context): Long = SignalStore.emojiValues().nextScheduledCheck
|
||||
|
||||
override fun onAlarm(context: Context, scheduledTime: Long): Long {
|
||||
ApplicationDependencies.getJobManager().add(DownloadLatestEmojiDataJob(false))
|
||||
|
||||
val nextTime: Long = System.currentTimeMillis() + if (EmojiFiles.Version.exists(context)) INTERVAL_WITH_REMOTE_DOWNLOAD else INTERVAL_WITHOUT_REMOTE_DOWNLOAD
|
||||
|
||||
SignalStore.emojiValues().nextScheduledCheck = nextTime
|
||||
|
||||
return nextTime
|
||||
}
|
||||
|
||||
companion object {
|
||||
@JvmStatic
|
||||
fun schedule(context: Context) {
|
||||
EmojiDownloadListener().onReceive(context, Intent())
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,205 @@
|
||||
package org.thoughtcrime.securesms.emoji
|
||||
|
||||
import android.content.Context
|
||||
import android.net.Uri
|
||||
import com.fasterxml.jackson.annotation.JsonIgnore
|
||||
import com.fasterxml.jackson.annotation.JsonProperty
|
||||
import com.fasterxml.jackson.databind.ObjectMapper
|
||||
import com.fasterxml.jackson.module.kotlin.readValue
|
||||
import com.fasterxml.jackson.module.kotlin.registerKotlinModule
|
||||
import okio.Okio
|
||||
import org.signal.core.util.logging.Log
|
||||
import org.thoughtcrime.securesms.crypto.AttachmentSecretProvider
|
||||
import org.thoughtcrime.securesms.crypto.ModernDecryptingPartInputStream
|
||||
import org.thoughtcrime.securesms.crypto.ModernEncryptingPartOutputStream
|
||||
import org.thoughtcrime.securesms.mms.PartAuthority
|
||||
import java.io.File
|
||||
import java.io.IOException
|
||||
import java.io.InputStream
|
||||
import java.io.OutputStream
|
||||
import java.lang.Exception
|
||||
import java.util.UUID
|
||||
|
||||
/**
|
||||
* File structure:
|
||||
* <p>
|
||||
* emoji/
|
||||
* .version -- Contains MD5 hash of current version plus a uuid mapping
|
||||
* `uuid`/ -- Directory for a specific MD5hash underneath which all the data lives.
|
||||
* | .names -- Contains name mappings for downloaded files. When a file finishes downloading, we create a random UUID name for it and add it to .names
|
||||
* | `uuid1`
|
||||
* | `uuid2`
|
||||
* | ...
|
||||
* <p>
|
||||
* .version format:
|
||||
* <p>
|
||||
* {"version": ..., "uuid": "..."}
|
||||
* <p>
|
||||
* .name format:
|
||||
* [
|
||||
* {"name": "...", "uuid": "..."}
|
||||
* ]
|
||||
*/
|
||||
private const val TAG = "EmojiFiles"
|
||||
|
||||
private const val EMOJI_DIRECTORY = "emoji"
|
||||
private const val VERSION_FILE = ".version"
|
||||
private const val NAME_FILE = ".names"
|
||||
private const val EMOJI_JSON = "emoji_data.json"
|
||||
|
||||
private fun Context.getEmojiDirectory(): File = getDir(EMOJI_DIRECTORY, Context.MODE_PRIVATE)
|
||||
private fun Context.getVersionFile(): File = File(getEmojiDirectory(), VERSION_FILE)
|
||||
private fun Context.getNameFile(versionUuid: UUID): File = File(File(getEmojiDirectory(), versionUuid.toString()).apply { mkdir() }, NAME_FILE)
|
||||
|
||||
private fun getFilesUri(name: String, format: String): Uri = PartAuthority.getEmojiUri(name)
|
||||
|
||||
private fun getOutputStream(context: Context, outputFile: File): OutputStream {
|
||||
val attachmentSecret = AttachmentSecretProvider.getInstance(context).orCreateAttachmentSecret
|
||||
return ModernEncryptingPartOutputStream.createFor(attachmentSecret, outputFile, true).second
|
||||
}
|
||||
|
||||
private fun getInputStream(context: Context, inputFile: File): InputStream {
|
||||
val attachmentSecret = AttachmentSecretProvider.getInstance(context).orCreateAttachmentSecret
|
||||
return ModernDecryptingPartInputStream.createFor(attachmentSecret, inputFile, 0)
|
||||
}
|
||||
|
||||
object EmojiFiles {
|
||||
@JvmStatic
|
||||
fun getBaseDirectory(context: Context): File = context.getEmojiDirectory()
|
||||
|
||||
@JvmStatic
|
||||
fun delete(context: Context, version: Version, uuid: UUID) {
|
||||
try {
|
||||
version.getFile(context, uuid).delete()
|
||||
} catch (e: IOException) {
|
||||
Log.i(TAG, "Failed to delete file.")
|
||||
}
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun openForReading(context: Context, name: String): InputStream {
|
||||
val version: Version = Version.readVersion(context) ?: throw IOException("No emoji version is present on disk")
|
||||
val names: NameCollection = NameCollection.read(context, version)
|
||||
val dataUuid: UUID = names.getUUIDForName(name) ?: throw IOException("Could not get UUID for name $name")
|
||||
val file: File = version.getFile(context, dataUuid)
|
||||
|
||||
return getInputStream(context, file)
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun openForWriting(context: Context, version: Version, uuid: UUID): OutputStream {
|
||||
return getOutputStream(context, version.getFile(context, uuid))
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun getMd5(context: Context, version: Version, uuid: UUID): ByteArray? {
|
||||
val file = version.getFile(context, uuid)
|
||||
|
||||
try {
|
||||
getInputStream(context, file).use {
|
||||
return Okio.buffer(Okio.source(it)).buffer.md5().toByteArray()
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Log.i(TAG, "Could not read emoji data file md5", e)
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun getLatestEmojiData(context: Context, version: Version): EmojiData? {
|
||||
val names = NameCollection.read(context, version)
|
||||
val dataUuid = names.getUUIDForEmojiData() ?: return null
|
||||
val file = version.getFile(context, dataUuid)
|
||||
|
||||
getInputStream(context, file).use {
|
||||
return EmojiJsonParser.parse(it, ::getFilesUri).getOrElse { throwable ->
|
||||
Log.w(TAG, "Failed to parse emoji_data", throwable)
|
||||
null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class Version(@JsonProperty val version: Int, @JsonProperty val uuid: UUID, @JsonProperty val density: String) {
|
||||
|
||||
fun getFile(context: Context, uuid: UUID): File = File(getDirectory(context), uuid.toString())
|
||||
|
||||
private fun getDirectory(context: Context): File = File(context.getEmojiDirectory(), this.uuid.toString()).apply { mkdir() }
|
||||
|
||||
companion object {
|
||||
|
||||
private val objectMapper = ObjectMapper().registerKotlinModule()
|
||||
|
||||
@JvmStatic
|
||||
fun exists(context: Context): Boolean = context.getVersionFile().exists()
|
||||
|
||||
@JvmStatic
|
||||
fun readVersion(context: Context): Version? {
|
||||
try {
|
||||
getInputStream(context, context.getVersionFile()).use {
|
||||
return objectMapper.readValue(it, Version::class.java)
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Log.w(TAG, "Could not read current emoji version from disk.")
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun writeVersion(context: Context, version: Version) {
|
||||
val versionFile: File = context.getVersionFile()
|
||||
|
||||
try {
|
||||
if (versionFile.exists()) {
|
||||
versionFile.delete()
|
||||
}
|
||||
|
||||
getOutputStream(context, versionFile).use {
|
||||
objectMapper.writeValue(it, version)
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Log.w(TAG, "Could not write current emoji version from disk.")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class Name(@JsonProperty val name: String, @JsonProperty val uuid: UUID) {
|
||||
companion object {
|
||||
@JvmStatic
|
||||
fun forEmojiDataJson(): Name = Name(EMOJI_JSON, UUID.randomUUID())
|
||||
}
|
||||
}
|
||||
|
||||
class NameCollection(@JsonProperty val versionUuid: UUID, @JsonProperty val names: List<Name>) {
|
||||
companion object {
|
||||
|
||||
private val objectMapper = ObjectMapper().registerKotlinModule()
|
||||
|
||||
@JvmStatic
|
||||
fun read(context: Context, version: Version): NameCollection {
|
||||
try {
|
||||
getInputStream(context, context.getNameFile(version.uuid)).use {
|
||||
return objectMapper.readValue(it)
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
return NameCollection(version.uuid, listOf())
|
||||
}
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun append(context: Context, nameCollection: NameCollection, name: Name): NameCollection {
|
||||
val collection = NameCollection(nameCollection.versionUuid, nameCollection.names + name)
|
||||
getOutputStream(context, context.getNameFile(nameCollection.versionUuid)).use {
|
||||
objectMapper.writeValue(it, collection)
|
||||
}
|
||||
return collection
|
||||
}
|
||||
}
|
||||
|
||||
@JsonIgnore
|
||||
fun getUUIDForEmojiData(): UUID? = getUUIDForName(EMOJI_JSON)
|
||||
|
||||
@JsonIgnore
|
||||
fun getUUIDForName(name: String): UUID? = names.firstOrNull { it.name == name }?.uuid
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,115 @@
|
||||
package org.thoughtcrime.securesms.emoji
|
||||
|
||||
import android.net.Uri
|
||||
import com.fasterxml.jackson.databind.JsonNode
|
||||
import com.fasterxml.jackson.databind.ObjectMapper
|
||||
import com.google.android.gms.common.util.Hex
|
||||
import org.thoughtcrime.securesms.components.emoji.CompositeEmojiPageModel
|
||||
import org.thoughtcrime.securesms.components.emoji.Emoji
|
||||
import org.thoughtcrime.securesms.components.emoji.EmojiPageModel
|
||||
import org.thoughtcrime.securesms.components.emoji.StaticEmojiPageModel
|
||||
import java.io.InputStream
|
||||
import java.nio.charset.Charset
|
||||
|
||||
typealias UriFactory = (sprite: String, format: String) -> Uri
|
||||
|
||||
/**
|
||||
* Takes an emoji_data.json file data and parses it into an EmojiSource
|
||||
*/
|
||||
object EmojiJsonParser {
|
||||
private val OBJECT_MAPPER = ObjectMapper()
|
||||
|
||||
@JvmStatic
|
||||
fun verify(body: InputStream) {
|
||||
parse(body) { _, _ -> Uri.EMPTY }.getOrThrow()
|
||||
}
|
||||
|
||||
fun parse(body: InputStream, uriFactory: UriFactory): Result<ParsedEmojiData> {
|
||||
return try {
|
||||
Result.success(buildEmojiSourceFromNode(OBJECT_MAPPER.readTree(body), uriFactory))
|
||||
} catch (e: Exception) {
|
||||
Result.failure(e)
|
||||
}
|
||||
}
|
||||
|
||||
private fun buildEmojiSourceFromNode(node: JsonNode, uriFactory: UriFactory): ParsedEmojiData {
|
||||
val format: String = node["format"].textValue()
|
||||
val obsolete: List<ObsoleteEmoji> = node["obsolete"].toObseleteList()
|
||||
val dataPages: List<EmojiPageModel> = getDataPages(format, node["emoji"], uriFactory)
|
||||
val displayPages: List<EmojiPageModel> = mergeToDisplayPages(dataPages)
|
||||
val metrics: EmojiMetrics = node["metrics"].toEmojiMetrics()
|
||||
val densities: List<String> = node["densities"].toDensityList()
|
||||
|
||||
return ParsedEmojiData(metrics, densities, format, displayPages, dataPages, obsolete)
|
||||
}
|
||||
|
||||
private fun getDataPages(format: String, emoji: JsonNode, uriFactory: UriFactory): List<EmojiPageModel> {
|
||||
return emoji.fields()
|
||||
.asSequence()
|
||||
.sortedWith { lhs, rhs ->
|
||||
val lhsCategory = EmojiCategory.forKey(lhs.key.asCategoryKey())
|
||||
val rhsCategory = EmojiCategory.forKey(rhs.key.asCategoryKey())
|
||||
val comp = lhsCategory.priority.compareTo(rhsCategory.priority)
|
||||
|
||||
if (comp == 0) {
|
||||
val lhsIndex = lhs.key.getPageIndex()
|
||||
val rhsIndex = rhs.key.getPageIndex()
|
||||
|
||||
lhsIndex.compareTo(rhsIndex)
|
||||
} else {
|
||||
comp
|
||||
}
|
||||
}
|
||||
.map { createPage(it.key, format, it.value, uriFactory) }
|
||||
.toList()
|
||||
}
|
||||
|
||||
private fun createPage(pageName: String, format: String, page: JsonNode, uriFactory: UriFactory): EmojiPageModel {
|
||||
val category = EmojiCategory.forKey(pageName.asCategoryKey())
|
||||
val pageList = page.mapIndexed { i, data ->
|
||||
if (data.size() == 0) {
|
||||
throw IllegalStateException("Page index $pageName.$i had no data")
|
||||
} else {
|
||||
Emoji(data.map { it.textValue().encodeAsUtf16() })
|
||||
}
|
||||
}
|
||||
|
||||
return StaticEmojiPageModel(category.icon, pageList, uriFactory(pageName, format))
|
||||
}
|
||||
|
||||
private fun mergeToDisplayPages(dataPages: List<EmojiPageModel>): List<EmojiPageModel> {
|
||||
return dataPages.groupBy { it.iconAttr }
|
||||
.map { (icon, pages) -> if (pages.size <= 1) pages.first() else CompositeEmojiPageModel(icon, pages) }
|
||||
}
|
||||
}
|
||||
|
||||
private fun JsonNode?.toObseleteList(): List<ObsoleteEmoji> {
|
||||
return if (this == null) {
|
||||
listOf()
|
||||
} else {
|
||||
map { node ->
|
||||
ObsoleteEmoji(node["obsoleted"].textValue().encodeAsUtf16(), node["replace_with"].textValue().encodeAsUtf16())
|
||||
}.toList()
|
||||
}
|
||||
}
|
||||
|
||||
private fun JsonNode.toEmojiMetrics(): EmojiMetrics {
|
||||
return EmojiMetrics(this["raw_width"].asInt(), this["raw_height"].asInt(), this["per_row"].asInt())
|
||||
}
|
||||
|
||||
private fun JsonNode.toDensityList(): List<String> {
|
||||
return map { it.textValue() }
|
||||
}
|
||||
|
||||
private fun String.encodeAsUtf16() = String(Hex.stringToBytes(this), Charset.forName("UTF-16"))
|
||||
private fun String.asCategoryKey() = replace("(_\\d+)*$".toRegex(), "")
|
||||
private fun String.getPageIndex() = "^.*_(\\d+)+$".toRegex().find(this)?.let { it.groupValues[1] }?.toInt() ?: throw IllegalStateException("No index.")
|
||||
|
||||
data class ParsedEmojiData(
|
||||
override val metrics: EmojiMetrics,
|
||||
override val densities: List<String>,
|
||||
override val format: String,
|
||||
override val displayPages: List<EmojiPageModel>,
|
||||
override val dataPages: List<EmojiPageModel>,
|
||||
override val obsolete: List<ObsoleteEmoji>
|
||||
) : EmojiData
|
||||
@@ -0,0 +1,22 @@
|
||||
package org.thoughtcrime.securesms.emoji
|
||||
|
||||
import android.net.Uri
|
||||
import org.thoughtcrime.securesms.mms.DecryptableStreamUriLoader
|
||||
|
||||
/**
|
||||
* Used by Emoji provider to set up a glide request.
|
||||
*/
|
||||
class EmojiPageReference {
|
||||
|
||||
val model: Any
|
||||
|
||||
constructor(uri: Uri) {
|
||||
model = uri
|
||||
}
|
||||
|
||||
constructor(decryptableUri: DecryptableStreamUriLoader.DecryptableUri) {
|
||||
model = decryptableUri
|
||||
}
|
||||
}
|
||||
|
||||
typealias EmojiPageReferenceFactory = (uri: Uri) -> EmojiPageReference
|
||||
@@ -0,0 +1,95 @@
|
||||
package org.thoughtcrime.securesms.emoji
|
||||
|
||||
import okhttp3.Request
|
||||
import okhttp3.Response
|
||||
import org.signal.core.util.logging.Log
|
||||
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies
|
||||
import java.io.IOException
|
||||
|
||||
private const val VERSION_URL = "https://updates.signal.org/dynamic/android/emoji/version.txt"
|
||||
private const val BASE_STATIC_BUCKET_URL = "https://updates.signal.org/static/android/emoji"
|
||||
|
||||
/**
|
||||
* Responsible for communicating with S3 to download Emoji related objects.
|
||||
*/
|
||||
object EmojiRemote {
|
||||
|
||||
private const val TAG = "EmojiRemote"
|
||||
|
||||
private val okHttpClient = ApplicationDependencies.getOkHttpClient()
|
||||
|
||||
@JvmStatic
|
||||
@Throws(IOException::class)
|
||||
fun getVersion(): Int {
|
||||
val request = Request.Builder()
|
||||
.get()
|
||||
.url(VERSION_URL)
|
||||
.build()
|
||||
|
||||
try {
|
||||
okHttpClient.newCall(request).execute().use { response ->
|
||||
if (!response.isSuccessful) {
|
||||
throw IOException()
|
||||
}
|
||||
|
||||
return response.body()?.bytes()?.let { String(it).trim().toIntOrNull() } ?: throw IOException()
|
||||
}
|
||||
} catch (e: IOException) {
|
||||
throw e
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Downloads and returns the MD5 hash stored in an S3 object's ETag
|
||||
*/
|
||||
@JvmStatic
|
||||
fun getMd5(emojiRequest: EmojiRequest): ByteArray? {
|
||||
val request = Request.Builder()
|
||||
.head()
|
||||
.url(emojiRequest.url)
|
||||
.build()
|
||||
|
||||
try {
|
||||
okHttpClient.newCall(request).execute().use { response ->
|
||||
if (!response.isSuccessful) {
|
||||
throw IOException()
|
||||
}
|
||||
|
||||
return response.header("ETag")?.toByteArray()
|
||||
}
|
||||
} catch (e: IOException) {
|
||||
Log.w(TAG, "Could not retrieve md5", e)
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Downloads an object for the specified name.
|
||||
*/
|
||||
@JvmStatic
|
||||
fun getObject(emojiRequest: EmojiRequest): Response {
|
||||
val request = Request.Builder()
|
||||
.get()
|
||||
.url(emojiRequest.url)
|
||||
.build()
|
||||
|
||||
return okHttpClient.newCall(request).execute()
|
||||
}
|
||||
}
|
||||
|
||||
interface EmojiRequest {
|
||||
val url: String
|
||||
}
|
||||
|
||||
class EmojiJsonRequest(version: Int) : EmojiRequest {
|
||||
override val url: String = "$BASE_STATIC_BUCKET_URL/$version/emoji_data.json"
|
||||
}
|
||||
|
||||
class EmojiImageRequest(
|
||||
version: Int,
|
||||
density: String,
|
||||
name: String,
|
||||
format: String
|
||||
) : EmojiRequest {
|
||||
override val url: String = "$BASE_STATIC_BUCKET_URL/$version/$density/$name.$format"
|
||||
}
|
||||
@@ -0,0 +1,159 @@
|
||||
package org.thoughtcrime.securesms.emoji
|
||||
|
||||
import android.net.Uri
|
||||
import androidx.annotation.WorkerThread
|
||||
import org.thoughtcrime.securesms.R
|
||||
import org.thoughtcrime.securesms.components.emoji.Emoji
|
||||
import org.thoughtcrime.securesms.components.emoji.EmojiPageModel
|
||||
import org.thoughtcrime.securesms.components.emoji.StaticEmojiPageModel
|
||||
import org.thoughtcrime.securesms.components.emoji.parsing.EmojiDrawInfo
|
||||
import org.thoughtcrime.securesms.components.emoji.parsing.EmojiTree
|
||||
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies
|
||||
import org.thoughtcrime.securesms.mms.DecryptableStreamUriLoader
|
||||
import org.thoughtcrime.securesms.util.ScreenDensity
|
||||
import java.io.InputStream
|
||||
import java.util.concurrent.CountDownLatch
|
||||
import java.util.concurrent.atomic.AtomicReference
|
||||
import kotlin.math.min
|
||||
|
||||
/**
|
||||
* The entry point for the application to request Emoji data for custom emojis.
|
||||
*/
|
||||
class EmojiSource(
|
||||
val decodeScale: Float,
|
||||
private val emojiData: EmojiData,
|
||||
private val emojiPageReferenceFactory: EmojiPageReferenceFactory
|
||||
) : EmojiData by emojiData {
|
||||
|
||||
val variationMap: Map<String, String> by lazy {
|
||||
val map = mutableMapOf<String, String>()
|
||||
|
||||
for (page: EmojiPageModel in dataPages) {
|
||||
for (emoji: Emoji in page.displayEmoji) {
|
||||
for (variation: String in emoji.variations) {
|
||||
map[variation] = emoji.value
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
map
|
||||
}
|
||||
|
||||
val maxEmojiLength: Int by lazy {
|
||||
dataPages.map { it.emoji.map(String::length) }
|
||||
.flatten()
|
||||
.maxOrZero()
|
||||
}
|
||||
|
||||
val emojiTree: EmojiTree by lazy {
|
||||
val tree = EmojiTree()
|
||||
|
||||
dataPages
|
||||
.filter { it.spriteUri != null }
|
||||
.forEach { page ->
|
||||
val reference = emojiPageReferenceFactory(page.spriteUri!!)
|
||||
page.emoji.forEachIndexed { idx, emoji ->
|
||||
tree.add(emoji, EmojiDrawInfo(reference, idx))
|
||||
}
|
||||
}
|
||||
|
||||
obsolete.forEach {
|
||||
tree.add(it.obsolete, tree.getEmoji(it.replaceWith, 0, it.replaceWith.length))
|
||||
}
|
||||
|
||||
tree
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
private val emojiSource = AtomicReference<EmojiSource>()
|
||||
private val emojiLatch = CountDownLatch(1)
|
||||
|
||||
@JvmStatic
|
||||
val latest: EmojiSource
|
||||
get() {
|
||||
emojiLatch.await()
|
||||
return emojiSource.get()
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
@WorkerThread
|
||||
fun refresh() {
|
||||
emojiSource.set(getEmojiSource())
|
||||
emojiLatch.countDown()
|
||||
}
|
||||
|
||||
private fun getEmojiSource(): EmojiSource {
|
||||
return loadRemoteBasedEmojis() ?: loadAssetBasedEmojis()
|
||||
}
|
||||
|
||||
private fun loadRemoteBasedEmojis(): EmojiSource? {
|
||||
val context = ApplicationDependencies.getApplication()
|
||||
val version = EmojiFiles.Version.readVersion(context) ?: return null
|
||||
val emojiData = EmojiFiles.getLatestEmojiData(context, version)
|
||||
val density = ScreenDensity.xhdpiRelativeDensityScaleFactor(version.density)
|
||||
|
||||
return emojiData?.let {
|
||||
val decodeScale = min(1f, context.resources.getDimension(R.dimen.emoji_drawer_size) / it.metrics.rawHeight)
|
||||
|
||||
EmojiSource(decodeScale * density, it) { uri: Uri -> EmojiPageReference(DecryptableStreamUriLoader.DecryptableUri(uri)) }
|
||||
}
|
||||
}
|
||||
|
||||
private fun loadAssetBasedEmojis(): EmojiSource {
|
||||
val context = ApplicationDependencies.getApplication()
|
||||
val emojiData: InputStream = ApplicationDependencies.getApplication().assets.open("emoji/emoji_data.json")
|
||||
|
||||
emojiData.use {
|
||||
val parsedData: ParsedEmojiData = EmojiJsonParser.parse(it, ::getAssetsUri).getOrThrow()
|
||||
val decodeScale = min(1f, context.resources.getDimension(R.dimen.emoji_drawer_size) / parsedData.metrics.rawHeight)
|
||||
return EmojiSource(
|
||||
decodeScale * ScreenDensity.xhdpiRelativeDensityScaleFactor("xhdpi"),
|
||||
parsedData.copy(
|
||||
displayPages = parsedData.displayPages + PAGE_EMOTICONS,
|
||||
dataPages = parsedData.dataPages + PAGE_EMOTICONS
|
||||
)
|
||||
) { uri: Uri -> EmojiPageReference(uri) }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun List<Int>.maxOrZero(): Int = maxOrNull() ?: 0
|
||||
|
||||
interface EmojiData {
|
||||
val metrics: EmojiMetrics
|
||||
val densities: List<String>
|
||||
val format: String
|
||||
val displayPages: List<EmojiPageModel>
|
||||
val dataPages: List<EmojiPageModel>
|
||||
val obsolete: List<ObsoleteEmoji>
|
||||
}
|
||||
|
||||
data class ObsoleteEmoji(val obsolete: String, val replaceWith: String)
|
||||
|
||||
data class EmojiMetrics(val rawHeight: Int, val rawWidth: Int, val perRow: Int)
|
||||
|
||||
private fun getAssetsUri(name: String, format: String): Uri = Uri.parse("file:///android_asset/emoji/$name.$format")
|
||||
|
||||
private val PAGE_EMOTICONS: EmojiPageModel = StaticEmojiPageModel(
|
||||
EmojiCategory.EMOTICONS.icon,
|
||||
arrayOf(
|
||||
":-)", ";-)", "(-:", ":->", ":-D", "\\o/",
|
||||
":-P", "B-)", ":-$", ":-*", "O:-)", "=-O",
|
||||
"O_O", "O_o", "o_O", ":O", ":-!", ":-x",
|
||||
":-|", ":-\\", ":-(", ":'(", ":-[", ">:-(",
|
||||
"^.^", "^_^", "\\(\u02c6\u02da\u02c6)/",
|
||||
"\u30fd(\u00b0\u25c7\u00b0 )\u30ce", "\u00af\\(\u00b0_o)/\u00af",
|
||||
"\u00af\\_(\u30c4)_/\u00af", "(\u00ac_\u00ac)",
|
||||
"(>_<)", "(\u2565\ufe4f\u2565)", "(\u261e\uff9f\u30ee\uff9f)\u261e",
|
||||
"\u261c(\uff9f\u30ee\uff9f\u261c)", "\u261c(\u2312\u25bd\u2312)\u261e",
|
||||
"(\u256f\u00b0\u25a1\u00b0)\u256f\ufe35", "\u253b\u2501\u253b",
|
||||
"\u252c\u2500\u252c", "\u30ce(\u00b0\u2013\u00b0\u30ce)",
|
||||
"(^._.^)\uff89", "\u0e05^\u2022\ufecc\u2022^\u0e05",
|
||||
"\u0295\u2022\u1d25\u2022\u0294", "(\u2022_\u2022)",
|
||||
" \u25a0-\u25a0\u00ac <(\u2022_\u2022) ", "(\u25a0_\u25a0\u00ac)",
|
||||
"\u01aa(\u0693\u05f2)\u200e\u01aa\u200b\u200b"
|
||||
),
|
||||
null
|
||||
)
|
||||
@@ -15,6 +15,7 @@ import androidx.recyclerview.widget.StaggeredGridLayoutManager;
|
||||
|
||||
import org.thoughtcrime.securesms.R;
|
||||
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
|
||||
import org.thoughtcrime.securesms.net.ContentProxySelector;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@@ -47,7 +48,7 @@ public class GiphyMp4Fragment extends Fragment {
|
||||
ContentLoadingProgressBar progressBar = view.findViewById(R.id.content_loading);
|
||||
TextView nothingFound = view.findViewById(R.id.nothing_found);
|
||||
GiphyMp4ViewModel viewModel = ViewModelProviders.of(requireActivity(), new GiphyMp4ViewModel.Factory(isForMms)).get(GiphyMp4ViewModel.class);
|
||||
GiphyMp4MediaSourceFactory mediaSourceFactory = new GiphyMp4MediaSourceFactory(ApplicationDependencies.getOkHttpClient());
|
||||
GiphyMp4MediaSourceFactory mediaSourceFactory = new GiphyMp4MediaSourceFactory(ApplicationDependencies.getOkHttpClient().newBuilder().proxySelector(new ContentProxySelector()).build());
|
||||
GiphyMp4Adapter adapter = new GiphyMp4Adapter(mediaSourceFactory, viewModel::saveToBlob);
|
||||
List<GiphyMp4ProjectionPlayerHolder> holders = GiphyMp4ProjectionPlayerHolder.injectVideoViews(requireContext(),
|
||||
getViewLifecycleOwner().getLifecycle(),
|
||||
|
||||
@@ -6,17 +6,15 @@ import android.text.TextUtils;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import org.signal.core.util.ThreadUtil;
|
||||
import org.signal.core.util.logging.Log;
|
||||
import org.signal.paging.PagedDataSource;
|
||||
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
|
||||
import org.thoughtcrime.securesms.giph.model.GiphyImage;
|
||||
import org.thoughtcrime.securesms.giph.model.GiphyResponse;
|
||||
import org.thoughtcrime.securesms.net.ContentProxySelector;
|
||||
import org.thoughtcrime.securesms.util.JsonUtils;
|
||||
import org.whispersystems.util.Base64;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
@@ -36,7 +34,7 @@ final class GiphyMp4PagedDataSource implements PagedDataSource<GiphyImage> {
|
||||
|
||||
GiphyMp4PagedDataSource(@Nullable String searchQuery) {
|
||||
this.searchString = searchQuery;
|
||||
this.client = ApplicationDependencies.getOkHttpClient();
|
||||
this.client = ApplicationDependencies.getOkHttpClient().newBuilder().proxySelector(new ContentProxySelector()).build();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -0,0 +1,80 @@
|
||||
package org.thoughtcrime.securesms.jobmanager.impl;
|
||||
|
||||
import android.app.job.JobInfo;
|
||||
import android.content.Context;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.RequiresApi;
|
||||
|
||||
import org.thoughtcrime.securesms.jobmanager.Constraint;
|
||||
import org.thoughtcrime.securesms.util.NetworkUtil;
|
||||
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* Constraint for Emoji Downloads which respects users settings regarding image downloads and requires network.
|
||||
*/
|
||||
public class AutoDownloadEmojiConstraint implements Constraint {
|
||||
|
||||
public static final String KEY = "AutoDownloadEmojiConstraint";
|
||||
|
||||
private static final String IMAGE_TYPE = "images";
|
||||
|
||||
private final Context context;
|
||||
|
||||
private AutoDownloadEmojiConstraint(@NonNull Context context) {
|
||||
this.context = context.getApplicationContext();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isMet() {
|
||||
return canAutoDownloadEmoji(context);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NonNull String getFactoryKey() {
|
||||
return KEY;
|
||||
}
|
||||
|
||||
@RequiresApi(26)
|
||||
@Override
|
||||
public void applyToJobInfo(@NonNull JobInfo.Builder jobInfoBuilder) {
|
||||
boolean canDownloadWhileRoaming = TextSecurePreferences.getRoamingMediaDownloadAllowed(context).contains(IMAGE_TYPE);
|
||||
boolean canDownloadWhileMobile = TextSecurePreferences.getMobileMediaDownloadAllowed(context).contains(IMAGE_TYPE);
|
||||
|
||||
if (canDownloadWhileRoaming) {
|
||||
jobInfoBuilder.setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY);
|
||||
} else if (canDownloadWhileMobile) {
|
||||
jobInfoBuilder.setRequiredNetworkType(JobInfo.NETWORK_TYPE_NOT_ROAMING);
|
||||
} else {
|
||||
jobInfoBuilder.setRequiredNetworkType(JobInfo.NETWORK_TYPE_UNMETERED);
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean canAutoDownloadEmoji(@NonNull Context context) {
|
||||
return getAllowedAutoDownloadTypes(context).contains(IMAGE_TYPE);
|
||||
}
|
||||
|
||||
private static @NonNull Set<String> getAllowedAutoDownloadTypes(@NonNull Context context) {
|
||||
if (NetworkUtil.isConnectedWifi(context)) return Collections.singleton(IMAGE_TYPE);
|
||||
else if (NetworkUtil.isConnectedRoaming(context)) return TextSecurePreferences.getRoamingMediaDownloadAllowed(context);
|
||||
else if (NetworkUtil.isConnectedMobile(context)) return TextSecurePreferences.getMobileMediaDownloadAllowed(context);
|
||||
else return Collections.emptySet();
|
||||
}
|
||||
|
||||
public static final class Factory implements Constraint.Factory<AutoDownloadEmojiConstraint> {
|
||||
|
||||
private final Context context;
|
||||
|
||||
public Factory(@NonNull Context context) {
|
||||
this.context = context.getApplicationContext();
|
||||
}
|
||||
|
||||
@Override
|
||||
public AutoDownloadEmojiConstraint create() {
|
||||
return new AutoDownloadEmojiConstraint(context);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,461 @@
|
||||
package org.thoughtcrime.securesms.jobs;
|
||||
|
||||
import android.content.Context;
|
||||
import android.net.Uri;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.annimon.stream.IntPair;
|
||||
import com.annimon.stream.Stream;
|
||||
import com.mobilecoin.lib.util.Hex;
|
||||
|
||||
import org.signal.core.util.logging.Log;
|
||||
import org.thoughtcrime.securesms.components.emoji.EmojiPageModel;
|
||||
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
|
||||
import org.thoughtcrime.securesms.emoji.EmojiData;
|
||||
import org.thoughtcrime.securesms.emoji.EmojiFiles;
|
||||
import org.thoughtcrime.securesms.emoji.EmojiImageRequest;
|
||||
import org.thoughtcrime.securesms.emoji.EmojiJsonRequest;
|
||||
import org.thoughtcrime.securesms.emoji.EmojiRemote;
|
||||
import org.thoughtcrime.securesms.emoji.EmojiSource;
|
||||
import org.thoughtcrime.securesms.jobmanager.Data;
|
||||
import org.thoughtcrime.securesms.jobmanager.Job;
|
||||
import org.thoughtcrime.securesms.jobmanager.impl.AutoDownloadEmojiConstraint;
|
||||
import org.thoughtcrime.securesms.jobmanager.impl.NetworkConstraint;
|
||||
import org.thoughtcrime.securesms.keyvalue.SignalStore;
|
||||
import org.thoughtcrime.securesms.util.FileUtils;
|
||||
import org.thoughtcrime.securesms.util.ScreenDensity;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import okhttp3.Response;
|
||||
import okhttp3.ResponseBody;
|
||||
import okio.HashingSink;
|
||||
import okio.Okio;
|
||||
import okio.Source;
|
||||
|
||||
/**
|
||||
* Downloads Emoji JSON and Images to local persistent storage.
|
||||
* <p>
|
||||
*/
|
||||
public class DownloadLatestEmojiDataJob extends BaseJob {
|
||||
|
||||
private static final long INTERVAL_WITHOUT_REMOTE_DOWNLOAD = TimeUnit.DAYS.toMillis(1);
|
||||
private static final long INTERVAL_WITH_REMOTE_DOWNLOAD = TimeUnit.DAYS.toMillis(7);
|
||||
|
||||
private static final String TAG = Log.tag(DownloadLatestEmojiDataJob.class);
|
||||
|
||||
public static final String KEY = "DownloadLatestEmojiDataJob";
|
||||
private static final String QUEUE_KEY = "EmojiDownloadJobs";
|
||||
private static final String VERSION_INT = "version_int";
|
||||
private static final String VERSION_UUID = "version_uuid";
|
||||
private static final String VERSION_DENSITY = "version_density";
|
||||
|
||||
private EmojiFiles.Version targetVersion;
|
||||
|
||||
public static void scheduleIfNecessary() {
|
||||
long nextScheduledCheck = SignalStore.emojiValues().getNextScheduledCheck();
|
||||
|
||||
if (nextScheduledCheck <= System.currentTimeMillis()) {
|
||||
Log.i(TAG, "Scheduling DownloadLatestEmojiDataJob.");
|
||||
ApplicationDependencies.getJobManager().add(new DownloadLatestEmojiDataJob(false));
|
||||
|
||||
long interval;
|
||||
if (EmojiFiles.Version.exists(ApplicationDependencies.getApplication())) {
|
||||
interval = INTERVAL_WITH_REMOTE_DOWNLOAD;
|
||||
} else {
|
||||
interval = INTERVAL_WITHOUT_REMOTE_DOWNLOAD;
|
||||
}
|
||||
|
||||
SignalStore.emojiValues().setNextScheduledCheck(System.currentTimeMillis() + interval);
|
||||
}
|
||||
}
|
||||
|
||||
public DownloadLatestEmojiDataJob(boolean force) {
|
||||
this(new Job.Parameters.Builder()
|
||||
.setQueue(QUEUE_KEY)
|
||||
.addConstraint(force ? NetworkConstraint.KEY : AutoDownloadEmojiConstraint.KEY)
|
||||
.setMaxInstancesForQueue(1)
|
||||
.setMaxAttempts(5)
|
||||
.setLifespan(TimeUnit.DAYS.toMillis(1))
|
||||
.build(), null);
|
||||
}
|
||||
|
||||
public DownloadLatestEmojiDataJob(@NonNull Parameters parameters, @Nullable EmojiFiles.Version targetVersion) {
|
||||
super(parameters);
|
||||
this.targetVersion = targetVersion;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onRun() throws Exception {
|
||||
EmojiFiles.Version version = EmojiFiles.Version.readVersion(context);
|
||||
int localVersion = (version != null) ? version.getVersion() : 0;
|
||||
int serverVersion = EmojiRemote.getVersion();
|
||||
String bucket;
|
||||
|
||||
if (targetVersion == null) {
|
||||
ScreenDensity density = ScreenDensity.get(context);
|
||||
|
||||
bucket = getDesiredRemoteBucketForDensity(density);
|
||||
} else {
|
||||
bucket = targetVersion.getDensity();
|
||||
}
|
||||
|
||||
Log.d(TAG, "LocalVersion: " + localVersion + ", SeverVersion: " + serverVersion + ", Bucket: " + bucket);
|
||||
|
||||
if (bucket == null) {
|
||||
Log.d(TAG, "This device has too low a display density to download remote emoji.");
|
||||
} else if (localVersion == serverVersion) {
|
||||
Log.d(TAG, "Already have latest emoji data. Skipping.");
|
||||
} else if (serverVersion > localVersion) {
|
||||
Log.d(TAG, "New server data detected. Starting download...");
|
||||
|
||||
if (targetVersion == null || targetVersion.getVersion() != serverVersion) {
|
||||
targetVersion = new EmojiFiles.Version(serverVersion, UUID.randomUUID(), bucket);
|
||||
}
|
||||
|
||||
if (isCanceled()) {
|
||||
Log.w(TAG, "Job was cancelled prior to downloading json.");
|
||||
return;
|
||||
}
|
||||
|
||||
EmojiData emojiData = downloadJson(context, targetVersion);
|
||||
List<String> supportedDensities = emojiData.getDensities();
|
||||
String format = emojiData.getFormat();
|
||||
List<String> imagePaths = Stream.of(emojiData.getDataPages())
|
||||
.map(EmojiPageModel::getSpriteUri)
|
||||
.map(Uri::getLastPathSegment)
|
||||
.toList();
|
||||
|
||||
String density = resolveDensity(supportedDensities, targetVersion.getDensity());
|
||||
targetVersion = new EmojiFiles.Version(targetVersion.getVersion(), targetVersion.getUuid(), density);
|
||||
|
||||
if (isCanceled()) {
|
||||
Log.w(TAG, "Job was cancelled after downloading json.");
|
||||
return;
|
||||
}
|
||||
|
||||
downloadImages(context, targetVersion, imagePaths, format, this::isCanceled);
|
||||
|
||||
if (isCanceled()) {
|
||||
Log.w(TAG, "Job was cancelled during or after downloading images.");
|
||||
return;
|
||||
}
|
||||
|
||||
clearOldEmojiData(context, targetVersion);
|
||||
markComplete(targetVersion);
|
||||
EmojiSource.refresh();
|
||||
} else {
|
||||
Log.d(TAG, "Server has an older version than we do. Skipping.");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean onShouldRetry(@NonNull Exception e) {
|
||||
return e instanceof IOException;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NonNull Data serialize() {
|
||||
if (targetVersion == null) {
|
||||
return Data.EMPTY;
|
||||
} else {
|
||||
return new Data.Builder()
|
||||
.putInt(VERSION_INT, targetVersion.getVersion())
|
||||
.putString(VERSION_UUID, targetVersion.getUuid().toString())
|
||||
.putString(VERSION_DENSITY, targetVersion.getDensity())
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NonNull String getFactoryKey() {
|
||||
return KEY;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure() {
|
||||
}
|
||||
|
||||
private static @Nullable String getDesiredRemoteBucketForDensity(@NonNull ScreenDensity screenDensity) {
|
||||
if (screenDensity.isKnownDensity()) {
|
||||
return screenDensity.getBucket();
|
||||
} else {
|
||||
return "xhdpi";
|
||||
}
|
||||
}
|
||||
|
||||
private static @Nullable String resolveDensity(@NonNull List<String> supportedDensities, @NonNull String desiredDensity) {
|
||||
if (supportedDensities.isEmpty()) {
|
||||
throw new IllegalStateException("Version does not have any supported densities.");
|
||||
}
|
||||
|
||||
if (supportedDensities.contains(desiredDensity)) {
|
||||
Log.d(TAG, "Version supports our density.");
|
||||
return desiredDensity;
|
||||
} else {
|
||||
Log.d(TAG, "Version does not support our density.");
|
||||
}
|
||||
|
||||
List<String> allDensities = Arrays.asList("ldpi", "mdpi", "hdpi", "xhdpi", "xxhdpi", "xxxhdpi");
|
||||
|
||||
int desiredIndex = allDensities.indexOf(desiredDensity);
|
||||
|
||||
if (desiredIndex == -1) {
|
||||
Log.d(TAG, "Unknown density. Falling back...");
|
||||
if (supportedDensities.contains("xhdpi")) {
|
||||
return "xhdpi";
|
||||
} else {
|
||||
return supportedDensities.get(0);
|
||||
}
|
||||
}
|
||||
|
||||
return Stream.of(allDensities)
|
||||
.indexed()
|
||||
.sorted((lhs, rhs) -> {
|
||||
int lhsDistance = Math.abs(desiredIndex - lhs.getFirst());
|
||||
int rhsDistance = Math.abs(desiredIndex - rhs.getFirst());
|
||||
|
||||
int comp = Integer.compare(lhsDistance, rhsDistance);
|
||||
if (comp == 0) {
|
||||
return Integer.compare(lhs.getFirst(), rhs.getFirst());
|
||||
} else {
|
||||
return comp;
|
||||
}
|
||||
})
|
||||
.map(IntPair::getSecond)
|
||||
.filter(supportedDensities::contains)
|
||||
.findFirst()
|
||||
.orElseThrow(() -> new IllegalStateException("No density available."));
|
||||
}
|
||||
|
||||
private static @Nullable byte[] getRemoteImageHash(@NonNull EmojiFiles.Version version, @NonNull String imagePath, @NonNull String format) {
|
||||
return EmojiRemote.getMd5(new EmojiImageRequest(version.getVersion(), version.getDensity(), imagePath, format));
|
||||
}
|
||||
|
||||
private static @NonNull EmojiData downloadJson(@NonNull Context context, @NonNull EmojiFiles.Version version) throws IOException, InvalidEmojiDataJsonException {
|
||||
EmojiFiles.NameCollection names = EmojiFiles.NameCollection.read(context, version);
|
||||
UUID emojiData = names.getUUIDForEmojiData();
|
||||
byte[] remoteHash = EmojiRemote.getMd5(new EmojiJsonRequest(version.getVersion()));
|
||||
byte[] localHash;
|
||||
|
||||
if (emojiData != null) {
|
||||
localHash = EmojiFiles.getMd5(context, version, emojiData);
|
||||
} else {
|
||||
localHash = null;
|
||||
}
|
||||
|
||||
if (!Arrays.equals(localHash, remoteHash)) {
|
||||
Log.d(TAG, "Downloading JSON from Remote");
|
||||
assertRemoteDownloadConstraints(context);
|
||||
EmojiFiles.Name name = downloadAndVerifyJsonFromRemote(context, version);
|
||||
EmojiFiles.NameCollection.append(context, names, name);
|
||||
} else {
|
||||
Log.d(TAG, "Already have JSON from remote, skipping download");
|
||||
}
|
||||
|
||||
EmojiData latestData = EmojiFiles.getLatestEmojiData(context, version);
|
||||
|
||||
if (latestData == null) {
|
||||
throw new InvalidEmojiDataJsonException();
|
||||
}
|
||||
|
||||
return latestData;
|
||||
}
|
||||
|
||||
private static void downloadImages(@NonNull Context context,
|
||||
@NonNull EmojiFiles.Version version,
|
||||
@NonNull List<String> imagePaths,
|
||||
@NonNull String format,
|
||||
@NonNull Producer<Boolean> cancelled) throws IOException
|
||||
{
|
||||
EmojiFiles.NameCollection names = EmojiFiles.NameCollection.read(context, version);
|
||||
for (final String imagePath : imagePaths) {
|
||||
if (cancelled.produce()) {
|
||||
Log.w(TAG, "Job was cancelled while downloading images.");
|
||||
return;
|
||||
}
|
||||
|
||||
UUID uuid = names.getUUIDForName(imagePath);
|
||||
byte[] hash;
|
||||
|
||||
if (uuid != null) {
|
||||
hash = EmojiFiles.getMd5(context, version, uuid);
|
||||
} else {
|
||||
hash = null;
|
||||
}
|
||||
|
||||
byte[] ImageHash = getRemoteImageHash(version, imagePath, format);
|
||||
if (hash == null || !Arrays.equals(hash, ImageHash)) {
|
||||
if (hash != null) {
|
||||
Log.d(TAG, "Hash mismatch. Deleting data and re-downloading file.");
|
||||
EmojiFiles.delete(context, version, uuid);
|
||||
}
|
||||
|
||||
assertRemoteDownloadConstraints(context);
|
||||
EmojiFiles.Name name = downloadAndVerifyImageFromRemote(context, version, version.getDensity(), imagePath, format);
|
||||
names = EmojiFiles.NameCollection.append(context, names, name);
|
||||
} else {
|
||||
Log.d(TAG, "Already have Image from remote, skipping download");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void markComplete(@NonNull EmojiFiles.Version version) {
|
||||
EmojiFiles.Version.writeVersion(context, version);
|
||||
}
|
||||
|
||||
private static void assertRemoteDownloadConstraints(@NonNull Context context) throws IOException {
|
||||
if (!AutoDownloadEmojiConstraint.canAutoDownloadEmoji(context)) {
|
||||
throw new IOException("Network conditions no longer permit download.");
|
||||
}
|
||||
}
|
||||
|
||||
private static @NonNull EmojiFiles.Name downloadAndVerifyJsonFromRemote(@NonNull Context context, @NonNull EmojiFiles.Version version) throws IOException {
|
||||
return downloadAndVerifyFromRemote(context,
|
||||
version,
|
||||
() -> EmojiRemote.getObject(new EmojiJsonRequest(version.getVersion())),
|
||||
EmojiFiles.Name::forEmojiDataJson);
|
||||
}
|
||||
|
||||
private static @NonNull EmojiFiles.Name downloadAndVerifyImageFromRemote(@NonNull Context context,
|
||||
@NonNull EmojiFiles.Version version,
|
||||
@NonNull String bucket,
|
||||
@NonNull String imagePath,
|
||||
@NonNull String format) throws IOException
|
||||
{
|
||||
return downloadAndVerifyFromRemote(context,
|
||||
version,
|
||||
() -> EmojiRemote.getObject(new EmojiImageRequest(version.getVersion(), bucket, imagePath, format)),
|
||||
() -> new EmojiFiles.Name(imagePath, UUID.randomUUID()));
|
||||
}
|
||||
|
||||
private static @NonNull EmojiFiles.Name downloadAndVerifyFromRemote(@NonNull Context context,
|
||||
@NonNull EmojiFiles.Version version,
|
||||
@NonNull Producer<Response> responseProducer,
|
||||
@NonNull Producer<EmojiFiles.Name> nameProducer) throws IOException
|
||||
{
|
||||
try (Response response = responseProducer.produce()) {
|
||||
if (!response.isSuccessful()) {
|
||||
throw new IOException("Unsuccessful response " + response.code());
|
||||
}
|
||||
|
||||
ResponseBody responseBody = response.body();
|
||||
if (responseBody == null) {
|
||||
throw new IOException("No response body");
|
||||
}
|
||||
|
||||
String responseMD5 = getMD5FromResponse(response);
|
||||
if (responseMD5 == null) {
|
||||
throw new IOException("Invalid ETag on response");
|
||||
}
|
||||
|
||||
EmojiFiles.Name name = nameProducer.produce();
|
||||
|
||||
byte[] savedMd5;
|
||||
|
||||
try (OutputStream outputStream = EmojiFiles.openForWriting(context, version, name.getUuid())) {
|
||||
Source source = response.body().source();
|
||||
HashingSink sink = HashingSink.md5(Okio.sink(outputStream));
|
||||
|
||||
Okio.buffer(source).readAll(sink);
|
||||
|
||||
savedMd5 = sink.hash().toByteArray();
|
||||
}
|
||||
|
||||
if (!Arrays.equals(savedMd5, Hex.toByteArray(responseMD5))) {
|
||||
EmojiFiles.delete(context, version, name.getUuid());
|
||||
throw new IOException("MD5 Mismatch.");
|
||||
}
|
||||
|
||||
return name;
|
||||
}
|
||||
}
|
||||
|
||||
private static @Nullable String getMD5FromResponse(@NonNull Response response) {
|
||||
Pattern pattern = Pattern.compile(".*([a-f0-9]{32}).*");
|
||||
String header = response.header("etag");
|
||||
Matcher matcher = pattern.matcher(header);
|
||||
|
||||
if (matcher.find()) {
|
||||
return matcher.group(1);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private static void clearOldEmojiData(@NonNull Context context, @Nullable EmojiFiles.Version newVersion) {
|
||||
EmojiFiles.Version version = EmojiFiles.Version.readVersion(context);
|
||||
|
||||
final String currentDirectoryName;
|
||||
final String newVersionDirectoryName;
|
||||
|
||||
if (version != null) {
|
||||
currentDirectoryName = version.getUuid().toString();
|
||||
} else {
|
||||
currentDirectoryName = "";
|
||||
}
|
||||
|
||||
if (newVersion != null) {
|
||||
newVersionDirectoryName = newVersion.getUuid().toString();
|
||||
} else {
|
||||
newVersionDirectoryName = "";
|
||||
}
|
||||
|
||||
File emojiDirectory = EmojiFiles.getBaseDirectory(context);
|
||||
File[] files = emojiDirectory.listFiles();
|
||||
|
||||
if (files == null) {
|
||||
Log.d(TAG, "No emoji data to delete.");
|
||||
return;
|
||||
}
|
||||
|
||||
Log.d(TAG, "Deleting old folders of emoji data");
|
||||
|
||||
Stream.of(files)
|
||||
.filter(File::isDirectory)
|
||||
.filterNot(file -> file.getName().equals(currentDirectoryName))
|
||||
.filterNot(file -> file.getName().equals(newVersionDirectoryName))
|
||||
.forEach(FileUtils::deleteDirectory);
|
||||
}
|
||||
|
||||
public static final class Factory implements Job.Factory<DownloadLatestEmojiDataJob> {
|
||||
@Override
|
||||
public @NonNull DownloadLatestEmojiDataJob create(@NonNull Parameters parameters, @NonNull Data data) {
|
||||
final EmojiFiles.Version version;
|
||||
|
||||
if (data.hasInt(VERSION_INT) &&
|
||||
data.hasString(VERSION_UUID) &&
|
||||
data.hasString(VERSION_DENSITY)) {
|
||||
int versionInt = data.getInt(VERSION_INT);
|
||||
UUID uuid = UUID.fromString(data.getString(VERSION_UUID));
|
||||
String density = data.getString(VERSION_DENSITY);
|
||||
|
||||
version = new EmojiFiles.Version(versionInt, uuid, density);
|
||||
} else {
|
||||
version = null;
|
||||
}
|
||||
|
||||
return new DownloadLatestEmojiDataJob(parameters, version);
|
||||
}
|
||||
}
|
||||
|
||||
private interface Producer<T> {
|
||||
@NonNull T produce();
|
||||
}
|
||||
|
||||
/**
|
||||
* Thrown when the JSON on the server is invalid. In this case, we should NOT
|
||||
* try to download this version again.
|
||||
*/
|
||||
private static class InvalidEmojiDataJsonException extends Exception { }
|
||||
}
|
||||
@@ -9,6 +9,7 @@ import org.thoughtcrime.securesms.jobmanager.Constraint;
|
||||
import org.thoughtcrime.securesms.jobmanager.ConstraintObserver;
|
||||
import org.thoughtcrime.securesms.jobmanager.Job;
|
||||
import org.thoughtcrime.securesms.jobmanager.JobMigration;
|
||||
import org.thoughtcrime.securesms.jobmanager.impl.AutoDownloadEmojiConstraint;
|
||||
import org.thoughtcrime.securesms.jobmanager.impl.CellServiceConstraintObserver;
|
||||
import org.thoughtcrime.securesms.jobmanager.impl.ChargingConstraint;
|
||||
import org.thoughtcrime.securesms.jobmanager.impl.ChargingConstraintObserver;
|
||||
@@ -77,6 +78,7 @@ public final class JobManagerFactories {
|
||||
put(ConversationShortcutUpdateJob.KEY, new ConversationShortcutUpdateJob.Factory());
|
||||
put(CreateSignedPreKeyJob.KEY, new CreateSignedPreKeyJob.Factory());
|
||||
put(DirectoryRefreshJob.KEY, new DirectoryRefreshJob.Factory());
|
||||
put(DownloadLatestEmojiDataJob.KEY, new DownloadLatestEmojiDataJob.Factory());
|
||||
put(FcmRefreshJob.KEY, new FcmRefreshJob.Factory());
|
||||
put(GroupV1MigrationJob.KEY, new GroupV1MigrationJob.Factory());
|
||||
put(GroupCallUpdateSendJob.KEY, new GroupCallUpdateSendJob.Factory());
|
||||
@@ -197,6 +199,7 @@ public final class JobManagerFactories {
|
||||
|
||||
public static Map<String, Constraint.Factory> getConstraintFactories(@NonNull Application application) {
|
||||
return new HashMap<String, Constraint.Factory>() {{
|
||||
put(AutoDownloadEmojiConstraint.KEY, new AutoDownloadEmojiConstraint.Factory(application));
|
||||
put(ChargingConstraint.KEY, new ChargingConstraint.Factory());
|
||||
put(NetworkConstraint.KEY, new NetworkConstraint.Factory(application));
|
||||
put(NetworkOrCellServiceConstraint.KEY, new NetworkOrCellServiceConstraint.Factory(application));
|
||||
|
||||
@@ -10,6 +10,7 @@ import java.util.List;
|
||||
public class EmojiValues extends SignalStoreValues {
|
||||
|
||||
private static final String PREFIX = "emojiPref__";
|
||||
private static final String NEXT_SCHEDULED_CHECK = PREFIX + "next_scheduled_check";
|
||||
|
||||
EmojiValues(@NonNull KeyValueStore store) {
|
||||
super(store);
|
||||
@@ -25,6 +26,14 @@ public class EmojiValues extends SignalStoreValues {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
public long getNextScheduledCheck() {
|
||||
return getStore().getLong(NEXT_SCHEDULED_CHECK, 0);
|
||||
}
|
||||
|
||||
public void setNextScheduledCheck(long nextScheduledCheck) {
|
||||
putLong(NEXT_SCHEDULED_CHECK, nextScheduledCheck);
|
||||
}
|
||||
|
||||
public void setPreferredVariation(@NonNull String emoji) {
|
||||
String canonical = EmojiUtil.getCanonicalRepresentation(emoji);
|
||||
|
||||
|
||||
@@ -18,6 +18,7 @@ import org.thoughtcrime.securesms.util.AppSignatureUtil;
|
||||
import org.thoughtcrime.securesms.util.ByteUnit;
|
||||
import org.thoughtcrime.securesms.util.CensorshipUtil;
|
||||
import org.thoughtcrime.securesms.util.DeviceProperties;
|
||||
import org.thoughtcrime.securesms.util.ScreenDensity;
|
||||
import org.thoughtcrime.securesms.util.ServiceUtil;
|
||||
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
||||
import org.thoughtcrime.securesms.util.Util;
|
||||
@@ -46,8 +47,8 @@ public class LogSectionSystemInfo implements LogSection {
|
||||
builder.append("Model : ").append(Build.MODEL).append("\n");
|
||||
builder.append("Product : ").append(Build.PRODUCT).append("\n");
|
||||
builder.append("Screen : ").append(getScreenResolution(context)).append(", ")
|
||||
.append(getScreenDensityClass(context)).append(", ")
|
||||
.append(getScreenRefreshRate(context)).append("\n");
|
||||
.append(ScreenDensity.get(context)).append(", ")
|
||||
.append(getScreenRefreshRate(context)).append("\n");
|
||||
builder.append("Font Scale : ").append(context.getResources().getConfiguration().fontScale).append("\n");
|
||||
builder.append("Android : ").append(Build.VERSION.RELEASE).append(" (")
|
||||
.append(Build.VERSION.INCREMENTAL).append(", ")
|
||||
@@ -133,30 +134,6 @@ public class LogSectionSystemInfo implements LogSection {
|
||||
return displayMetrics.widthPixels + "x" + displayMetrics.heightPixels;
|
||||
}
|
||||
|
||||
private static @NonNull String getScreenDensityClass(@NonNull Context context) {
|
||||
int density = context.getResources().getDisplayMetrics().densityDpi;
|
||||
|
||||
LinkedHashMap<Integer, String> levels = new LinkedHashMap<Integer, String>() {{
|
||||
put(DisplayMetrics.DENSITY_LOW, "ldpi");
|
||||
put(DisplayMetrics.DENSITY_MEDIUM, "mdpi");
|
||||
put(DisplayMetrics.DENSITY_HIGH, "hdpi");
|
||||
put(DisplayMetrics.DENSITY_XHIGH, "xhdpi");
|
||||
put(DisplayMetrics.DENSITY_XXHIGH, "xxhdpi");
|
||||
put(DisplayMetrics.DENSITY_XXXHIGH, "xxxhdpi");
|
||||
}};
|
||||
|
||||
String densityString = "unknown";
|
||||
|
||||
for (Map.Entry<Integer, String> entry : levels.entrySet()) {
|
||||
densityString = entry.getValue();
|
||||
if (entry.getKey() > density) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return densityString + " (" + density + ")";
|
||||
}
|
||||
|
||||
private static @NonNull String getScreenRefreshRate(@NonNull Context context) {
|
||||
return String.format(Locale.ENGLISH, "%.2f hz", ServiceUtil.getWindowManager(context).getDefaultDisplay().getRefreshRate());
|
||||
}
|
||||
|
||||
@@ -8,10 +8,12 @@ import android.net.Uri;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import org.apache.http.auth.AUTH;
|
||||
import org.thoughtcrime.securesms.BuildConfig;
|
||||
import org.thoughtcrime.securesms.attachments.Attachment;
|
||||
import org.thoughtcrime.securesms.attachments.AttachmentId;
|
||||
import org.thoughtcrime.securesms.database.DatabaseFactory;
|
||||
import org.thoughtcrime.securesms.emoji.EmojiFiles;
|
||||
import org.thoughtcrime.securesms.providers.BlobProvider;
|
||||
import org.thoughtcrime.securesms.providers.DeprecatedPersistentBlobProvider;
|
||||
import org.thoughtcrime.securesms.providers.PartProvider;
|
||||
@@ -26,15 +28,18 @@ public class PartAuthority {
|
||||
private static final String PART_URI_STRING = "content://" + AUTHORITY + "/part";
|
||||
private static final String STICKER_URI_STRING = "content://" + AUTHORITY + "/sticker";
|
||||
private static final String WALLPAPER_URI_STRING = "content://" + AUTHORITY + "/wallpaper";
|
||||
private static final String EMOJI_URI_STRING = "content://" + AUTHORITY + "/emoji";
|
||||
private static final Uri PART_CONTENT_URI = Uri.parse(PART_URI_STRING);
|
||||
private static final Uri STICKER_CONTENT_URI = Uri.parse(STICKER_URI_STRING);
|
||||
private static final Uri WALLPAPER_CONTENT_URI = Uri.parse(WALLPAPER_URI_STRING);
|
||||
private static final Uri EMOJI_CONTENT_URI = Uri.parse(EMOJI_URI_STRING);
|
||||
|
||||
private static final int PART_ROW = 1;
|
||||
private static final int PERSISTENT_ROW = 2;
|
||||
private static final int BLOB_ROW = 3;
|
||||
private static final int STICKER_ROW = 4;
|
||||
private static final int WALLPAPER_ROW = 5;
|
||||
private static final int EMOJI_ROW = 6;
|
||||
|
||||
private static final UriMatcher uriMatcher;
|
||||
|
||||
@@ -43,6 +48,7 @@ public class PartAuthority {
|
||||
uriMatcher.addURI(AUTHORITY, "part/*/#", PART_ROW);
|
||||
uriMatcher.addURI(AUTHORITY, "sticker/#", STICKER_ROW);
|
||||
uriMatcher.addURI(AUTHORITY, "wallpaper/*", WALLPAPER_ROW);
|
||||
uriMatcher.addURI(AUTHORITY, "emoji/*", EMOJI_ROW);
|
||||
uriMatcher.addURI(DeprecatedPersistentBlobProvider.AUTHORITY, DeprecatedPersistentBlobProvider.EXPECTED_PATH_OLD, PERSISTENT_ROW);
|
||||
uriMatcher.addURI(DeprecatedPersistentBlobProvider.AUTHORITY, DeprecatedPersistentBlobProvider.EXPECTED_PATH_NEW, PERSISTENT_ROW);
|
||||
uriMatcher.addURI(BlobProvider.AUTHORITY, BlobProvider.PATH, BLOB_ROW);
|
||||
@@ -65,6 +71,7 @@ public class PartAuthority {
|
||||
case PERSISTENT_ROW: return DeprecatedPersistentBlobProvider.getInstance(context).getStream(context, ContentUris.parseId(uri));
|
||||
case BLOB_ROW: return BlobProvider.getInstance().getStream(context, uri);
|
||||
case WALLPAPER_ROW: return WallpaperStorage.read(context, getWallpaperFilename(uri));
|
||||
case EMOJI_ROW: return EmojiFiles.openForReading(context, getEmojiFilename(uri));
|
||||
default: return context.getContentResolver().openInputStream(uri);
|
||||
}
|
||||
} catch (SecurityException se) {
|
||||
@@ -162,10 +169,18 @@ public class PartAuthority {
|
||||
return Uri.withAppendedPath(WALLPAPER_CONTENT_URI, filename);
|
||||
}
|
||||
|
||||
public static Uri getEmojiUri(String sprite) {
|
||||
return Uri.withAppendedPath(EMOJI_CONTENT_URI, sprite);
|
||||
}
|
||||
|
||||
public static String getWallpaperFilename(Uri uri) {
|
||||
return uri.getPathSegments().get(1);
|
||||
}
|
||||
|
||||
public static String getEmojiFilename(Uri uri) {
|
||||
return uri.getPathSegments().get(1);
|
||||
}
|
||||
|
||||
public static boolean isLocalUri(final @NonNull Uri uri) {
|
||||
int match = uriMatcher.match(uri);
|
||||
switch (match) {
|
||||
|
||||
@@ -19,6 +19,7 @@ import org.thoughtcrime.securesms.database.MessageDatabase;
|
||||
import org.thoughtcrime.securesms.database.NoSuchMessageException;
|
||||
import org.thoughtcrime.securesms.database.model.MessageRecord;
|
||||
import org.thoughtcrime.securesms.database.model.ReactionRecord;
|
||||
import org.thoughtcrime.securesms.emoji.EmojiSource;
|
||||
import org.thoughtcrime.securesms.reactions.ReactionDetails;
|
||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||
import org.thoughtcrime.securesms.sms.MessageSender;
|
||||
@@ -41,7 +42,7 @@ final class ReactWithAnyEmojiRepository {
|
||||
this.recentEmojiPageModel = new RecentEmojiPageModel(context, storageKey);
|
||||
this.emojiPages = new LinkedList<>();
|
||||
|
||||
emojiPages.addAll(Stream.of(EmojiUtil.getDisplayPages())
|
||||
emojiPages.addAll(Stream.of(EmojiSource.getLatest().getDisplayPages())
|
||||
.map(page -> new ReactWithAnyEmojiPage(Collections.singletonList(new ReactWithAnyEmojiPageBlock(getCategoryLabel(page.getIconAttr()), page))))
|
||||
.toList());
|
||||
emojiPages.remove(emojiPages.size() - 1);
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
package org.thoughtcrime.securesms.reactions.any;
|
||||
|
||||
import android.net.Uri;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
@@ -38,12 +40,7 @@ class ThisMessageEmojiPageModel implements EmojiPageModel {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasSpriteMap() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable String getSprite() {
|
||||
public @Nullable Uri getSpriteUri() {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,82 @@
|
||||
package org.thoughtcrime.securesms.util;
|
||||
|
||||
import android.content.Context;
|
||||
import android.util.DisplayMetrics;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Helper class to get density information about a device's display
|
||||
*/
|
||||
public final class ScreenDensity {
|
||||
|
||||
private static final String UNKNOWN = "unknown";
|
||||
|
||||
private static final float XHDPI_TO_LDPI = 0.25f;
|
||||
private static final float XHDPI_TO_MDPI = 0.5f;
|
||||
private static final float XHDPI_TO_HDPI = 0.75f;
|
||||
|
||||
private static final LinkedHashMap<Integer, String> LEVELS = new LinkedHashMap<Integer, String>() {{
|
||||
put(DisplayMetrics.DENSITY_LOW, "ldpi");
|
||||
put(DisplayMetrics.DENSITY_MEDIUM, "mdpi");
|
||||
put(DisplayMetrics.DENSITY_HIGH, "hdpi");
|
||||
put(DisplayMetrics.DENSITY_XHIGH, "xhdpi");
|
||||
put(DisplayMetrics.DENSITY_XXHIGH, "xxhdpi");
|
||||
put(DisplayMetrics.DENSITY_XXXHIGH, "xxxhdpi");
|
||||
}};
|
||||
|
||||
private final String bucket;
|
||||
private final int density;
|
||||
|
||||
public ScreenDensity(String bucket, int density) {
|
||||
this.bucket = bucket;
|
||||
this.density = density;
|
||||
}
|
||||
|
||||
public static @NonNull ScreenDensity get(@NonNull Context context) {
|
||||
int density = context.getResources().getDisplayMetrics().densityDpi;
|
||||
|
||||
String bucket = UNKNOWN;
|
||||
|
||||
for (Map.Entry<Integer, String> entry : LEVELS.entrySet()) {
|
||||
bucket = entry.getValue();
|
||||
if (entry.getKey() > density) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return new ScreenDensity(bucket, density);
|
||||
}
|
||||
|
||||
public String getBucket() {
|
||||
return bucket;
|
||||
}
|
||||
|
||||
public boolean isKnownDensity() {
|
||||
return !bucket.equals(UNKNOWN);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NonNull String toString() {
|
||||
return bucket + " (" + density + ")";
|
||||
}
|
||||
|
||||
public static float xhdpiRelativeDensityScaleFactor(@NonNull String density) {
|
||||
switch (density) {
|
||||
case "ldpi":
|
||||
return XHDPI_TO_LDPI;
|
||||
case "mdpi":
|
||||
return XHDPI_TO_MDPI;
|
||||
case "hdpi":
|
||||
return XHDPI_TO_HDPI;
|
||||
case "xhdpi":
|
||||
return 1f;
|
||||
default:
|
||||
throw new IllegalStateException("Unsupported density: " + density);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -5,42 +5,59 @@ import android.content.Context;
|
||||
|
||||
import androidx.test.core.app.ApplicationProvider;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.powermock.api.mockito.PowerMockito;
|
||||
import org.powermock.core.classloader.annotations.PowerMockIgnore;
|
||||
import org.powermock.core.classloader.annotations.PrepareForTest;
|
||||
import org.powermock.modules.junit4.rule.PowerMockRule;
|
||||
import org.robolectric.ParameterizedRobolectricTestRunner;
|
||||
import org.robolectric.annotation.Config;
|
||||
import org.thoughtcrime.securesms.crypto.AttachmentSecretProvider;
|
||||
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
|
||||
import org.thoughtcrime.securesms.emoji.EmojiSource;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.Mockito.mock;
|
||||
|
||||
@RunWith(ParameterizedRobolectricTestRunner.class)
|
||||
@Config(manifest = Config.NONE, application = Application.class)
|
||||
@PowerMockIgnore({"org.mockito.*", "org.robolectric.*", "android.*", "androidx.*" })
|
||||
@PrepareForTest({ApplicationDependencies.class, AttachmentSecretProvider.class})
|
||||
public class EmojiUtilTest_isEmoji {
|
||||
|
||||
public @Rule PowerMockRule rule = new PowerMockRule();
|
||||
|
||||
private final String input;
|
||||
private final boolean output;
|
||||
|
||||
@ParameterizedRobolectricTestRunner.Parameters
|
||||
public static Collection<Object[]> data() {
|
||||
return Arrays.asList(new Object[][]{
|
||||
{ null, false },
|
||||
{ "", false },
|
||||
{ "cat", false },
|
||||
{ "ᑢᗩᖶ", false },
|
||||
{ "♍︎♋︎⧫︎", false },
|
||||
{ "ᑢ", false },
|
||||
{ "¯\\_(ツ)_/¯", false},
|
||||
{ "\uD83D\uDE0D", true }, // Smiling face with heart-shaped eyes
|
||||
{ "\uD83D\uDD77", true }, // Spider
|
||||
{ "\uD83E\uDD37", true }, // Person shrugging
|
||||
{ "\uD83E\uDD37\uD83C\uDFFF\u200D♂️", true }, // Man shrugging dark skin tone
|
||||
{ "\uD83D\uDC68\u200D\uD83D\uDC69\u200D\uD83D\uDC67\u200D\uD83D\uDC66", true }, // Family: Man, Woman, Girl, Boy
|
||||
{ "\uD83D\uDC68\uD83C\uDFFB\u200D\uD83D\uDC69\uD83C\uDFFB\u200D\uD83D\uDC67\uD83C\uDFFB\u200D\uD83D\uDC66\uD83C\uDFFB", true }, // Family - Man: Light Skin Tone, Woman: Light Skin Tone, Girl: Light Skin Tone, Boy: Light Skin Tone (NOTE: Not widely supported, good stretch test)
|
||||
{ "\uD83D\uDE0Dhi", false }, // Smiling face with heart-shaped eyes, text afterwards
|
||||
{ "\uD83D\uDE0D ", false }, // Smiling face with heart-shaped eyes, space afterwards
|
||||
{ "\uD83D\uDE0D\uD83D\uDE0D", false }, // Smiling face with heart-shaped eyes, twice
|
||||
{null, false},
|
||||
{"", false},
|
||||
{"cat", false},
|
||||
{"ᑢᗩᖶ", false},
|
||||
{"♍︎♋︎⧫︎", false},
|
||||
{"ᑢ", false},
|
||||
{"¯\\_(ツ)_/¯", false},
|
||||
{"\uD83D\uDE0D", true}, // Smiling face with heart-shaped eyes
|
||||
{"\uD83D\uDD77", true}, // Spider
|
||||
{"\uD83E\uDD37", true}, // Person shrugging
|
||||
{"\uD83E\uDD37\uD83C\uDFFF\u200D♂️", true}, // Man shrugging dark skin tone
|
||||
{"\uD83D\uDC68\u200D\uD83D\uDC69\u200D\uD83D\uDC67\u200D\uD83D\uDC66", true}, // Family: Man, Woman, Girl, Boy
|
||||
{"\uD83D\uDC68\uD83C\uDFFB\u200D\uD83D\uDC69\uD83C\uDFFB\u200D\uD83D\uDC67\uD83C\uDFFB\u200D\uD83D\uDC66\uD83C\uDFFB", true}, // Family - Man: Light Skin Tone, Woman: Light Skin Tone, Girl: Light Skin Tone, Boy: Light Skin Tone (NOTE: Not widely supported, good stretch test)
|
||||
{"\uD83D\uDE0Dhi", false}, // Smiling face with heart-shaped eyes, text afterwards
|
||||
{"\uD83D\uDE0D ", false}, // Smiling face with heart-shaped eyes, space afterwards
|
||||
{"\uD83D\uDE0D\uD83D\uDE0D", false}, // Smiling face with heart-shaped eyes, twice
|
||||
});
|
||||
}
|
||||
|
||||
@@ -54,6 +71,12 @@ public class EmojiUtilTest_isEmoji {
|
||||
public void isEmoji() {
|
||||
Context context = ApplicationProvider.getApplicationContext();
|
||||
|
||||
PowerMockito.mockStatic(ApplicationDependencies.class);
|
||||
PowerMockito.when(ApplicationDependencies.getApplication()).thenReturn((Application) context);
|
||||
PowerMockito.mockStatic(AttachmentSecretProvider.class);
|
||||
PowerMockito.when(AttachmentSecretProvider.getInstance(any())).thenThrow(IOException.class);
|
||||
EmojiSource.refresh();
|
||||
|
||||
assertEquals(output, EmojiUtil.isEmoji(context, input));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,138 @@
|
||||
package org.thoughtcrime.securesms.emoji
|
||||
|
||||
import android.app.Application
|
||||
import android.net.Uri
|
||||
import com.fasterxml.jackson.core.JsonParseException
|
||||
import org.junit.Assert
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.robolectric.RobolectricTestRunner
|
||||
import org.robolectric.annotation.Config
|
||||
import org.thoughtcrime.securesms.components.emoji.CompositeEmojiPageModel
|
||||
import org.thoughtcrime.securesms.components.emoji.Emoji
|
||||
import org.thoughtcrime.securesms.components.emoji.EmojiPageModel
|
||||
import org.thoughtcrime.securesms.components.emoji.StaticEmojiPageModel
|
||||
|
||||
private const val INVALID_JSON = "{{}"
|
||||
private const val EMPTY_JSON = "{}"
|
||||
private const val SAMPLE_JSON_WITHOUT_OBSOLETE = """
|
||||
{
|
||||
"emoji": {
|
||||
"Places": [["d83cdf0d"], ["0003", "0004", "0005"]],
|
||||
"Foods": [["0001"], ["0002", "0003", "0004"]]
|
||||
},
|
||||
"metrics": {
|
||||
"raw_height": 64,
|
||||
"raw_width": 64,
|
||||
"per_row": 16
|
||||
},
|
||||
"densities": [ "xhdpi" ],
|
||||
"format": "png"
|
||||
}
|
||||
"""
|
||||
|
||||
private const val SAMPLE_JSON_WITH_OBSOLETE = """
|
||||
{
|
||||
"emoji": {
|
||||
"Places_1": [["0002"], ["0003", "0004", "0005"]],
|
||||
"Places_2": [["0003"], ["0008", "0009", "0000"]],
|
||||
"Foods": [["0001"], ["0002", "0003", "0004"]]
|
||||
},
|
||||
"obsolete": [
|
||||
{"obsoleted": "0012", "replace_with": "0023"}
|
||||
],
|
||||
"metrics": {
|
||||
"raw_height": 64,
|
||||
"raw_width": 64,
|
||||
"per_row": 16
|
||||
},
|
||||
"densities": [ "xhdpi" ],
|
||||
"format": "png"
|
||||
}
|
||||
"""
|
||||
|
||||
private val SAMPLE_JSON_WITHOUT_OBSOLETE_EXPECTED = listOf(
|
||||
StaticEmojiPageModel(EmojiCategory.FOODS.icon, listOf(Emoji("\u0001"), Emoji("\u0002", "\u0003", "\u0004")), Uri.parse("file:///Foods")),
|
||||
StaticEmojiPageModel(EmojiCategory.PLACES.icon, listOf(Emoji("\ud83c\udf0d"), Emoji("\u0003", "\u0004", "\u0005")), Uri.parse("file:///Places"))
|
||||
)
|
||||
|
||||
private val SAMPLE_JSON_WITH_OBSOLETE_EXPECTED_DISPLAY = listOf(
|
||||
StaticEmojiPageModel(EmojiCategory.FOODS.icon, listOf(Emoji("\u0001"), Emoji("\u0002", "\u0003", "\u0004")), Uri.parse("file:///Foods")),
|
||||
CompositeEmojiPageModel(
|
||||
EmojiCategory.PLACES.icon,
|
||||
listOf(
|
||||
StaticEmojiPageModel(EmojiCategory.PLACES.icon, listOf(Emoji("\u0002"), Emoji("\u0003", "\u0004", "\u0005")), Uri.parse("file:///Places_1")),
|
||||
StaticEmojiPageModel(EmojiCategory.PLACES.icon, listOf(Emoji("\u0003"), Emoji("\u0008", "\u0009", "\u0000")), Uri.parse("file:///Places_2"))
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
private val SAMPLE_JSON_WITH_OBSOLETE_EXPECTED_DATA = listOf(
|
||||
StaticEmojiPageModel(EmojiCategory.FOODS.icon, listOf(Emoji("\u0001"), Emoji("\u0002", "\u0003", "\u0004")), Uri.parse("file:///Foods")),
|
||||
StaticEmojiPageModel(EmojiCategory.PLACES.icon, listOf(Emoji("\u0002"), Emoji("\u0003", "\u0004", "\u0005")), Uri.parse("file:///Places_1")),
|
||||
StaticEmojiPageModel(EmojiCategory.PLACES.icon, listOf(Emoji("\u0003"), Emoji("\u0008", "\u0009", "\u0000")), Uri.parse("file:///Places_2"))
|
||||
)
|
||||
|
||||
@RunWith(RobolectricTestRunner::class)
|
||||
@Config(manifest = Config.NONE, application = Application::class)
|
||||
class EmojiJsonParserTest {
|
||||
|
||||
@Test(expected = NullPointerException::class)
|
||||
fun `Given empty json, when I parse, then I expect a NullPointerException`() {
|
||||
val result = EmojiJsonParser.parse(EMPTY_JSON.byteInputStream(), this::uriFactory)
|
||||
|
||||
result.getOrThrow()
|
||||
}
|
||||
|
||||
@Test(expected = JsonParseException::class)
|
||||
fun `Given invalid json, when I parse, then I expect a JsonParseException`() {
|
||||
val result = EmojiJsonParser.parse(INVALID_JSON.byteInputStream(), this::uriFactory)
|
||||
|
||||
result.getOrThrow()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `Given sample without obselete, when I parse, then I expect source without obsolete`() {
|
||||
val result: ParsedEmojiData = EmojiJsonParser.parse(SAMPLE_JSON_WITHOUT_OBSOLETE.byteInputStream(), this::uriFactory).getOrThrow()
|
||||
|
||||
Assert.assertTrue(result.obsolete.isEmpty())
|
||||
Assert.assertTrue(result.displayPages == result.dataPages)
|
||||
Assert.assertEquals(SAMPLE_JSON_WITHOUT_OBSOLETE_EXPECTED.size, result.dataPages.size)
|
||||
|
||||
result.dataPages.zip(SAMPLE_JSON_WITHOUT_OBSOLETE_EXPECTED).forEach { (actual, expected) ->
|
||||
Assert.assertTrue(actual.isSameAs(expected))
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `Given sample with obsolete, when I parse, then I expect source with obsolete`() {
|
||||
val result: ParsedEmojiData = EmojiJsonParser.parse(SAMPLE_JSON_WITH_OBSOLETE.byteInputStream(), this::uriFactory).getOrThrow()
|
||||
|
||||
Assert.assertTrue(result.obsolete.size == 1)
|
||||
Assert.assertEquals("\u0012", result.obsolete[0].obsolete)
|
||||
Assert.assertEquals("\u0023", result.obsolete[0].replaceWith)
|
||||
Assert.assertFalse(result.displayPages == result.dataPages)
|
||||
Assert.assertEquals(SAMPLE_JSON_WITH_OBSOLETE_EXPECTED_DISPLAY.size, result.displayPages.size)
|
||||
|
||||
result.displayPages.zip(SAMPLE_JSON_WITH_OBSOLETE_EXPECTED_DISPLAY).forEach { (actual, expected) ->
|
||||
Assert.assertTrue(actual.isSameAs(expected))
|
||||
}
|
||||
|
||||
Assert.assertEquals(SAMPLE_JSON_WITH_OBSOLETE_EXPECTED_DATA.size, result.dataPages.size)
|
||||
|
||||
result.dataPages.zip(SAMPLE_JSON_WITH_OBSOLETE_EXPECTED_DATA).forEach { (actual, expected) ->
|
||||
Assert.assertTrue(actual.isSameAs(expected))
|
||||
}
|
||||
|
||||
Assert.assertEquals(result.densities, listOf("xhdpi"))
|
||||
Assert.assertEquals(result.format, "png")
|
||||
}
|
||||
|
||||
private fun uriFactory(sprite: String, format: String) = Uri.parse("file:///$sprite")
|
||||
|
||||
private fun EmojiPageModel.isSameAs(other: EmojiPageModel) =
|
||||
this.javaClass == other.javaClass &&
|
||||
this.emoji == other.emoji &&
|
||||
this.iconAttr == other.iconAttr &&
|
||||
this.spriteUri == other.spriteUri
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
package org.thoughtcrime.securesms.emoji
|
||||
|
||||
import android.net.Uri
|
||||
import org.junit.Assert
|
||||
import org.junit.Test
|
||||
import org.thoughtcrime.securesms.components.emoji.Emoji
|
||||
import org.thoughtcrime.securesms.components.emoji.EmojiPageModel
|
||||
|
||||
class EmojiSourceTest {
|
||||
|
||||
@Test
|
||||
fun `Given a bunch of data pages with max value 100100, when I get the maxEmojiLength, then I expect 6`() {
|
||||
val emojiDataFake = ParsedEmojiData(EmojiMetrics(-1, -1, -1), listOf(), "png", listOf(), dataPages = generatePages(), listOf())
|
||||
val testSubject = EmojiSource(0f, emojiDataFake, ::EmojiPageReference)
|
||||
|
||||
Assert.assertEquals(6, testSubject.maxEmojiLength)
|
||||
}
|
||||
|
||||
private fun generatePages() = (1..10).map { EmojiPageModelFake((1..100).shuffled().map { Emoji("$it$it") }) }
|
||||
|
||||
private class EmojiPageModelFake(private val displayE: List<Emoji>) : EmojiPageModel {
|
||||
|
||||
override fun getEmoji(): List<String> = displayE.map { it.variations }.flatten()
|
||||
|
||||
override fun getDisplayEmoji(): List<Emoji> = displayE
|
||||
|
||||
override fun getIconAttr(): Int = TODO("Not yet implemented")
|
||||
|
||||
override fun getSpriteUri(): Uri = TODO("Not yet implemented")
|
||||
|
||||
override fun isDynamic(): Boolean = TODO("Not yet implemented")
|
||||
}
|
||||
}
|
||||
@@ -213,14 +213,17 @@ dependencyVerification {
|
||||
['com.davemorrissey.labs:subsampling-scale-image-view:3.6.0',
|
||||
'550c5baa07e0bb4ff0a18b705e96d34436d22619248bd8c08c08c730b1f55cfe'],
|
||||
|
||||
['com.fasterxml.jackson.core:jackson-annotations:2.9.0',
|
||||
'45d32ac61ef8a744b464c54c2b3414be571016dd46bfc2bec226761cf7ae457a'],
|
||||
['com.fasterxml.jackson.core:jackson-annotations:2.12.0',
|
||||
'c28fbe62e7be1e29df75953fa8a887ff875d4482291fbfddb1aec5c91191ecda'],
|
||||
|
||||
['com.fasterxml.jackson.core:jackson-core:2.9.9',
|
||||
'3083079be6088db2ed0a0c6ff92204e0aa48fa1de9db5b59c468f35acf882c2c'],
|
||||
['com.fasterxml.jackson.core:jackson-core:2.12.0',
|
||||
'8acab5ef6e4f332bbb331b3fcd24d716598770d13a47e7215aa5ee625d1fd9c9'],
|
||||
|
||||
['com.fasterxml.jackson.core:jackson-databind:2.9.9.2',
|
||||
'fb262d42ea2de98044b62d393950a5aa050435fec38bbcadf2325cf7dc41b848'],
|
||||
['com.fasterxml.jackson.core:jackson-databind:2.12.0',
|
||||
'75d470eda0dd559e43f2ad08209fa09ecd268833492ba93fa46f6f3607acbab7'],
|
||||
|
||||
['com.fasterxml.jackson.module:jackson-module-kotlin:2.12.0',
|
||||
'ee69650831f72ff2411026b507f3583e90ca88b40e9dae3067f87b34088e0ced'],
|
||||
|
||||
['com.github.bumptech.glide:annotations:4.11.0',
|
||||
'd219d238006d824962176229d4708abcdddcfe342c6a18a5d0fa48d6f0479b3e'],
|
||||
@@ -462,6 +465,9 @@ dependencyVerification {
|
||||
['org.greenrobot:eventbus:3.0.0',
|
||||
'180d4212467df06f2fbc9c8d8a2984533ac79c87769ad883bc421612f0b4e17c'],
|
||||
|
||||
['org.jetbrains.kotlin:kotlin-reflect:1.4.10',
|
||||
'3ab3413ec945f801448360ad97bc6e14fec6d606889ede3c707cc277b4467f45'],
|
||||
|
||||
['org.jetbrains.kotlin:kotlin-stdlib-common:1.4.32',
|
||||
'e1ff6f55ee9e7591dcc633f7757bac25a7edb1cc7f738b37ec652f10f66a4145'],
|
||||
|
||||
|
||||