update and adapt signal sources to have the new media keyboard including new emojis
Before Width: | Height: | Size: 337 KiB |
BIN
assets/emoji/Activity.webp
Normal file
After Width: | Height: | Size: 225 KiB |
Before Width: | Height: | Size: 812 KiB |
BIN
assets/emoji/Flags_0.webp
Normal file
After Width: | Height: | Size: 415 KiB |
BIN
assets/emoji/Flags_1.webp
Normal file
After Width: | Height: | Size: 23 KiB |
Before Width: | Height: | Size: 610 KiB |
BIN
assets/emoji/Foods.webp
Normal file
After Width: | Height: | Size: 344 KiB |
Before Width: | Height: | Size: 694 KiB |
BIN
assets/emoji/Nature.webp
Normal file
After Width: | Height: | Size: 395 KiB |
Before Width: | Height: | Size: 873 KiB |
BIN
assets/emoji/Objects.webp
Normal file
After Width: | Height: | Size: 622 KiB |
Before Width: | Height: | Size: 2 MiB |
BIN
assets/emoji/People_0.webp
Normal file
After Width: | Height: | Size: 599 KiB |
BIN
assets/emoji/People_1.webp
Normal file
After Width: | Height: | Size: 559 KiB |
BIN
assets/emoji/People_2.webp
Normal file
After Width: | Height: | Size: 643 KiB |
BIN
assets/emoji/People_3.webp
Normal file
After Width: | Height: | Size: 647 KiB |
BIN
assets/emoji/People_4.webp
Normal file
After Width: | Height: | Size: 602 KiB |
BIN
assets/emoji/People_5.webp
Normal file
After Width: | Height: | Size: 589 KiB |
BIN
assets/emoji/People_6.webp
Normal file
After Width: | Height: | Size: 531 KiB |
Before Width: | Height: | Size: 1.1 MiB |
BIN
assets/emoji/Places.webp
Normal file
After Width: | Height: | Size: 589 KiB |
Before Width: | Height: | Size: 482 KiB |
BIN
assets/emoji/Symbols.webp
Normal file
After Width: | Height: | Size: 384 KiB |
BIN
delta-chat-2019-10-29-0.bak
Normal 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>
|
|
@ -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>
|
9
res/drawable/ic_emoji_activity_dark_20.xml
Normal 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>
|
9
res/drawable/ic_emoji_activity_light_20.xml
Normal 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>
|
9
res/drawable/ic_emoji_animal_dark_20.xml
Normal 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>
|
9
res/drawable/ic_emoji_animal_light_20.xml
Normal 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>
|
9
res/drawable/ic_emoji_emoticon_dark_20.xml
Normal 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>
|
9
res/drawable/ic_emoji_emoticon_light_20.xml
Normal 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>
|
4
res/drawable/ic_emoji_filled.xml
Normal 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>
|
9
res/drawable/ic_emoji_flag_dark_20.xml
Normal 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>
|
9
res/drawable/ic_emoji_flag_light_20.xml
Normal 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>
|
9
res/drawable/ic_emoji_food_dark_20.xml
Normal 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>
|
9
res/drawable/ic_emoji_food_light_20.xml
Normal 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>
|
4
res/drawable/ic_emoji_object_dark_20.xml
Normal 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>
|
4
res/drawable/ic_emoji_object_light_20.xml
Normal 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>
|
9
res/drawable/ic_emoji_outline.xml
Normal 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>
|
9
res/drawable/ic_emoji_people_dark_20.xml
Normal 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>
|
9
res/drawable/ic_emoji_people_light_20.xml
Normal 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>
|
9
res/drawable/ic_emoji_smiley_outline_24.xml
Normal 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>
|
9
res/drawable/ic_emoji_smiley_solid_24.xml
Normal 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>
|
4
res/drawable/ic_emoji_symbol_dark_20.xml
Normal 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>
|
4
res/drawable/ic_emoji_symbol_light_20.xml
Normal 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>
|
9
res/drawable/ic_emoji_travel_dark_20.xml
Normal 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>
|
9
res/drawable/ic_emoji_travel_light_20.xml
Normal 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>
|
9
res/drawable/media_keyboard_selected_background_dark.xml
Normal 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>
|
|
@ -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>
|
13
res/drawable/triangle_bottom_right_corner.xml
Normal 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>
|
|
@ -53,10 +53,10 @@
|
||||||
|
|
||||||
<ViewStub
|
<ViewStub
|
||||||
android:id="@+id/emoji_drawer_stub"
|
android:id="@+id/emoji_drawer_stub"
|
||||||
android:layout="@layout/conversation_activity_emojidrawer_stub"
|
|
||||||
android:inflatedId="@+id/emoji_drawer"
|
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"/>
|
android:layout_height="wrap_content"
|
||||||
|
android:inflatedId="@+id/emoji_drawer"
|
||||||
|
android:layout="@layout/conversation_activity_emojidrawer_stub" />
|
||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
</org.thoughtcrime.securesms.components.camera.QuickAttachmentDrawer>
|
</org.thoughtcrime.securesms.components.camera.QuickAttachmentDrawer>
|
||||||
|
|
|
@ -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"
|
||||||
|
|
38
res/layout/emoji_display_item.xml
Normal 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>
|
|
@ -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>
|
16
res/layout/emoji_keyboard_icon_dark.xml
Normal 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>
|
17
res/layout/emoji_keyboard_icon_dark_selected.xml
Normal 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>
|
16
res/layout/emoji_keyboard_icon_light.xml
Normal 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>
|
17
res/layout/emoji_keyboard_icon_light_selected.xml
Normal 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>
|
14
res/layout/emoji_variation_selector.xml
Normal 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>
|
8
res/layout/emoji_variation_selector_item.xml
Normal 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>
|
|
@ -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>
|
||||||
|
|
149
res/layout/media_keyboard.xml
Normal 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>
|
33
res/layout/media_keyboard_bottom_tab_item.xml
Normal 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>
|
||||||
|
|
|
@ -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" />
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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,13 +302,6 @@ public class CreateProfileActivity extends BaseActionBarActivity {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void initializeEmojiInput() {
|
|
||||||
|
|
||||||
this.emojiDrawer.setEmojiEventListener(new EmojiDrawer.EmojiEventListener() {
|
|
||||||
@Override
|
|
||||||
public void onKeyEvent(KeyEvent keyEvent) {
|
|
||||||
name.dispatchKeyEvent(keyEvent);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onEmojiSelected(String emoji) {
|
public void onEmojiSelected(String emoji) {
|
||||||
|
@ -316,8 +311,21 @@ public class CreateProfileActivity extends BaseActionBarActivity {
|
||||||
name.getText().replace(Math.min(start, end), Math.max(start, end), emoji);
|
name.getText().replace(Math.min(start, end), Math.max(start, end), emoji);
|
||||||
name.setSelection(start + emoji.length());
|
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() {
|
||||||
|
initializeMediaKeyboardProviders(emojiDrawer);
|
||||||
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);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -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,14 +38,14 @@ 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;
|
||||||
|
@ -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);
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
21
src/org/thoughtcrime/securesms/components/emoji/Emoji.java
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -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();
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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));
|
||||||
|
}
|
||||||
|
}
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -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();
|
||||||
|
|
|
@ -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) {
|
|
||||||
this.listener = listener;
|
|
||||||
}
|
|
||||||
|
|
||||||
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
|
@Override
|
||||||
public Object getItem(int position) {
|
protected void onVisibilityChanged(@NonNull View changedView, int visibility) {
|
||||||
return null;
|
if (visibility != VISIBLE) {
|
||||||
|
popup.dismiss();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public long getItemId(int position) {
|
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
|
||||||
return position;
|
int idealWidth = getContext().getResources().getDimensionPixelOffset(R.dimen.emoji_drawer_item_width);
|
||||||
|
layoutManager.setSpanCount(Math.max(w / idealWidth, 1));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public View getView(final int position, final View convertView, final ViewGroup parent) {
|
public void onVariationSelectorStateChanged(boolean open) {
|
||||||
final EmojiView view;
|
if (open) {
|
||||||
final int pad = context.getResources().getDimensionPixelSize(R.dimen.emoji_drawer_item_padding);
|
recyclerView.addOnItemTouchListener(scrollDisabler);
|
||||||
if (convertView != null && convertView instanceof EmojiView) {
|
|
||||||
view = (EmojiView)convertView;
|
|
||||||
} else {
|
} else {
|
||||||
final EmojiView emojiView = new EmojiView(context);
|
post(() -> recyclerView.removeOnItemTouchListener(scrollDisabler));
|
||||||
emojiView.setPadding(pad, pad, pad, pad);
|
|
||||||
emojiView.setLayoutParams(new AbsListView.LayoutParams(emojiSize + 2 * pad, emojiSize + 2 * pad));
|
|
||||||
view = emojiView;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
view.setEmoji(model.getEmoji()[position]);
|
if (variationSelectorListener != null) {
|
||||||
return view;
|
variationSelectorListener.onVariationSelectorStateChanged(open);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public interface EmojiSelectionListener {
|
public void setRecyclerNestedScrollingEnabled(boolean enabled) {
|
||||||
void onEmojiSelected(String emoji);
|
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) { }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -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,22 +106,15 @@ 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) {
|
|
||||||
try {
|
|
||||||
drawable.setBitmap(drawInfo.getPage().loadPage(), background);
|
|
||||||
} catch (IOException e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
drawInfo.getPage().get().addListener(new FutureTaskListener<Bitmap>() {
|
drawInfo.getPage().get().addListener(new FutureTaskListener<Bitmap>() {
|
||||||
@Override public void onSuccess(final Bitmap result) {
|
@Override public void onSuccess(final Bitmap result) {
|
||||||
Util.runOnMain(() -> drawable.setBitmap(result));
|
Util.runOnMain(() -> drawable.setBitmap(result));
|
||||||
|
@ -131,7 +124,6 @@ class EmojiProvider {
|
||||||
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
@TargetApi(VERSION_CODES.HONEYCOMB_MR1)
|
|
||||||
public void setBitmap(Bitmap bitmap, boolean background) {
|
|
||||||
if (!background) {
|
|
||||||
Util.assertMainThread();
|
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();
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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;
|
||||||
|
previousOverflowText = overflowText;
|
||||||
previousBufferType = type;
|
previousBufferType = type;
|
||||||
useSystemEmoji = useSystemEmoji();
|
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(previousOverflowText, overflowText) &&
|
||||||
Util.equals(previousBufferType, bufferType) &&
|
Util.equals(previousBufferType, bufferType) &&
|
||||||
useSystemEmoji == useSystemEmoji() &&
|
useSystemEmoji == useSystemEmoji() &&
|
||||||
!sizeChangeInProgress;
|
!sizeChangeInProgress;
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean useSystemEmoji() {
|
private boolean useSystemEmoji() {
|
||||||
return Prefs.isSystemEmojiPreferred(getContext());
|
return !forceCustom && Prefs.isSystemEmojiPreferred(getContext());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -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);*/
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -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.preferenceName = preferenceName;
|
||||||
this.recentlyUsed = getPersistedCache();
|
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);
|
||||||
|
|
|
@ -4,34 +4,63 @@ import androidx.annotation.AttrRes;
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.LinkedList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
public class StaticEmojiPageModel implements EmojiPageModel {
|
public class StaticEmojiPageModel implements EmojiPageModel {
|
||||||
@AttrRes private final int iconAttr;
|
@AttrRes private final int iconAttr;
|
||||||
@NonNull private final String[] emoji;
|
@NonNull private final List<Emoji> emoji;
|
||||||
@Nullable private final String sprite;
|
@Nullable private final String sprite;
|
||||||
|
|
||||||
public StaticEmojiPageModel(@AttrRes int iconAttr, @NonNull String[] emoji, @Nullable 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.iconAttr = iconAttr;
|
||||||
this.emoji = emoji;
|
this.emoji = emoji;
|
||||||
this.sprite = sprite;
|
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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 +
|
||||||
|
|
|
@ -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();
|
||||||
|
|
||||||
|
if (Util.isLowMemory(context)) {
|
||||||
|
Log.i(TAG, "Low memory detected. Changing sample size.");
|
||||||
|
options.inSampleSize = 2;
|
||||||
|
scale = decodeScale * 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
Stopwatch stopwatch = new Stopwatch(model.getSprite());
|
||||||
|
Bitmap bitmap = BitmapFactory.decodeStream(assetStream, null, options);
|
||||||
|
stopwatch.split("decode");
|
||||||
|
|
||||||
|
Bitmap scaledBitmap = Bitmap.createScaledBitmap(bitmap, (int)(bitmap.getWidth() * scale), (int)(bitmap.getHeight() * scale), true);
|
||||||
|
stopwatch.split("scale");
|
||||||
|
stopwatch.stop(TAG);
|
||||||
|
|
||||||
bitmapReference = new SoftReference<>(scaledBitmap);
|
bitmapReference = new SoftReference<>(scaledBitmap);
|
||||||
Log.w(TAG, "onPageLoaded(" + model.getSprite() + ")");
|
Log.i(TAG, "onPageLoaded(" + model.getSprite() + ") originalByteCount: " + bitmap.getByteCount()
|
||||||
|
+ " scaledByteCount: " + scaledBitmap.getByteCount()
|
||||||
|
+ " scaledSize: " + scaledBitmap.getWidth() + "x" + scaledBitmap.getHeight());
|
||||||
return scaledBitmap;
|
return scaledBitmap;
|
||||||
} catch (InterruptedException e) {
|
|
||||||
Log.w(TAG, e);
|
|
||||||
throw new IOException(e);
|
|
||||||
} catch (ExecutionException e) {
|
|
||||||
Log.w(TAG, e);
|
|
||||||
throw new IOException(e);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public @NonNull String toString() {
|
||||||
return model.getSprite();
|
return model.getSprite();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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) {
|
||||||
|
try {
|
||||||
return ContextCompat.getDrawable(c, getDrawableRes(c, attr));
|
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) {
|
||||||
|
|
59
src/org/thoughtcrime/securesms/util/Stopwatch.java
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|