update and adapt signal sources to have the new media keyboard including new emojis

This commit is contained in:
cyberta 2020-06-11 17:11:23 +02:00
parent c0f726e2be
commit 56d85e01e1
98 changed files with 2378 additions and 621 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 337 KiB

BIN
assets/emoji/Activity.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 225 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 812 KiB

BIN
assets/emoji/Flags_0.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 415 KiB

BIN
assets/emoji/Flags_1.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 610 KiB

BIN
assets/emoji/Foods.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 344 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 694 KiB

BIN
assets/emoji/Nature.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 395 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 873 KiB

BIN
assets/emoji/Objects.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 622 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2 MiB

BIN
assets/emoji/People_0.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 599 KiB

BIN
assets/emoji/People_1.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 559 KiB

BIN
assets/emoji/People_2.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 643 KiB

BIN
assets/emoji/People_3.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 647 KiB

BIN
assets/emoji/People_4.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 602 KiB

BIN
assets/emoji/People_5.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 589 KiB

BIN
assets/emoji/People_6.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 531 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 MiB

BIN
assets/emoji/Places.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 589 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 482 KiB

BIN
assets/emoji/Symbols.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 384 KiB

BIN
delta-chat-2019-10-29-0.bak Normal file

Binary file not shown.

View file

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<shape
xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<corners android:radius="5dp" />
<solid android:color="@color/grey_800" />
</shape>

View file

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<shape
xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<corners android:radius="5dp" />
<solid android:color="@color/core_white" />
</shape>

View file

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="20dp"
android:height="20dp"
android:viewportWidth="20"
android:viewportHeight="20">
<path
android:fillColor="@color/grey_400"
android:pathData="M10,1c-5,0 -9,4 -9,9s4,9 9,9s9,-4 9,-9S15,1 10,1zM15.5,15.1c-0.1,-1 -0.2,-2.2 -0.3,-3.2l-3.2,-1.2c-0.9,0.7 -1.7,1.4 -2.5,2L10,16c0.8,0.3 1.9,0.6 2.8,0.9c-2,0.8 -4.2,0.7 -6.1,-0.2L9,16l-0.6,-3.3c-0.8,-0.6 -1.7,-1.2 -2.6,-1.8L4,12c0,0.7 -0.1,1.6 -0.1,2.3C3,13.1 2.5,11.6 2.5,10c0,-0.3 0,-0.6 0.1,-0.9L3.8,11l1.8,-1l0,0c0.3,-0.9 0.6,-1.9 0.9,-2.8L5.3,5.6L3.5,6.4C4.3,4.8 5.7,3.6 7.4,3C7,3.6 6.5,4.4 6.1,5l1.1,1.6C8.2,6.5 9.1,6.4 10,6.4L12.4,4c-0.3,-0.4 -0.8,-1 -1.2,-1.4c1.7,0.3 3.3,1.1 4.4,2.4c-0.7,-0.2 -1.5,-0.3 -2.2,-0.5l-2.4,2.4c0.4,1 0.8,1.9 1.2,2.8l3.3,1.3c0.6,-0.8 1.3,-1.7 1.9,-2.6c0.1,0.5 0.2,1.1 0.2,1.6C17.5,11.9 16.8,13.7 15.5,15.1z"/>
</vector>

View file

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="20dp"
android:height="20dp"
android:viewportWidth="20"
android:viewportHeight="20">
<path
android:fillColor="@color/grey_600"
android:pathData="M10,1c-5,0 -9,4 -9,9s4,9 9,9s9,-4 9,-9S15,1 10,1zM17.3,8.4c-0.6,0.9 -1.3,1.8 -1.9,2.6l-3.3,-1.3c-0.4,-0.9 -0.8,-1.8 -1.2,-2.8l2.4,-2.4C14,4.6 14.8,4.8 15.6,5C16.4,5.9 17,7.1 17.3,8.4zM11.1,2.6C11.6,3.1 12,3.5 12.4,4L10,6.4c-0.9,0 -1.9,0 -2.8,0.1L6.1,5C6.5,4.3 7,3.6 7.4,3C8.2,2.7 9.1,2.5 10,2.5C10.4,2.5 10.8,2.5 11.1,2.6zM5.6,10L5.6,10l-1.8,1C3.4,10.4 3,9.7 2.6,9.1c0.1,-1 0.4,-1.9 0.9,-2.7l1.9,-0.7l1.1,1.6C6.1,8.1 5.8,9.1 5.6,10zM3.9,14.3C3.9,13.5 4,12.7 4,12l1.8,-1c0.9,0.6 1.8,1.2 2.6,1.8L9,16l-2.3,0.7C5.6,16.1 4.6,15.3 3.9,14.3zM12.8,17c-1,-0.3 -1.9,-0.6 -2.8,-0.9l-0.6,-3.3c0.8,-0.7 1.6,-1.4 2.5,-2l3.2,1.2c0.1,1 0.2,2.1 0.3,3.2C14.7,16 13.8,16.6 12.8,17z"/>
</vector>

View file

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="20dp"
android:height="20dp"
android:viewportWidth="20"
android:viewportHeight="20">
<path
android:fillColor="@color/grey_400"
android:pathData="M16.9,9c0,-0.2 -0.1,-0.5 -0.1,-0.7c0.1,-0.1 0.2,-0.1 0.2,-0.2c1.8,-1.8 2.1,-4.3 0.7,-5.7S13.8,1.2 12,3c-0.1,0.1 -0.1,0.2 -0.2,0.2c-1.2,-0.3 -2.4,-0.3 -3.6,0C8.1,3.2 8.1,3.1 8,3C6.2,1.2 3.7,0.9 2.3,2.3S1.2,6.2 3,8l0.2,0.2C3.2,8.4 3.1,8.7 3.1,8.9c-2.2,1.6 -2.7,4.7 -1.1,7C2.9,17.2 4.4,18 6,18c0,0 0.8,0 1.8,-0.1c0.9,1.2 2.6,1.4 3.8,0.5c0.2,-0.2 0.4,-0.3 0.5,-0.5C13.1,18 14,18 14,18c2.8,0 5,-2.3 4.9,-5.1C18.9,11.4 18.1,9.9 16.9,9zM3.8,6.7C3.4,6.2 3.1,5.5 3,4.8C2.9,4.3 3.1,3.8 3.4,3.4C3.7,3.1 4.1,3 4.5,3c0.8,0 1.6,0.4 2.1,0.9l0,0C5.5,4.5 4.5,5.5 3.8,6.7zM7,12.5c-0.6,0 -1,-0.7 -1,-1.5s0.4,-1.5 1,-1.5s1,0.7 1,1.5S7.6,12.5 7,12.5zM11.2,16l-0.8,0.7c-0.2,0.2 -0.5,0.2 -0.7,0L8.8,16c-0.4,-0.3 -0.5,-0.9 -0.2,-1.3c0.2,-0.2 0.5,-0.4 0.7,-0.4h1.4c0.5,0 0.9,0.4 0.9,0.9C11.6,15.5 11.4,15.8 11.2,16zM13,12.5c-0.6,0 -1,-0.7 -1,-1.5s0.4,-1.5 1,-1.5s1,0.7 1,1.5S13.6,12.5 13,12.5zM13.3,3.8L13.3,3.8C13.9,3.3 14.7,3 15.5,3c0.4,0 0.8,0.1 1.2,0.4c0.3,0.4 0.5,0.9 0.4,1.4c-0.1,0.7 -0.4,1.3 -0.9,1.8C15.5,5.5 14.5,4.5 13.3,3.8z"/>
</vector>

View file

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="20dp"
android:height="20dp"
android:viewportWidth="20"
android:viewportHeight="20">
<path
android:fillColor="@color/grey_600"
android:pathData="M8,11c0,0.8 -0.4,1.5 -1,1.5S6,11.8 6,11s0.4,-1.5 1,-1.5S8,10.2 8,11zM13,9.5c-0.6,0 -1,0.7 -1,1.5s0.4,1.5 1,1.5s1,-0.7 1,-1.5S13.6,9.5 13,9.5zM19,13c0,2.8 -2.2,5 -5,5l0,0c-0.1,0 -0.9,0 -1.8,-0.1c-0.9,1.2 -2.6,1.4 -3.8,0.5c-0.2,-0.2 -0.4,-0.3 -0.5,-0.5C6.9,18 6.1,18 6,18l0,0c-2.8,0 -5,-2.3 -4.9,-5.1c0,-1.5 0.8,-3 2,-3.9c0,-0.3 0.1,-0.5 0.2,-0.8C3.2,8.1 3.1,8.1 3,8C1.2,6.2 0.9,3.7 2.3,2.3c0.6,-0.6 1.4,-0.9 2.2,-0.9C5.9,1.5 7.1,2 8,3c0.1,0.1 0.1,0.2 0.2,0.2c1.2,-0.3 2.4,-0.3 3.6,0C11.9,3.2 11.9,3.1 12,3c0.9,-1 2.1,-1.5 3.5,-1.5c0.8,0 1.6,0.3 2.2,0.9C19.1,3.7 18.8,6.2 17,8c-0.1,0.1 -0.2,0.1 -0.2,0.2c0.1,0.2 0.1,0.5 0.2,0.8C18.2,9.9 19,11.4 19,13zM13.3,3.8c1.2,0.7 2.2,1.6 2.8,2.8c0.4,-0.5 0.7,-1.2 0.8,-1.8c0.1,-0.5 -0.1,-1.1 -0.4,-1.5C16.3,3.1 15.9,3 15.5,3C14.7,3 13.9,3.4 13.3,3.8L13.3,3.8zM3.8,6.7c0.6,-1.2 1.6,-2.2 2.8,-2.9l0,0C6.1,3.3 5.3,3 4.5,3C4.1,3 3.7,3.1 3.4,3.4C3.1,3.8 2.9,4.3 3,4.8C3.1,5.5 3.4,6.2 3.8,6.7zM17.5,13c0,-1.1 -0.6,-2.2 -1.5,-2.8l-0.5,-0.4l-0.1,-0.6c-0.4,-3 -3.2,-5.1 -6.2,-4.6C6.8,4.9 4.9,6.8 4.5,9.2L4.4,9.8L4,10.2c-0.9,0.6 -1.5,1.7 -1.5,2.8c0,1.9 1.6,3.5 3.5,3.5c0,0 0.8,0 1.7,-0.1l0.8,-0.1L9,17c0.4,0.6 1.1,0.7 1.7,0.3c0.1,-0.1 0.2,-0.2 0.3,-0.3l0.5,-0.6l0.8,0.1c0.9,0.1 1.6,0.1 1.7,0.1C15.9,16.5 17.5,14.9 17.5,13zM11.8,14.8c0,-0.6 -0.4,-1 -1,-1H9.3c-0.6,0 -1,0.4 -1,1c0,0.3 0.2,0.7 0.4,0.8l0.9,0.7c0.2,0.2 0.6,0.2 0.8,0l0.9,-0.7C11.6,15.4 11.8,15.1 11.8,14.8z"/>
</vector>

View file

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="20dp"
android:height="20dp"
android:viewportWidth="20"
android:viewportHeight="20">
<path
android:fillColor="@color/grey_400"
android:pathData="m16.614,10.1801c0.0155,3.1165 -1.1329,6.4181 -3.6417,8.3195 -0.7118,0.4002 -1.0296,-1.1937 -0.2831,-1.3774 1.6236,-1.8414 2.0655,-4.4306 2.0997,-6.8333 0.0249,-2.4959 -0.4452,-5.1675 -2.0694,-7.1137 -0.4547,-0.2685 -0.5838,-0.7136 -0.3174,-1.1536 0.0661,-0.8558 0.7305,-0.1301 1.1203,0.172 2.1748,1.9746 3.1179,5.0755 3.0915,7.9866zM11.2592,10.6311c-1.4007,0 -2.8015,0 -4.2022,0 0,-0.5357 0,-1.0715 0,-1.6072 1.4007,0 2.8015,0 4.2022,0 0,0.5357 0,1.0715 0,1.6072zM4.3036,12.8041C5.2775,12.6653 5.8523,14.1022 5.1035,14.7279 4.4431,15.3258 3.1038,14.9005 3.1695,13.9029 3.1464,13.2907 3.7055,12.7411 4.3036,12.8041ZM4.3036,5.8422c0.9739,-0.1388 1.5488,1.2981 0.7999,1.9237 -0.6604,0.5979 -1.9997,0.1727 -1.934,-0.8249 -0.0231,-0.6123 0.536,-1.1618 1.1341,-1.0988z" />
</vector>

View file

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="20dp"
android:height="20dp"
android:viewportWidth="20"
android:viewportHeight="20">
<path
android:fillColor="@color/grey_600"
android:pathData="m16.614,10.1801c0.0155,3.1165 -1.1329,6.4181 -3.6417,8.3195 -0.7118,0.4002 -1.0296,-1.1937 -0.2831,-1.3774 1.6236,-1.8414 2.0655,-4.4306 2.0997,-6.8333 0.0249,-2.4959 -0.4452,-5.1675 -2.0694,-7.1137 -0.4547,-0.2685 -0.5838,-0.7136 -0.3174,-1.1536 0.0661,-0.8558 0.7305,-0.1301 1.1203,0.172 2.1748,1.9746 3.1179,5.0755 3.0915,7.9866zM11.2592,10.6311c-1.4007,0 -2.8015,0 -4.2022,0 0,-0.5357 0,-1.0715 0,-1.6072 1.4007,0 2.8015,0 4.2022,0 0,0.5357 0,1.0715 0,1.6072zM4.3036,12.8041C5.2775,12.6653 5.8523,14.1022 5.1035,14.7279 4.4431,15.3258 3.1038,14.9005 3.1695,13.9029 3.1464,13.2907 3.7055,12.7411 4.3036,12.8041ZM4.3036,5.8422c0.9739,-0.1388 1.5488,1.2981 0.7999,1.9237 -0.6604,0.5979 -1.9997,0.1727 -1.934,-0.8249 -0.0231,-0.6123 0.536,-1.1618 1.1341,-1.0988z" />
</vector>

View file

@ -0,0 +1,4 @@
<vector android:height="24dp" android:viewportHeight="28"
android:viewportWidth="28" android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="#FFBBBDBE" android:pathData="M14,2C7.6,1.8 2.2,6.9 2,13.3c0,0.2 0,0.4 0,0.7c-0.2,6.4 4.9,11.8 11.3,12c0.2,0 0.4,0 0.7,0c6.4,0.2 11.8,-4.9 12,-11.3c0,-0.2 0,-0.4 0,-0.7c0.2,-6.4 -4.9,-11.8 -11.3,-12C14.4,2 14.2,2 14,2zM17.6,9.5c0.9,0 1.6,1 1.6,2.2S18.5,14 17.6,14S16,13 16,11.8S16.7,9.5 17.6,9.5zM10.4,9.5c0.9,0 1.6,1 1.6,2.2S11.3,14 10.4,14s-1.6,-1 -1.6,-2.2S9.5,9.5 10.4,9.5zM20,18.3c-2.5,3.3 -7.2,3.9 -10.5,1.4c-0.5,-0.4 -1,-0.9 -1.4,-1.4c-0.3,-0.3 -0.2,-0.8 0.1,-1.1C8.5,16.9 9,17 9.2,17.3c0,0 0.1,0.1 0.1,0.1c1.1,1.5 2.8,2.3 4.7,2.3c1.9,0 3.6,-0.8 4.7,-2.3c0.2,-0.3 0.7,-0.4 1,-0.2S20.2,17.9 20,18.3L20,18.3z"/>
</vector>

View file

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="20dp"
android:height="20dp"
android:viewportWidth="20"
android:viewportHeight="20">
<path
android:fillColor="@color/grey_400"
android:pathData="M17.5,3.3c-2.4,-0.8 -4.9,-0.8 -7.3,0C8.1,4 5.8,4 3.8,3.3L3.5,3.2V2H2v16h1.5v-4.2c1,0.4 2.1,0.5 3.2,0.6c1.3,0 2.6,-0.2 3.8,-0.6c2.1,-0.7 4.4,-0.7 6.4,0l1,0.4V3.5L17.5,3.3zM5,12.6c-0.4,-0.1 -0.8,-0.2 -1.2,-0.4l-0.2,-0.1V4.8C4,4.9 4.5,5.1 5,5.2V12.6z"/>
</vector>

View file

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="20dp"
android:height="20dp"
android:viewportWidth="20"
android:viewportHeight="20">
<path
android:fillColor="@color/grey_600"
android:pathData="M17.5,3.3c-2.4,-0.8 -4.9,-0.8 -7.3,0C8.1,4 5.8,4 3.8,3.3L3.5,3.2V2H2v16h1.5v-4.2c1,0.4 2.1,0.5 3.2,0.6c1.3,0 2.6,-0.2 3.8,-0.6c2.1,-0.7 4.4,-0.7 6.4,0l1,0.4V3.5L17.5,3.3zM16.5,12c-2.1,-0.5 -4.3,-0.4 -6.3,0.3c-2.1,0.7 -4.4,0.7 -6.4,0l-0.2,-0.1V4.8c2.3,0.7 4.8,0.7 7.1,0c1.9,-0.7 4,-0.7 5.9,-0.2V12z"/>
</vector>

View file

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="20dp"
android:height="20dp"
android:viewportWidth="20"
android:viewportHeight="20">
<path
android:fillColor="@color/grey_400"
android:pathData="M9.1,10.4C8.7,9.7 8.5,9 8.5,8.2c0,-0.8 0.2,-1.6 0.6,-2.3c0.3,-0.5 0.4,-1.1 0.4,-1.8c0,-0.6 -0.2,-1.2 -0.4,-1.7l1.4,-0.6C10.8,2.6 11,3.4 11,4.2c0,0.8 -0.2,1.6 -0.6,2.3C10.2,7 10,7.6 10,8.2c0,0.6 0.2,1.1 0.4,1.6L9.1,10.4zM13.4,9.8C13.2,9.3 13,8.8 13,8.2c0,-0.6 0.2,-1.2 0.4,-1.7C13.8,5.8 14,5 14,4.2c0,-0.8 -0.2,-1.6 -0.6,-2.3l-1.4,0.6c0.3,0.5 0.4,1.1 0.4,1.7c0,0.6 -0.2,1.2 -0.4,1.8c-0.4,0.7 -0.5,1.5 -0.6,2.3c0,0.8 0.2,1.6 0.6,2.3L13.4,9.8zM19,10.3L19,10.3c0,2.7 -2.3,8.7 -9,8.7c-4.9,0 -8.8,-3.8 -9,-8.7l0,0C1,9.1 2.8,8 5.6,7.4c0.1,-0.5 0.3,-1 0.5,-1.5c0.3,-0.5 0.4,-1.1 0.4,-1.8c0,-0.6 -0.2,-1.2 -0.4,-1.7l1.4,-0.6C7.8,2.6 8,3.4 8,4.2C8,5 7.8,5.8 7.4,6.5C7.2,7 7,7.6 7,8.2c0,0.2 0,0.3 0,0.5c0.1,0.4 0.2,0.8 0.4,1.2L6,10.4C5.8,10 5.7,9.5 5.6,9c-1.1,0.2 -2.2,0.6 -3,1.4c0.4,0.6 2.9,1.9 7.5,1.9s7.1,-1.3 7.5,-1.9c-0.8,-0.7 -1.8,-1.2 -2.9,-1.4c-0.1,-0.3 -0.1,-0.5 -0.1,-0.8c0,-0.2 0,-0.5 0.1,-0.7C17.2,8 19,9.1 19,10.3z"/>
</vector>

View file

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="20dp"
android:height="20dp"
android:viewportWidth="20"
android:viewportHeight="20">
<path
android:fillColor="@color/grey_600"
android:pathData="M12.1,10.4c-0.4,-0.7 -0.6,-1.5 -0.6,-2.3c0,-0.8 0.2,-1.6 0.6,-2.3c0.3,-0.5 0.4,-1.1 0.4,-1.8c0,-0.6 -0.2,-1.2 -0.4,-1.7l1.4,-0.6C13.8,2.6 14,3.4 14,4.2c0,0.8 -0.2,1.6 -0.6,2.3C13.2,7 13,7.6 13,8.2c0,0.6 0.2,1.1 0.4,1.6L12.1,10.4zM10.4,9.8C10.2,9.3 10,8.8 10,8.2c0,-0.6 0.2,-1.2 0.4,-1.7C10.8,5.8 11,5 11,4.2c0,-0.8 -0.2,-1.6 -0.6,-2.3L9.1,2.5C9.3,3 9.5,3.6 9.5,4.2c0,0.6 -0.2,1.2 -0.4,1.8C8.7,6.6 8.5,7.4 8.5,8.2c0,0.8 0.2,1.6 0.6,2.3L10.4,9.8zM19,10.3L19,10.3c0,2.7 -2.3,8.7 -9,8.7c-4.9,0 -8.8,-3.8 -9,-8.7l0,0C1,9.1 2.8,8 5.6,7.4c0.1,-0.5 0.3,-1 0.5,-1.5c0.3,-0.5 0.4,-1.1 0.4,-1.8c0,-0.6 -0.2,-1.2 -0.4,-1.7l1.4,-0.6C7.8,2.6 8,3.4 8,4.2C8,5 7.8,5.8 7.4,6.5C7.2,7 7,7.6 7,8.2c0,0.2 0,0.3 0,0.5c0.1,0.4 0.2,0.8 0.4,1.2L6,10.4C5.8,10 5.7,9.5 5.6,9c-1.1,0.2 -2.2,0.6 -3,1.4c0.4,0.6 2.9,1.9 7.5,1.9s7.1,-1.3 7.5,-1.9c-0.8,-0.7 -1.8,-1.2 -2.9,-1.4c-0.1,-0.3 -0.1,-0.5 -0.1,-0.8c0,-0.2 0,-0.5 0.1,-0.7C17.2,8 19,9.1 19,10.3zM17.2,12c-0.2,0.3 -0.5,0.5 -0.8,0.6c-2.1,0.7 -4.2,1 -6.4,1c-2.2,0 -4.3,-0.3 -6.3,-1C3.3,12.5 3,12.3 2.8,12c0.1,0.4 0.2,0.7 0.4,1.1c1.1,2.8 3.8,4.5 6.8,4.5c3,0.1 5.7,-1.7 6.9,-4.5C17,12.7 17.1,12.4 17.2,12z"/>
</vector>

View file

@ -0,0 +1,4 @@
<vector android:height="24dp" android:viewportHeight="20"
android:viewportWidth="20" android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="@color/grey_400" android:pathData="M10,1A6.487,6.487 0,0 0,3.5 7.5a6.773,6.773 0,0 0,1.4 4.1S7.49,13.987 7.49,16v1.5A1.5,1.5 0,0 0,8.99 19h2a1.5,1.5 0,0 0,1.5 -1.5L12.49,16h0.01c0,-2 2.5,-4.4 2.5,-4.4a6.1,6.1 0,0 0,1.5 -4.1A6.487,6.487 0,0 0,10 1ZM11,17.5L9,17.5v-2h2Z"/>
</vector>

View file

@ -0,0 +1,4 @@
<vector android:height="24dp" android:viewportHeight="20"
android:viewportWidth="20" android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="@color/grey_600" android:pathData="M14.1,2.5A6.291,6.291 0,0 0,10 1,6.487 6.487,0 0,0 3.5,7.5a6.773,6.773 0,0 0,1.4 4.1c1.3,1.7 2.6,2.9 2.6,4.4v1.5A1.5,1.5 0,0 0,9 19h2a1.5,1.5 0,0 0,1.5 -1.5L12.5,16c0,-1.5 1.2,-2.8 2.6,-4.4A6.4,6.4 0,0 0,14.1 2.5ZM11.01,17.5h-2L9.01,16h2ZM13.9,10.7a25.578,25.578 0,0 0,-2.6 3.8L8.7,14.5a28.167,28.167 0,0 0,-2.6 -3.9A4.887,4.887 0,0 1,5 7.5a5,5 0,0 1,10 0A5.167,5.167 0,0 1,13.9 10.7Z"/>
</vector>

View file

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="#FF636467"
android:pathData="M12,18.7c-2.1,0 -4.2,-1 -5.5,-2.7c-0.2,-0.3 -0.2,-0.8 0.2,-1.1s0.8,-0.2 1.1,0.2l0,0c1,1.3 2.6,2.1 4.3,2.1c1.7,0 3.3,-0.8 4.3,-2.1c0.2,-0.3 0.7,-0.4 1,-0.2c0,0 0,0 0,0c0.3,0.2 0.4,0.7 0.2,1C16.2,17.6 14.2,18.7 12,18.7zM12,2.5c-5.1,-0.2 -9.3,3.8 -9.5,8.9c0,0.2 0,0.4 0,0.6c-0.2,5.1 3.8,9.3 8.9,9.5c0.2,0 0.4,0 0.6,0c5.1,0.2 9.3,-3.8 9.5,-8.9c0,-0.2 0,-0.4 0,-0.6c0.2,-5.1 -3.8,-9.3 -8.9,-9.5C12.4,2.5 12.2,2.5 12,2.5M12,1c5.9,-0.2 10.8,4.5 11,10.4c0,0.2 0,0.4 0,0.6c0.2,5.9 -4.5,10.8 -10.4,11c-0.2,0 -0.4,0 -0.6,0C6.1,23.2 1.2,18.5 1,12.6c0,-0.2 0,-0.4 0,-0.6C0.8,6.1 5.5,1.2 11.4,1C11.6,1 11.8,1 12,1zM8.5,8C7.7,8 7,8.9 7,10s0.7,2 1.5,2s1.5,-0.9 1.5,-2S9.3,8 8.5,8zM15.5,8C14.7,8 14,8.9 14,10s0.7,2 1.5,2s1.5,-0.9 1.5,-2S16.3,8 15.5,8z"/>
</vector>

View file

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="20dp"
android:height="20dp"
android:viewportWidth="20"
android:viewportHeight="20">
<path
android:fillColor="@color/grey_400"
android:pathData="M10,1C5.2,0.9 1.1,4.7 1,9.5C1,9.7 1,9.8 1,10c-0.1,4.8 3.7,8.9 8.5,9c0.2,0 0.3,0 0.5,0c4.8,0.1 8.9,-3.7 9,-8.5c0,-0.2 0,-0.3 0,-0.5c0.1,-4.8 -3.7,-8.9 -8.5,-9C10.3,1 10.2,1 10,1zM12.8,6.8c0.8,0.1 1.3,0.8 1.2,1.6c0.1,0.8 -0.5,1.5 -1.2,1.6c-0.8,-0.1 -1.3,-0.8 -1.2,-1.6C11.4,7.6 12,6.9 12.8,6.8zM7.2,6.8C8,6.9 8.6,7.6 8.5,8.4C8.6,9.2 8,9.9 7.2,10C6.5,9.9 5.9,9.2 6,8.4C5.9,7.6 6.5,6.9 7.2,6.8zM14.6,13.2c-1.9,2.5 -5.5,3.1 -8.1,1.1c-0.4,-0.3 -0.8,-0.7 -1.1,-1.1c-0.2,-0.3 -0.2,-0.8 0.2,-1.1s0.8,-0.2 1.1,0.2c1.4,1.9 4.1,2.2 6,0.8c0.3,-0.2 0.6,-0.5 0.8,-0.8c0.2,-0.3 0.7,-0.4 1.1,-0.2S14.9,12.9 14.6,13.2z"/>
</vector>

View file

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="20dp"
android:height="20dp"
android:viewportWidth="20"
android:viewportHeight="20">
<path
android:fillColor="@color/grey_600"
android:pathData="M10,15.5c-1.8,0 -3.5,-0.8 -4.6,-2.3c-0.2,-0.3 -0.2,-0.8 0.2,-1.1c0.3,-0.2 0.8,-0.2 1.1,0.2c1.4,1.9 4.1,2.2 6,0.8c0.3,-0.2 0.6,-0.5 0.8,-0.8c0.2,-0.3 0.7,-0.4 1.1,-0.2s0.4,0.7 0.2,1.1l0,0C13.5,14.7 11.8,15.5 10,15.5zM10,2.5c-4,-0.1 -7.4,3 -7.5,7c0,0.2 0,0.3 0,0.5c-0.1,4 3,7.4 7,7.5c0.2,0 0.3,0 0.5,0c4,0.1 7.4,-3 7.5,-7c0,-0.2 0,-0.3 0,-0.5c0.1,-4 -3,-7.4 -7,-7.5C10.3,2.5 10.2,2.5 10,2.5M10,1c4.8,-0.1 8.9,3.7 9,8.5c0,0.2 0,0.3 0,0.5c0.1,4.8 -3.7,8.9 -8.5,9c-0.2,0 -0.3,0 -0.5,0c-4.8,0.1 -8.9,-3.7 -9,-8.5c0,-0.2 0,-0.3 0,-0.5C0.9,5.2 4.7,1.1 9.5,1C9.7,1 9.8,1 10,1zM7.2,6.8C6.5,6.9 5.9,7.6 6,8.4C5.9,9.2 6.5,9.9 7.2,10C8,9.9 8.6,9.2 8.5,8.4C8.6,7.6 8,6.9 7.2,6.8zM12.8,6.8c-0.8,0.1 -1.3,0.8 -1.2,1.6C11.4,9.2 12,9.9 12.8,10c0.8,-0.1 1.3,-0.8 1.2,-1.6C14.1,7.6 13.5,6.9 12.8,6.8z"/>
</vector>

View file

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="?icon_tint"
android:pathData="M12,18.663a6.817,6.817 0,0 1,-5.491 -2.7,0.75 0.75,0 1,1 1.207,-0.89A5.31,5.31 0,0 0,12 17.163a5.3,5.3 0,0 0,4.3 -2.105,0.75 0.75,0 1,1 1.211,0.884A6.8,6.8 0,0 1,12 18.663ZM12,2.5A9.188,9.188 0,0 0,2.5 12,9.188 9.188,0 0,0 12,21.5 9.188,9.188 0,0 0,21.5 12,9.188 9.188,0 0,0 12,2.5M12,1A10.7,10.7 0,0 1,23 12,10.7 10.7,0 0,1 12,23 10.7,10.7 0,0 1,1 12,10.7 10.7,0 0,1 12,1ZM8.5,8C7.672,8 7,8.9 7,10s0.672,2 1.5,2 1.5,-0.895 1.5,-2S9.328,8 8.5,8ZM15.5,8C14.672,8 14,8.9 14,10s0.672,2 1.5,2 1.5,-0.895 1.5,-2S16.328,8 15.5,8Z"/>
</vector>

View file

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="?icon_tint"
android:pathData="M12,1A10.7,10.7 0,0 0,1 12,10.7 10.7,0 0,0 12,23 10.7,10.7 0,0 0,23 12,10.7 10.7,0 0,0 12,1ZM15.5,8c0.828,0 1.5,0.895 1.5,2s-0.672,2 -1.5,2S14,11.105 14,10 14.672,8 15.5,8ZM8.5,8C9.328,8 10,8.9 10,10s-0.672,2 -1.5,2S7,11.105 7,10 7.672,8 8.5,8ZM17.509,15.942A6.8,6.8 0,0 1,12 18.663a6.817,6.817 0,0 1,-5.491 -2.7,0.75 0.75,0 1,1 1.207,-0.89A5.31,5.31 0,0 0,12 17.163a5.3,5.3 0,0 0,4.3 -2.105,0.75 0.75,0 1,1 1.211,0.884Z"/>
</vector>

View file

@ -0,0 +1,4 @@
<vector android:height="24dp" android:viewportHeight="20"
android:viewportWidth="20" android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="@color/grey_400" android:pathData="M16,2L4,2A3,3 0,0 0,1 5L1,15a3,3 0,0 0,3 3L16,18a3,3 0,0 0,3 -3L19,5A3,3 0,0 0,16 2ZM15.171,8.911L12.793,8.911l-0.464,2.178h2.077L14.406,12.54h-2.38L11.5,15L10.011,15l0.523,-2.46L8.4,12.54L7.873,15L6.381,15l0.524,-2.46L4.829,12.54L4.829,11.089L7.207,11.089l0.464,-2.178L5.594,8.911L5.594,7.46h2.38L8.5,5L9.99,5L9.466,7.46L11.6,7.46L12.127,5h1.492L13.1,7.46h2.076ZM9.163,8.911L11.3,8.911l-0.464,2.178L8.7,11.089Z"/>
</vector>

View file

@ -0,0 +1,4 @@
<vector android:height="24dp" android:viewportHeight="20"
android:viewportWidth="20" android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="@color/grey_600" android:pathData="M16,3.5A1.5,1.5 0,0 1,17.5 5V15A1.5,1.5 0,0 1,16 16.5H4A1.5,1.5 0,0 1,2.5 15V5A1.5,1.5 0,0 1,4 3.5H16M16,2H4A3,3 0,0 0,1 5V15a3,3 0,0 0,3 3H16a3,3 0,0 0,3 -3V5a3,3 0,0 0,-3 -3ZM10.011,15l0.523,-2.46H8.4L7.873,15H6.381l0.524,-2.46H4.829V11.089H7.207l0.464,-2.178H5.594V7.46h2.38L8.5,5H9.99L9.466,7.46H11.6L12.127,5h1.492L13.1,7.46h2.076V8.911H12.793l-0.464,2.178h2.077V12.54h-2.38L11.5,15ZM11.3,8.911H9.163L8.7,11.089h2.138Z"/>
</vector>

View file

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="20dp"
android:height="20dp"
android:viewportWidth="20"
android:viewportHeight="20">
<path
android:fillColor="@color/grey_400"
android:pathData="M14.5,8.5l2.2,-2.2c1.6,-1.6 2.2,-3.6 1.4,-4.4s-2.9,-0.1 -4.4,1.4l-2.2,2.2L7.7,4.2l-4.8,-2L1.1,4l4.5,3.9l1.7,1.7l-1.2,1.2c-0.2,0.3 -0.4,0.5 -0.6,0.9H2.1l-1,1.1l3.1,1.9c-0.2,0.5 -0.3,0.9 -0.3,0.9l0.6,0.6l0.9,-0.3l1.9,3l1,-1v-3.4c0.3,-0.2 0.6,-0.4 0.9,-0.6l1.1,-1.1l1.7,1.8l3.8,4.6l1.8,-1.8l-1.9,-4.9L14.5,8.5z"/>
</vector>

View file

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="20dp"
android:height="20dp"
android:viewportWidth="20"
android:viewportHeight="20">
<path
android:fillColor="@color/grey_600"
android:pathData="M16,8.2L17.2,7c1.6,-1.6 1.9,-3.9 0.8,-5c-0.5,-0.4 -1.1,-0.7 -1.8,-0.7c-1.2,0 -2.4,0.5 -3.2,1.4L11.7,4L8.3,3.6L3.2,1.9L0.9,4.2L5.5,8l1.1,1.1l-1.2,1.2c-0.3,0.4 -0.5,0.8 -0.7,1.2l-2.6,0.2l-1.2,1.2l2.9,1.7c-0.1,0.6 -0.2,1.1 -0.2,1.1l0.8,0.8l1,-0.2L7,19l1.2,-1.2l0.2,-2.5c0.4,-0.2 0.8,-0.4 1.2,-0.7l1.2,-1.2l1.1,1.1l3.7,4.7l2.3,-2.3l-1.5,-5.3L16,8.2zM6.5,6.9l-3.6,-3l0.5,-0.5L8,5.1l2.5,0.2L7.6,8.1L6.5,6.9zM8.1,13.9c-1,0.6 -2.1,1 -3.2,1.2C5.1,14 5.5,12.8 6,11.8l8,-8c0.6,-0.6 1.3,-1 2.2,-1c0.3,0 0.5,0.1 0.7,0.2c0.4,0.4 0.3,1.8 -0.8,2.9L8.1,13.9zM15.9,17L13,13.4l-1.1,-1.1l2.8,-2.8l0.2,2.5l1.5,4.7L15.9,17z"/>
</vector>

View file

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<shape
xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<corners android:radius="8dp" />
<solid android:color="@color/grey_700" />
</shape>

View file

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<shape
xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<corners android:radius="8dp" />
<solid android:color="@color/grey_100" />
</shape>

View file

@ -0,0 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:pathData="M24,0 L0,24 L24,24 Z"
android:strokeColor="#00000000"
android:fillColor="#ffffff"/>
</vector>

View file

@ -52,11 +52,11 @@
<include layout="@layout/conversation_input_panel"/> <include layout="@layout/conversation_input_panel"/>
<ViewStub <ViewStub
android:id="@+id/emoji_drawer_stub" android:id="@+id/emoji_drawer_stub"
android:layout="@layout/conversation_activity_emojidrawer_stub" android:layout_width="match_parent"
android:inflatedId="@+id/emoji_drawer" android:layout_height="wrap_content"
android:layout_width="match_parent" android:inflatedId="@+id/emoji_drawer"
android:layout_height="wrap_content"/> android:layout="@layout/conversation_activity_emojidrawer_stub" />
</LinearLayout> </LinearLayout>
</org.thoughtcrime.securesms.components.camera.QuickAttachmentDrawer> </org.thoughtcrime.securesms.components.camera.QuickAttachmentDrawer>

View file

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<org.thoughtcrime.securesms.components.emoji.EmojiDrawer <org.thoughtcrime.securesms.components.emoji.MediaKeyboard
xmlns:android="http://schemas.android.com/apk/res/android" xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/emoji_drawer" android:id="@+id/emoji_drawer"
android:layout_width="match_parent" android:layout_width="match_parent"

View file

@ -0,0 +1,38 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="2dp"
android:background="?selectableItemBackground">
<ImageView
android:id="@+id/emoji_image"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:paddingTop="6dp"
android:paddingEnd="6dp"
android:paddingStart="6dp"
android:adjustViewBounds="true"
android:scaleType="fitCenter" />
<org.thoughtcrime.securesms.components.emoji.AsciiEmojiView
android:id="@+id/emoji_text"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingTop="6dp"
android:paddingEnd="6dp"
android:paddingStart="6dp"
android:visibility="gone"/>
<ImageView
android:id="@+id/emoji_variation_hint"
android:layout_width="7dp"
android:layout_height="7dp"
android:layout_gravity="bottom|right|end"
app:srcCompat="@drawable/triangle_bottom_right_corner"
android:tint="@color/grey_400_transparent"/>
</FrameLayout>

View file

@ -4,16 +4,11 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent"> android:layout_height="match_parent">
<GridView android:id="@+id/emoji" <androidx.recyclerview.widget.RecyclerView
android:visibility="visible" android:id="@+id/emoji"
android:layout_width="fill_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:stretchMode="columnWidth" android:clipToPadding="false"
android:columnWidth="@dimen/emoji_drawer_size" android:paddingBottom="8dp"/>
android:horizontalSpacing="0dp"
android:verticalSpacing="0dp"
android:numColumns="auto_fit"
android:gravity="center"
android:background="?emoji_tab_strip_background"/>
</FrameLayout> </FrameLayout>

View file

@ -0,0 +1,16 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="@dimen/media_keyboard_provider_icon_margin"
android:layout_marginRight="@dimen/media_keyboard_provider_icon_margin"
android:padding="@dimen/media_keyboard_provider_icon_padding">
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:srcCompat="@drawable/ic_emoji_filled" />
</FrameLayout>

View file

@ -0,0 +1,17 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="@dimen/media_keyboard_provider_icon_margin"
android:layout_marginRight="@dimen/media_keyboard_provider_icon_margin"
android:padding="@dimen/media_keyboard_provider_icon_padding"
android:background="@drawable/media_keyboard_selected_background_dark">
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:srcCompat="@drawable/ic_emoji_filled" />
</FrameLayout>

View file

@ -0,0 +1,16 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="@dimen/media_keyboard_provider_icon_margin"
android:layout_marginRight="@dimen/media_keyboard_provider_icon_margin"
android:padding="@dimen/media_keyboard_provider_icon_padding">
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:srcCompat="@drawable/ic_emoji_outline" />
</FrameLayout>

View file

@ -0,0 +1,17 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="@dimen/media_keyboard_provider_icon_margin"
android:layout_marginRight="@dimen/media_keyboard_provider_icon_margin"
android:padding="@dimen/media_keyboard_provider_icon_padding"
android:background="@drawable/media_keyboard_selected_background_light">
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:srcCompat="@drawable/ic_emoji_outline" />
</FrameLayout>

View file

@ -0,0 +1,14 @@
<?xml version="1.0" encoding="utf-8"?>
<HorizontalScrollView
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<LinearLayout
android:id="@+id/emoji_variation_container"
android:orientation="horizontal"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="?attr/emoji_variation_selector_background" />
</HorizontalScrollView>

View file

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<ImageView
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="50dp"
android:layout_height="50dp"
android:padding="6dp">
</ImageView>

View file

@ -48,7 +48,6 @@
android:paddingTop="@dimen/message_bubble_top_padding" android:paddingTop="@dimen/message_bubble_top_padding"
android:textColor="?conversation_item_outgoing_text_primary_color" android:textColor="?conversation_item_outgoing_text_primary_color"
android:textColorLink="?conversation_item_outgoing_text_primary_color" android:textColorLink="?conversation_item_outgoing_text_primary_color"
app:createInBackground="true"
app:scaleEmojis="true" app:scaleEmojis="true"
tools:text="Mango pickle lorem ipsum"/> tools:text="Mango pickle lorem ipsum"/>
</LinearLayout> </LinearLayout>

View file

@ -0,0 +1,149 @@
<?xml version="1.0" encoding="utf-8"?>
<merge
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
tools:parentTag="org.thoughtcrime.securesms.components.emoji.MediaKeyboard">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="?emoji_drawer_background">
<!-- <ImageView
android:id="@+id/media_keyboard_search"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="6dp"
android:layout_marginLeft="6dp"
android:padding="6dp"
app:srcCompat="@drawable/ic_search_24"
android:tint="?media_keyboard_button_color"
android:background="?selectableItemBackgroundBorderless"
android:visibility="invisible"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="@id/media_keyboard_provider_tabs"
app:layout_constraintBottom_toBottomOf="@id/media_keyboard_provider_tabs"
tools:visibility="visible"/> -->
<ImageView
android:id="@+id/media_keyboard_search"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="6dp"
android:layout_marginLeft="6dp"
android:padding="6dp"
android:background="?selectableItemBackgroundBorderless"
android:visibility="gone"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="@id/media_keyboard_provider_tabs"
app:layout_constraintBottom_toBottomOf="@id/media_keyboard_provider_tabs"
tools:visibility="gone"/>
<LinearLayout
android:id="@+id/media_keyboard_provider_tabs"
android:layout_width="0dp"
android:layout_height="40dp"
android:layout_marginStart="8dp"
android:layout_marginLeft="8dp"
android:gravity="center"
android:orientation="horizontal"
app:layout_constraintEnd_toStartOf="@id/media_keyboard_backspace"
app:layout_constraintStart_toEndOf="@id/media_keyboard_search"
app:layout_constraintTop_toBottomOf="@id/media_keyboard_tabs_top" />
<org.thoughtcrime.securesms.components.RepeatableImageKey
android:id="@+id/media_keyboard_backspace"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="6dp"
android:layout_marginRight="6dp"
android:padding="6dp"
android:src="@drawable/ic_backspace_grey600_24dp"
android:background="?selectableItemBackgroundBorderless"
android:visibility="invisible"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="@id/media_keyboard_provider_tabs"
app:layout_constraintBottom_toBottomOf="@id/media_keyboard_provider_tabs"
tools:visibility="visible"/>
<org.thoughtcrime.securesms.components.ControllableViewPager
android:id="@+id/media_keyboard_pager"
android:layout_width="match_parent"
android:layout_height="0dp"
android:visibility="visible"
app:layout_constraintTop_toBottomOf="@id/media_keyboard_provider_tabs"
app:layout_constraintBottom_toTopOf="@id/media_keyboard_tabs"/>
<View
android:layout_width="match_parent"
android:layout_height="0dp"
android:background="?emoji_tab_strip_background"
app:layout_constraintBottom_toBottomOf="@id/media_keyboard_tabs"
app:layout_constraintTop_toTopOf="@+id/media_keyboard_tabs" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/media_keyboard_tabs_top"
android:layout_width="0dp"
android:layout_height="40dp"
android:visibility="gone"
app:layout_constraintEnd_toStartOf="@id/media_keyboard_add"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:layout_height="40dp"
tools:visibility="visible" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/media_keyboard_tabs"
android:layout_width="0dp"
android:layout_height="40dp"
android:visibility="gone"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@id/media_keyboard_add"
app:layout_constraintStart_toStartOf="parent"
tools:layout_height="40dp"
tools:visibility="visible" />
<org.thoughtcrime.securesms.components.RepeatableImageKey
android:id="@+id/media_keyboard_backspace_backup"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="6dp"
android:layout_marginRight="6dp"
android:padding="6dp"
android:src="@drawable/ic_backspace_grey600_24dp"
android:background="?selectableItemBackgroundBorderless"
android:visibility="invisible"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="@id/media_keyboard_tabs"
app:layout_constraintBottom_toBottomOf="@id/media_keyboard_tabs"
tools:visibility="visible"/>
<ImageView
android:id="@+id/media_keyboard_add"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:visibility="gone"
app:layout_constraintBottom_toBottomOf="@id/media_keyboard_tabs"
app:layout_constraintTop_toTopOf="@id/media_keyboard_tabs"
app:layout_constraintEnd_toStartOf="@id/media_keyboard_backspace_backup"
/>
<!-- <ImageView
android:id="@+id/media_keyboard_add"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="12dp"
android:layout_marginRight="12dp"
android:scaleType="fitCenter"
android:tint="?media_keyboard_button_color"
android:visibility="gone"
android:background="?selectableItemBackground"
app:srcCompat="@drawable/ic_plus_24"
app:layout_constraintBottom_toBottomOf="@id/media_keyboard_tabs"
app:layout_constraintTop_toTopOf="@id/media_keyboard_tabs"
app:layout_constraintEnd_toStartOf="@id/media_keyboard_backspace_backup"
tools:visibility="visible"/>-->
</androidx.constraintlayout.widget.ConstraintLayout>
</merge>

View file

@ -0,0 +1,33 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
xmlns:tools="http://schemas.android.com/tools"
android:orientation="vertical"
android:background="?selectableItemBackground"
android:animateLayoutChanges="true">
<View
android:id="@+id/media_keyboard_top_tab_indicator"
android:layout_width="match_parent"
android:layout_height="2dp"
android:background="?emoji_tab_indicator"
android:visibility="gone"
tools:visibility="visible" />
<ImageView
android:id="@+id/media_keyboard_bottom_tab_image"
android:layout_width="36dp"
android:layout_height="36dp"
android:padding="6dp" />
<View
android:id="@+id/media_keyboard_bottom_tab_indicator"
android:layout_width="match_parent"
android:layout_height="2dp"
android:background="?emoji_tab_indicator"
android:visibility="gone"
tools:visibility="visible" />
</LinearLayout>

View file

@ -1,6 +1,8 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<resources> <resources>
<attr name="theme_type" format="string"/> <attr name="theme_type" format="string"/>
<attr name="icon_tint" format="color" />
<attr name="icon_tint_dark" format="color" />
<attr name="attachment_type_selector_background" format="color"/> <attr name="attachment_type_selector_background" format="color"/>
@ -51,6 +53,7 @@
<attr name="emoji_tab_indicator" format="color" /> <attr name="emoji_tab_indicator" format="color" />
<attr name="emoji_tab_underline" format="color" /> <attr name="emoji_tab_underline" format="color" />
<attr name="emoji_tab_seperator" format="color" /> <attr name="emoji_tab_seperator" format="color" />
<attr name="emoji_drawer_background" format="color" />
<attr name="emoji_background" format="color" /> <attr name="emoji_background" format="color" />
<attr name="emoji_text_color" format="color" /> <attr name="emoji_text_color" format="color" />
@ -61,9 +64,11 @@
<attr name="emoji_category_activity" format="reference"/> <attr name="emoji_category_activity" format="reference"/>
<attr name="emoji_category_places" format="reference"/> <attr name="emoji_category_places" format="reference"/>
<attr name="emoji_category_objects" format="reference"/> <attr name="emoji_category_objects" format="reference"/>
<attr name="emoji_category_symbols" format="reference"/>
<attr name="emoji_category_symbol" format="reference"/> <attr name="emoji_category_symbol" format="reference"/>
<attr name="emoji_category_flags" format="reference"/> <attr name="emoji_category_flags" format="reference"/>
<attr name="emoji_category_emoticons" format="reference"/> <attr name="emoji_category_emoticons" format="reference"/>
<attr name="emoji_variation_selector_background" format="reference|color" />
<attr name="quick_camera_icon" format="reference"/> <attr name="quick_camera_icon" format="reference"/>
<attr name="quick_mic_icon" format="reference"/> <attr name="quick_mic_icon" format="reference"/>
@ -146,6 +151,19 @@
<attr name="login_top_background" format="color"/> <attr name="login_top_background" format="color"/>
<attr name="login_floating_background" format="reference"/> <attr name="login_floating_background" format="reference"/>
<declare-styleable name="MediaKeyboard">
<attr name="tabs_gravity" format="enum">
<enum name="bottom" value="0" />
<enum name="top" value="1" />
</attr>
</declare-styleable>
<declare-styleable name="EmojiTextView">
<attr name="scaleEmojis" format="boolean" />
<attr name="emoji_maxLength" format="integer" />
<attr name="emoji_forceCustom" format="boolean" />
</declare-styleable>
<declare-styleable name="ColorPreference"> <declare-styleable name="ColorPreference">
<attr name="itemLayout" format="reference" /> <attr name="itemLayout" format="reference" />
<attr name="choices" format="reference" /> <attr name="choices" format="reference" />
@ -187,11 +205,6 @@
<attr name="pickerColors" format="reference" /> <attr name="pickerColors" format="reference" />
</declare-styleable> </declare-styleable>
<declare-styleable name="EmojiTextView">
<attr name="scaleEmojis" format="boolean" />
<attr name="createInBackground" format="boolean" />
</declare-styleable>
<declare-styleable name="RingtonePreference"> <declare-styleable name="RingtonePreference">
<attr name="showAdd" format="boolean" /> <attr name="showAdd" format="boolean" />
<attr name="summaryHasRingtone" format="string|reference" /> <attr name="summaryHasRingtone" format="string|reference" />

View file

@ -6,6 +6,7 @@
<dimen name="min_custom_keyboard_size">110dp</dimen> <dimen name="min_custom_keyboard_size">110dp</dimen>
<dimen name="min_custom_keyboard_top_margin">170dp</dimen> <dimen name="min_custom_keyboard_top_margin">170dp</dimen>
<dimen name="emoji_drawer_item_padding">5dp</dimen> <dimen name="emoji_drawer_item_padding">5dp</dimen>
<dimen name="emoji_drawer_item_width">48dp</dimen>
<dimen name="emoji_drawer_indicator_height">1.5dp</dimen> <dimen name="emoji_drawer_indicator_height">1.5dp</dimen>
<dimen name="emoji_drawer_left_right_padding">5dp</dimen> <dimen name="emoji_drawer_left_right_padding">5dp</dimen>
<dimen name="conversation_item_body_text_size">16sp</dimen> <dimen name="conversation_item_body_text_size">16sp</dimen>
@ -36,6 +37,10 @@
<dimen name="gallery_thumbnail_radius">1dp</dimen> <dimen name="gallery_thumbnail_radius">1dp</dimen>
<dimen name="media_keyboard_provider_icon_padding">5dp</dimen>
<dimen name="media_keyboard_provider_icon_margin">4dp</dimen>
<dimen name="conversation_compose_height">40dp</dimen>
<dimen name="conversation_individual_right_gutter">12dp</dimen> <dimen name="conversation_individual_right_gutter">12dp</dimen>
<dimen name="conversation_individual_left_gutter">12dp</dimen> <dimen name="conversation_individual_left_gutter">12dp</dimen>
<dimen name="conversation_group_left_gutter">52dp</dimen> <dimen name="conversation_group_left_gutter">52dp</dimen>

View file

@ -137,6 +137,7 @@
<item name="emoji_tab_underline">#44555555</item> <item name="emoji_tab_underline">#44555555</item>
<item name="emoji_tab_seperator">@color/gray20</item> <item name="emoji_tab_seperator">@color/gray20</item>
<item name="emoji_text_color">@color/black</item> <item name="emoji_text_color">@color/black</item>
<item name="emoji_drawer_background">@color/grey_100</item>
<item name="emoji_category_recent">@drawable/emoji_category_recent_light</item> <item name="emoji_category_recent">@drawable/emoji_category_recent_light</item>
<item name="emoji_category_people">@drawable/emoji_category_people_light</item> <item name="emoji_category_people">@drawable/emoji_category_people_light</item>
@ -148,7 +149,7 @@
<item name="emoji_category_symbol">@drawable/emoji_category_symbol_light</item> <item name="emoji_category_symbol">@drawable/emoji_category_symbol_light</item>
<item name="emoji_category_flags">@drawable/emoji_category_flags_light</item> <item name="emoji_category_flags">@drawable/emoji_category_flags_light</item>
<item name="emoji_category_emoticons">@drawable/emoji_category_emoticons_light</item> <item name="emoji_category_emoticons">@drawable/emoji_category_emoticons_light</item>
<item name="emoji_variation_selector_background">@drawable/emoji_variation_selector_background_light</item>
<!-- light theme --> <!-- light theme -->
<item name="conversation_item_incoming_bubble_color">@color/white</item> <item name="conversation_item_incoming_bubble_color">@color/white</item>
@ -202,6 +203,8 @@
<item name="reminder_header_background">#ff1d85d7</item> <item name="reminder_header_background">#ff1d85d7</item>
<item name="pref_icon_tint">@color/delta_primary_dark</item> <item name="pref_icon_tint">@color/delta_primary_dark</item>
<item name="icon_tint">@color/grey_700</item>
<item name="icon_tint_dark">@color/grey_100</item>
<item name="group_members_dialog_icon">@drawable/ic_group_grey600_24dp</item> <item name="group_members_dialog_icon">@drawable/ic_group_grey600_24dp</item>
<item name="preferenceTheme">@style/PreferenceThemeOverlay.Fix</item> <item name="preferenceTheme">@style/PreferenceThemeOverlay.Fix</item>
@ -313,6 +316,7 @@
<item name="emoji_tab_seperator">@color/gray70</item> <item name="emoji_tab_seperator">@color/gray70</item>
<item name="emoji_background">@color/black</item> <item name="emoji_background">@color/black</item>
<item name="emoji_text_color">@color/white</item> <item name="emoji_text_color">@color/white</item>
<item name="emoji_drawer_background">@color/grey_700</item>
<item name="emoji_category_recent">@drawable/emoji_category_recent_dark</item> <item name="emoji_category_recent">@drawable/emoji_category_recent_dark</item>
<item name="emoji_category_people">@drawable/emoji_category_people_dark</item> <item name="emoji_category_people">@drawable/emoji_category_people_dark</item>
@ -324,6 +328,7 @@
<item name="emoji_category_symbol">@drawable/emoji_category_symbol_dark</item> <item name="emoji_category_symbol">@drawable/emoji_category_symbol_dark</item>
<item name="emoji_category_flags">@drawable/emoji_category_flags_dark</item> <item name="emoji_category_flags">@drawable/emoji_category_flags_dark</item>
<item name="emoji_category_emoticons">@drawable/emoji_category_emoticons_dark</item> <item name="emoji_category_emoticons">@drawable/emoji_category_emoticons_dark</item>
<item name="emoji_variation_selector_background">@drawable/emoji_variation_selector_background_dark</item>
<item name="quick_camera_icon">@drawable/quick_camera_dark</item> <item name="quick_camera_icon">@drawable/quick_camera_dark</item>
<item name="quick_mic_icon">@drawable/ic_mic_white_24dp</item> <item name="quick_mic_icon">@drawable/ic_mic_white_24dp</item>
@ -346,6 +351,9 @@
<item name="reminder_header_background">@color/delta_primary_dark</item> <item name="reminder_header_background">@color/delta_primary_dark</item>
<item name="pref_icon_tint">@color/core_white</item> <item name="pref_icon_tint">@color/core_white</item>
<item name="icon_tint">@color/grey_100</item>
<item name="icon_tint_dark">?icon_tint</item>
<item name="group_members_dialog_icon">@drawable/ic_group_white_24dp</item> <item name="group_members_dialog_icon">@drawable/ic_group_white_24dp</item>
<item name="preferenceTheme">@style/PreferenceThemeOverlay.Fix</item> <item name="preferenceTheme">@style/PreferenceThemeOverlay.Fix</item>

View file

@ -80,7 +80,8 @@ import org.thoughtcrime.securesms.components.SendButton;
import org.thoughtcrime.securesms.components.camera.QuickAttachmentDrawer; import org.thoughtcrime.securesms.components.camera.QuickAttachmentDrawer;
import org.thoughtcrime.securesms.components.camera.QuickAttachmentDrawer.AttachmentDrawerListener; import org.thoughtcrime.securesms.components.camera.QuickAttachmentDrawer.AttachmentDrawerListener;
import org.thoughtcrime.securesms.components.camera.QuickAttachmentDrawer.DrawerState; import org.thoughtcrime.securesms.components.camera.QuickAttachmentDrawer.DrawerState;
import org.thoughtcrime.securesms.components.emoji.EmojiDrawer; import org.thoughtcrime.securesms.components.emoji.EmojiKeyboardProvider;
import org.thoughtcrime.securesms.components.emoji.MediaKeyboard;
import org.thoughtcrime.securesms.components.reminder.ReminderView; import org.thoughtcrime.securesms.components.reminder.ReminderView;
import org.thoughtcrime.securesms.connect.ApplicationDcContext; import org.thoughtcrime.securesms.connect.ApplicationDcContext;
import org.thoughtcrime.securesms.connect.DcHelper; import org.thoughtcrime.securesms.connect.DcHelper;
@ -139,6 +140,17 @@ import static org.thoughtcrime.securesms.util.RelayUtil.isSharing;
*/ */
@SuppressLint("StaticFieldLeak") @SuppressLint("StaticFieldLeak")
public class ConversationActivity extends PassphraseRequiredActionBarActivity public class ConversationActivity extends PassphraseRequiredActionBarActivity
/*implements xAttachmentManager.AttachmentListener,
xOnKeyboardShownListener,
xInputPanel.Listener,
xInputPanel.MediaListener,
xComposeText.CursorPositionChangedListener,
xConversationSearchBottomBar.EventListener,
StickerKeyboardProvider.StickerEventListener,
AttachmentKeyboard.Callback,
xConversationReactionOverlay.OnReactionSelectedListener,
xReactWithAnyEmojiBottomSheetDialogFragment.Callback*/
implements ConversationFragment.ConversationFragmentListener, implements ConversationFragment.ConversationFragmentListener,
AttachmentManager.AttachmentListener, AttachmentManager.AttachmentListener,
SearchView.OnQueryTextListener, SearchView.OnQueryTextListener,
@ -180,7 +192,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
private AttachmentTypeSelector attachmentTypeSelector; private AttachmentTypeSelector attachmentTypeSelector;
private AttachmentManager attachmentManager; private AttachmentManager attachmentManager;
private AudioRecorder audioRecorder; private AudioRecorder audioRecorder;
private Stub<EmojiDrawer> emojiDrawerStub; private Stub<MediaKeyboard> emojiDrawerStub;
protected HidingLinearLayout quickAttachmentToggle; protected HidingLinearLayout quickAttachmentToggle;
private QuickAttachmentDrawer quickAttachmentDrawer; private QuickAttachmentDrawer quickAttachmentDrawer;
private InputPanel inputPanel; private InputPanel inputPanel;
@ -258,6 +270,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
@Override @Override
protected void onNewIntent(Intent intent) { protected void onNewIntent(Intent intent) {
super.onNewIntent(intent);
Log.w(TAG, "onNewIntent()"); Log.w(TAG, "onNewIntent()");
if (isFinishing()) { if (isFinishing()) {
@ -1371,8 +1384,8 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
@Override @Override
public void onEmojiToggle() { public void onEmojiToggle() {
if (!emojiDrawerStub.resolved()) { if (!emojiDrawerStub.resolved()) {
inputPanel.setEmojiDrawer(emojiDrawerStub.get()); initializeMediaKeyboardProviders(emojiDrawerStub.get(), false);
emojiDrawerStub.get().setEmojiEventListener(inputPanel); inputPanel.setMediaKeyboard(emojiDrawerStub.get());
} }
if (container.getCurrentInput() == emojiDrawerStub.get()) { if (container.getCurrentInput() == emojiDrawerStub.get()) {
@ -1396,6 +1409,13 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
} }
private void initializeMediaKeyboardProviders(@NonNull MediaKeyboard mediaKeyboard, boolean stickersAvailable) {
boolean isSystemEmojiPreferred = Prefs.isSystemEmojiPreferred(this);
if (!isSystemEmojiPreferred) {
mediaKeyboard.setProviders(0, new EmojiKeyboardProvider(this, inputPanel));
}
}
// Listeners // Listeners
private class AttachmentTypeListener implements AttachmentTypeSelector.AttachmentClickedListener { private class AttachmentTypeListener implements AttachmentTypeSelector.AttachmentClickedListener {
@ -1521,6 +1541,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
@Override @Override
public void handleReplyMessage(DcMsg messageRecord) { public void handleReplyMessage(DcMsg messageRecord) {
inputPanel.clickOnComposeInput();
} }
@Override @Override

View file

@ -33,7 +33,8 @@ import com.bumptech.glide.load.engine.DiskCacheStrategy;
import com.soundcloud.android.crop.Crop; import com.soundcloud.android.crop.Crop;
import org.thoughtcrime.securesms.components.InputAwareLayout; import org.thoughtcrime.securesms.components.InputAwareLayout;
import org.thoughtcrime.securesms.components.emoji.EmojiDrawer; import org.thoughtcrime.securesms.components.emoji.EmojiKeyboardProvider;
import org.thoughtcrime.securesms.components.emoji.MediaKeyboard;
import org.thoughtcrime.securesms.connect.DcHelper; import org.thoughtcrime.securesms.connect.DcHelper;
import org.thoughtcrime.securesms.contacts.avatars.ResourceContactPhoto; import org.thoughtcrime.securesms.contacts.avatars.ResourceContactPhoto;
import org.thoughtcrime.securesms.mms.GlideApp; import org.thoughtcrime.securesms.mms.GlideApp;
@ -49,6 +50,7 @@ import org.thoughtcrime.securesms.util.IntentUtils;
import org.thoughtcrime.securesms.util.Prefs; import org.thoughtcrime.securesms.util.Prefs;
import org.thoughtcrime.securesms.util.Util; import org.thoughtcrime.securesms.util.Util;
import org.thoughtcrime.securesms.util.ViewUtil; import org.thoughtcrime.securesms.util.ViewUtil;
import org.thoughtcrime.securesms.util.views.Stub;
import java.io.ByteArrayOutputStream; import java.io.ByteArrayOutputStream;
import java.io.File; import java.io.File;
@ -61,7 +63,7 @@ import java.util.List;
import static android.provider.MediaStore.EXTRA_OUTPUT; import static android.provider.MediaStore.EXTRA_OUTPUT;
@SuppressLint("StaticFieldLeak") @SuppressLint("StaticFieldLeak")
public class CreateProfileActivity extends BaseActionBarActivity { public class CreateProfileActivity extends BaseActionBarActivity implements EmojiKeyboardProvider.EmojiEventListener {
private static final String TAG = CreateProfileActivity.class.getSimpleName(); private static final String TAG = CreateProfileActivity.class.getSimpleName();
@ -75,7 +77,7 @@ public class CreateProfileActivity extends BaseActionBarActivity {
private InputAwareLayout container; private InputAwareLayout container;
private ImageView avatar; private ImageView avatar;
private EditText name; private EditText name;
private EmojiDrawer emojiDrawer; private MediaKeyboard emojiDrawer;
private TextInputEditText statusView; private TextInputEditText statusView;
private View reveal; private View reveal;
@ -300,24 +302,30 @@ public class CreateProfileActivity extends BaseActionBarActivity {
} }
} }
@Override
public void onEmojiSelected(String emoji) {
final int start = name.getSelectionStart();
final int end = name.getSelectionEnd();
name.getText().replace(Math.min(start, end), Math.max(start, end), emoji);
name.setSelection(start + emoji.length());
}
@Override
public void onKeyEvent(KeyEvent keyEvent) {
name.dispatchKeyEvent(keyEvent);
}
private void initializeMediaKeyboardProviders(@NonNull MediaKeyboard mediaKeyboard) {
boolean isSystemEmojiPreferred = Prefs.isSystemEmojiPreferred(this);
if (!isSystemEmojiPreferred) {
mediaKeyboard.setProviders(0, new EmojiKeyboardProvider(this, this));
}
}
private void initializeEmojiInput() { private void initializeEmojiInput() {
initializeMediaKeyboardProviders(emojiDrawer);
this.emojiDrawer.setEmojiEventListener(new EmojiDrawer.EmojiEventListener() {
@Override
public void onKeyEvent(KeyEvent keyEvent) {
name.dispatchKeyEvent(keyEvent);
}
@Override
public void onEmojiSelected(String emoji) {
final int start = name.getSelectionStart();
final int end = name.getSelectionEnd();
name.getText().replace(Math.min(start, end), Math.max(start, end), emoji);
name.setSelection(start + emoji.length());
}
});
this.name.setOnClickListener(v -> container.showSoftkey(name)); this.name.setOnClickListener(v -> container.showSoftkey(name));
} }
@ -420,4 +428,5 @@ public class CreateProfileActivity extends BaseActionBarActivity {
String newStatus = statusView.getText().toString().trim(); String newStatus = statusView.getText().toString().trim();
DcHelper.set(this, DcHelper.CONFIG_SELF_STATUS, newStatus); DcHelper.set(this, DcHelper.CONFIG_SELF_STATUS, newStatus);
} }
} }

View file

@ -0,0 +1,34 @@
package org.thoughtcrime.securesms.components;
import android.content.Context;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.viewpager.widget.ViewPager;
import android.util.AttributeSet;
import android.view.MotionEvent;
import org.thoughtcrime.securesms.components.viewpager.HackyViewPager;
/**
* An implementation of {@link ViewPager} that disables swiping when the view is disabled.
*/
public class ControllableViewPager extends HackyViewPager {
public ControllableViewPager(@NonNull Context context) {
super(context);
}
public ControllableViewPager(@NonNull Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
}
@Override
public boolean onTouchEvent(MotionEvent ev) {
return isEnabled() && super.onTouchEvent(ev);
}
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
return isEnabled() && super.onInterceptTouchEvent(ev);
}
}

View file

@ -4,9 +4,6 @@ import android.annotation.TargetApi;
import android.content.Context; import android.content.Context;
import android.net.Uri; import android.net.Uri;
import android.os.Build; import android.os.Build;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.core.view.ViewCompat;
import android.text.format.DateUtils; import android.text.format.DateUtils;
import android.util.AttributeSet; import android.util.AttributeSet;
import android.util.Log; import android.util.Log;
@ -20,9 +17,14 @@ import android.widget.LinearLayout;
import android.widget.TextView; import android.widget.TextView;
import android.widget.Toast; import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.core.view.ViewCompat;
import org.thoughtcrime.securesms.R; import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.components.emoji.EmojiDrawer; import org.thoughtcrime.securesms.components.emoji.EmojiKeyboardProvider;
import org.thoughtcrime.securesms.components.emoji.EmojiToggle; import org.thoughtcrime.securesms.components.emoji.EmojiToggle;
import org.thoughtcrime.securesms.components.emoji.MediaKeyboard;
import org.thoughtcrime.securesms.util.Prefs; import org.thoughtcrime.securesms.util.Prefs;
import org.thoughtcrime.securesms.util.Util; import org.thoughtcrime.securesms.util.Util;
import org.thoughtcrime.securesms.util.ViewUtil; import org.thoughtcrime.securesms.util.ViewUtil;
@ -36,26 +38,26 @@ import java.util.concurrent.atomic.AtomicLong;
public class InputPanel extends LinearLayout public class InputPanel extends LinearLayout
implements MicrophoneRecorderView.Listener, implements MicrophoneRecorderView.Listener,
KeyboardAwareLinearLayout.OnKeyboardShownListener, KeyboardAwareLinearLayout.OnKeyboardShownListener,
EmojiDrawer.EmojiEventListener EmojiKeyboardProvider.EmojiEventListener
{ {
private static final String TAG = InputPanel.class.getSimpleName(); private static final String TAG = InputPanel.class.getSimpleName();
private static final int FADE_TIME = 150; private static final int FADE_TIME = 150;
private EmojiToggle emojiToggle; private EmojiToggle mediaKeyboard;
private ComposeText composeText; private ComposeText composeText;
private View quickCameraToggle; private View quickCameraToggle;
private View quickAudioToggle; private View quickAudioToggle;
private View buttonToggle; private View buttonToggle;
private View recordingContainer; private View recordingContainer;
private MicrophoneRecorderView microphoneRecorderView; private MicrophoneRecorderView microphoneRecorderView;
private SlideToCancel slideToCancel; private SlideToCancel slideToCancel;
private RecordTime recordTime; private RecordTime recordTime;
private @Nullable Listener listener; private @Nullable Listener listener;
private boolean emojiVisible; private boolean emojiVisible;
public InputPanel(Context context) { public InputPanel(Context context) {
super(context); super(context);
@ -74,13 +76,13 @@ public class InputPanel extends LinearLayout
public void onFinishInflate() { public void onFinishInflate() {
super.onFinishInflate(); super.onFinishInflate();
this.emojiToggle = findViewById(R.id.emoji_toggle);
this.mediaKeyboard = findViewById(R.id.emoji_toggle);
this.composeText = findViewById(R.id.embedded_text_editor); this.composeText = findViewById(R.id.embedded_text_editor);
this.quickCameraToggle = findViewById(R.id.quick_camera_toggle); this.quickCameraToggle = findViewById(R.id.quick_camera_toggle);
this.quickAudioToggle = findViewById(R.id.quick_audio_toggle); this.quickAudioToggle = findViewById(R.id.quick_audio_toggle);
this.buttonToggle = findViewById(R.id.button_toggle); this.buttonToggle = findViewById(R.id.button_toggle);
this.recordingContainer = findViewById(R.id.recording_container); this.recordingContainer = findViewById(R.id.recording_container);
this.recordTime = new RecordTime(findViewById(R.id.record_time));
this.slideToCancel = new SlideToCancel(findViewById(R.id.slide_to_cancel)); this.slideToCancel = new SlideToCancel(findViewById(R.id.slide_to_cancel));
this.microphoneRecorderView = findViewById(R.id.recorder_view); this.microphoneRecorderView = findViewById(R.id.recorder_view);
this.microphoneRecorderView.setListener(this); this.microphoneRecorderView.setListener(this);
@ -91,10 +93,10 @@ public class InputPanel extends LinearLayout
} }
if (Prefs.isSystemEmojiPreferred(getContext())) { if (Prefs.isSystemEmojiPreferred(getContext())) {
emojiToggle.setVisibility(View.GONE); mediaKeyboard.setVisibility(View.GONE);
emojiVisible = false; emojiVisible = false;
} else { } else {
emojiToggle.setVisibility(View.VISIBLE); mediaKeyboard.setVisibility(View.VISIBLE);
emojiVisible = true; emojiVisible = true;
} }
} }
@ -102,15 +104,36 @@ public class InputPanel extends LinearLayout
public void setListener(final @NonNull Listener listener) { public void setListener(final @NonNull Listener listener) {
this.listener = listener; this.listener = listener;
emojiToggle.setOnClickListener(v -> listener.onEmojiToggle()); mediaKeyboard.setOnClickListener(v -> listener.onEmojiToggle());
} }
public void setMediaListener(@NonNull MediaListener listener) { public void setMediaListener(@NonNull MediaListener listener) {
composeText.setMediaListener(listener); composeText.setMediaListener(listener);
} }
public void setEmojiDrawer(@NonNull EmojiDrawer emojiDrawer) { public void clickOnComposeInput() {
emojiToggle.attach(emojiDrawer); composeText.performClick();
}
public void setMediaKeyboard(@NonNull MediaKeyboard mediaKeyboard) {
this.mediaKeyboard.attach(mediaKeyboard);
}
public void showMediaKeyboardToggle(boolean show) {
emojiVisible = show;
mediaKeyboard.setVisibility(show ? View.VISIBLE : GONE);
}
public void setMediaKeyboardToggleMode(boolean isSticker) {
mediaKeyboard.setStickerMode(isSticker);
}
public boolean isStickerMode() {
return mediaKeyboard.isStickerMode();
}
public View getMediaKeyboardToggleAnchorView() {
return mediaKeyboard;
} }
@Override @Override
@ -124,7 +147,7 @@ public class InputPanel extends LinearLayout
recordTime.display(); recordTime.display();
slideToCancel.display(startPositionX); slideToCancel.display(startPositionX);
if (emojiVisible) ViewUtil.fadeOut(emojiToggle, FADE_TIME, View.INVISIBLE); if (emojiVisible) ViewUtil.fadeOut(mediaKeyboard, FADE_TIME, View.INVISIBLE);
ViewUtil.fadeOut(composeText, FADE_TIME, View.INVISIBLE); ViewUtil.fadeOut(composeText, FADE_TIME, View.INVISIBLE);
ViewUtil.fadeOut(quickCameraToggle, FADE_TIME, View.INVISIBLE); ViewUtil.fadeOut(quickCameraToggle, FADE_TIME, View.INVISIBLE);
ViewUtil.fadeOut(quickAudioToggle, FADE_TIME, View.INVISIBLE); ViewUtil.fadeOut(quickAudioToggle, FADE_TIME, View.INVISIBLE);
@ -154,7 +177,7 @@ public class InputPanel extends LinearLayout
float position = absoluteX / recordingContainer.getWidth(); float position = absoluteX / recordingContainer.getWidth();
if (direction == ViewCompat.LAYOUT_DIRECTION_LTR && position <= 0.5 || if (direction == ViewCompat.LAYOUT_DIRECTION_LTR && position <= 0.5 ||
direction == ViewCompat.LAYOUT_DIRECTION_RTL && position >= 0.6) direction == ViewCompat.LAYOUT_DIRECTION_RTL && position >= 0.6)
{ {
this.microphoneRecorderView.cancelAction(); this.microphoneRecorderView.cancelAction();
} }
@ -172,7 +195,7 @@ public class InputPanel extends LinearLayout
public void setEnabled(boolean enabled) { public void setEnabled(boolean enabled) {
composeText.setEnabled(enabled); composeText.setEnabled(enabled);
emojiToggle.setEnabled(enabled); mediaKeyboard.setEnabled(enabled);
quickAudioToggle.setEnabled(enabled); quickAudioToggle.setEnabled(enabled);
quickCameraToggle.setEnabled(enabled); quickCameraToggle.setEnabled(enabled);
} }
@ -184,7 +207,7 @@ public class InputPanel extends LinearLayout
future.addListener(new AssertedSuccessListener<Void>() { future.addListener(new AssertedSuccessListener<Void>() {
@Override @Override
public void onSuccess(Void result) { public void onSuccess(Void result) {
if (emojiVisible) ViewUtil.fadeIn(emojiToggle, FADE_TIME); if (emojiVisible) ViewUtil.fadeIn(mediaKeyboard, FADE_TIME);
ViewUtil.fadeIn(composeText, FADE_TIME); ViewUtil.fadeIn(composeText, FADE_TIME);
ViewUtil.fadeIn(quickCameraToggle, FADE_TIME); ViewUtil.fadeIn(quickCameraToggle, FADE_TIME);
ViewUtil.fadeIn(quickAudioToggle, FADE_TIME); ViewUtil.fadeIn(quickAudioToggle, FADE_TIME);
@ -197,7 +220,7 @@ public class InputPanel extends LinearLayout
@Override @Override
public void onKeyboardShown() { public void onKeyboardShown() {
emojiToggle.setToEmoji(); mediaKeyboard.setToMedia();
} }
@Override @Override
@ -210,7 +233,6 @@ public class InputPanel extends LinearLayout
composeText.insertEmoji(emoji); composeText.insertEmoji(emoji);
} }
public interface Listener { public interface Listener {
void onRecorderStarted(); void onRecorderStarted();
void onRecorderFinished(); void onRecorderFinished();
@ -274,7 +296,6 @@ public class InputPanel extends LinearLayout
return ViewCompat.getLayoutDirection(slideToCancelView) == ViewCompat.LAYOUT_DIRECTION_LTR ? return ViewCompat.getLayoutDirection(slideToCancelView) == ViewCompat.LAYOUT_DIRECTION_LTR ?
-Math.max(0, this.startPositionX - x) : Math.max(0, x - this.startPositionX); -Math.max(0, this.startPositionX - x) : Math.max(0, x - this.startPositionX);
} }
} }
private static class RecordTime implements Runnable { private static class RecordTime implements Runnable {
@ -283,7 +304,7 @@ public class InputPanel extends LinearLayout
private final AtomicLong startTime = new AtomicLong(0); private final AtomicLong startTime = new AtomicLong(0);
private final int UPDATE_EVERY_MS = 137; private final int UPDATE_EVERY_MS = 137;
private RecordTime(TextView recordTimeView) { private RecordTime(@NonNull TextView recordTimeView, @NonNull View microphone, long limitSeconds, @NonNull Runnable onLimitHit) {
this.recordTimeView = recordTimeView; this.recordTimeView = recordTimeView;
} }
@ -320,6 +341,6 @@ public class InputPanel extends LinearLayout
} }
public interface MediaListener { public interface MediaListener {
public void onMediaSelected(@NonNull Uri uri, String contentType); void onMediaSelected(@NonNull Uri uri, String contentType);
} }
} }

View file

@ -0,0 +1,60 @@
package org.thoughtcrime.securesms.components.emoji;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Paint;
import androidx.annotation.Nullable;
import android.text.TextUtils;
import android.util.AttributeSet;
import android.view.View;
import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.util.ResUtil;
public class AsciiEmojiView extends View {
private final Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.FILTER_BITMAP_FLAG);
private String emoji;
public AsciiEmojiView(Context context) {
super(context);
}
public AsciiEmojiView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
}
public void setEmoji(String emoji) {
this.emoji = emoji;
}
@Override
protected void onDraw(Canvas canvas) {
if (TextUtils.isEmpty(emoji)) {
return;
}
float targetFontSize = 0.75f * getHeight() - getPaddingTop() - getPaddingBottom();
paint.setTextSize(targetFontSize);
paint.setColor(ResUtil.getColor(getContext(), R.attr.emoji_text_color));
paint.setTextAlign(Paint.Align.CENTER);
int xPos = (getWidth() / 2);
int yPos = (int) ((getHeight() / 2) - ((paint.descent() + paint.ascent()) / 2));
float overflow = paint.measureText(emoji) / (getWidth() - getPaddingLeft() - getPaddingRight());
if (overflow > 1f) {
paint.setTextSize(targetFontSize / overflow);
yPos = (int) ((getHeight() / 2) - ((paint.descent() + paint.ascent()) / 2));
}
canvas.drawText(emoji, xPos, yPos, paint);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
//noinspection SuspiciousNameCombination
super.onMeasure(widthMeasureSpec, widthMeasureSpec);
}
}

View file

@ -0,0 +1,55 @@
package org.thoughtcrime.securesms.components.emoji;
import androidx.annotation.AttrRes;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import java.util.LinkedList;
import java.util.List;
public class CompositeEmojiPageModel implements EmojiPageModel {
@AttrRes private final int iconAttr;
@NonNull private final EmojiPageModel[] models;
public CompositeEmojiPageModel(@AttrRes int iconAttr, @NonNull EmojiPageModel... models) {
this.iconAttr = iconAttr;
this.models = models;
}
public int getIconAttr() {
return iconAttr;
}
@Override
public @NonNull List<String> getEmoji() {
List<String> emojis = new LinkedList<>();
for (EmojiPageModel model : models) {
emojis.addAll(model.getEmoji());
}
return emojis;
}
@Override
public @NonNull List<Emoji> getDisplayEmoji() {
List<Emoji> emojis = new LinkedList<>();
for (EmojiPageModel model : models) {
emojis.addAll(model.getDisplayEmoji());
}
return emojis;
}
@Override
public boolean hasSpriteMap() {
return false;
}
@Override
public @Nullable String getSprite() {
return null;
}
@Override
public boolean isDynamic() {
return false;
}
}

View file

@ -0,0 +1,21 @@
package org.thoughtcrime.securesms.components.emoji;
import java.util.Arrays;
import java.util.List;
public class Emoji {
private final List<String> variations;
public Emoji(String... variations) {
this.variations = Arrays.asList(variations);
}
public String getValue() {
return variations.get(0);
}
public List<String> getVariations() {
return variations;
}
}

View file

@ -1,191 +0,0 @@
package org.thoughtcrime.securesms.components.emoji;
import android.content.Context;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.viewpager.widget.PagerAdapter;
import androidx.viewpager.widget.ViewPager;
import android.util.AttributeSet;
import android.util.Log;
import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.ImageView.ScaleType;
import android.widget.LinearLayout;
import com.astuetz.PagerSlidingTabStrip;
import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.components.InputAwareLayout.InputView;
import org.thoughtcrime.securesms.components.RepeatableImageKey;
import org.thoughtcrime.securesms.components.RepeatableImageKey.KeyEventListener;
import org.thoughtcrime.securesms.components.emoji.EmojiPageView.EmojiSelectionListener;
import org.thoughtcrime.securesms.util.ResUtil;
import java.util.LinkedList;
import java.util.List;
public class EmojiDrawer extends LinearLayout implements InputView {
private static final KeyEvent DELETE_KEY_EVENT = new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_DEL);
private ViewPager pager;
private List<EmojiPageModel> models;
private PagerSlidingTabStrip strip;
private RecentEmojiPageModel recentModel;
private EmojiEventListener listener;
private EmojiDrawerListener drawerListener;
public EmojiDrawer(Context context) {
this(context, null);
}
public EmojiDrawer(Context context, AttributeSet attrs) {
super(context, attrs);
setOrientation(VERTICAL);
}
private void initView() {
final View v = LayoutInflater.from(getContext()).inflate(R.layout.emoji_drawer, this, true);
initializeResources(v);
initializePageModels();
initializeEmojiGrid();
}
public void setEmojiEventListener(EmojiEventListener listener) {
this.listener = listener;
}
public void setDrawerListener(EmojiDrawerListener listener) {
this.drawerListener = listener;
}
private void initializeResources(View v) {
Log.w("EmojiDrawer", "initializeResources()");
this.pager = (ViewPager) v.findViewById(R.id.emoji_pager);
this.strip = (PagerSlidingTabStrip) v.findViewById(R.id.tabs);
RepeatableImageKey backspace = (RepeatableImageKey)v.findViewById(R.id.backspace);
backspace.setOnKeyEventListener(new KeyEventListener() {
@Override
public void onKeyEvent() {
if (listener != null) listener.onKeyEvent(DELETE_KEY_EVENT);
}
});
}
@Override
public boolean isShowing() {
return getVisibility() == VISIBLE;
}
@Override
public void show(int height, boolean immediate) {
if (this.pager == null) initView();
ViewGroup.LayoutParams params = getLayoutParams();
params.height = height;
Log.w("EmojiDrawer", "showing emoji drawer with height " + params.height);
setLayoutParams(params);
setVisibility(VISIBLE);
if (drawerListener != null) drawerListener.onShown();
}
@Override
public void hide(boolean immediate) {
setVisibility(GONE);
if (drawerListener != null) drawerListener.onHidden();
Log.w("EmojiDrawer", "hide()");
}
private void initializeEmojiGrid() {
pager.setAdapter(new EmojiPagerAdapter(getContext(),
models,
new EmojiSelectionListener() {
@Override
public void onEmojiSelected(String emoji) {
Log.w("EmojiDrawer", "onEmojiSelected()");
recentModel.onCodePointSelected(emoji);
if (listener != null) listener.onEmojiSelected(emoji);
}
}));
if (recentModel.getEmoji().length == 0) {
pager.setCurrentItem(1);
}
strip.setViewPager(pager);
}
private void initializePageModels() {
this.models = new LinkedList<>();
this.recentModel = new RecentEmojiPageModel(getContext());
this.models.add(recentModel);
this.models.addAll(EmojiPages.PAGES);
}
public static class EmojiPagerAdapter extends PagerAdapter
implements PagerSlidingTabStrip.CustomTabProvider
{
private Context context;
private List<EmojiPageModel> pages;
private EmojiSelectionListener listener;
public EmojiPagerAdapter(@NonNull Context context,
@NonNull List<EmojiPageModel> pages,
@Nullable EmojiSelectionListener listener)
{
super();
this.context = context;
this.pages = pages;
this.listener = listener;
}
@Override
public int getCount() {
return pages.size();
}
@Override
public Object instantiateItem(ViewGroup container, int position) {
EmojiPageView page = new EmojiPageView(context);
page.setModel(pages.get(position));
page.setEmojiSelectedListener(listener);
container.addView(page);
return page;
}
@Override
public void destroyItem(ViewGroup container, int position, Object object) {
container.removeView((View)object);
}
@Override
public void setPrimaryItem(ViewGroup container, int position, Object object) {
EmojiPageView current = (EmojiPageView) object;
current.onSelected();
super.setPrimaryItem(container, position, object);
}
@Override
public boolean isViewFromObject(View view, Object object) {
return view == object;
}
@Override
public View getCustomTabView(ViewGroup viewGroup, int i) {
ImageView image = new ImageView(context);
image.setScaleType(ScaleType.CENTER_INSIDE);
image.setImageResource(ResUtil.getDrawableRes(context, pages.get(i).getIconAttr()));
return image;
}
}
public interface EmojiEventListener extends EmojiSelectionListener {
void onKeyEvent(KeyEvent keyEvent);
}
public interface EmojiDrawerListener {
void onShown();
void onHidden();
}
}

View file

@ -0,0 +1,20 @@
package org.thoughtcrime.securesms.components.emoji;
import android.content.Context;
import android.util.AttributeSet;
import androidx.appcompat.widget.AppCompatImageView;
public class EmojiImageView extends AppCompatImageView {
public EmojiImageView(Context context) {
super(context);
}
public EmojiImageView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public void setImageEmoji(CharSequence emoji) {
setImageDrawable(EmojiProvider.getInstance(getContext()).getEmojiDrawable(emoji));
}
}

View file

@ -0,0 +1,166 @@
package org.thoughtcrime.securesms.components.emoji;
import android.content.Context;
import android.graphics.drawable.Drawable;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.viewpager.widget.PagerAdapter;
import android.view.KeyEvent;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.components.emoji.EmojiPageViewGridAdapter.VariationSelectorListener;
import org.thoughtcrime.securesms.mms.GlideRequests;
import org.thoughtcrime.securesms.util.ResUtil;
import org.thoughtcrime.securesms.util.ThemeUtil;
import java.util.LinkedList;
import java.util.List;
/**
* A provider to select emoji in the {@link org.thoughtcrime.securesms.components.emoji.MediaKeyboard}.
*/
public class EmojiKeyboardProvider implements MediaKeyboardProvider,
MediaKeyboardProvider.TabIconProvider,
MediaKeyboardProvider.BackspaceObserver,
VariationSelectorListener
{
private static final KeyEvent DELETE_KEY_EVENT = new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_DEL);
private static final String RECENT_STORAGE_KEY = "pref_recent_emoji2";
private final Context context;
private final List<EmojiPageModel> models;
private final RecentEmojiPageModel recentModel;
private final EmojiPagerAdapter emojiPagerAdapter;
private final EmojiEventListener emojiEventListener;
private Controller controller;
public EmojiKeyboardProvider(@NonNull Context context, @Nullable EmojiEventListener emojiEventListener) {
this.context = context;
this.emojiEventListener = emojiEventListener;
this.models = new LinkedList<>();
this.recentModel = new RecentEmojiPageModel(context, RECENT_STORAGE_KEY);
this.emojiPagerAdapter = new EmojiPagerAdapter(context, models, new EmojiEventListener() {
@Override
public void onEmojiSelected(String emoji) {
recentModel.onCodePointSelected(emoji);
if (emojiEventListener != null) {
emojiEventListener.onEmojiSelected(emoji);
}
}
@Override
public void onKeyEvent(KeyEvent keyEvent) {
if (emojiEventListener != null) {
emojiEventListener.onKeyEvent(keyEvent);
}
}
}, this);
models.add(recentModel);
models.addAll(EmojiPages.DISPLAY_PAGES);
}
@Override
public void requestPresentation(@NonNull Presenter presenter, boolean isSoloProvider) {
presenter.present(this, emojiPagerAdapter, this, this, null, null, recentModel.getEmoji().size() > 0 ? 0 : 1);
}
@Override
public void setController(@Nullable Controller controller) {
this.controller = controller;
}
@Override
public int getProviderIconView(boolean selected) {
if (selected) {
return ThemeUtil.isDarkTheme(context) ? R.layout.emoji_keyboard_icon_dark_selected : R.layout.emoji_keyboard_icon_light_selected;
} else {
return ThemeUtil.isDarkTheme(context) ? R.layout.emoji_keyboard_icon_dark : R.layout.emoji_keyboard_icon_light;
}
}
@Override
public void loadCategoryTabIcon(@NonNull GlideRequests glideRequests, @NonNull ImageView imageView, int index) {
Drawable drawable = ResUtil.getDrawable(context, models.get(index).getIconAttr());
imageView.setImageDrawable(drawable);
}
@Override
public void onBackspaceClicked() {
if (emojiEventListener != null) {
emojiEventListener.onKeyEvent(DELETE_KEY_EVENT);
}
}
@Override
public void onVariationSelectorStateChanged(boolean open) {
if (controller != null) {
controller.setViewPagerEnabled(!open);
}
}
@Override
public boolean equals(@Nullable Object obj) {
return obj instanceof EmojiKeyboardProvider;
}
private static class EmojiPagerAdapter extends PagerAdapter {
private Context context;
private List<EmojiPageModel> pages;
private EmojiEventListener emojiSelectionListener;
private VariationSelectorListener variationSelectorListener;
public EmojiPagerAdapter(@NonNull Context context,
@NonNull List<EmojiPageModel> pages,
@NonNull EmojiEventListener emojiSelectionListener,
@NonNull VariationSelectorListener variationSelectorListener)
{
super();
this.context = context;
this.pages = pages;
this.emojiSelectionListener = emojiSelectionListener;
this.variationSelectorListener = variationSelectorListener;
}
@Override
public int getCount() {
return pages.size();
}
@Override
public @NonNull Object instantiateItem(@NonNull ViewGroup container, int position) {
EmojiPageView page = new EmojiPageView(context, emojiSelectionListener, variationSelectorListener, true);
page.setModel(pages.get(position));
container.addView(page);
return page;
}
@Override
public void destroyItem(ViewGroup container, int position, Object object) {
container.removeView((View)object);
}
@Override
public void setPrimaryItem(ViewGroup container, int position, Object object) {
EmojiPageView current = (EmojiPageView) object;
current.onSelected();
super.setPrimaryItem(container, position, object);
}
@Override
public boolean isViewFromObject(View view, Object object) {
return view == object;
}
}
public interface EmojiEventListener {
void onEmojiSelected(String emoji);
void onKeyEvent(KeyEvent keyEvent);
}
}

View file

@ -1,8 +1,11 @@
package org.thoughtcrime.securesms.components.emoji; package org.thoughtcrime.securesms.components.emoji;
import java.util.List;
public interface EmojiPageModel { public interface EmojiPageModel {
int getIconAttr(); int getIconAttr();
String[] getEmoji(); List<String> getEmoji();
List<Emoji> getDisplayEmoji();
boolean hasSpriteMap(); boolean hasSpriteMap();
String getSprite(); String getSprite();
boolean isDynamic(); boolean isDynamic();

View file

@ -1,106 +1,104 @@
package org.thoughtcrime.securesms.components.emoji; package org.thoughtcrime.securesms.components.emoji;
import android.content.Context; import android.content.Context;
import android.util.AttributeSet; import androidx.annotation.NonNull;
import androidx.recyclerview.widget.GridLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View; import android.view.View;
import android.view.ViewGroup;
import android.widget.AbsListView;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemClickListener;
import android.widget.BaseAdapter;
import android.widget.FrameLayout; import android.widget.FrameLayout;
import android.widget.GridView;
import org.thoughtcrime.securesms.R; import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.components.emoji.EmojiKeyboardProvider.EmojiEventListener;
import org.thoughtcrime.securesms.components.emoji.EmojiPageViewGridAdapter.VariationSelectorListener;
public class EmojiPageView extends FrameLayout { public class EmojiPageView extends FrameLayout implements VariationSelectorListener {
private static final String TAG = EmojiPageView.class.getSimpleName(); private static final String TAG = EmojiPageView.class.getSimpleName();
private EmojiPageModel model; private EmojiPageModel model;
private EmojiSelectionListener listener; private EmojiPageViewGridAdapter adapter;
private GridView grid; private RecyclerView recyclerView;
private GridLayoutManager layoutManager;
private RecyclerView.OnItemTouchListener scrollDisabler;
private VariationSelectorListener variationSelectorListener;
private EmojiVariationSelectorPopup popup;
public EmojiPageView(Context context) { public EmojiPageView(@NonNull Context context,
this(context, null); @NonNull EmojiEventListener emojiSelectionListener,
} @NonNull VariationSelectorListener variationSelectorListener,
boolean allowVariations)
public EmojiPageView(Context context, AttributeSet attrs) { {
this(context, attrs, 0); super(context);
}
public EmojiPageView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
final View view = LayoutInflater.from(getContext()).inflate(R.layout.emoji_grid_layout, this, true); final View view = LayoutInflater.from(getContext()).inflate(R.layout.emoji_grid_layout, this, true);
grid = (GridView) view.findViewById(R.id.emoji);
grid.setColumnWidth(getResources().getDimensionPixelSize(R.dimen.emoji_drawer_size) + 2 * getResources().getDimensionPixelSize(R.dimen.emoji_drawer_item_padding)); this.variationSelectorListener = variationSelectorListener;
grid.setOnItemClickListener(new OnItemClickListener() {
@Override public void onItemClick(AdapterView<?> parent, View view, int position, long id) { recyclerView = view.findViewById(R.id.emoji);
if (listener != null) listener.onEmojiSelected(((EmojiView)view).getEmoji()); layoutManager = new GridLayoutManager(context, 8);
} scrollDisabler = new ScrollDisabler();
}); popup = new EmojiVariationSelectorPopup(context, emojiSelectionListener);
adapter = new EmojiPageViewGridAdapter(EmojiProvider.getInstance(context),
popup,
emojiSelectionListener,
this,
allowVariations);
recyclerView.setLayoutManager(layoutManager);
recyclerView.setAdapter(adapter);
} }
public void onSelected() { public void onSelected() {
if (model.isDynamic() && grid != null && grid.getAdapter() != null) { if (model.isDynamic() && adapter != null) {
((EmojiGridAdapter)grid.getAdapter()).notifyDataSetChanged(); adapter.notifyDataSetChanged();
} }
} }
public void setModel(EmojiPageModel model) { public void setModel(EmojiPageModel model) {
this.model = model; this.model = model;
grid.setAdapter(new EmojiGridAdapter(getContext(), model)); adapter.setEmoji(model.getDisplayEmoji());
} }
public void setEmojiSelectedListener(EmojiSelectionListener listener) { @Override
this.listener = listener; protected void onVisibilityChanged(@NonNull View changedView, int visibility) {
} if (visibility != VISIBLE) {
popup.dismiss();
private static class EmojiGridAdapter extends BaseAdapter {
protected final Context context;
private final int emojiSize;
private final EmojiPageModel model;
public EmojiGridAdapter(Context context, EmojiPageModel model) {
this.context = context;
this.emojiSize = (int) context.getResources().getDimension(R.dimen.emoji_drawer_size);
this.model = model;
}
@Override public int getCount() {
return model.getEmoji().length;
}
@Override
public Object getItem(int position) {
return null;
}
@Override
public long getItemId(int position) {
return position;
}
@Override
public View getView(final int position, final View convertView, final ViewGroup parent) {
final EmojiView view;
final int pad = context.getResources().getDimensionPixelSize(R.dimen.emoji_drawer_item_padding);
if (convertView != null && convertView instanceof EmojiView) {
view = (EmojiView)convertView;
} else {
final EmojiView emojiView = new EmojiView(context);
emojiView.setPadding(pad, pad, pad, pad);
emojiView.setLayoutParams(new AbsListView.LayoutParams(emojiSize + 2 * pad, emojiSize + 2 * pad));
view = emojiView;
}
view.setEmoji(model.getEmoji()[position]);
return view;
} }
} }
public interface EmojiSelectionListener { @Override
void onEmojiSelected(String emoji); protected void onSizeChanged(int w, int h, int oldw, int oldh) {
int idealWidth = getContext().getResources().getDimensionPixelOffset(R.dimen.emoji_drawer_item_width);
layoutManager.setSpanCount(Math.max(w / idealWidth, 1));
}
@Override
public void onVariationSelectorStateChanged(boolean open) {
if (open) {
recyclerView.addOnItemTouchListener(scrollDisabler);
} else {
post(() -> recyclerView.removeOnItemTouchListener(scrollDisabler));
}
if (variationSelectorListener != null) {
variationSelectorListener.onVariationSelectorStateChanged(open);
}
}
public void setRecyclerNestedScrollingEnabled(boolean enabled) {
recyclerView.setNestedScrollingEnabled(enabled);
}
private static class ScrollDisabler implements RecyclerView.OnItemTouchListener {
@Override
public boolean onInterceptTouchEvent(@NonNull RecyclerView recyclerView, @NonNull MotionEvent motionEvent) {
return true;
}
@Override
public void onTouchEvent(@NonNull RecyclerView recyclerView, @NonNull MotionEvent motionEvent) { }
@Override
public void onRequestDisallowInterceptTouchEvent(boolean b) { }
} }
} }

View file

@ -0,0 +1,119 @@
package org.thoughtcrime.securesms.components.emoji;
import android.graphics.drawable.Drawable;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.PopupWindow;
import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.components.emoji.EmojiKeyboardProvider.EmojiEventListener;
import java.util.ArrayList;
import java.util.List;
public class EmojiPageViewGridAdapter extends RecyclerView.Adapter<EmojiPageViewGridAdapter.EmojiViewHolder> implements PopupWindow.OnDismissListener {
private final List<Emoji> emojiList;
private final EmojiProvider emojiProvider;
private final EmojiVariationSelectorPopup popup;
private final VariationSelectorListener variationSelectorListener;
private final EmojiEventListener emojiEventListener;
private final boolean allowVariations;
public EmojiPageViewGridAdapter(@NonNull EmojiProvider emojiProvider,
@NonNull EmojiVariationSelectorPopup popup,
@NonNull EmojiEventListener emojiEventListener,
@NonNull VariationSelectorListener variationSelectorListener,
boolean allowVariations)
{
this.emojiList = new ArrayList<>();
this.emojiProvider = emojiProvider;
this.popup = popup;
this.emojiEventListener = emojiEventListener;
this.variationSelectorListener = variationSelectorListener;
this.allowVariations = allowVariations;
popup.setOnDismissListener(this);
}
@NonNull
@Override
public EmojiViewHolder onCreateViewHolder(@NonNull ViewGroup viewGroup, int i) {
return new EmojiViewHolder(LayoutInflater.from(viewGroup.getContext()).inflate(R.layout.emoji_display_item, viewGroup, false));
}
@Override
public void onBindViewHolder(@NonNull EmojiViewHolder viewHolder, int i) {
Emoji emoji = emojiList.get(i);
Drawable drawable = emojiProvider.getEmojiDrawable(emoji.getValue());
if (drawable != null) {
viewHolder.textView.setVisibility(View.GONE);
viewHolder.imageView.setVisibility(View.VISIBLE);
viewHolder.imageView.setImageDrawable(drawable);
} else {
viewHolder.textView.setVisibility(View.VISIBLE);
viewHolder.imageView.setVisibility(View.GONE);
viewHolder.textView.setEmoji(emoji.getValue());
}
viewHolder.itemView.setOnClickListener(v -> {
emojiEventListener.onEmojiSelected(emoji.getValue());
});
if (allowVariations && emoji.getVariations().size() > 1) {
viewHolder.itemView.setOnLongClickListener(v -> {
popup.dismiss();
popup.setVariations(emoji.getVariations());
popup.showAsDropDown(viewHolder.itemView, 0, -(2 * viewHolder.itemView.getHeight()));
variationSelectorListener.onVariationSelectorStateChanged(true);
return true;
});
viewHolder.hintCorner.setVisibility(View.VISIBLE);
} else {
viewHolder.itemView.setOnLongClickListener(null);
viewHolder.hintCorner.setVisibility(View.GONE);
}
}
@Override
public int getItemCount() {
return emojiList.size();
}
public void setEmoji(@NonNull List<Emoji> emojiList) {
this.emojiList.clear();
this.emojiList.addAll(emojiList);
notifyDataSetChanged();
}
@Override
public void onDismiss() {
variationSelectorListener.onVariationSelectorStateChanged(false);
}
static class EmojiViewHolder extends RecyclerView.ViewHolder {
private final ImageView imageView;
private final AsciiEmojiView textView;
private final ImageView hintCorner;
public EmojiViewHolder(@NonNull View itemView) {
super(itemView);
this.imageView = itemView.findViewById(R.id.emoji_image);
this.textView = itemView.findViewById(R.id.emoji_text);
this.hintCorner = itemView.findViewById(R.id.emoji_variation_hint);
}
}
public interface VariationSelectorListener {
void onVariationSelectorStateChanged(boolean open);
}
}

File diff suppressed because one or more lines are too long

View file

@ -27,13 +27,13 @@ import org.thoughtcrime.securesms.util.FutureTaskListener;
import org.thoughtcrime.securesms.util.Pair; import org.thoughtcrime.securesms.util.Pair;
import org.thoughtcrime.securesms.util.Util; import org.thoughtcrime.securesms.util.Util;
import java.io.IOException; import java.util.List;
import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutionException;
class EmojiProvider { class EmojiProvider {
private static final String TAG = EmojiProvider.class.getSimpleName(); private static final String TAG = EmojiProvider.class.getSimpleName();
protected static volatile EmojiProvider instance = null; private static volatile EmojiProvider instance = null;
private static final Paint paint = new Paint(Paint.FILTER_BITMAP_FLAG | Paint.ANTI_ALIAS_FLAG); private static final Paint paint = new Paint(Paint.FILTER_BITMAP_FLAG | Paint.ANTI_ALIAS_FLAG);
private final EmojiTree emojiTree = new EmojiTree(); private final EmojiTree emojiTree = new EmojiTree();
@ -41,7 +41,7 @@ class EmojiProvider {
private static final int EMOJI_RAW_HEIGHT = 64; private static final int EMOJI_RAW_HEIGHT = 64;
private static final int EMOJI_RAW_WIDTH = 64; private static final int EMOJI_RAW_WIDTH = 64;
private static final int EMOJI_VERT_PAD = 0; private static final int EMOJI_VERT_PAD = 0;
private static final int EMOJI_PER_ROW = 32; private static final int EMOJI_PER_ROW = 16;
private final float decodeScale; private final float decodeScale;
private final float verticalPad; private final float verticalPad;
@ -57,16 +57,17 @@ class EmojiProvider {
return instance; return instance;
} }
protected EmojiProvider(Context context) { private EmojiProvider(Context context) {
this.decodeScale = Math.min(1f, context.getResources().getDimension(R.dimen.emoji_drawer_size) / EMOJI_RAW_HEIGHT); this.decodeScale = Math.min(1f, context.getResources().getDimension(R.dimen.emoji_drawer_size) / EMOJI_RAW_HEIGHT);
this.verticalPad = EMOJI_VERT_PAD * this.decodeScale; this.verticalPad = EMOJI_VERT_PAD * this.decodeScale;
for (EmojiPageModel page : EmojiPages.PAGES) { for (EmojiPageModel page : EmojiPages.DATA_PAGES) {
if (page.hasSpriteMap()) { if (page.hasSpriteMap()) {
EmojiPageBitmap pageBitmap = new EmojiPageBitmap(context, page, decodeScale); EmojiPageBitmap pageBitmap = new EmojiPageBitmap(context, page, decodeScale);
for (int i=0;i<page.getEmoji().length;i++) { List<String> emojis = page.getEmoji();
emojiTree.add(page.getEmoji()[i], new EmojiDrawInfo(pageBitmap, i)); for (int i = 0; i < emojis.size(); i++) {
emojiTree.add(emojis.get(i), new EmojiDrawInfo(pageBitmap, i));
} }
} }
} }
@ -82,18 +83,17 @@ class EmojiProvider {
} }
@Nullable Spannable emojify(@Nullable CharSequence text, @NonNull TextView tv) { @Nullable Spannable emojify(@Nullable CharSequence text, @NonNull TextView tv) {
return emojify(getCandidates(text), text, tv, false); return emojify(getCandidates(text), text, tv);
} }
@Nullable Spannable emojify(@Nullable EmojiParser.CandidateList matches, @Nullable Spannable emojify(@Nullable EmojiParser.CandidateList matches,
@Nullable CharSequence text, @Nullable CharSequence text,
@NonNull TextView tv, @NonNull TextView tv) {
boolean background) {
if (matches == null || text == null) return null; if (matches == null || text == null) return null;
SpannableStringBuilder builder = new SpannableStringBuilder(text); SpannableStringBuilder builder = new SpannableStringBuilder(text);
for (EmojiParser.Candidate candidate : matches) { for (EmojiParser.Candidate candidate : matches) {
Drawable drawable = getEmojiDrawable(candidate.getDrawInfo(), background); Drawable drawable = getEmojiDrawable(candidate.getDrawInfo());
if (drawable != null) { if (drawable != null) {
builder.setSpan(new EmojiSpan(drawable, tv), candidate.getStartIndex(), candidate.getEndIndex(), builder.setSpan(new EmojiSpan(drawable, tv), candidate.getStartIndex(), candidate.getEndIndex(),
@ -106,32 +106,24 @@ class EmojiProvider {
@Nullable Drawable getEmojiDrawable(CharSequence emoji) { @Nullable Drawable getEmojiDrawable(CharSequence emoji) {
EmojiDrawInfo drawInfo = emojiTree.getEmoji(emoji, 0, emoji.length()); EmojiDrawInfo drawInfo = emojiTree.getEmoji(emoji, 0, emoji.length());
return getEmojiDrawable(drawInfo, false); return getEmojiDrawable(drawInfo);
} }
protected @Nullable Drawable getEmojiDrawable(@Nullable EmojiDrawInfo drawInfo, boolean background) { private @Nullable Drawable getEmojiDrawable(@Nullable EmojiDrawInfo drawInfo) {
if (drawInfo == null) { if (drawInfo == null) {
return null; return null;
} }
final EmojiDrawable drawable = new EmojiDrawable(drawInfo, decodeScale); final EmojiDrawable drawable = new EmojiDrawable(drawInfo, decodeScale);
if (background) { drawInfo.getPage().get().addListener(new FutureTaskListener<Bitmap>() {
try { @Override public void onSuccess(final Bitmap result) {
drawable.setBitmap(drawInfo.getPage().loadPage(), background); Util.runOnMain(() -> drawable.setBitmap(result));
} catch (IOException e) {
e.printStackTrace();
} }
} else {
drawInfo.getPage().get().addListener(new FutureTaskListener<Bitmap>() {
@Override public void onSuccess(final Bitmap result) {
Util.runOnMain(() -> drawable.setBitmap(result));
}
@Override public void onFailure(ExecutionException error) { @Override public void onFailure(ExecutionException error) {
Log.w(TAG, error); Log.w(TAG, error);
} }
}); });
}
return drawable; return drawable;
} }
@ -177,14 +169,7 @@ class EmojiProvider {
@TargetApi(VERSION_CODES.HONEYCOMB_MR1) @TargetApi(VERSION_CODES.HONEYCOMB_MR1)
public void setBitmap(Bitmap bitmap) { public void setBitmap(Bitmap bitmap) {
setBitmap(bitmap, false); Util.assertMainThread();
}
@TargetApi(VERSION_CODES.HONEYCOMB_MR1)
public void setBitmap(Bitmap bitmap, boolean background) {
if (!background) {
Util.assertMainThread();
}
if (VERSION.SDK_INT < VERSION_CODES.HONEYCOMB_MR1 || bmp == null || !bmp.sameAs(bitmap)) { if (VERSION.SDK_INT < VERSION_CODES.HONEYCOMB_MR1 || bmp == null || !bmp.sameAs(bitmap)) {
bmp = bitmap; bmp = bitmap;
invalidateSelf(); invalidateSelf();

View file

@ -25,7 +25,7 @@ public class EmojiSpan extends AnimatingImageSpan {
} }
@Override @Override
public int getSize(Paint paint, CharSequence text, int start, int end, FontMetricsInt fm) { public int getSize(@NonNull Paint paint, CharSequence text, int start, int end, FontMetricsInt fm) {
if (fm != null && this.fm != null) { if (fm != null && this.fm != null) {
fm.ascent = this.fm.ascent; fm.ascent = this.fm.ascent;
fm.descent = this.fm.descent; fm.descent = this.fm.descent;
@ -39,7 +39,7 @@ public class EmojiSpan extends AnimatingImageSpan {
} }
@Override @Override
public void draw(Canvas canvas, CharSequence text, int start, int end, float x, int top, int y, int bottom, Paint paint) { public void draw(@NonNull Canvas canvas, CharSequence text, int start, int end, float x, int top, int y, int bottom, @NonNull Paint paint) {
int height = bottom - top; int height = bottom - top;
int centeringMargin = (height - size) / 2; int centeringMargin = (height - size) / 2;
int adjustedMargin = (int) (centeringMargin * SHIFT_FACTOR); int adjustedMargin = (int) (centeringMargin * SHIFT_FACTOR);

View file

@ -3,32 +3,39 @@ package org.thoughtcrime.securesms.components.emoji;
import android.content.Context; import android.content.Context;
import android.content.res.TypedArray; import android.content.res.TypedArray;
import android.graphics.drawable.Drawable; import android.graphics.drawable.Drawable;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.core.widget.TextViewCompat;
import androidx.appcompat.widget.AppCompatTextView;
import android.text.SpannableStringBuilder; import android.text.SpannableStringBuilder;
import android.text.TextUtils; import android.text.TextUtils;
import android.util.AttributeSet; import android.util.AttributeSet;
import android.util.TypedValue; import android.util.TypedValue;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.widget.AppCompatTextView;
import androidx.core.widget.TextViewCompat;
import org.thoughtcrime.securesms.R; import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.components.emoji.EmojiProvider.EmojiDrawable; import org.thoughtcrime.securesms.components.emoji.EmojiProvider.EmojiDrawable;
import org.thoughtcrime.securesms.components.emoji.parsing.EmojiParser; import org.thoughtcrime.securesms.components.emoji.parsing.EmojiParser;
import org.thoughtcrime.securesms.util.Prefs; import org.thoughtcrime.securesms.util.Prefs;
import org.thoughtcrime.securesms.util.Util; import org.thoughtcrime.securesms.util.Util;
import org.thoughtcrime.securesms.util.guava.Optional;
public class EmojiTextView extends AppCompatTextView { public class EmojiTextView extends AppCompatTextView {
private final boolean scaleEmojis; private final boolean scaleEmojis;
private final boolean createInBackground; private final boolean forceCustom;
private static final char ELLIPSIS = '…';
private CharSequence previousText; private CharSequence previousText;
private BufferType previousBufferType; private BufferType previousBufferType;
private float originalFontSize; private float originalFontSize;
private boolean useSystemEmoji; private boolean useSystemEmoji;
private boolean sizeChangeInProgress; private boolean sizeChangeInProgress;
private int maxLength;
private CharSequence overflowText;
private CharSequence previousOverflowText;
public EmojiTextView(Context context) { public EmojiTextView(Context context) {
this(context, null); this(context, null);
@ -43,7 +50,8 @@ public class EmojiTextView extends AppCompatTextView {
TypedArray a = context.getTheme().obtainStyledAttributes(attrs, R.styleable.EmojiTextView, 0, 0); TypedArray a = context.getTheme().obtainStyledAttributes(attrs, R.styleable.EmojiTextView, 0, 0);
scaleEmojis = a.getBoolean(R.styleable.EmojiTextView_scaleEmojis, false); scaleEmojis = a.getBoolean(R.styleable.EmojiTextView_scaleEmojis, false);
createInBackground = a.getBoolean(R.styleable.EmojiTextView_createInBackground, false); maxLength = a.getInteger(R.styleable.EmojiTextView_emoji_maxLength, -1);
forceCustom = a.getBoolean(R.styleable.EmojiTextView_emoji_forceCustom, false);
a.recycle(); a.recycle();
a = context.obtainStyledAttributes(attrs, new int[]{android.R.attr.textSize}); a = context.obtainStyledAttributes(attrs, new int[]{android.R.attr.textSize});
@ -69,38 +77,67 @@ public class EmojiTextView extends AppCompatTextView {
super.setTextSize(TypedValue.COMPLEX_UNIT_PX, originalFontSize); super.setTextSize(TypedValue.COMPLEX_UNIT_PX, originalFontSize);
} }
if (unchanged(text, type)) { if (unchanged(text, overflowText, type)) {
return; return;
} }
previousText = text; previousText = text;
previousBufferType = type; previousOverflowText = overflowText;
useSystemEmoji = useSystemEmoji(); previousBufferType = type;
useSystemEmoji = useSystemEmoji();
if (useSystemEmoji || candidates == null || candidates.size() == 0) { if (useSystemEmoji || candidates == null || candidates.size() == 0) {
super.setText(text, BufferType.NORMAL); super.setText(new SpannableStringBuilder(Optional.fromNullable(text).or("")).append(Optional.fromNullable(overflowText).or("")), BufferType.NORMAL);
return;
}
CharSequence emojified = provider.emojify(candidates, text, this, createInBackground); if (getEllipsize() == TextUtils.TruncateAt.END && maxLength > 0) {
super.setText(emojified, BufferType.SPANNABLE); ellipsizeAnyTextForMaxLength();
}
} else {
CharSequence emojified = provider.emojify(candidates, text, this);
super.setText(new SpannableStringBuilder(emojified).append(Optional.fromNullable(overflowText).or("")), BufferType.SPANNABLE);
// Android fails to ellipsize spannable strings. (https://issuetracker.google.com/issues/36991688) // Android fails to ellipsize spannable strings. (https://issuetracker.google.com/issues/36991688)
// We ellipsize them ourselves by manually truncating the appropriate section. // We ellipsize them ourselves by manually truncating the appropriate section.
if (getEllipsize() == TextUtils.TruncateAt.END) { if (getEllipsize() == TextUtils.TruncateAt.END) {
ellipsize(); if (maxLength > 0) {
ellipsizeAnyTextForMaxLength();
} else {
ellipsizeEmojiTextForMaxLines();
}
}
} }
} }
private void ellipsize() { public void setOverflowText(@Nullable CharSequence overflowText) {
this.overflowText = overflowText;
setText(previousText, BufferType.SPANNABLE);
}
private void ellipsizeAnyTextForMaxLength() {
if (maxLength > 0 && getText().length() > maxLength + 1) {
SpannableStringBuilder newContent = new SpannableStringBuilder();
newContent.append(getText().subSequence(0, maxLength)).append(ELLIPSIS).append(Optional.fromNullable(overflowText).or(""));
EmojiParser.CandidateList newCandidates = EmojiProvider.getInstance(getContext()).getCandidates(newContent);
if (useSystemEmoji || newCandidates == null || newCandidates.size() == 0) {
super.setText(newContent, BufferType.NORMAL);
} else {
CharSequence emojified = EmojiProvider.getInstance(getContext()).emojify(newCandidates, newContent, this);
super.setText(emojified, BufferType.SPANNABLE);
}
}
}
private void ellipsizeEmojiTextForMaxLines() {
post(() -> { post(() -> {
if (getLayout() == null) { if (getLayout() == null) {
ellipsize(); ellipsizeEmojiTextForMaxLines();
return; return;
} }
int maxLines = TextViewCompat.getMaxLines(EmojiTextView.this); int maxLines = TextViewCompat.getMaxLines(EmojiTextView.this);
if (maxLines <= 0) { if (maxLines <= 0 && maxLength < 0) {
return; return;
} }
@ -112,25 +149,27 @@ public class EmojiTextView extends AppCompatTextView {
SpannableStringBuilder newContent = new SpannableStringBuilder(); SpannableStringBuilder newContent = new SpannableStringBuilder();
newContent.append(getText().subSequence(0, overflowStart)) newContent.append(getText().subSequence(0, overflowStart))
.append(ellipsized.subSequence(0, ellipsized.length())); .append(ellipsized.subSequence(0, ellipsized.length()))
.append(Optional.fromNullable(overflowText).or(""));
EmojiParser.CandidateList newCandidates = EmojiProvider.getInstance(getContext()).getCandidates(newContent); EmojiParser.CandidateList newCandidates = EmojiProvider.getInstance(getContext()).getCandidates(newContent);
CharSequence emojified = EmojiProvider.getInstance(getContext()).emojify(newCandidates, newContent, this, createInBackground); CharSequence emojified = EmojiProvider.getInstance(getContext()).emojify(newCandidates, newContent, this);
super.setText(emojified, BufferType.SPANNABLE); super.setText(emojified, BufferType.SPANNABLE);
} }
}); });
} }
private boolean unchanged(CharSequence text, BufferType bufferType) { private boolean unchanged(CharSequence text, CharSequence overflowText, BufferType bufferType) {
return Util.equals(previousText, text) && return Util.equals(previousText, text) &&
Util.equals(previousBufferType, bufferType) && Util.equals(previousOverflowText, overflowText) &&
useSystemEmoji == useSystemEmoji() && Util.equals(previousBufferType, bufferType) &&
useSystemEmoji == useSystemEmoji() &&
!sizeChangeInProgress; !sizeChangeInProgress;
} }
private boolean useSystemEmoji() { private boolean useSystemEmoji() {
return Prefs.isSystemEmojiPreferred(getContext()); return !forceCustom && Prefs.isSystemEmojiPreferred(getContext());
} }
@Override @Override

View file

@ -1,20 +1,24 @@
package org.thoughtcrime.securesms.components.emoji; package org.thoughtcrime.securesms.components.emoji;
import android.content.Context; import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.drawable.Drawable; import android.graphics.drawable.Drawable;
import androidx.annotation.NonNull;
import androidx.appcompat.widget.AppCompatImageButton;
import android.util.AttributeSet; import android.util.AttributeSet;
import android.widget.ImageButton;
import org.thoughtcrime.securesms.R; import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.components.emoji.EmojiDrawer.EmojiDrawerListener; import org.thoughtcrime.securesms.util.Prefs;
import org.thoughtcrime.securesms.util.ResUtil;
public class EmojiToggle extends ImageButton implements EmojiDrawerListener { public class EmojiToggle extends AppCompatImageButton implements MediaKeyboard.MediaKeyboardListener {
private Drawable emojiToggle; private Drawable emojiToggle;
// private Drawable stickerToggle;
private Drawable mediaToggle;
private Drawable imeToggle; private Drawable imeToggle;
public EmojiToggle(Context context) { public EmojiToggle(Context context) {
super(context); super(context);
initialize(); initialize();
@ -30,8 +34,8 @@ public class EmojiToggle extends ImageButton implements EmojiDrawerListener {
initialize(); initialize();
} }
public void setToEmoji() { public void setToMedia() {
setImageDrawable(emojiToggle); setImageDrawable(mediaToggle);
} }
public void setToIme() { public void setToIme() {
@ -39,19 +43,29 @@ public class EmojiToggle extends ImageButton implements EmojiDrawerListener {
} }
private void initialize() { private void initialize() {
int attributes[] = new int[] {R.attr.conversation_emoji_toggle, this.emojiToggle = ResUtil.getDrawable(getContext(), R.attr.conversation_emoji_toggle);
R.attr.conversation_keyboard_toggle}; // this.stickerToggle = ResUtil.getDrawable(getContext(), R.attr.conversation_sticker_toggle);
this.imeToggle = ResUtil.getDrawable(getContext(), R.attr.conversation_keyboard_toggle);
this.mediaToggle = emojiToggle;
TypedArray drawables = getContext().obtainStyledAttributes(attributes); setToMedia();
this.emojiToggle = drawables.getDrawable(0);
this.imeToggle = drawables.getDrawable(1);
drawables.recycle();
setToEmoji();
} }
public void attach(EmojiDrawer drawer) { public void attach(MediaKeyboard drawer) {
drawer.setDrawerListener(this); drawer.setKeyboardListener(this);
}
public void setStickerMode(boolean stickerMode) {
this.mediaToggle = /*stickerMode ? stickerToggle :*/ emojiToggle;
if (getDrawable() != imeToggle) {
setToMedia();
}
}
public boolean isStickerMode() {
//return this.mediaToggle == stickerToggle;
return false;
} }
@Override public void onShown() { @Override public void onShown() {
@ -59,6 +73,14 @@ public class EmojiToggle extends ImageButton implements EmojiDrawerListener {
} }
@Override public void onHidden() { @Override public void onHidden() {
setToEmoji(); setToMedia();
}
@Override
public void onKeyboardProviderChanged(@NonNull MediaKeyboardProvider provider) {
setStickerMode(false);
//setStickerMode(provider instanceof StickerKeyboardProvider);
/*TextSecurePreferences.setMediaKeyboardMode(getContext(), (provider instanceof StickerKeyboardProvider) ? TextSecurePreferences.MediaKeyboardMode.STICKER
: TextSecurePreferences.MediaKeyboardMode.EMOJI);*/
} }
} }

View file

@ -0,0 +1,75 @@
package org.thoughtcrime.securesms.components.emoji;
import androidx.annotation.NonNull;
import org.thoughtcrime.securesms.util.Pair;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
public final class EmojiUtil {
private static final Map<String, String> VARIATION_MAP = new HashMap<>();
static {
for (EmojiPageModel page : EmojiPages.DATA_PAGES) {
for (Emoji emoji : page.getDisplayEmoji()) {
for (String variation : emoji.getVariations()) {
VARIATION_MAP.put(variation, emoji.getValue());
}
}
}
}
public static final int MAX_EMOJI_LENGTH;
static {
int max = 0;
for (EmojiPageModel page : EmojiPages.DATA_PAGES) {
for (String emoji : page.getEmoji()) {
max = Math.max(max, emoji.length());
}
}
MAX_EMOJI_LENGTH = max;
}
private EmojiUtil() {}
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'.
*/
public static @NonNull Set<String> getAllRepresentations(@NonNull String emoji) {
Set<String> out = new HashSet<>();
out.add(emoji);
for (Pair<String, String> pair : EmojiPages.OBSOLETE) {
if (pair.first().equals(emoji)) {
out.add(pair.second());
} else if (pair.second().equals(emoji)) {
out.add(pair.first());
}
}
return out;
}
/**
* When provided an emoji that is a skin variation of another, this will return the default yellow
* version. This is to aid in search, so using a variation will still find all emojis tagged with
* the default version.
*
* If the emoji has no skin variations, this function will return the original emoji.
*/
public static @NonNull String getCanonicalRepresentation(@NonNull String emoji) {
String canonical = VARIATION_MAP.get(emoji);
return canonical != null ? canonical : emoji;
}
}

View file

@ -0,0 +1,51 @@
package org.thoughtcrime.securesms.components.emoji;
import android.content.Context;
import android.os.Build;
import androidx.annotation.NonNull;
import android.view.LayoutInflater;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.PopupWindow;
import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.components.emoji.EmojiKeyboardProvider.EmojiEventListener;
import java.util.List;
public class EmojiVariationSelectorPopup extends PopupWindow {
private final Context context;
private final ViewGroup list;
private final EmojiEventListener listener;
public EmojiVariationSelectorPopup(@NonNull Context context, @NonNull EmojiEventListener listener) {
super(LayoutInflater.from(context).inflate(R.layout.emoji_variation_selector, null),
ViewGroup.LayoutParams.WRAP_CONTENT,
ViewGroup.LayoutParams.WRAP_CONTENT);
this.context = context;
this.listener = listener;
this.list = (ViewGroup) getContentView().findViewById(R.id.emoji_variation_container);
setBackgroundDrawable(null);
setOutsideTouchable(true);
if (Build.VERSION.SDK_INT >= 21) {
setElevation(20);
}
}
public void setVariations(List<String> variations) {
list.removeAllViews();
for (String variation : variations) {
ImageView imageView = (ImageView) LayoutInflater.from(context).inflate(R.layout.emoji_variation_selector_item, list, false);
imageView.setImageDrawable(EmojiProvider.getInstance(context).getEmojiDrawable(variation));
imageView.setOnClickListener(v -> {
listener.onEmojiSelected(variation);
dismiss();
});
list.addView(imageView);
}
}
}

View file

@ -1,77 +0,0 @@
package org.thoughtcrime.securesms.components.emoji;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.drawable.Drawable;
import androidx.annotation.NonNull;
import android.util.AttributeSet;
import android.view.View;
import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.util.ResUtil;
public class EmojiView extends View implements Drawable.Callback {
private String emoji;
private Drawable drawable;
private final Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.FILTER_BITMAP_FLAG);
public EmojiView(Context context) {
this(context, null);
}
public EmojiView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public EmojiView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
public void setEmoji(String emoji) {
this.emoji = emoji;
this.drawable = EmojiProvider.getInstance(getContext())
.getEmojiDrawable(emoji);
postInvalidate();
}
public String getEmoji() {
return emoji;
}
@Override protected void onDraw(Canvas canvas) {
if (drawable != null) {
drawable.setBounds(getPaddingLeft(),
getPaddingTop(),
getWidth() - getPaddingRight(),
getHeight() - getPaddingBottom());
drawable.setCallback(this);
drawable.draw(canvas);
} else {
float targetFontSize = 0.75f * getHeight() - getPaddingTop() - getPaddingBottom();
paint.setTextSize(targetFontSize);
paint.setColor(ResUtil.getColor(getContext(), R.attr.emoji_text_color));
paint.setTextAlign(Paint.Align.CENTER);
int xPos = (canvas.getWidth() / 2);
int yPos = (int) ((canvas.getHeight() / 2) - ((paint.descent() + paint.ascent()) / 2));
float overflow = paint.measureText(emoji) /
(getWidth() - getPaddingLeft() - getPaddingRight());
if (overflow > 1f) {
paint.setTextSize(targetFontSize / overflow);
yPos = (int) ((canvas.getHeight() / 2) - ((paint.descent() + paint.ascent()) / 2));
}
canvas.drawText(emoji, xPos, yPos, paint);
}
}
@Override public void invalidateDrawable(@NonNull Drawable drawable) {
super.invalidateDrawable(drawable);
postInvalidate();
}
@Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
}

View file

@ -0,0 +1,296 @@
package org.thoughtcrime.securesms.components.emoji;
import android.content.Context;
import android.content.res.TypedArray;
import android.util.AttributeSet;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.FrameLayout;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import androidx.viewpager.widget.PagerAdapter;
import androidx.viewpager.widget.ViewPager;
import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.components.InputAwareLayout.InputView;
import org.thoughtcrime.securesms.components.RepeatableImageKey;
import org.thoughtcrime.securesms.mms.GlideApp;
import java.util.Arrays;
public class MediaKeyboard extends FrameLayout implements InputView,
MediaKeyboardProvider.Presenter,
MediaKeyboardProvider.Controller,
MediaKeyboardBottomTabAdapter.EventListener
{
private static final String TAG = MediaKeyboard.class.getSimpleName();
private RecyclerView categoryTabs;
private ViewPager categoryPager;
private ViewGroup providerTabs;
private RepeatableImageKey backspaceButton;
private RepeatableImageKey backspaceButtonBackup;
private View searchButton;
private View addButton;
@Nullable private MediaKeyboardListener keyboardListener;
private MediaKeyboardProvider[] providers;
private int providerIndex;
private final boolean tabsAtBottom;
private MediaKeyboardBottomTabAdapter categoryTabAdapter;
public MediaKeyboard(Context context) {
this(context, null);
}
public MediaKeyboard(Context context, AttributeSet attrs) {
super(context, attrs);
TypedArray typedArray = context.getTheme().obtainStyledAttributes(attrs, R.styleable.MediaKeyboard, 0, 0);
try {
tabsAtBottom = typedArray.getInt(R.styleable.MediaKeyboard_tabs_gravity, 0) == 0;
} finally {
typedArray.recycle();
}
}
public void setProviders(int startIndex, MediaKeyboardProvider... providers) {
if (!Arrays.equals(this.providers, providers)) {
this.providers = providers;
this.providerIndex = startIndex;
requestPresent(providers, providerIndex);
}
}
public void setKeyboardListener(@Nullable MediaKeyboardListener listener) {
this.keyboardListener = listener;
}
@Override
public boolean isShowing() {
return getVisibility() == VISIBLE;
}
@Override
public void show(int height, boolean immediate) {
if (this.categoryPager == null) initView();
ViewGroup.LayoutParams params = getLayoutParams();
params.height = height;
Log.i(TAG, "showing emoji drawer with height " + params.height);
setLayoutParams(params);
show();
}
public void show() {
if (this.categoryPager == null) initView();
setVisibility(VISIBLE);
if (keyboardListener != null) keyboardListener.onShown();
requestPresent(providers, providerIndex);
}
@Override
public void hide(boolean immediate) {
setVisibility(GONE);
if (keyboardListener != null) keyboardListener.onHidden();
Log.i(TAG, "hide()");
}
@Override
public void present(@NonNull MediaKeyboardProvider provider,
@NonNull PagerAdapter pagerAdapter,
@NonNull MediaKeyboardProvider.TabIconProvider tabIconProvider,
@Nullable MediaKeyboardProvider.BackspaceObserver backspaceObserver,
@Nullable MediaKeyboardProvider.AddObserver addObserver,
@Nullable MediaKeyboardProvider.SearchObserver searchObserver,
int startingIndex)
{
if (categoryPager == null) return;
if (!provider.equals(providers[providerIndex])) return;
if (keyboardListener != null) keyboardListener.onKeyboardProviderChanged(provider);
boolean isSolo = providers.length == 1;
presentProviderStrip(isSolo);
presentCategoryPager(pagerAdapter, tabIconProvider, startingIndex);
presentProviderTabs(providers, providerIndex);
presentSearchButton(searchObserver);
presentBackspaceButton(backspaceObserver, isSolo);
presentAddButton(addObserver);
}
@Override
public int getCurrentPosition() {
return categoryPager != null ? categoryPager.getCurrentItem() : 0;
}
@Override
public void requestDismissal() {
hide(true);
providerIndex = 0;
if (keyboardListener != null) keyboardListener.onKeyboardProviderChanged(providers[providerIndex]);
}
@Override
public boolean isVisible() {
return getVisibility() == View.VISIBLE;
}
@Override
public void onTabSelected(int index) {
if (categoryPager != null) {
categoryPager.setCurrentItem(index);
categoryTabs.smoothScrollToPosition(index);
}
}
@Override
public void setViewPagerEnabled(boolean enabled) {
if (categoryPager != null) {
categoryPager.setEnabled(enabled);
}
}
private void initView() {
final View view = LayoutInflater.from(getContext()).inflate(R.layout.media_keyboard, this, true);
RecyclerView categoryTabsTop = view.findViewById(R.id.media_keyboard_tabs_top);
RecyclerView categoryTabsBottom = view.findViewById(R.id.media_keyboard_tabs);
this.categoryTabs = tabsAtBottom ? categoryTabsBottom : categoryTabsTop;
this.categoryPager = view.findViewById(R.id.media_keyboard_pager);
this.providerTabs = view.findViewById(R.id.media_keyboard_provider_tabs);
this.backspaceButton = view.findViewById(R.id.media_keyboard_backspace);
this.backspaceButtonBackup = view.findViewById(R.id.media_keyboard_backspace_backup);
this.searchButton = view.findViewById(R.id.media_keyboard_search);
this.addButton = view.findViewById(R.id.media_keyboard_add);
this.categoryTabAdapter = new MediaKeyboardBottomTabAdapter(GlideApp.with(this), this, tabsAtBottom);
categoryTabs.setLayoutManager(new LinearLayoutManager(getContext(), LinearLayoutManager.HORIZONTAL, false));
categoryTabs.setAdapter(categoryTabAdapter);
categoryTabs.setVisibility(VISIBLE);
}
private void requestPresent(@NonNull MediaKeyboardProvider[] providers, int newIndex) {
providers[providerIndex].setController(null);
providerIndex = newIndex;
providers[providerIndex].setController(this);
providers[providerIndex].requestPresentation(this, providers.length == 1);
}
private void presentCategoryPager(@NonNull PagerAdapter pagerAdapter,
@NonNull MediaKeyboardProvider.TabIconProvider iconProvider,
int startingIndex) {
if (categoryPager.getAdapter() != pagerAdapter) {
categoryPager.setAdapter(pagerAdapter);
}
categoryPager.setCurrentItem(startingIndex);
categoryPager.clearOnPageChangeListeners();
categoryPager.addOnPageChangeListener(new ViewPager.OnPageChangeListener() {
@Override
public void onPageScrolled(int i, float v, int i1) {
}
@Override
public void onPageSelected(int i) {
categoryTabAdapter.setActivePosition(i);
categoryTabs.smoothScrollToPosition(i);
}
@Override
public void onPageScrollStateChanged(int i) {
}
});
categoryTabAdapter.setTabIconProvider(iconProvider, pagerAdapter.getCount());
categoryTabAdapter.setActivePosition(startingIndex);
}
private void presentProviderTabs(@NonNull MediaKeyboardProvider[] providers, int selected) {
providerTabs.removeAllViews();
LayoutInflater inflater = LayoutInflater.from(getContext());
for (int i = 0; i < providers.length; i++) {
MediaKeyboardProvider provider = providers[i];
View view = inflater.inflate(provider.getProviderIconView(i == selected), providerTabs, false);
view.setTag(provider);
final int index = i;
view.setOnClickListener(v -> {
requestPresent(providers, index);
});
providerTabs.addView(view);
}
}
private void presentBackspaceButton(@Nullable MediaKeyboardProvider.BackspaceObserver backspaceObserver,
boolean useBackupPosition)
{
if (backspaceObserver != null) {
if (useBackupPosition) {
backspaceButton.setVisibility(INVISIBLE);
backspaceButton.setOnKeyEventListener(null);
backspaceButtonBackup.setVisibility(VISIBLE);
backspaceButtonBackup.setOnKeyEventListener(backspaceObserver::onBackspaceClicked);
} else {
backspaceButton.setVisibility(VISIBLE);
backspaceButton.setOnKeyEventListener(backspaceObserver::onBackspaceClicked);
backspaceButtonBackup.setVisibility(GONE);
backspaceButtonBackup.setOnKeyEventListener(null);
}
} else {
backspaceButton.setVisibility(INVISIBLE);
backspaceButton.setOnKeyEventListener(null);
backspaceButtonBackup.setVisibility(GONE);
backspaceButton.setOnKeyEventListener(null);
}
}
private void presentAddButton(@Nullable MediaKeyboardProvider.AddObserver addObserver) {
if (addObserver != null) {
addButton.setVisibility(VISIBLE);
addButton.setOnClickListener(v -> addObserver.onAddClicked());
} else {
addButton.setVisibility(GONE);
addButton.setOnClickListener(null);
}
}
private void presentSearchButton(@Nullable MediaKeyboardProvider.SearchObserver searchObserver) {
searchButton.setVisibility(searchObserver != null ? VISIBLE : INVISIBLE);
}
private void presentProviderStrip(boolean isSolo) {
int visibility = isSolo ? View.GONE : View.VISIBLE;
searchButton.setVisibility(visibility);
backspaceButton.setVisibility(visibility);
providerTabs.setVisibility(visibility);
}
public interface MediaKeyboardListener {
void onShown();
void onHidden();
void onKeyboardProviderChanged(@NonNull MediaKeyboardProvider provider);
}
}

View file

@ -0,0 +1,103 @@
package org.thoughtcrime.securesms.components.emoji;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.components.emoji.MediaKeyboardProvider.TabIconProvider;
import org.thoughtcrime.securesms.mms.GlideRequests;
public class MediaKeyboardBottomTabAdapter extends RecyclerView.Adapter<MediaKeyboardBottomTabAdapter.MediaKeyboardBottomTabViewHolder> {
private final GlideRequests glideRequests;
private final EventListener eventListener;
private final boolean highlightTop;
private TabIconProvider tabIconProvider;
private int activePosition;
private int count;
public MediaKeyboardBottomTabAdapter(@NonNull GlideRequests glideRequests, @NonNull EventListener eventListener, boolean highlightTop) {
this.glideRequests = glideRequests;
this.eventListener = eventListener;
this.highlightTop = highlightTop;
}
@Override
public @NonNull MediaKeyboardBottomTabViewHolder onCreateViewHolder(@NonNull ViewGroup viewGroup, int i) {
return new MediaKeyboardBottomTabViewHolder(LayoutInflater.from(viewGroup.getContext()).inflate(R.layout.media_keyboard_bottom_tab_item, viewGroup, false),
highlightTop);
}
@Override
public void onBindViewHolder(@NonNull MediaKeyboardBottomTabViewHolder viewHolder, int i) {
viewHolder.bind(glideRequests, eventListener, tabIconProvider, i, i == activePosition);
}
@Override
public void onViewRecycled(@NonNull MediaKeyboardBottomTabViewHolder holder) {
holder.recycle();
}
@Override
public int getItemCount() {
return count;
}
public void setTabIconProvider(@NonNull TabIconProvider iconProvider, int count) {
this.tabIconProvider = iconProvider;
this.count = count;
notifyDataSetChanged();
}
public void setActivePosition(int position) {
this.activePosition = position;
notifyDataSetChanged();
}
static class MediaKeyboardBottomTabViewHolder extends RecyclerView.ViewHolder {
private final ImageView image;
private final View indicator;
public MediaKeyboardBottomTabViewHolder(@NonNull View itemView, boolean highlightTop) {
super(itemView);
View indicatorTop = itemView.findViewById(R.id.media_keyboard_top_tab_indicator);
View indicatorBottom = itemView.findViewById(R.id.media_keyboard_bottom_tab_indicator);
this.image = itemView.findViewById(R.id.media_keyboard_bottom_tab_image);
this.indicator = highlightTop ? indicatorTop : indicatorBottom;
this.indicator.setVisibility(View.VISIBLE);
}
void bind(@NonNull GlideRequests glideRequests,
@NonNull EventListener eventListener,
@NonNull TabIconProvider tabIconProvider,
int index,
boolean selected)
{
tabIconProvider.loadCategoryTabIcon(glideRequests, image, index);
image.setAlpha(selected ? 1 : 0.5f);
image.setSelected(selected);
indicator.setVisibility(selected ? View.VISIBLE : View.INVISIBLE);
itemView.setOnClickListener(v -> eventListener.onTabSelected(index));
}
void recycle() {
itemView.setOnClickListener(null);
}
}
interface EventListener {
void onTabSelected(int index);
}
}

View file

@ -0,0 +1,51 @@
package org.thoughtcrime.securesms.components.emoji;
import androidx.annotation.LayoutRes;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.viewpager.widget.PagerAdapter;
import android.widget.ImageView;
import org.thoughtcrime.securesms.mms.GlideRequests;
public interface MediaKeyboardProvider {
@LayoutRes int getProviderIconView(boolean selected);
/** @return True if the click was handled with provider-specific logic, otherwise false */
void requestPresentation(@NonNull Presenter presenter, boolean isSoloProvider);
void setController(@Nullable Controller controller);
interface BackspaceObserver {
void onBackspaceClicked();
}
interface AddObserver {
void onAddClicked();
}
interface SearchObserver {
void onSearchOpened();
void onSearchClosed();
void onSearchChanged(@NonNull String query);
}
interface Controller {
void setViewPagerEnabled(boolean enabled);
}
interface Presenter {
void present(@NonNull MediaKeyboardProvider provider,
@NonNull PagerAdapter pagerAdapter,
@NonNull TabIconProvider iconProvider,
@Nullable BackspaceObserver backspaceObserver,
@Nullable AddObserver addObserver,
@Nullable SearchObserver searchObserver,
int startingIndex);
int getCurrentPosition();
void requestDismissal();
boolean isVisible();
}
interface TabIconProvider {
void loadCategoryTabIcon(@NonNull GlideRequests glideRequests, @NonNull ImageView imageView, int index);
}
}

View file

@ -4,9 +4,11 @@ import android.content.Context;
import android.content.SharedPreferences; import android.content.SharedPreferences;
import android.os.AsyncTask; import android.os.AsyncTask;
import android.preference.PreferenceManager; import android.preference.PreferenceManager;
import androidx.annotation.NonNull;
import android.util.Log; import android.util.Log;
import androidx.annotation.NonNull;
import com.annimon.stream.Stream;
import com.fasterxml.jackson.databind.type.CollectionType; import com.fasterxml.jackson.databind.type.CollectionType;
import com.fasterxml.jackson.databind.type.TypeFactory; import com.fasterxml.jackson.databind.type.TypeFactory;
@ -14,24 +16,28 @@ import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.util.JsonUtils; import org.thoughtcrime.securesms.util.JsonUtils;
import java.io.IOException; import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator; import java.util.Iterator;
import java.util.LinkedHashSet; import java.util.LinkedHashSet;
import java.util.List;
public class RecentEmojiPageModel implements EmojiPageModel { public class RecentEmojiPageModel implements EmojiPageModel {
private static final String TAG = RecentEmojiPageModel.class.getSimpleName(); private static final String TAG = RecentEmojiPageModel.class.getSimpleName();
private static final String EMOJI_LRU_PREFERENCE = "pref_recent_emoji2"; private static final int EMOJI_LRU_SIZE = 50;
private static final int EMOJI_LRU_SIZE = 50;
private final SharedPreferences prefs; private final SharedPreferences prefs;
private final String preferenceName;
private final LinkedHashSet<String> recentlyUsed; private final LinkedHashSet<String> recentlyUsed;
public RecentEmojiPageModel(Context context) { public RecentEmojiPageModel(Context context, @NonNull String preferenceName) {
this.prefs = PreferenceManager.getDefaultSharedPreferences(context); this.prefs = PreferenceManager.getDefaultSharedPreferences(context);
this.recentlyUsed = getPersistedCache(); this.preferenceName = preferenceName;
this.recentlyUsed = getPersistedCache();
} }
private LinkedHashSet<String> getPersistedCache() { private LinkedHashSet<String> getPersistedCache() {
String serialized = prefs.getString(EMOJI_LRU_PREFERENCE, "[]"); String serialized = prefs.getString(preferenceName, "[]");
try { try {
CollectionType collectionType = TypeFactory.defaultInstance() CollectionType collectionType = TypeFactory.defaultInstance()
.constructCollectionType(LinkedHashSet.class, String.class); .constructCollectionType(LinkedHashSet.class, String.class);
@ -46,8 +52,14 @@ public class RecentEmojiPageModel implements EmojiPageModel {
return R.attr.emoji_category_recent; return R.attr.emoji_category_recent;
} }
@Override public String[] getEmoji() { @Override public List<String> getEmoji() {
return toReversePrimitiveArray(recentlyUsed); List<String> emoji = new ArrayList<>(recentlyUsed);
Collections.reverse(emoji);
return emoji;
}
@Override public List<Emoji> getDisplayEmoji() {
return Stream.of(getEmoji()).map(Emoji::new).toList();
} }
@Override public boolean hasSpriteMap() { @Override public boolean hasSpriteMap() {
@ -63,7 +75,6 @@ public class RecentEmojiPageModel implements EmojiPageModel {
} }
public void onCodePointSelected(String emoji) { public void onCodePointSelected(String emoji) {
Log.w(TAG, "onCodePointSelected(" + emoji + ")");
recentlyUsed.remove(emoji); recentlyUsed.remove(emoji);
recentlyUsed.add(emoji); recentlyUsed.add(emoji);
@ -81,7 +92,7 @@ public class RecentEmojiPageModel implements EmojiPageModel {
try { try {
String serialized = JsonUtils.toJson(latestRecentlyUsed); String serialized = JsonUtils.toJson(latestRecentlyUsed);
prefs.edit() prefs.edit()
.putString(EMOJI_LRU_PREFERENCE, serialized) .putString(preferenceName, serialized)
.apply(); .apply();
} catch (IOException e) { } catch (IOException e) {
Log.w(TAG, e); Log.w(TAG, e);

View file

@ -4,34 +4,63 @@ import androidx.annotation.AttrRes;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
public class StaticEmojiPageModel implements EmojiPageModel { import java.util.ArrayList;
@AttrRes private final int iconAttr; import java.util.Arrays;
@NonNull private final String[] emoji; import java.util.LinkedList;
@Nullable private final String sprite; import java.util.List;
public StaticEmojiPageModel(@AttrRes int iconAttr, @NonNull String[] emoji, @Nullable String sprite) { public class StaticEmojiPageModel implements EmojiPageModel {
this.iconAttr = iconAttr; @AttrRes private final int iconAttr;
this.emoji = emoji; @NonNull private final List<Emoji> emoji;
this.sprite = sprite; @Nullable private final String sprite;
public StaticEmojiPageModel(@AttrRes int iconAttr, @NonNull String[] strings, @Nullable String sprite) {
List<Emoji> emoji = new ArrayList<>(strings.length);
for (String s : strings) {
emoji.add(new Emoji(s));
}
this.iconAttr = iconAttr;
this.emoji = emoji;
this.sprite = sprite;
}
public StaticEmojiPageModel(@AttrRes int iconAttr, @NonNull Emoji[] emoji, @Nullable String sprite) {
this.iconAttr = iconAttr;
this.emoji = Arrays.asList(emoji);
this.sprite = sprite;
} }
public int getIconAttr() { public int getIconAttr() {
return iconAttr; return iconAttr;
} }
@NonNull public String[] getEmoji() { @Override
public @NonNull List<String> getEmoji() {
List<String> emojis = new LinkedList<>();
for (Emoji e : emoji) {
emojis.addAll(e.getVariations());
}
return emojis;
}
@Override
public @NonNull List<Emoji> getDisplayEmoji() {
return emoji; return emoji;
} }
@Override public boolean hasSpriteMap() { @Override
public boolean hasSpriteMap() {
return sprite != null; return sprite != null;
} }
@Override @Nullable public String getSprite() { @Override
public @Nullable String getSprite() {
return sprite; return sprite;
} }
@Override public boolean isDynamic() { @Override
public boolean isDynamic() {
return false; return false;
} }
} }

View file

@ -22,7 +22,7 @@ public class EmojiDrawInfo {
} }
@Override @Override
public String toString() { public @NonNull String toString() {
return "DrawInfo{" + return "DrawInfo{" +
"page=" + page + "page=" + page +
", index=" + index + ", index=" + index +

View file

@ -1,26 +1,28 @@
package org.thoughtcrime.securesms.components.emoji.parsing; package org.thoughtcrime.securesms.components.emoji.parsing;
import android.annotation.SuppressLint;
import android.content.Context; import android.content.Context;
import android.content.res.AssetManager;
import android.graphics.Bitmap; import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.AsyncTask; import android.os.AsyncTask;
import androidx.annotation.NonNull;
import android.util.Log; import android.util.Log;
import com.bumptech.glide.load.engine.DiskCacheStrategy; import androidx.annotation.NonNull;
import org.thoughtcrime.securesms.components.emoji.EmojiPageModel; import org.thoughtcrime.securesms.components.emoji.EmojiPageModel;
import org.thoughtcrime.securesms.mms.GlideApp;
import org.thoughtcrime.securesms.util.ListenableFutureTask; import org.thoughtcrime.securesms.util.ListenableFutureTask;
import org.thoughtcrime.securesms.util.Stopwatch;
import org.thoughtcrime.securesms.util.Util; import org.thoughtcrime.securesms.util.Util;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream;
import java.lang.ref.SoftReference; import java.lang.ref.SoftReference;
import java.util.concurrent.Callable; import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
public class EmojiPageBitmap { public class EmojiPageBitmap {
private static final String TAG = EmojiPageBitmap.class.getName(); private static final String TAG = EmojiPageBitmap.class.getSimpleName();
private final Context context; private final Context context;
private final EmojiPageModel model; private final EmojiPageModel model;
@ -35,6 +37,7 @@ public class EmojiPageBitmap {
this.decodeScale = decodeScale; this.decodeScale = decodeScale;
} }
@SuppressLint("StaticFieldLeak")
public ListenableFutureTask<Bitmap> get() { public ListenableFutureTask<Bitmap> get() {
Util.assertMainThread(); Util.assertMainThread();
@ -45,7 +48,7 @@ public class EmojiPageBitmap {
} else { } else {
Callable<Bitmap> callable = () -> { Callable<Bitmap> callable = () -> {
try { try {
Log.w(TAG, "loading page " + model.getSprite()); Log.i(TAG, "loading page " + model.getSprite());
return loadPage(); return loadPage();
} catch (IOException ioe) { } catch (IOException ioe) {
Log.w(TAG, ioe); Log.w(TAG, ioe);
@ -67,34 +70,38 @@ public class EmojiPageBitmap {
return task; return task;
} }
public Bitmap loadPage() throws IOException { private Bitmap loadPage() throws IOException {
if (bitmapReference != null && bitmapReference.get() != null) return bitmapReference.get(); if (bitmapReference != null && bitmapReference.get() != null) return bitmapReference.get();
try {
Bitmap originalBitmap = GlideApp.with(context.getApplicationContext())
.asBitmap()
.load("file:///android_asset/" + model.getSprite())
.skipMemoryCache(true)
.diskCacheStrategy(DiskCacheStrategy.NONE)
.submit()
.get();
Bitmap scaledBitmap = Bitmap.createScaledBitmap(originalBitmap, (int)(originalBitmap.getWidth() * decodeScale), (int)(originalBitmap.getHeight() * decodeScale), false); float scale = decodeScale;
AssetManager assetManager = context.getAssets();
InputStream assetStream = assetManager.open(model.getSprite());
BitmapFactory.Options options = new BitmapFactory.Options();
bitmapReference = new SoftReference<>(scaledBitmap); if (Util.isLowMemory(context)) {
Log.w(TAG, "onPageLoaded(" + model.getSprite() + ")"); Log.i(TAG, "Low memory detected. Changing sample size.");
return scaledBitmap; options.inSampleSize = 2;
} catch (InterruptedException e) { scale = decodeScale * 2;
Log.w(TAG, e);
throw new IOException(e);
} catch (ExecutionException e) {
Log.w(TAG, e);
throw new IOException(e);
} }
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 @Override
public String toString() { public @NonNull String toString() {
return model.getSprite(); return model.getSprite();
} }
} }

View file

@ -42,7 +42,9 @@ public class EmojiParser {
public @NonNull CandidateList findCandidates(@Nullable CharSequence text) { public @NonNull CandidateList findCandidates(@Nullable CharSequence text) {
List<Candidate> results = new LinkedList<>(); List<Candidate> results = new LinkedList<>();
if (text == null) return new CandidateList(results, false); if (text == null) {
return new CandidateList(results, false);
}
boolean allEmojis = text.length() > 0; boolean allEmojis = text.length() > 0;
@ -61,11 +63,13 @@ public class EmojiParser {
results.add(new Candidate(i, emojiEnd, drawInfo)); results.add(new Candidate(i, emojiEnd, drawInfo));
i = emojiEnd - 1; i = emojiEnd - 1;
} else { } else if (text.charAt(i) != ' '){
allEmojis = false; allEmojis = false;
} }
} }
allEmojis &= !results.isEmpty();
return new CandidateList(results, allEmojis); return new CandidateList(results, allEmojis);
} }
@ -124,7 +128,7 @@ public class EmojiParser {
} }
@Override @Override
public Iterator<Candidate> iterator() { public @NonNull Iterator<Candidate> iterator() {
return list.iterator(); return list.iterator();
} }
} }

View file

@ -48,7 +48,12 @@ public class ResUtil {
} }
public static Drawable getDrawable(Context c, @AttrRes int attr) { public static Drawable getDrawable(Context c, @AttrRes int attr) {
return ContextCompat.getDrawable(c, getDrawableRes(c, attr)); try {
return ContextCompat.getDrawable(c, getDrawableRes(c, attr));
} catch (Exception e) {
e.printStackTrace();
return null;
}
} }
public static int[] getResourceIds(Context c, @ArrayRes int array) { public static int[] getResourceIds(Context c, @ArrayRes int array) {

View file

@ -0,0 +1,59 @@
package org.thoughtcrime.securesms.util;
import android.util.Log;
import androidx.annotation.NonNull;
import java.util.LinkedList;
import java.util.List;
public class Stopwatch {
private final long startTime;
private final String title;
private final List<Split> splits;
public Stopwatch(@NonNull String title) {
this.startTime = System.currentTimeMillis();
this.title = title;
this.splits = new LinkedList<>();
}
public void split(@NonNull String label) {
splits.add(new Split(System.currentTimeMillis(), label));
}
public void stop(@NonNull String tag) {
StringBuilder out = new StringBuilder();
out.append("[").append(title).append("] ");
if (splits.size() > 0) {
out.append(splits.get(0).label).append(": ");
out.append(splits.get(0).time - startTime);
out.append(" ");
}
if (splits.size() > 1) {
for (int i = 1; i < splits.size(); i++) {
out.append(splits.get(i).label).append(": ");
out.append(splits.get(i).time - splits.get(i - 1).time);
out.append(" ");
}
out.append("total: ").append(splits.get(splits.size() - 1).time - startTime);
}
Log.d(tag, out.toString());
}
private static class Split {
final long time;
final String label;
Split(long time, String label) {
this.time = time;
this.label = label;
}
}
}