refactor emoji code into package

1) EmojiTextView and EmojiEditText are used instead of
   using code to emojify text.

2) Emoji categories' code points are specified in XML

3) EmojiDrawer itself is a fragment, and its pages are
   also fragments, allowing for better memory
   management.

Fixes #2938
Fixes #2936
Closes #3153

// FREEBIE
master
Jake McGinty 2015-05-06 13:53:55 -07:00 committed by Moxie Marlinspike
parent 2bee953560
commit bf133c65c3
27 changed files with 1695 additions and 810 deletions

View File

@ -5,7 +5,7 @@
android:versionCode="118"
android:versionName="2.14.4">
<uses-sdk tools:overrideLibrary="com.amulyakhare.textdrawable"/>
<uses-sdk tools:overrideLibrary="com.amulyakhare.textdrawable,com.astuetz.pagerslidingtabstrip"/>
<permission android:name="org.thoughtcrime.securesms.ACCESS_SECRETS"
android:label="Access to TextSecure Secrets"

View File

@ -37,7 +37,7 @@ dependencies {
compile 'me.leolin:ShortcutBadger:1.1.0-WS1'
compile 'se.emilsjolander:stickylistheaders:2.2.0'
compile 'com.google.android.gms:play-services-base:6.5.87'
compile 'com.astuetz:pagerslidingtabstrip:1.0.1'
compile 'com.jpardogo.materialtabstrip:library:1.0.9'
compile 'org.w3c:smil:1.0.0'
compile 'org.apache.httpcomponents:httpclient-android:4.3.5'
compile 'com.github.chrisbanes.photoview:library:1.2.3'
@ -89,7 +89,6 @@ dependencyVerification {
'me.leolin:ShortcutBadger:3142d017234bfa0cdd69ccded7cc5ea63f13b97574803c8c616c9bbeaad33ad9',
'se.emilsjolander:stickylistheaders:89146b46c96fea0e40200474a2625cda10fe94891e4128f53cdb42375091b9b6',
'com.google.android.gms:play-services-base:832cb6b3130e871db6a412c4ab585656dbcc5e7948101f190186757785703f75',
'com.astuetz:pagerslidingtabstrip:f1641396732c7132a7abb837e482e5ee2b0ebb8d10813fc52bbaec2c15c184c2',
'org.w3c:smil:085dc40f2bb249651578bfa07499fd08b16ad0886dbe2c4078586a408da62f9b',
'org.apache.httpcomponents:httpclient-android:6f56466a9bd0d42934b90bfbfe9977a8b654c058bf44a12bdc2877c4e1f033f1',
'com.github.chrisbanes.photoview:library:8b5344e206f125e7ba9d684008f36c4992d03853c57e5814125f88496126e3cc',

View File

@ -67,7 +67,7 @@
android:orientation="horizontal"
android:background="?conversation_editor_background">
<org.thoughtcrime.securesms.components.EmojiToggle
<org.thoughtcrime.securesms.components.emoji.EmojiToggle
android:id="@+id/emoji_toggle"
android:layout_width="wrap_content"
android:layout_height="fill_parent"
@ -123,10 +123,8 @@
</LinearLayout>
</RelativeLayout>
<ViewStub android:id="@+id/emoji_drawer_stub"
android:inflatedId="@+id/emoji_drawer"
android:layout="@layout/emoji_drawer_stub"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<LinearLayout android:id="@+id/emoji_drawer"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</LinearLayout>

View File

@ -42,7 +42,8 @@
android:layout_below="@id/thumbnail_container"
android:orientation="vertical">
<TextView android:id="@+id/conversation_item_body"
<org.thoughtcrime.securesms.components.emoji.EmojiTextView
android:id="@+id/conversation_item_body"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingTop="5dip"

View File

@ -44,7 +44,8 @@
android:layout_marginRight="12dp"
android:orientation="vertical">
<TextView android:id="@+id/conversation_item_body"
<org.thoughtcrime.securesms.components.emoji.EmojiTextView
android:id="@+id/conversation_item_body"
android:autoLink="all"
android:layout_width="wrap_content"
android:layout_height="wrap_content"

View File

@ -78,7 +78,8 @@
android:layout_gravity="top" />
</LinearLayout>
<TextView android:id="@+id/subject"
<org.thoughtcrime.securesms.components.emoji.EmojiTextView
android:id="@+id/subject"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textAppearance="?android:attr/textAppearanceSmall"

View File

@ -1,6 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<org.thoughtcrime.securesms.components.KeyboardAwareLinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/container"
android:orientation="vertical"
android:layout_width="match_parent"
@ -14,7 +15,13 @@
<com.astuetz.PagerSlidingTabStrip android:id="@+id/tabs"
android:layout_weight="1"
android:layout_width="0dp"
android:layout_height="45dp" />
android:layout_height="45dp"
app:pstsShouldExpand="true"
app:pstsTabPaddingLeftRight="@dimen/emoji_drawer_left_right_padding"
app:pstsUnderlineColor="@color/emoji_tab_underline"
app:pstsIndicatorColor="@color/emoji_tab_indicator"
app:pstsIndicatorHeight="@dimen/emoji_drawer_indicator_height"
app:pstsTextAllCaps="false"/>
<org.thoughtcrime.securesms.components.RepeatableImageKey
android:id="@+id/backspace"
@ -27,10 +34,11 @@
</LinearLayout>
<android.support.v4.view.ViewPager android:id="@+id/emoji_pager"
android:visibility="visible"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#ff333333" />
<android.support.v4.view.ViewPager
android:id="@+id/emoji_pager"
android:visibility="visible"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#ff333333" />
</org.thoughtcrime.securesms.components.KeyboardAwareLinearLayout>

View File

@ -1,6 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<org.thoughtcrime.securesms.components.EmojiDrawer
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_weight="1.1" />

View File

@ -113,5 +113,4 @@
</declare-styleable>
<attr name="group_members_dialog_icon" format="reference"/>
</resources>

View File

@ -0,0 +1,867 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<integer-array name="emoji_smile">
<item>0x263a</item>
<item>0x1f60a</item>
<item>0x1f600</item>
<item>0x1f601</item>
<item>0x1f602</item>
<item>0x1f603</item>
<item>0x1f604</item>
<item>0x1f605</item>
<item>0x1f606</item>
<item>0x1f607</item>
<item>0x1f608</item>
<item>0x1f609</item>
<item>0x1f62f</item>
<item>0x1f610</item>
<item>0x1f611</item>
<item>0x1f615</item>
<item>0x1f620</item>
<item>0x1f62c</item>
<item>0x1f621</item>
<item>0x1f622</item>
<item>0x1f634</item>
<item>0x1f62e</item>
<item>0x1f623</item>
<item>0x1f624</item>
<item>0x1f625</item>
<item>0x1f626</item>
<item>0x1f627</item>
<item>0x1f628</item>
<item>0x1f629</item>
<item>0x1f630</item>
<item>0x1f61f</item>
<item>0x1f631</item>
<item>0x1f632</item>
<item>0x1f633</item>
<item>0x1f635</item>
<item>0x1f636</item>
<item>0x1f637</item>
<item>0x1f61e</item>
<item>0x1f612</item>
<item>0x1f60d</item>
<item>0x1f61b</item>
<item>0x1f61c</item>
<item>0x1f61d</item>
<item>0x1f60b</item>
<item>0x1f617</item>
<item>0x1f619</item>
<item>0x1f618</item>
<item>0x1f61a</item>
<item>0x1f60e</item>
<item>0x1f62d</item>
<item>0x1f60c</item>
<item>0x1f616</item>
<item>0x1f614</item>
<item>0x1f62a</item>
<item>0x1f60f</item>
<item>0x1f613</item>
<item>0x1f62b</item>
<item>0x1f64b</item>
<item>0x1f64c</item>
<item>0x1f64d</item>
<item>0x1f645</item>
<item>0x1f646</item>
<item>0x1f647</item>
<item>0x1f64e</item>
<item>0x1f64f</item>
<item>0x1f63a</item>
<item>0x1f63c</item>
<item>0x1f638</item>
<item>0x1f639</item>
<item>0x1f63b</item>
<item>0x1f63d</item>
<item>0x1f63f</item>
<item>0x1f63e</item>
<item>0x1f640</item>
<item>0x1f648</item>
<item>0x1f649</item>
<item>0x1f64a</item>
<item>0x1f4a9</item>
<item>0x1f476</item>
<item>0x1f466</item>
<item>0x1f467</item>
<item>0x1f468</item>
<item>0x1f469</item>
<item>0x1f474</item>
<item>0x1f475</item>
<item>0x1f48f</item>
<item>0x1f491</item>
<item>0x1f46a</item>
<item>0x1f46b</item>
<item>0x1f46c</item>
<item>0x1f46d</item>
<item>0x1f464</item>
<item>0x1f465</item>
<item>0x1f46e</item>
<item>0x1f477</item>
<item>0x1f481</item>
<item>0x1f482</item>
<item>0x1f46f</item>
<item>0x1f470</item>
<item>0x1f478</item>
<item>0x1f385</item>
<item>0x1f47c</item>
<item>0x1f471</item>
<item>0x1f472</item>
<item>0x1f473</item>
<item>0x1f483</item>
<item>0x1f486</item>
<item>0x1f487</item>
<item>0x1f485</item>
<item>0x1f47b</item>
<item>0x1f479</item>
<item>0x1f47a</item>
<item>0x1f47d</item>
<item>0x1f47e</item>
<item>0x1f47f</item>
<item>0x1f480</item>
<item>0x1f4aa</item>
<item>0x1f440</item>
<item>0x1f442</item>
<item>0x1f443</item>
<item>0x1f463</item>
<item>0x1f444</item>
<item>0x1f445</item>
<item>0x1f48b</item>
<item>0x2764</item>
<item>0x1f499</item>
<item>0x1f49a</item>
<item>0x1f49b</item>
<item>0x1f49c</item>
<item>0x1f493</item>
<item>0x1f494</item>
<item>0x1f495</item>
<item>0x1f496</item>
<item>0x1f497</item>
<item>0x1f498</item>
<item>0x1f49d</item>
<item>0x1f49e</item>
<item>0x1f49f</item>
<item>0x1f44d</item>
<item>0x1f44e</item>
<item>0x1f44c</item>
<item>0x270a</item>
<item>0x270c</item>
<item>0x270b</item>
<item>0x1f44a</item>
<item>0x261d</item>
<item>0x1f446</item>
<item>0x1f447</item>
<item>0x1f448</item>
<item>0x1f449</item>
<item>0x1f44b</item>
<item>0x1f44f</item>
<item>0x1f450</item>
</integer-array>
<integer-array name="emoji_flower">
<item>0x1f530</item>
<item>0x1f484</item>
<item>0x1f45e</item>
<item>0x1f45f</item>
<item>0x1f451</item>
<item>0x1f452</item>
<item>0x1f3a9</item>
<item>0x1f393</item>
<item>0x1f453</item>
<item>0x231a</item>
<item>0x1f454</item>
<item>0x1f455</item>
<item>0x1f456</item>
<item>0x1f457</item>
<item>0x1f458</item>
<item>0x1f459</item>
<item>0x1f460</item>
<item>0x1f461</item>
<item>0x1f462</item>
<item>0x1f45a</item>
<item>0x1f45c</item>
<item>0x1f4bc</item>
<item>0x1f392</item>
<item>0x1f45d</item>
<item>0x1f45b</item>
<item>0x1f4b0</item>
<item>0x1f4b3</item>
<item>0x1f4b2</item>
<item>0x1f4b5</item>
<item>0x1f4b4</item>
<item>0x1f4b6</item>
<item>0x1f4b7</item>
<item>0x1f4b8</item>
<item>0x1f4b1</item>
<item>0x1f4b9</item>
<item>0x1f52b</item>
<item>0x1f52a</item>
<item>0x1f4a3</item>
<item>0x1f489</item>
<item>0x1f48a</item>
<item>0x1f6ac</item>
<item>0x1f514</item>
<item>0x1f515</item>
<item>0x1f6aa</item>
<item>0x1f52c</item>
<item>0x1f52d</item>
<item>0x1f52e</item>
<item>0x1f526</item>
<item>0x1f50b</item>
<item>0x1f50c</item>
<item>0x1f4dc</item>
<item>0x1f4d7</item>
<item>0x1f4d8</item>
<item>0x1f4d9</item>
<item>0x1f4da</item>
<item>0x1f4d4</item>
<item>0x1f4d2</item>
<item>0x1f4d1</item>
<item>0x1f4d3</item>
<item>0x1f4d5</item>
<item>0x1f4d6</item>
<item>0x1f4f0</item>
<item>0x1f4db</item>
<item>0x1f383</item>
<item>0x1f384</item>
<item>0x1f380</item>
<item>0x1f381</item>
<item>0x1f382</item>
<item>0x1f388</item>
<item>0x1f386</item>
<item>0x1f387</item>
<item>0x1f389</item>
<item>0x1f38a</item>
<item>0x1f38d</item>
<item>0x1f38f</item>
<item>0x1f38c</item>
<item>0x1f390</item>
<item>0x1f38b</item>
<item>0x1f38e</item>
<item>0x1f4f1</item>
<item>0x1f4f2</item>
<item>0x1f4df</item>
<item>0x260e</item>
<item>0x1f4de</item>
<item>0x1f4e0</item>
<item>0x1f4e6</item>
<item>0x2709</item>
<item>0x1f4e8</item>
<item>0x1f4e9</item>
<item>0x1f4ea</item>
<item>0x1f4eb</item>
<item>0x1f4ed</item>
<item>0x1f4ec</item>
<item>0x1f4ee</item>
<item>0x1f4e4</item>
<item>0x1f4e5</item>
<item>0x1f4ef</item>
<item>0x1f4e2</item>
<item>0x1f4e3</item>
<item>0x1f4e1</item>
<item>0x1f4ac</item>
<item>0x1f4ad</item>
<item>0x2712</item>
<item>0x270f</item>
<item>0x1f4dd</item>
<item>0x1f4cf</item>
<item>0x1f4d0</item>
<item>0x1f4cd</item>
<item>0x1f4cc</item>
<item>0x1f4ce</item>
<item>0x2702</item>
<item>0x1f4ba</item>
<item>0x1f4bb</item>
<item>0x1f4bd</item>
<item>0x1f4be</item>
<item>0x1f4bf</item>
<item>0x1f4c6</item>
<item>0x1f4c5</item>
<item>0x1f4c7</item>
<item>0x1f4cb</item>
<item>0x1f4c1</item>
<item>0x1f4c2</item>
<item>0x1f4c3</item>
<item>0x1f4c4</item>
<item>0x1f4ca</item>
<item>0x1f4c8</item>
<item>0x1f4c9</item>
<item>0x26fa</item>
<item>0x1f3a1</item>
<item>0x1f3a2</item>
<item>0x1f3a0</item>
<item>0x1f3aa</item>
<item>0x1f3a8</item>
<item>0x1f3ac</item>
<item>0x1f3a5</item>
<item>0x1f4f7</item>
<item>0x1f4f9</item>
<item>0x1f3a6</item>
<item>0x1f3ad</item>
<item>0x1f3ab</item>
<item>0x1f3ae</item>
<item>0x1f3b2</item>
<item>0x1f3b0</item>
<item>0x1f0cf</item>
<item>0x1f3b4</item>
<item>0x1f004</item>
<item>0x1f3af</item>
<item>0x1f4fa</item>
<item>0x1f4fb</item>
<item>0x1f4c0</item>
<item>0x1f4fc</item>
<item>0x1f3a7</item>
<item>0x1f3a4</item>
<item>0x1f3b5</item>
<item>0x1f3b6</item>
<item>0x1f3bc</item>
<item>0x1f3bb</item>
<item>0x1f3b9</item>
<item>0x1f3b7</item>
<item>0x1f3ba</item>
<item>0x1f3b8</item>
<item>0x303d</item>
</integer-array>
<integer-array name="emoji_bell">
<item>0x1f415</item>
<item>0x1f436</item>
<item>0x1f429</item>
<item>0x1f408</item>
<item>0x1f431</item>
<item>0x1f400</item>
<item>0x1f401</item>
<item>0x1f42d</item>
<item>0x1f439</item>
<item>0x1f422</item>
<item>0x1f407</item>
<item>0x1f430</item>
<item>0x1f413</item>
<item>0x1f414</item>
<item>0x1f423</item>
<item>0x1f424</item>
<item>0x1f425</item>
<item>0x1f426</item>
<item>0x1f40f</item>
<item>0x1f411</item>
<item>0x1f410</item>
<item>0x1f43a</item>
<item>0x1f403</item>
<item>0x1f402</item>
<item>0x1f404</item>
<item>0x1f42e</item>
<item>0x1f434</item>
<item>0x1f417</item>
<item>0x1f416</item>
<item>0x1f437</item>
<item>0x1f43d</item>
<item>0x1f438</item>
<item>0x1f40d</item>
<item>0x1f43c</item>
<item>0x1f427</item>
<item>0x1f418</item>
<item>0x1f428</item>
<item>0x1f412</item>
<item>0x1f435</item>
<item>0x1f406</item>
<item>0x1f42f</item>
<item>0x1f43b</item>
<item>0x1f42b</item>
<item>0x1f42a</item>
<item>0x1f40a</item>
<item>0x1f433</item>
<item>0x1f40b</item>
<item>0x1f41f</item>
<item>0x1f420</item>
<item>0x1f421</item>
<item>0x1f419</item>
<item>0x1f41a</item>
<item>0x1f42c</item>
<item>0x1f40c</item>
<item>0x1f41b</item>
<item>0x1f41c</item>
<item>0x1f41d</item>
<item>0x1f41e</item>
<item>0x1f432</item>
<item>0x1f409</item>
<item>0x1f43e</item>
<item>0x1f378</item>
<item>0x1f37a</item>
<item>0x1f37b</item>
<item>0x1f377</item>
<item>0x1f379</item>
<item>0x1f376</item>
<item>0x2615</item>
<item>0x1f375</item>
<item>0x1f37c</item>
<item>0x1f374</item>
<item>0x1f368</item>
<item>0x1f367</item>
<item>0x1f366</item>
<item>0x1f369</item>
<item>0x1f370</item>
<item>0x1f36a</item>
<item>0x1f36b</item>
<item>0x1f36c</item>
<item>0x1f36d</item>
<item>0x1f36e</item>
<item>0x1f36f</item>
<item>0x1f373</item>
<item>0x1f354</item>
<item>0x1f35f</item>
<item>0x1f35d</item>
<item>0x1f355</item>
<item>0x1f356</item>
<item>0x1f357</item>
<item>0x1f364</item>
<item>0x1f363</item>
<item>0x1f371</item>
<item>0x1f35e</item>
<item>0x1f35c</item>
<item>0x1f359</item>
<item>0x1f35a</item>
<item>0x1f35b</item>
<item>0x1f372</item>
<item>0x1f365</item>
<item>0x1f362</item>
<item>0x1f361</item>
<item>0x1f358</item>
<item>0x1f360</item>
<item>0x1f34c</item>
<item>0x1f34e</item>
<item>0x1f34f</item>
<item>0x1f34a</item>
<item>0x1f34b</item>
<item>0x1f344</item>
<item>0x1f345</item>
<item>0x1f346</item>
<item>0x1f347</item>
<item>0x1f348</item>
<item>0x1f349</item>
<item>0x1f350</item>
<item>0x1f351</item>
<item>0x1f352</item>
<item>0x1f353</item>
<item>0x1f34d</item>
<item>0x1f330</item>
<item>0x1f331</item>
<item>0x1f332</item>
<item>0x1f333</item>
<item>0x1f334</item>
<item>0x1f335</item>
<item>0x1f337</item>
<item>0x1f338</item>
<item>0x1f339</item>
<item>0x1f340</item>
<item>0x1f341</item>
<item>0x1f342</item>
<item>0x1f343</item>
<item>0x1f33a</item>
<item>0x1f33b</item>
<item>0x1f33c</item>
<item>0x1f33d</item>
<item>0x1f33e</item>
<item>0x1f33f</item>
<item>0x2600</item>
<item>0x1f308</item>
<item>0x26c5</item>
<item>0x2601</item>
<item>0x1f301</item>
<item>0x1f302</item>
<item>0x2614</item>
<item>0x1f4a7</item>
<item>0x26a1</item>
<item>0x1f300</item>
<item>0x2744</item>
<item>0x26c4</item>
<item>0x1f319</item>
<item>0x1f31e</item>
<item>0x1f31d</item>
<item>0x1f31a</item>
<item>0x1f31b</item>
<item>0x1f31c</item>
<item>0x1f311</item>
<item>0x1f312</item>
<item>0x1f313</item>
<item>0x1f314</item>
<item>0x1f315</item>
<item>0x1f316</item>
<item>0x1f317</item>
<item>0x1f318</item>
<item>0x1f391</item>
<item>0x1f304</item>
<item>0x1f305</item>
<item>0x1f307</item>
<item>0x1f306</item>
<item>0x1f303</item>
<item>0x1f30c</item>
<item>0x1f309</item>
<item>0x1f30a</item>
<item>0x1f30b</item>
<item>0x1f30e</item>
<item>0x1f30f</item>
<item>0x1f30d</item>
<item>0x1f310</item>
</integer-array>
<integer-array name="emoji_car">
<item>0x1f3e0</item>
<item>0x1f3e1</item>
<item>0x1f3e2</item>
<item>0x1f3e3</item>
<item>0x1f3e4</item>
<item>0x1f3e5</item>
<item>0x1f3e6</item>
<item>0x1f3e7</item>
<item>0x1f3e8</item>
<item>0x1f3e9</item>
<item>0x1f3ea</item>
<item>0x1f3eb</item>
<item>0x26ea</item>
<item>0x26f2</item>
<item>0x1f3ec</item>
<item>0x1f3ef</item>
<item>0x1f3f0</item>
<item>0x1f3ed</item>
<item>0x1f5fb</item>
<item>0x1f5fc</item>
<item>0x1f5fd</item>
<item>0x1f5fe</item>
<item>0x1f5ff</item>
<item>0x2693</item>
<item>0x1f3ee</item>
<item>0x1f488</item>
<item>0x1f527</item>
<item>0x1f528</item>
<item>0x1f529</item>
<item>0x1f6bf</item>
<item>0x1f6c1</item>
<item>0x1f6c0</item>
<item>0x1f6bd</item>
<item>0x1f6be</item>
<item>0x1f3bd</item>
<item>0x1f3a3</item>
<item>0x1f3b1</item>
<item>0x1f3b3</item>
<item>0x26be</item>
<item>0x26f3</item>
<item>0x1f3be</item>
<item>0x26bd</item>
<item>0x1f3bf</item>
<item>0x1f3c0</item>
<item>0x1f3c1</item>
<item>0x1f3c2</item>
<item>0x1f3c3</item>
<item>0x1f3c4</item>
<item>0x1f3c6</item>
<item>0x1f3c7</item>
<item>0x1f40e</item>
<item>0x1f3c8</item>
<item>0x1f3c9</item>
<item>0x1f3ca</item>
<item>0x1f682</item>
<item>0x1f683</item>
<item>0x1f684</item>
<item>0x1f685</item>
<item>0x1f686</item>
<item>0x1f687</item>
<item>0x24c2</item>
<item>0x1f688</item>
<item>0x1f68a</item>
<item>0x1f68b</item>
<item>0x1f68c</item>
<item>0x1f68d</item>
<item>0x1f68e</item>
<item>0x1f68f</item>
<item>0x1f690</item>
<item>0x1f691</item>
<item>0x1f692</item>
<item>0x1f693</item>
<item>0x1f694</item>
<item>0x1f695</item>
<item>0x1f696</item>
<item>0x1f697</item>
<item>0x1f698</item>
<item>0x1f699</item>
<item>0x1f69a</item>
<item>0x1f69b</item>
<item>0x1f69c</item>
<item>0x1f69d</item>
<item>0x1f69e</item>
<item>0x1f69f</item>
<item>0x1f6a0</item>
<item>0x1f6a1</item>
<item>0x1f6a2</item>
<item>0x1f6a3</item>
<item>0x1f681</item>
<item>0x2708</item>
<item>0x1f6c2</item>
<item>0x1f6c3</item>
<item>0x1f6c4</item>
<item>0x1f6c5</item>
<item>0x26f5</item>
<item>0x1f6b2</item>
<item>0x1f6b3</item>
<item>0x1f6b4</item>
<item>0x1f6b5</item>
<item>0x1f6b7</item>
<item>0x1f6b8</item>
<item>0x1f689</item>
<item>0x1f680</item>
<item>0x1f6a4</item>
<item>0x1f6b6</item>
<item>0x26fd</item>
<item>0x1f17f</item>
<item>0x1f6a5</item>
<item>0x1f6a6</item>
<item>0x1f6a7</item>
<item>0x1f6a8</item>
<item>0x2668</item>
<item>0x1f48c</item>
<item>0x1f48d</item>
<item>0x1f48e</item>
<item>0x1f490</item>
<item>0x1f492</item>
<item>0xfe4e5</item>
<item>0xfe4e6</item>
<item>0xfe4e7</item>
<item>0xfe4e8</item>
<item>0xfe4e9</item>
<item>0xfe4ea</item>
<item>0xfe4eb</item>
<item>0xfe4ec</item>
<item>0xfe4ed</item>
<item>0xfe4ee</item>
</integer-array>
<integer-array name="emoji_symbol">
<item>0x1f51d</item>
<item>0x1f519</item>
<item>0x1f51b</item>
<item>0x1f51c</item>
<item>0x1f51a</item>
<item>0x23f3</item>
<item>0x231b</item>
<item>0x23f0</item>
<item>0x2648</item>
<item>0x2649</item>
<item>0x264a</item>
<item>0x264b</item>
<item>0x264c</item>
<item>0x264d</item>
<item>0x264e</item>
<item>0x264f</item>
<item>0x2650</item>
<item>0x2651</item>
<item>0x2652</item>
<item>0x2653</item>
<item>0x26ce</item>
<item>0x1f531</item>
<item>0x1f52f</item>
<item>0x1f6bb</item>
<item>0x1f6ae</item>
<item>0x1f6af</item>
<item>0x1f6b0</item>
<item>0x1f6b1</item>
<item>0x1f170</item>
<item>0x1f171</item>
<item>0x1f18e</item>
<item>0x1f17e</item>
<item>0x1f4ae</item>
<item>0x1f4af</item>
<item>0x1f520</item>
<item>0x1f521</item>
<item>0x1f522</item>
<item>0x1f523</item>
<item>0x1f524</item>
<item>0x27bf</item>
<item>0x1f4f6</item>
<item>0x1f4f3</item>
<item>0x1f4f4</item>
<item>0x1f4f5</item>
<item>0x1f6b9</item>
<item>0x1f6ba</item>
<item>0x1f6bc</item>
<item>0x267f</item>
<item>0x267b</item>
<item>0x1f6ad</item>
<item>0x1f6a9</item>
<item>0x26a0</item>
<item>0x1f201</item>
<item>0x1f51e</item>
<item>0x26d4</item>
<item>0x1f192</item>
<item>0x1f197</item>
<item>0x1f195</item>
<item>0x1f198</item>
<item>0x1f199</item>
<item>0x1f193</item>
<item>0x1f196</item>
<item>0x1f19a</item>
<item>0x1f232</item>
<item>0x1f233</item>
<item>0x1f234</item>
<item>0x1f235</item>
<item>0x1f236</item>
<item>0x1f237</item>
<item>0x1f238</item>
<item>0x1f239</item>
<item>0x1f202</item>
<item>0x1f23a</item>
<item>0x1f250</item>
<item>0x1f251</item>
<item>0x3299</item>
<item>0x00ae</item>
<item>0x00a9</item>
<item>0x2122</item>
<item>0x1f21a</item>
<item>0x1f22f</item>
<item>0x3297</item>
<item>0x2b55</item>
<item>0x274c</item>
<item>0x274e</item>
<item>0x2139</item>
<item>0x1f6ab</item>
<item>0x2705</item>
<item>0x2714</item>
<item>0x1f517</item>
<item>0x2734</item>
<item>0x2733</item>
<item>0x2795</item>
<item>0x2796</item>
<item>0x2716</item>
<item>0x2797</item>
<item>0x1f4a0</item>
<item>0x1f4a1</item>
<item>0x1f4a4</item>
<item>0x1f4a2</item>
<item>0x1f525</item>
<item>0x1f4a5</item>
<item>0x1f4a8</item>
<item>0x1f4a6</item>
<item>0x1f4ab</item>
<item>0x1f55b</item>
<item>0x1f567</item>
<item>0x1f550</item>
<item>0x1f55c</item>
<item>0x1f551</item>
<item>0x1f55d</item>
<item>0x1f552</item>
<item>0x1f55e</item>
<item>0x1f553</item>
<item>0x1f55f</item>
<item>0x1f554</item>
<item>0x1f560</item>
<item>0x1f555</item>
<item>0x1f561</item>
<item>0x1f556</item>
<item>0x1f562</item>
<item>0x1f557</item>
<item>0x1f563</item>
<item>0x1f558</item>
<item>0x1f564</item>
<item>0x1f559</item>
<item>0x1f565</item>
<item>0x1f55a</item>
<item>0x1f566</item>
<item>0x2195</item>
<item>0x2b06</item>
<item>0x2197</item>
<item>0x27a1</item>
<item>0x2198</item>
<item>0x2b07</item>
<item>0x2199</item>
<item>0x2b05</item>
<item>0x2196</item>
<item>0x2194</item>
<item>0x2934</item>
<item>0x2935</item>
<item>0x23ea</item>
<item>0x23eb</item>
<item>0x23ec</item>
<item>0x23e9</item>
<item>0x25c0</item>
<item>0x25b6</item>
<item>0x1f53d</item>
<item>0x1f53c</item>
<item>0x2747</item>
<item>0x2728</item>
<item>0x1f534</item>
<item>0x1f535</item>
<item>0x26aa</item>
<item>0x26ab</item>
<item>0x1f533</item>
<item>0x1f532</item>
<item>0x2b50</item>
<item>0x1f31f</item>
<item>0x1f320</item>
<item>0x25ab</item>
<item>0x25aa</item>
<item>0x25fd</item>
<item>0x25fe</item>
<item>0x25fb</item>
<item>0x25fc</item>
<item>0x2b1c</item>
<item>0x2b1b</item>
<item>0x1f538</item>
<item>0x1f539</item>
<item>0x1f536</item>
<item>0x1f537</item>
<item>0x1f53a</item>
<item>0x1f53b</item>
<item>0x1f51f</item>
<!--<item>0x20e3</item>-->
<item>0x2754</item>
<item>0x2753</item>
<item>0x2755</item>
<item>0x2757</item>
<item>0x203c</item>
<item>0x2049</item>
<item>0x3030</item>
<item>0x27b0</item>
<item>0x2660</item>
<item>0x2665</item>
<item>0x2663</item>
<item>0x2666</item>
<item>0x1f194</item>
<item>0x1f511</item>
<item>0x21a9</item>
<item>0x1f191</item>
<item>0x1f50d</item>
<item>0x1f512</item>
<item>0x1f513</item>
<item>0x21aa</item>
<item>0x1f510</item>
<item>0x2611</item>
<item>0x1f518</item>
<item>0x1f50e</item>
<item>0x1f516</item>
<item>0x1f50f</item>
<item>0x1f503</item>
<item>0x1f500</item>
<item>0x1f501</item>
<item>0x1f502</item>
<item>0x1f504</item>
<item>0x1f4e7</item>
<item>0x1f505</item>
<item>0x1f506</item>
<item>0x1f507</item>
<item>0x1f508</item>
<item>0x1f509</item>
<item>0x1f50a</item>
</integer-array>
<integer-array name="emoji_categories">
<item>@array/emoji_smile</item>
<item>@array/emoji_flower</item>
<item>@array/emoji_bell</item>
<item>@array/emoji_car</item>
<item>@array/emoji_symbol</item>
</integer-array>
<integer-array name="emoji_category_icons">
<item>@drawable/emoji_category_smile</item>
<item>@drawable/emoji_category_flower</item>
<item>@drawable/emoji_category_bell</item>
<item>@drawable/emoji_category_car</item>
<item>@drawable/emoji_category_symbol</item>
</integer-array>
</resources>

View File

@ -42,7 +42,6 @@ import android.view.View;
import android.view.View.OnClickListener;
import android.view.View.OnFocusChangeListener;
import android.view.View.OnKeyListener;
import android.view.ViewStub;
import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.InputMethodManager;
import android.widget.TextView;
@ -53,8 +52,9 @@ import com.google.protobuf.ByteString;
import org.thoughtcrime.securesms.TransportOptions.OnTransportChangedListener;
import org.thoughtcrime.securesms.components.ComposeText;
import org.thoughtcrime.securesms.components.EmojiDrawer;
import org.thoughtcrime.securesms.components.EmojiToggle;
import org.thoughtcrime.securesms.components.emoji.EmojiDrawer;
import org.thoughtcrime.securesms.components.emoji.EmojiProvider;
import org.thoughtcrime.securesms.components.emoji.EmojiToggle;
import org.thoughtcrime.securesms.components.SendButton;
import org.thoughtcrime.securesms.contacts.ContactAccessor;
import org.thoughtcrime.securesms.contacts.ContactAccessor.ContactData;
@ -93,7 +93,6 @@ import org.thoughtcrime.securesms.util.Dialogs;
import org.thoughtcrime.securesms.util.DirectoryHelper;
import org.thoughtcrime.securesms.util.DynamicLanguage;
import org.thoughtcrime.securesms.util.DynamicTheme;
import org.thoughtcrime.securesms.util.Emoji;
import org.thoughtcrime.securesms.util.GroupUtil;
import org.thoughtcrime.securesms.util.TextSecurePreferences;
import org.thoughtcrime.securesms.util.Util;
@ -323,8 +322,8 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
@Override
public void onBackPressed() {
if (emojiDrawer.isPresent() && emojiDrawer.get().getVisibility() == View.VISIBLE) {
emojiDrawer.get().setVisibility(View.GONE);
if (isEmojiDrawerOpen()) {
getEmojiDrawer().hide();
emojiToggle.toggle();
} else {
super.onBackPressed();
@ -621,15 +620,8 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
@Override
protected void onPostExecute(List<Draft> drafts) {
boolean nativeEmojiSupported = Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT;
Context context = ConversationActivity.this;
for (Draft draft : drafts) {
if (draft.getType().equals(Draft.TEXT) && !nativeEmojiSupported) {
composeText.setText(Emoji.getInstance(context).emojify(draft.getValue(),
new Emoji.InvalidatingPageLoadedListener(composeText)),
TextView.BufferType.SPANNABLE);
} else if (draft.getType().equals(Draft.TEXT)) {
if (draft.getType().equals(Draft.TEXT)) {
composeText.setText(draft.getValue());
} else if (draft.getType().equals(Draft.IMAGE)) {
addAttachmentImage(Uri.parse(draft.getValue()));
@ -713,10 +705,21 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
emojiToggle.setOnClickListener(new EmojiToggleListener());
}
private EmojiDrawer initializeEmojiDrawer() {
EmojiDrawer emojiDrawer = (EmojiDrawer)((ViewStub)findViewById(R.id.emoji_drawer_stub)).inflate();
emojiDrawer.setComposeEditText(composeText);
return emojiDrawer;
private EmojiDrawer getEmojiDrawer() {
if (emojiDrawer.isPresent()) return emojiDrawer.get();
EmojiDrawer emojiDrawerFragment = EmojiDrawer.newInstance();
emojiDrawerFragment.setComposeEditText(composeText);
getSupportFragmentManager().beginTransaction()
.replace(R.id.emoji_drawer, emojiDrawerFragment)
.commit();
getSupportFragmentManager().executePendingTransactions();
emojiDrawer = Optional.of(emojiDrawerFragment);
return emojiDrawerFragment;
}
private boolean isEmojiDrawerOpen() {
return emojiDrawer.isPresent() && emojiDrawer.get().isOpen();
}
private void initializeResources() {
@ -1094,16 +1097,13 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
public void onClick(View v) {
InputMethodManager input = (InputMethodManager)getSystemService(Context.INPUT_METHOD_SERVICE);
if (emojiDrawer.isPresent() && emojiDrawer.get().isOpen()) {
if (isEmojiDrawerOpen()) {
input.showSoftInput(composeText, 0);
emojiDrawer.get().hide();
getEmojiDrawer().hide();
} else {
if (!emojiDrawer.isPresent()) {
emojiDrawer = Optional.of(initializeEmojiDrawer());
}
input.hideSoftInputFromWindow(composeText.getWindowToken(), 0);
emojiDrawer.get().show();
getEmojiDrawer().show();
}
}
}
@ -1141,7 +1141,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
@Override
public void onClick(View v) {
if (emojiDrawer.isPresent() && emojiDrawer.get().isOpen()) {
if (isEmojiDrawerOpen()) {
emojiToggle.performClick();
}
}
@ -1157,7 +1157,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
@Override
public void onFocusChange(View v, boolean hasFocus) {
if (hasFocus && emojiDrawer.isPresent() && emojiDrawer.get().isOpen()) {
if (hasFocus && isEmojiDrawerOpen()) {
emojiToggle.performClick();
}
}

View File

@ -53,7 +53,6 @@ import org.thoughtcrime.securesms.mms.PartAuthority;
import org.thoughtcrime.securesms.mms.Slide;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.util.DateUtils;
import org.thoughtcrime.securesms.util.Emoji;
import java.util.Locale;
import java.util.Set;
@ -239,9 +238,7 @@ public class ConversationItem extends LinearLayout {
if (isCaptionlessMms(messageRecord)) {
bodyText.setVisibility(View.GONE);
} else {
bodyText.setText(Emoji.getInstance(context).emojify(messageRecord.getDisplayBody(),
new Emoji.InvalidatingPageLoadedListener(bodyText)),
TextView.BufferType.SPANNABLE);
bodyText.setText(messageRecord.getDisplayBody());
bodyText.setVisibility(View.VISIBLE);
}

View File

@ -31,7 +31,6 @@ import org.thoughtcrime.securesms.database.model.ThreadRecord;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.recipients.Recipients;
import org.thoughtcrime.securesms.util.DateUtils;
import org.thoughtcrime.securesms.util.Emoji;
import java.util.Locale;
import java.util.Set;
@ -96,10 +95,7 @@ public class ConversationListItem extends RelativeLayout
this.recipients.addListener(this);
this.fromView.setText(recipients, read);
this.subjectView.setText(Emoji.getInstance(context).emojify(thread.getDisplayBody(),
Emoji.EMOJI_SMALL,
new Emoji.InvalidatingPageLoadedListener(subjectView)),
TextView.BufferType.SPANNABLE);
this.subjectView.setText(thread.getDisplayBody());
this.subjectView.setTypeface(read ? LIGHT_TYPEFACE : BOLD_TYPEFACE);
if (thread.getDate() > 0) {

View File

@ -9,7 +9,7 @@ import android.text.TextUtils;
import android.text.style.RelativeSizeSpan;
import android.util.AttributeSet;
public class ComposeText extends AppCompatEditText {
public class ComposeText extends EmojiEditText {
public ComposeText(Context context) {
super(context);
}

View File

@ -1,273 +0,0 @@
package org.thoughtcrime.securesms.components;
import android.annotation.TargetApi;
import android.content.Context;
import android.graphics.drawable.Drawable;
import android.os.Build.VERSION_CODES;
import android.support.v4.view.PagerAdapter;
import android.support.v4.view.ViewPager;
import android.util.AttributeSet;
import android.util.Log;
import android.util.Pair;
import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AbsListView;
import android.widget.AdapterView;
import android.widget.BaseAdapter;
import android.widget.EditText;
import android.widget.FrameLayout;
import android.widget.GridView;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.RelativeLayout;
import com.astuetz.PagerSlidingTabStrip;
import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.components.RepeatableImageKey.KeyEventListener;
import org.thoughtcrime.securesms.util.Emoji;
public class EmojiDrawer extends KeyboardAwareLinearLayout {
private static final KeyEvent DELETE_KEY_EVENT = new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_DEL);
private static final int RECENT_TYPE = 0;
private static final int ALL_TYPE = 1;
private FrameLayout[] gridLayouts = new FrameLayout[Emoji.PAGES.length+1];
private EditText composeText;
private Emoji emoji;
private ViewPager pager;
private PagerSlidingTabStrip strip;
@SuppressWarnings("unused")
public EmojiDrawer(Context context) {
super(context);
initialize();
}
@SuppressWarnings("unused")
public EmojiDrawer(Context context, AttributeSet attrs) {
super(context, attrs);
initialize();
}
@SuppressWarnings("unused")
@TargetApi(VERSION_CODES.HONEYCOMB)
public EmojiDrawer(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
initialize();
}
public void setComposeEditText(EditText composeText) {
this.composeText = composeText;
}
public boolean isOpen() {
return getVisibility() == View.VISIBLE;
}
private void initialize() {
LayoutInflater inflater = (LayoutInflater)getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
inflater.inflate(R.layout.emoji_drawer, this, true);
initializeResources();
if (!this.isInEditMode()) {
initializeEmojiGrid();
}
}
private void initializeResources() {
this.pager = (ViewPager) findViewById(R.id.emoji_pager);
this.strip = (PagerSlidingTabStrip) findViewById(R.id.tabs);
this.emoji = Emoji.getInstance(getContext());
RepeatableImageKey backspace = (RepeatableImageKey)findViewById(R.id.backspace);
backspace.setOnKeyEventListener(new KeyEventListener() {
@Override public void onKeyEvent() {
if (composeText != null && composeText.getText().length() > 0) {
composeText.dispatchKeyEvent(DELETE_KEY_EVENT);
}
}
});
}
public void hide() {
setVisibility(View.GONE);
}
public void show() {
int keyboardHeight = getKeyboardHeight();
Log.w("EmojiDrawer", "setting emoji drawer to height " + keyboardHeight);
setLayoutParams(new LinearLayout.LayoutParams(RelativeLayout.LayoutParams.MATCH_PARENT, keyboardHeight));
requestLayout();
setVisibility(View.VISIBLE);
}
private void initializeEmojiGrid() {
LayoutInflater inflater = LayoutInflater.from(getContext());
for (int i = 0; i < gridLayouts.length; i++) {
gridLayouts[i] = (FrameLayout) inflater.inflate(R.layout.emoji_grid_layout, pager, false);
final GridView gridView = (GridView) gridLayouts[i].findViewById(R.id.emoji);
gridLayouts[i].setTag(gridView);
final int type = (i == 0 ? RECENT_TYPE : ALL_TYPE);
gridView.setColumnWidth(getResources().getDimensionPixelSize(R.dimen.emoji_drawer_size) + 2*getResources().getDimensionPixelSize(R.dimen.emoji_drawer_item_padding));
gridView.setAdapter(new EmojiGridAdapter(type, i-1));
gridView.setOnItemClickListener(new EmojiClickListener(ALL_TYPE));
}
pager.setAdapter(new EmojiPagerAdapter());
if (emoji.getRecentlyUsedAssetCount() <= 0) {
pager.setCurrentItem(1);
}
strip.setTabPaddingLeftRight(getResources().getDimensionPixelSize(R.dimen.emoji_drawer_left_right_padding));
strip.setAllCaps(false);
strip.setShouldExpand(true);
strip.setUnderlineColorResource(R.color.emoji_tab_underline);
strip.setIndicatorColorResource(R.color.emoji_tab_indicator);
strip.setIndicatorHeight(getResources().getDimensionPixelSize(R.dimen.emoji_drawer_indicator_height));
strip.setViewPager(pager);
}
private class EmojiClickListener implements AdapterView.OnItemClickListener {
private final int type;
public EmojiClickListener(int type) {
this.type = type;
}
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
Integer unicodePoint = (Integer) view.getTag();
insertEmoji(composeText, unicodePoint);
if (type != RECENT_TYPE) {
emoji.setRecentlyUsed(Integer.toHexString(unicodePoint));
((BaseAdapter)((GridView)gridLayouts[0].getTag()).getAdapter()).notifyDataSetChanged();
}
}
private void insertEmoji(EditText editText, Integer unicodePoint) {
final char[] chars = Character.toChars(unicodePoint);
String characters = new String(chars);
int start = editText.getSelectionStart();
int end = editText.getSelectionEnd();
CharSequence text = emoji.emojify(characters, new Emoji.InvalidatingPageLoadedListener(composeText));
editText.getText().replace(Math.min(start, end), Math.max(start, end), text, 0, text.length());
editText.setSelection(end+chars.length);
}
}
private class EmojiGridAdapter extends BaseAdapter {
private final int type;
private final int page;
private final int emojiSize;
public EmojiGridAdapter(int type, int page) {
this.type = type;
this.page = page;
emojiSize = (int) getResources().getDimension(R.dimen.emoji_drawer_size);
}
@Override
public int getCount() {
if (type == RECENT_TYPE) return emoji.getRecentlyUsedAssetCount();
else return Emoji.PAGES[page].length;
}
@Override
public Object getItem(int position) {
return null;
}
@Override
public long getItemId(int position) {
return position;
}
@Override
public View getView(final int position, final View convertView, final ViewGroup parent) {
final ImageView view;
final int pad = getResources().getDimensionPixelSize(R.dimen.emoji_drawer_item_padding);
if (convertView != null && convertView instanceof ImageView) {
view = (ImageView) convertView;
} else {
ImageView imageView = new ImageView(getContext());
imageView.setPadding(pad, pad, pad, pad);
imageView.setLayoutParams(new AbsListView.LayoutParams(emojiSize + 2*pad, emojiSize + 2*pad));
view = imageView;
}
final Drawable drawable;
final Integer unicodeTag;
if (type == ALL_TYPE) {
unicodeTag = Emoji.PAGES[page][position];
drawable = emoji.getEmojiDrawable(new Emoji.DrawInfo(page, position),
Emoji.EMOJI_HUGE,
new Emoji.InvalidatingPageLoadedListener(view));
} else {
Pair<Integer, Drawable> recentlyUsed = emoji.getRecentlyUsed(position,
Emoji.EMOJI_HUGE,
new Emoji.InvalidatingPageLoadedListener(view));
unicodeTag = recentlyUsed.first;
drawable = recentlyUsed.second;
}
view.setImageDrawable(drawable);
view.setPadding(pad, pad, pad, pad);
view.setTag(unicodeTag);
return view;
}
}
private class EmojiPagerAdapter extends PagerAdapter implements PagerSlidingTabStrip.IconTabProvider {
@Override
public int getCount() {
return gridLayouts.length;
}
@Override
public boolean isViewFromObject(View view, Object o) {
return view == o;
}
public Object instantiateItem(ViewGroup container, int position) {
if (position < 0 || position >= gridLayouts.length)
throw new AssertionError("position out of range!");
container.addView(gridLayouts[position], 0);
return gridLayouts[position];
}
@Override
public void destroyItem(ViewGroup container, int position, Object object) {
Log.w("EmojiDrawer", "destroying item at " + position);
container.removeView(gridLayouts[position]);
}
@Override
public int getPageIconResId(int i) {
switch (i) {
case 0: return R.drawable.emoji_category_recent;
case 1: return R.drawable.emoji_category_smile;
case 2: return R.drawable.emoji_category_flower;
case 3: return R.drawable.emoji_category_bell;
case 4: return R.drawable.emoji_category_car;
case 5: return R.drawable.emoji_category_symbol;
default: return 0;
}
}
}
}

View File

@ -0,0 +1,156 @@
package org.thoughtcrime.securesms.components.emoji;
import android.content.Context;
import android.os.Bundle;
import android.support.annotation.ArrayRes;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentManager;
import android.support.v4.app.FragmentStatePagerAdapter;
import android.support.v4.view.ViewPager;
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.LinearLayout;
import android.widget.RelativeLayout;
import com.astuetz.PagerSlidingTabStrip;
import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.components.KeyboardAwareLinearLayout;
import org.thoughtcrime.securesms.components.RepeatableImageKey;
import org.thoughtcrime.securesms.components.RepeatableImageKey.KeyEventListener;
import org.thoughtcrime.securesms.components.emoji.EmojiPageFragment.EmojiSelectionListener;
import org.thoughtcrime.securesms.util.ResUtil;
import java.util.LinkedList;
import java.util.List;
public class EmojiDrawer extends Fragment {
private static final KeyEvent DELETE_KEY_EVENT = new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_DEL);
private EmojiEditText composeText;
private KeyboardAwareLinearLayout container;
private ViewPager pager;
private PagerSlidingTabStrip strip;
public static EmojiDrawer newInstance(@ArrayRes int categories, @ArrayRes int icons) {
final EmojiDrawer fragment = new EmojiDrawer();
final Bundle args = new Bundle();
args.putInt("categories", categories);
args.putInt("icons", icons);
fragment.setArguments(args);
return fragment;
}
public static EmojiDrawer newInstance() {
return newInstance(R.array.emoji_categories, R.array.emoji_category_icons);
}
public void setComposeEditText(EmojiEditText composeText) {
this.composeText = composeText;
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
final View v = inflater.inflate(R.layout.emoji_drawer, container, false);
initializeResources(v);
initializeEmojiGrid();
return v;
}
private void initializeResources(View v) {
Log.w("EmojiDrawer", "initializeResources()");
this.container = (KeyboardAwareLinearLayout) v.findViewById(R.id.container);
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 (composeText != null && composeText.getText().length() > 0) {
composeText.dispatchKeyEvent(DELETE_KEY_EVENT);
}
}
});
}
public void hide() {
container.setVisibility(View.GONE);
}
public void show() {
int keyboardHeight = container.getKeyboardHeight();
Log.w("EmojiDrawer", "setting emoji drawer to height " + keyboardHeight);
container.setLayoutParams(new LinearLayout.LayoutParams(RelativeLayout.LayoutParams.MATCH_PARENT, keyboardHeight));
container.requestLayout();
container.setVisibility(View.VISIBLE);
}
public boolean isOpen() {
return container.getVisibility() == View.VISIBLE;
}
private void initializeEmojiGrid() {
pager.setAdapter(new EmojiPagerAdapter(getActivity(),
getFragmentManager(),
getPageModels(getArguments().getInt("categories"),
getArguments().getInt("icons")),
new EmojiSelectionListener() {
@Override public void onEmojiSelected(int emojiCode) {
composeText.insertEmoji(emojiCode);
}
}));
strip.setViewPager(pager);
}
private List<EmojiPageModel> getPageModels(@ArrayRes int pagesRes, @ArrayRes int iconsRes) {
final int[] icons = ResUtil.getResourceIds(getActivity(), iconsRes);
final int[] pages = ResUtil.getResourceIds(getActivity(), pagesRes);
final List<EmojiPageModel> models = new LinkedList<>();
models.add(new RecentEmojiPageModel(getActivity()));
for (int i = 0; i < icons.length; i++) {
models.add(new StaticEmojiPageModel(icons[i], getResources().getIntArray(pages[i])));
}
return models;
}
public static class EmojiPagerAdapter extends FragmentStatePagerAdapter
implements PagerSlidingTabStrip.CustomTabProvider
{
private Context context;
private List<EmojiPageModel> pages;
private EmojiSelectionListener listener;
public EmojiPagerAdapter(@NonNull Context context,
@NonNull FragmentManager fm,
@NonNull List<EmojiPageModel> pages,
@Nullable EmojiSelectionListener listener)
{
super(fm);
this.context = context;
this.pages = pages;
this.listener = listener;
}
@Override
public int getCount() {
return pages.size();
}
@Override public Fragment getItem(int i) {
return EmojiPageFragment.newInstance(pages.get(i), listener);
}
@Override public View getCustomTabView(ViewGroup viewGroup, int i) {
ImageView image = new ImageView(context);
image.setImageResource(pages.get(i).getIconRes());
return image;
}
}
}

View File

@ -0,0 +1,38 @@
package org.thoughtcrime.securesms.components.emoji;
import android.content.Context;
import android.support.v7.widget.AppCompatEditText;
import android.util.AttributeSet;
public class EmojiEditText extends AppCompatEditText {
public EmojiEditText(Context context) {
super(context);
init();
}
public EmojiEditText(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
public EmojiEditText(Context context, AttributeSet attrs,
int defStyleAttr)
{
super(context, attrs, defStyleAttr);
init();
}
private void init() {
setTransformationMethod(new EmojiTransformationMethod());
}
public void insertEmoji(int codePoint) {
final char[] chars = Character.toChars(codePoint);
final String text = new String(chars);
final int start = getSelectionStart();
final int end = getSelectionEnd();
getText().replace(Math.min(start, end), Math.max(start, end), text, 0, text.length());
setSelection(end + chars.length);
}
}

View File

@ -0,0 +1,116 @@
package org.thoughtcrime.securesms.components.emoji;
import android.content.Context;
import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.v4.app.Fragment;
import android.view.LayoutInflater;
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.GridView;
import android.widget.ImageView;
import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.components.emoji.EmojiProvider.InvalidatingPageLoadedListener;
public class EmojiPageFragment extends Fragment {
private static final String TAG = EmojiPageFragment.class.getSimpleName();
private EmojiPageModel model;
private EmojiSelectionListener listener;
public static EmojiPageFragment newInstance(@NonNull EmojiPageModel model,
@Nullable EmojiSelectionListener listener)
{
EmojiPageFragment fragment = new EmojiPageFragment();
fragment.setModel(model);
fragment.setEmojiSelectedListener(listener);
return fragment;
}
@Nullable @Override public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState)
{
final View view = inflater.inflate(R.layout.emoji_grid_layout, container, false);
final GridView 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));
grid.setOnItemClickListener(new OnItemClickListener() {
@Override public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
model.onCodePointSelected((Integer)view.getTag());
if (listener != null) listener.onEmojiSelected((Integer)view.getTag());
}
});
grid.setAdapter(new EmojiGridAdapter(getActivity(), model));
return view;
}
public void setModel(EmojiPageModel model) {
this.model = model;
}
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.getCodePoints().length;
}
@Override
public Object getItem(int position) {
return null;
}
@Override
public long getItemId(int position) {
return position;
}
@Override
public View getView(final int position, final View convertView, final ViewGroup parent) {
final ImageView view;
final int pad = context.getResources().getDimensionPixelSize(R.dimen.emoji_drawer_item_padding);
if (convertView != null && convertView instanceof ImageView) {
view = (ImageView)convertView;
} else {
ImageView imageView = new ImageView(context);
imageView.setPadding(pad, pad, pad, pad);
imageView.setLayoutParams(new AbsListView.LayoutParams(emojiSize + 2 * pad, emojiSize + 2 * pad));
view = imageView;
}
final Integer unicodeTag = model.getCodePoints()[position];
final EmojiProvider provider = EmojiProvider.getInstance(context);
final Drawable drawable = provider.getEmojiDrawable(unicodeTag,
EmojiProvider.EMOJI_HUGE,
new InvalidatingPageLoadedListener(view));
view.setImageDrawable(drawable);
view.setPadding(pad, pad, pad, pad);
view.setTag(unicodeTag);
return view;
}
}
public interface EmojiSelectionListener {
void onEmojiSelected(int emojiCode);
}
}

View File

@ -0,0 +1,7 @@
package org.thoughtcrime.securesms.components.emoji;
public interface EmojiPageModel {
int getIconRes();
int[] getCodePoints();
void onCodePointSelected(int codePoint);
}

View File

@ -0,0 +1,257 @@
package org.thoughtcrime.securesms.components.emoji;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.ColorFilter;
import android.graphics.Paint;
import android.graphics.PixelFormat;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.text.Spannable;
import android.text.SpannableStringBuilder;
import android.text.style.ImageSpan;
import android.util.Log;
import android.util.SparseArray;
import android.view.View;
import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.util.BitmapDecodingException;
import org.thoughtcrime.securesms.util.BitmapUtil;
import org.thoughtcrime.securesms.util.ResUtil;
import org.thoughtcrime.securesms.util.Util;
import java.io.IOException;
import java.io.InputStream;
import java.lang.ref.SoftReference;
import java.util.Arrays;
import java.util.concurrent.ExecutorService;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class EmojiProvider {
private static final String TAG = EmojiProvider.class.getSimpleName();
private static final ExecutorService executor = Util.newSingleThreadedLifoExecutor();
private static volatile EmojiProvider instance = null;
private static final SparseArray<SoftReference<Bitmap>> bitmaps = new SparseArray<>();
private final SparseArray<DrawInfo> offsets = new SparseArray<>();
@SuppressWarnings("MalformedRegex")
// 0x20a0-0x32ff 0x1f00-0x1fff 0xfe4e5-0xfe4ee
// |==== misc ====||======== emoticons ========||========= flags ==========|
private static final Pattern EMOJI_RANGE = Pattern.compile("[\\u20a0-\\u32ff\\ud83c\\udc00-\\ud83d\\udeff\\udbb9\\udce5-\\udbb9\\udcee]");
public static final double EMOJI_HUGE = 1.00;
public static final double EMOJI_LARGE = 0.75;
public static final double EMOJI_SMALL = 0.60;
public static final int EMOJI_RAW_SIZE = 128;
public static final int EMOJI_PER_ROW = 16;
private final Context context;
private final int bigDrawSize;
private final int[] pages;
public static EmojiProvider getInstance(Context context) {
if (instance == null) {
synchronized (EmojiProvider.class) {
if (instance == null) {
instance = new EmojiProvider(context);
}
}
}
return instance;
}
private EmojiProvider(Context context) {
this.context = context.getApplicationContext();
this.bigDrawSize = context.getResources().getDimensionPixelSize(R.dimen.emoji_drawer_size);
this.pages = ResUtil.getResourceIds(context, R.array.emoji_categories);
for (int i = 0; i < pages.length; i++) {
final int[] page = context.getResources().getIntArray(pages[i]);
for (int j = 0; j < page.length; j++) {
offsets.put(page[j], new DrawInfo(i, j));
}
}
}
private void preloadPage(final int page, final PageLoadedListener pageLoadListener) {
executor.submit(new Runnable() {
@Override
public void run() {
try {
loadPage(page);
if (pageLoadListener != null) {
Log.w(TAG, "onPageLoaded("+page+")");
pageLoadListener.onPageLoaded();
}
} catch (IOException ioe) {
Log.w(TAG, ioe);
}
}
});
}
private void loadPage(int page) throws IOException {
if (page < 0 || page >= pages.length) {
throw new IndexOutOfBoundsException("can't load page that doesn't exist");
}
if (bitmaps.get(page) != null && bitmaps.get(page).get() != null) return;
try {
final String file = "emoji_" + page + "_wrapped.png";
final InputStream measureStream = context.getAssets().open(file);
final InputStream bitmapStream = context.getAssets().open(file);
final Bitmap bitmap = BitmapUtil.createScaledBitmap(measureStream, bitmapStream, (float) bigDrawSize / (float) EMOJI_RAW_SIZE);
bitmaps.put(page, new SoftReference<>(bitmap));
} catch (IOException ioe) {
Log.w(TAG, ioe);
throw ioe;
} catch (BitmapDecodingException bde) {
Log.w(TAG, bde);
throw new AssertionError("emoji sprite asset is corrupted or android decoding is broken");
}
}
public CharSequence emojify(CharSequence text, PageLoadedListener pageLoadedListener) {
return emojify(text, EMOJI_LARGE, pageLoadedListener);
}
public CharSequence emojify(CharSequence text, double size, PageLoadedListener pageLoadedListener) {
Matcher matches = EMOJI_RANGE.matcher(text);
SpannableStringBuilder builder = new SpannableStringBuilder(text);
while (matches.find()) {
int codePoint = matches.group().codePointAt(0);
Drawable drawable = getEmojiDrawable(codePoint, size, pageLoadedListener);
if (drawable != null) {
ImageSpan imageSpan = new ImageSpan(drawable, ImageSpan.ALIGN_BOTTOM);
char[] chars = new char[matches.end() - matches.start()];
Arrays.fill(chars, ' ');
builder.setSpan(imageSpan, matches.start(), matches.end(),
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
}
}
return builder;
}
public Drawable getEmojiDrawable(int emojiCode, double size, PageLoadedListener pageLoadedListener) {
return getEmojiDrawable(offsets.get(emojiCode), size, pageLoadedListener);
}
private Drawable getEmojiDrawable(DrawInfo drawInfo, double size, PageLoadedListener pageLoadedListener) {
if (drawInfo == null) {
return null;
}
final Drawable drawable = new EmojiDrawable(drawInfo, bigDrawSize);
drawable.setBounds(0, 0, (int)((double)bigDrawSize * size), (int)((double)bigDrawSize * size));
if (bitmaps.get(drawInfo.page) == null || bitmaps.get(drawInfo.page).get() == null) {
preloadPage(drawInfo.page, pageLoadedListener);
}
return drawable;
}
public static class EmojiDrawable extends Drawable {
private final int index;
private final int page;
private final int emojiSize;
private static final Paint paint;
private Bitmap bmp;
static {
paint = new Paint();
paint.setFilterBitmap(true);
}
public EmojiDrawable(DrawInfo info, int emojiSize) {
this.index = info.index;
this.page = info.page;
this.emojiSize = emojiSize;
}
@Override
public void draw(Canvas canvas) {
if (bitmaps.get(page) == null || bitmaps.get(page).get() == null) {
Log.w(TAG, "bitmap for this page was null");
return;
}
if (bmp == null) {
bmp = bitmaps.get(page).get();
}
Rect b = copyBounds();
final int row = index / EMOJI_PER_ROW;
final int row_index = index % EMOJI_PER_ROW;
canvas.drawBitmap(bmp,
new Rect(row_index * emojiSize,
row * emojiSize,
(row_index + 1) * emojiSize,
(row + 1) * emojiSize),
b,
paint);
}
@Override
public int getOpacity() {
return PixelFormat.TRANSLUCENT;
}
@Override
public void setAlpha(int alpha) { }
@Override
public void setColorFilter(ColorFilter cf) { }
@Override
public String toString() {
return "EmojiDrawable{" +
"page=" + page +
", index=" + index +
'}';
}
}
public static class InvalidatingPageLoadedListener implements PageLoadedListener {
private final View view;
public InvalidatingPageLoadedListener(final View view) {
this.view = view;
}
@Override
public void onPageLoaded() {
view.postInvalidate();
}
@Override
public String toString() {
return "InvalidatingPageLoadedListener{}";
}
}
class DrawInfo {
int page;
int index;
public DrawInfo(final int page, final int index) {
this.page = page;
this.index = index;
}
@Override
public String toString() {
return "DrawInfo{" +
"page=" + page +
", index=" + index +
'}';
}
}
interface PageLoadedListener {
void onPageLoaded();
}
}

View File

@ -0,0 +1,34 @@
package org.thoughtcrime.securesms.components.emoji;
import android.annotation.TargetApi;
import android.content.Context;
import android.os.Build.VERSION_CODES;
import android.util.AttributeSet;
import android.widget.TextView;
public class EmojiTextView extends TextView {
public EmojiTextView(Context context) {
super(context);
init();
}
public EmojiTextView(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
public EmojiTextView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
@TargetApi(VERSION_CODES.LOLLIPOP)
public EmojiTextView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
init();
}
private void init() {
setTransformationMethod(new EmojiTransformationMethod());
}
}

View File

@ -1,4 +1,4 @@
package org.thoughtcrime.securesms.components;
package org.thoughtcrime.securesms.components.emoji;
import android.content.Context;

View File

@ -0,0 +1,17 @@
package org.thoughtcrime.securesms.components.emoji;
import android.graphics.Rect;
import android.text.method.TransformationMethod;
import android.view.View;
import org.thoughtcrime.securesms.components.emoji.EmojiProvider.InvalidatingPageLoadedListener;
class EmojiTransformationMethod implements TransformationMethod {
@Override public CharSequence getTransformation(CharSequence source, View view) {
return EmojiProvider.getInstance(view.getContext()).emojify(source, EmojiProvider.EMOJI_SMALL, new InvalidatingPageLoadedListener(view));
}
@Override public void onFocusChanged(View view, CharSequence sourceText, boolean focused,
int direction, Rect previouslyFocusedRect) { }
}

View File

@ -0,0 +1,111 @@
package org.thoughtcrime.securesms.components.emoji;
import android.content.Context;
import android.content.SharedPreferences;
import android.os.AsyncTask;
import android.preference.PreferenceManager;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.util.Log;
import com.fasterxml.jackson.databind.type.CollectionType;
import com.fasterxml.jackson.databind.type.TypeFactory;
import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.util.JsonUtils;
import java.io.IOException;
import java.util.Iterator;
import java.util.LinkedHashSet;
public class RecentEmojiPageModel implements EmojiPageModel {
private static final String TAG = RecentEmojiPageModel.class.getSimpleName();
private static final String EMOJI_LRU_PREFERENCE = "pref_recent_emoji";
private static final int EMOJI_LRU_SIZE = 50;
private final SharedPreferences prefs;
private final LinkedHashSet<Integer> recentlyUsed;
public RecentEmojiPageModel(Context context) {
this.prefs = PreferenceManager.getDefaultSharedPreferences(context);
this.recentlyUsed = getPersistedCache();
}
private LinkedHashSet<Integer> getPersistedCache() {
String serialized = prefs.getString(EMOJI_LRU_PREFERENCE, "[]");
LinkedHashSet<String> recentlyUsedStrings;
try {
CollectionType collectionType = TypeFactory.defaultInstance()
.constructCollectionType(LinkedHashSet.class, String.class);
recentlyUsedStrings = JsonUtils.getMapper().readValue(serialized, collectionType);
return fromHexString(recentlyUsedStrings);
} catch (IOException e) {
Log.w(TAG, e);
return new LinkedHashSet<>();
}
}
@Override public int getIconRes() {
return R.drawable.emoji_category_recent;
}
@Override public int[] getCodePoints() {
return toPrimitiveArray(recentlyUsed);
}
@Override public void onCodePointSelected(int codePoint) {
recentlyUsed.remove(codePoint);
recentlyUsed.add(codePoint);
if (recentlyUsed.size() > EMOJI_LRU_SIZE) {
Iterator<Integer> iterator = recentlyUsed.iterator();
iterator.next();
iterator.remove();
}
final LinkedHashSet<Integer> latestRecentlyUsed = new LinkedHashSet<>(recentlyUsed);
new AsyncTask<Void, Void, Void>() {
@Override
protected Void doInBackground(Void... params) {
try {
String serialized = JsonUtils.toJson(toHexString(latestRecentlyUsed));
prefs.edit()
.putString(EMOJI_LRU_PREFERENCE, serialized)
.apply();
} catch (IOException e) {
Log.w(TAG, e);
}
return null;
}
}.execute();
}
private LinkedHashSet<Integer> fromHexString(@Nullable LinkedHashSet<String> stringSet) {
final LinkedHashSet<Integer> integerSet = new LinkedHashSet<>(stringSet != null ? stringSet.size() : 0);
if (stringSet != null) {
for (String hexString : stringSet) {
integerSet.add(Integer.valueOf(hexString, 16));
}
}
return integerSet;
}
private LinkedHashSet<String> toHexString(@NonNull LinkedHashSet<Integer> integerSet) {
final LinkedHashSet<String> stringSet = new LinkedHashSet<>(integerSet.size());
for (Integer integer : integerSet) {
stringSet.add(Integer.toHexString(integer));
}
return stringSet;
}
private int[] toPrimitiveArray(@NonNull LinkedHashSet<Integer> integerSet) {
int[] ints = new int[integerSet.size()];
int i = 0;
for (Integer integer : integerSet) {
ints[i++] = integer;
}
return ints;
}
}

View File

@ -0,0 +1,24 @@
package org.thoughtcrime.securesms.components.emoji;
import android.support.annotation.DrawableRes;
import android.support.annotation.NonNull;
public class StaticEmojiPageModel implements EmojiPageModel {
@DrawableRes private final int icon;
@NonNull private final int[] codePoints;
public StaticEmojiPageModel(@DrawableRes int icon, @NonNull int[] codePoints) {
this.icon = icon;
this.codePoints = codePoints;
}
public int getIconRes() {
return icon;
}
@NonNull public int[] getCodePoints() {
return codePoints;
}
@Override public void onCodePointSelected(int codePoint) { }
}

View File

@ -1,474 +0,0 @@
package org.thoughtcrime.securesms.util;
import android.content.Context;
import android.content.SharedPreferences;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.ColorFilter;
import android.graphics.Paint;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.os.AsyncTask;
import android.os.Build;
import android.preference.PreferenceManager;
import android.text.Spannable;
import android.text.SpannableString;
import android.text.style.ImageSpan;
import android.util.Log;
import android.util.Pair;
import android.util.SparseArray;
import android.view.View;
import com.fasterxml.jackson.databind.type.TypeFactory;
import org.thoughtcrime.securesms.R;
import java.io.IOException;
import java.io.InputStream;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.concurrent.ExecutorService;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class Emoji {
private static final String TAG = Emoji.class.getSimpleName();
private static ExecutorService executor = Util.newSingleThreadedLifoExecutor();
public static final int[][] PAGES = {
{
0x263a, 0x1f60a, 0x1f600, 0x1f601, 0x1f602, 0x1f603, 0x1f604, 0x1f605,
0x1f606, 0x1f607, 0x1f608, 0x1f609, 0x1f62f, 0x1f610, 0x1f611, 0x1f615,
0x1f620, 0x1f62c, 0x1f621, 0x1f622, 0x1f634, 0x1f62e, 0x1f623, 0x1f624,
0x1f625, 0x1f626, 0x1f627, 0x1f628, 0x1f629, 0x1f630, 0x1f61f, 0x1f631,
0x1f632, 0x1f633, 0x1f635, 0x1f636, 0x1f637, 0x1f61e, 0x1f612, 0x1f60d,
0x1f61b, 0x1f61c, 0x1f61d, 0x1f60b, 0x1f617, 0x1f619, 0x1f618, 0x1f61a,
0x1f60e, 0x1f62d, 0x1f60c, 0x1f616, 0x1f614, 0x1f62a, 0x1f60f, 0x1f613,
0x1f62b, 0x1f64b, 0x1f64c, 0x1f64d, 0x1f645, 0x1f646, 0x1f647, 0x1f64e,
0x1f64f, 0x1f63a, 0x1f63c, 0x1f638, 0x1f639, 0x1f63b, 0x1f63d, 0x1f63f,
0x1f63e, 0x1f640, 0x1f648, 0x1f649, 0x1f64a, 0x1f4a9, 0x1f476, 0x1f466,
0x1f467, 0x1f468, 0x1f469, 0x1f474, 0x1f475, 0x1f48f, 0x1f491, 0x1f46a,
0x1f46b, 0x1f46c, 0x1f46d, 0x1f464, 0x1f465, 0x1f46e, 0x1f477, 0x1f481,
0x1f482, 0x1f46f, 0x1f470, 0x1f478, 0x1f385, 0x1f47c, 0x1f471, 0x1f472,
0x1f473, 0x1f483, 0x1f486, 0x1f487, 0x1f485, 0x1f47b, 0x1f479, 0x1f47a,
0x1f47d, 0x1f47e, 0x1f47f, 0x1f480, 0x1f4aa, 0x1f440, 0x1f442, 0x1f443,
0x1f463, 0x1f444, 0x1f445, 0x1f48b, 0x2764, 0x1f499, 0x1f49a, 0x1f49b,
0x1f49c, 0x1f493, 0x1f494, 0x1f495, 0x1f496, 0x1f497, 0x1f498, 0x1f49d,
0x1f49e, 0x1f49f, 0x1f44d, 0x1f44e, 0x1f44c, 0x270a, 0x270c, 0x270b,
0x1f44a, 0x261d, 0x1f446, 0x1f447, 0x1f448, 0x1f449, 0x1f44b, 0x1f44f,
0x1f450
},
{
0x1f530, 0x1f484, 0x1f45e, 0x1f45f, 0x1f451, 0x1f452, 0x1f3a9, 0x1f393,
0x1f453, 0x231a, 0x1f454, 0x1f455, 0x1f456, 0x1f457, 0x1f458, 0x1f459,
0x1f460, 0x1f461, 0x1f462, 0x1f45a, 0x1f45c, 0x1f4bc, 0x1f392, 0x1f45d,
0x1f45b, 0x1f4b0, 0x1f4b3, 0x1f4b2, 0x1f4b5, 0x1f4b4, 0x1f4b6, 0x1f4b7,
0x1f4b8, 0x1f4b1, 0x1f4b9, 0x1f52b, 0x1f52a, 0x1f4a3, 0x1f489, 0x1f48a,
0x1f6ac, 0x1f514, 0x1f515, 0x1f6aa, 0x1f52c, 0x1f52d, 0x1f52e, 0x1f526,
0x1f50b, 0x1f50c, 0x1f4dc, 0x1f4d7, 0x1f4d8, 0x1f4d9, 0x1f4da, 0x1f4d4,
0x1f4d2, 0x1f4d1, 0x1f4d3, 0x1f4d5, 0x1f4d6, 0x1f4f0, 0x1f4db, 0x1f383,
0x1f384, 0x1f380, 0x1f381, 0x1f382, 0x1f388, 0x1f386, 0x1f387, 0x1f389,
0x1f38a, 0x1f38d, 0x1f38f, 0x1f38c, 0x1f390, 0x1f38b, 0x1f38e, 0x1f4f1,
0x1f4f2, 0x1f4df, 0x260e, 0x1f4de, 0x1f4e0, 0x1f4e6, 0x2709, 0x1f4e8,
0x1f4e9, 0x1f4ea, 0x1f4eb, 0x1f4ed, 0x1f4ec, 0x1f4ee, 0x1f4e4, 0x1f4e5,
0x1f4ef, 0x1f4e2, 0x1f4e3, 0x1f4e1, 0x1f4ac, 0x1f4ad, 0x2712, 0x270f,
0x1f4dd, 0x1f4cf, 0x1f4d0, 0x1f4cd, 0x1f4cc, 0x1f4ce, 0x2702, 0x1f4ba,
0x1f4bb, 0x1f4bd, 0x1f4be, 0x1f4bf, 0x1f4c6, 0x1f4c5, 0x1f4c7, 0x1f4cb,
0x1f4c1, 0x1f4c2, 0x1f4c3, 0x1f4c4, 0x1f4ca, 0x1f4c8, 0x1f4c9, 0x26fa,
0x1f3a1, 0x1f3a2, 0x1f3a0, 0x1f3aa, 0x1f3a8, 0x1f3ac, 0x1f3a5, 0x1f4f7,
0x1f4f9, 0x1f3a6, 0x1f3ad, 0x1f3ab, 0x1f3ae, 0x1f3b2, 0x1f3b0, 0x1f0cf,
0x1f3b4, 0x1f004, 0x1f3af, 0x1f4fa, 0x1f4fb, 0x1f4c0, 0x1f4fc, 0x1f3a7,
0x1f3a4, 0x1f3b5, 0x1f3b6, 0x1f3bc, 0x1f3bb, 0x1f3b9, 0x1f3b7, 0x1f3ba,
0x1f3b8, 0x303d
},
{
0x1f415, 0x1f436, 0x1f429, 0x1f408, 0x1f431, 0x1f400, 0x1f401, 0x1f42d,
0x1f439, 0x1f422, 0x1f407, 0x1f430, 0x1f413, 0x1f414, 0x1f423, 0x1f424,
0x1f425, 0x1f426, 0x1f40f, 0x1f411, 0x1f410, 0x1f43a, 0x1f403, 0x1f402,
0x1f404, 0x1f42e, 0x1f434, 0x1f417, 0x1f416, 0x1f437, 0x1f43d, 0x1f438,
0x1f40d, 0x1f43c, 0x1f427, 0x1f418, 0x1f428, 0x1f412, 0x1f435, 0x1f406,
0x1f42f, 0x1f43b, 0x1f42b, 0x1f42a, 0x1f40a, 0x1f433, 0x1f40b, 0x1f41f,
0x1f420, 0x1f421, 0x1f419, 0x1f41a, 0x1f42c, 0x1f40c, 0x1f41b, 0x1f41c,
0x1f41d, 0x1f41e, 0x1f432, 0x1f409, 0x1f43e, 0x1f378, 0x1f37a, 0x1f37b,
0x1f377, 0x1f379, 0x1f376, 0x2615, 0x1f375, 0x1f37c, 0x1f374, 0x1f368,
0x1f367, 0x1f366, 0x1f369, 0x1f370, 0x1f36a, 0x1f36b, 0x1f36c, 0x1f36d,
0x1f36e, 0x1f36f, 0x1f373, 0x1f354, 0x1f35f, 0x1f35d, 0x1f355, 0x1f356,
0x1f357, 0x1f364, 0x1f363, 0x1f371, 0x1f35e, 0x1f35c, 0x1f359, 0x1f35a,
0x1f35b, 0x1f372, 0x1f365, 0x1f362, 0x1f361, 0x1f358, 0x1f360, 0x1f34c,
0x1f34e, 0x1f34f, 0x1f34a, 0x1f34b, 0x1f344, 0x1f345, 0x1f346, 0x1f347,
0x1f348, 0x1f349, 0x1f350, 0x1f351, 0x1f352, 0x1f353, 0x1f34d, 0x1f330,
0x1f331, 0x1f332, 0x1f333, 0x1f334, 0x1f335, 0x1f337, 0x1f338, 0x1f339,
0x1f340, 0x1f341, 0x1f342, 0x1f343, 0x1f33a, 0x1f33b, 0x1f33c, 0x1f33d,
0x1f33e, 0x1f33f, 0x2600, 0x1f308, 0x26c5, 0x2601, 0x1f301, 0x1f302,
0x2614, 0x1f4a7, 0x26a1, 0x1f300, 0x2744, 0x26c4, 0x1f319, 0x1f31e,
0x1f31d, 0x1f31a, 0x1f31b, 0x1f31c, 0x1f311, 0x1f312, 0x1f313, 0x1f314,
0x1f315, 0x1f316, 0x1f317, 0x1f318, 0x1f391, 0x1f304, 0x1f305, 0x1f307,
0x1f306, 0x1f303, 0x1f30c, 0x1f309, 0x1f30a, 0x1f30b, 0x1f30e, 0x1f30f,
0x1f30d, 0x1f310
},
{
0x1f3e0, 0x1f3e1, 0x1f3e2, 0x1f3e3, 0x1f3e4, 0x1f3e5, 0x1f3e6, 0x1f3e7,
0x1f3e8, 0x1f3e9, 0x1f3ea, 0x1f3eb, 0x26ea, 0x26f2, 0x1f3ec, 0x1f3ef,
0x1f3f0, 0x1f3ed, 0x1f5fb, 0x1f5fc, 0x1f5fd, 0x1f5fe, 0x1f5ff, 0x2693,
0x1f3ee, 0x1f488, 0x1f527, 0x1f528, 0x1f529, 0x1f6bf, 0x1f6c1, 0x1f6c0,
0x1f6bd, 0x1f6be, 0x1f3bd, 0x1f3a3, 0x1f3b1, 0x1f3b3, 0x26be, 0x26f3,
0x1f3be, 0x26bd, 0x1f3bf, 0x1f3c0, 0x1f3c1, 0x1f3c2, 0x1f3c3, 0x1f3c4,
0x1f3c6, 0x1f3c7, 0x1f40e, 0x1f3c8, 0x1f3c9, 0x1f3ca, 0x1f682, 0x1f683,
0x1f684, 0x1f685, 0x1f686, 0x1f687, 0x24c2, 0x1f688, 0x1f68a, 0x1f68b,
0x1f68c, 0x1f68d, 0x1f68e, 0x1f68f, 0x1f690, 0x1f691, 0x1f692, 0x1f693,
0x1f694, 0x1f695, 0x1f696, 0x1f697, 0x1f698, 0x1f699, 0x1f69a, 0x1f69b,
0x1f69c, 0x1f69d, 0x1f69e, 0x1f69f, 0x1f6a0, 0x1f6a1, 0x1f6a2, 0x1f6a3,
0x1f681, 0x2708, 0x1f6c2, 0x1f6c3, 0x1f6c4, 0x1f6c5, 0x26f5, 0x1f6b2,
0x1f6b3, 0x1f6b4, 0x1f6b5, 0x1f6b7, 0x1f6b8, 0x1f689, 0x1f680, 0x1f6a4,
0x1f6b6, 0x26fd, 0x1f17f, 0x1f6a5, 0x1f6a6, 0x1f6a7, 0x1f6a8, 0x2668,
0x1f48c, 0x1f48d, 0x1f48e, 0x1f490, 0x1f492, 0xfe4e5, 0xfe4e6, 0xfe4e7,
0xfe4e8, 0xfe4e9, 0xfe4ea, 0xfe4eb, 0xfe4ec, 0xfe4ed, 0xfe4ee
},
{
0x1f51d, 0x1f519, 0x1f51b, 0x1f51c, 0x1f51a, 0x23f3, 0x231b, 0x23f0,
0x2648, 0x2649, 0x264a, 0x264b, 0x264c, 0x264d, 0x264e, 0x264f,
0x2650, 0x2651, 0x2652, 0x2653, 0x26ce, 0x1f531, 0x1f52f, 0x1f6bb,
0x1f6ae, 0x1f6af, 0x1f6b0, 0x1f6b1, 0x1f170, 0x1f171, 0x1f18e, 0x1f17e,
0x1f4ae, 0x1f4af, 0x1f520, 0x1f521, 0x1f522, 0x1f523, 0x1f524, 0x27bf,
0x1f4f6, 0x1f4f3, 0x1f4f4, 0x1f4f5, 0x1f6b9, 0x1f6ba, 0x1f6bc, 0x267f,
0x267b, 0x1f6ad, 0x1f6a9, 0x26a0, 0x1f201, 0x1f51e, 0x26d4, 0x1f192,
0x1f197, 0x1f195, 0x1f198, 0x1f199, 0x1f193, 0x1f196, 0x1f19a, 0x1f232,
0x1f233, 0x1f234, 0x1f235, 0x1f236, 0x1f237, 0x1f238, 0x1f239, 0x1f202,
0x1f23a, 0x1f250, 0x1f251, 0x3299, 0x00ae, 0x00a9, 0x2122, 0x1f21a,
0x1f22f, 0x3297, 0x2b55, 0x274c, 0x274e, 0x2139, 0x1f6ab, 0x2705,
0x2714, 0x1f517, 0x2734, 0x2733, 0x2795, 0x2796, 0x2716, 0x2797,
0x1f4a0, 0x1f4a1, 0x1f4a4, 0x1f4a2, 0x1f525, 0x1f4a5, 0x1f4a8, 0x1f4a6,
0x1f4ab, 0x1f55b, 0x1f567, 0x1f550, 0x1f55c, 0x1f551, 0x1f55d, 0x1f552,
0x1f55e, 0x1f553, 0x1f55f, 0x1f554, 0x1f560, 0x1f555, 0x1f561, 0x1f556,
0x1f562, 0x1f557, 0x1f563, 0x1f558, 0x1f564, 0x1f559, 0x1f565, 0x1f55a,
0x1f566, 0x2195, 0x2b06, 0x2197, 0x27a1, 0x2198, 0x2b07, 0x2199,
0x2b05, 0x2196, 0x2194, 0x2934, 0x2935, 0x23ea, 0x23eb, 0x23ec,
0x23e9, 0x25c0, 0x25b6, 0x1f53d, 0x1f53c, 0x2747, 0x2728, 0x1f534,
0x1f535, 0x26aa, 0x26ab, 0x1f533, 0x1f532, 0x2b50, 0x1f31f, 0x1f320,
0x25ab, 0x25aa, 0x25fd, 0x25fe, 0x25fb, 0x25fc, 0x2b1c, 0x2b1b,
0x1f538, 0x1f539, 0x1f536, 0x1f537, 0x1f53a, 0x1f53b, 0x1f51f, /*0x20e3,*/
0x2754, 0x2753, 0x2755, 0x2757, 0x203c, 0x2049, 0x3030, 0x27b0,
0x2660, 0x2665, 0x2663, 0x2666, 0x1f194, 0x1f511, 0x21a9, 0x1f191,
0x1f50d, 0x1f512, 0x1f513, 0x21aa, 0x1f510, 0x2611, 0x1f518, 0x1f50e,
0x1f516, 0x1f50f, 0x1f503, 0x1f500, 0x1f501, 0x1f502, 0x1f504, 0x1f4e7,
0x1f505, 0x1f506, 0x1f507, 0x1f508, 0x1f509, 0x1f50a
}
};
private static final SparseArray<DrawInfo> offsets;
static {
offsets = new SparseArray<DrawInfo>();
for (int i = 0; i < PAGES.length; i++) {
for (int j = 0; j < PAGES[i].length; j++) {
offsets.put(PAGES[i][j], new DrawInfo(i, j));
}
}
}
private static Bitmap[] bitmaps = new Bitmap[PAGES.length];
private static Emoji instance = null;
public synchronized static Emoji getInstance(Context context) {
if (instance == null) {
instance = new Emoji(context);
}
return instance;
}
@SuppressWarnings("MalformedRegex")
// 0x20a0-0x32ff 0x1f00-0x1fff 0xfe4e5-0xfe4ee
// |==== misc ====||======== emoticons ========||========= flags ==========|
private static final Pattern EMOJI_RANGE = Pattern.compile("[\\u20a0-\\u32ff\\ud83c\\udc00-\\ud83d\\udeff\\udbb9\\udce5-\\udbb9\\udcee]");
public static final double EMOJI_HUGE = 1.00;
public static final double EMOJI_LARGE = 0.75;
public static final double EMOJI_SMALL = 0.50;
public static final int EMOJI_RAW_SIZE = 128;
public static final int EMOJI_PER_ROW = 16;
private final Context context;
private final int bigDrawSize;
private Emoji(Context context) {
this.context = context.getApplicationContext();
this.bigDrawSize = context.getResources().getDimensionPixelSize(R.dimen.emoji_drawer_size);
}
private void preloadPage(final int page, final PageLoadedListener pageLoadListener) {
executor.submit(new Runnable() {
@Override
public void run() {
try {
loadPage(page);
if (pageLoadListener != null) pageLoadListener.onPageLoaded();
} catch (IOException ioe) {
Log.w("Emoji", ioe);
}
}
});
}
private void loadPage(int page) throws IOException {
if (page < 0 || page >= PAGES.length) {
throw new IndexOutOfBoundsException("can't load page that doesn't exist");
}
if (bitmaps[page] != null) return;
try {
final String file = "emoji_" + page + "_wrapped.png";
final InputStream measureStream = context.getAssets().open(file);
final InputStream bitmapStream = context.getAssets().open(file);
bitmaps[page] = BitmapUtil.createScaledBitmap(measureStream, bitmapStream, (float) bigDrawSize / (float) EMOJI_RAW_SIZE);
} catch (IOException ioe) {
Log.w("Emoji", ioe);
throw ioe;
} catch (BitmapDecodingException bde) {
Log.w("Emoji", bde);
throw new AssertionError("emoji sprite asset is corrupted or android decoding is broken");
}
}
public SpannableString emojify(String text, PageLoadedListener pageLoadedListener) {
return emojify(new SpannableString(text), pageLoadedListener);
}
public SpannableString emojify(SpannableString text, PageLoadedListener pageLoadedListener) {
return emojify(text, EMOJI_LARGE, pageLoadedListener);
}
public SpannableString emojify(SpannableString text, double size, PageLoadedListener pageLoadedListener) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) return text;
Matcher matches = EMOJI_RANGE.matcher(text);
while (matches.find()) {
String resource = Integer.toHexString(matches.group().codePointAt(0));
Drawable drawable = getEmojiDrawable(resource, size, pageLoadedListener);
if (drawable != null) {
ImageSpan imageSpan = new ImageSpan(drawable, ImageSpan.ALIGN_BOTTOM);
text.setSpan(imageSpan, matches.start(), matches.end(),
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
}
}
return text;
}
public Pair<Integer, Drawable> getRecentlyUsed(int position, double size, PageLoadedListener pageLoadedListener) {
String[] recentlyUsed = EmojiLRU.getRecentlyUsed(context);
String code = recentlyUsed[recentlyUsed.length - 1 - position];
return new Pair<Integer, Drawable>(Integer.parseInt(code, 16), getEmojiDrawable(code, size, pageLoadedListener));
}
public void setRecentlyUsed(String emojiCode) {
EmojiLRU.putRecentlyUsed(context, emojiCode);
}
public int getRecentlyUsedAssetCount() {
return EmojiLRU.getRecentlyUsedCount(context);
}
public Drawable getEmojiDrawable(String emojiCode, double size, PageLoadedListener pageLoadedListener) {
return getEmojiDrawable(offsets.get(Integer.parseInt(emojiCode, 16)), size, pageLoadedListener);
}
public Drawable getEmojiDrawable(DrawInfo drawInfo, double size, PageLoadedListener pageLoadedListener) {
if (drawInfo == null) {
return null;
}
final Drawable drawable = new EmojiDrawable(drawInfo, bigDrawSize);
drawable.setBounds(0, 0, (int) ((double) bigDrawSize * size), (int) ((double) bigDrawSize * size));
if (bitmaps[drawInfo.page] == null) {
preloadPage(drawInfo.page, pageLoadedListener);
}
return drawable;
}
private static class EmojiLRU {
private static SharedPreferences prefs = null;
private static LinkedHashSet<String> recentlyUsed = null;
private static final String EMOJI_LRU_PREFERENCE = "pref_recent_emoji";
private static final int EMOJI_LRU_SIZE = 50;
private static void initializeCache(Context context) {
if (prefs == null) {
prefs = PreferenceManager.getDefaultSharedPreferences(context);
}
String serialized = prefs.getString(EMOJI_LRU_PREFERENCE, "[]");
try {
recentlyUsed = JsonUtils.getMapper().readValue(serialized, TypeFactory.defaultInstance()
.constructCollectionType(LinkedHashSet.class, String.class));
} catch (IOException e) {
Log.w(TAG, e);
recentlyUsed = new LinkedHashSet<>();
}
}
public static String[] getRecentlyUsed(Context context) {
if (recentlyUsed == null) initializeCache(context);
return recentlyUsed.toArray(new String[recentlyUsed.size()]);
}
public static int getRecentlyUsedCount(Context context) {
if (recentlyUsed == null) initializeCache(context);
return recentlyUsed.size();
}
public static void putRecentlyUsed(Context context, String asset) {
if (recentlyUsed == null) initializeCache(context);
if (prefs == null) {
prefs = PreferenceManager.getDefaultSharedPreferences(context);
}
recentlyUsed.remove(asset);
recentlyUsed.add(asset);
if (recentlyUsed.size() > EMOJI_LRU_SIZE) {
Iterator<String> iterator = recentlyUsed.iterator();
iterator.next();
iterator.remove();
}
final LinkedHashSet<String> latestRecentlyUsed = new LinkedHashSet<String>(recentlyUsed);
new AsyncTask<Void, Void, Void>() {
@Override
protected Void doInBackground(Void... params) {
try {
String serialized = JsonUtils.toJson(latestRecentlyUsed);
prefs.edit()
.putString(EMOJI_LRU_PREFERENCE, serialized)
.apply();
} catch (IOException e) {
Log.w(TAG, e);
}
return null;
}
}.execute();
}
}
public static class EmojiDrawable extends Drawable {
private final int index;
private final int page;
private final int emojiSize;
private static final Paint paint;
private Bitmap bmp;
static {
paint = new Paint();
paint.setFilterBitmap(true);
}
public EmojiDrawable(DrawInfo info, int emojiSize) {
this.index = info.index;
this.page = info.page;
this.emojiSize = emojiSize;
}
@Override
public void draw(Canvas canvas) {
if (bitmaps[page] == null) {
Log.w("Emoji", "bitmap for this page was null");
return;
}
if (bmp == null) {
bmp = bitmaps[page];
}
Rect b = copyBounds();
// int cX = b.centerX(), cY = b.centerY();
// b.left = cX - emojiSize / 2;
// b.right = cX + emojiSize / 2;
// b.top = cY - emojiSize / 2;
// b.bottom = cY + emojiSize / 2;
final int row = index / EMOJI_PER_ROW;
final int row_index = index % EMOJI_PER_ROW;
canvas.drawBitmap(bmp,
new Rect(row_index * emojiSize,
row * emojiSize,
(row_index + 1) * emojiSize,
(row + 1) * emojiSize),
b,
paint);
}
@Override
public int getOpacity() {
return 0;
}
@Override
public void setAlpha(int alpha) { }
@Override
public void setColorFilter(ColorFilter cf) { }
@Override
public String toString() {
return "EmojiDrawable{" +
"page=" + page +
", index=" + index +
'}';
}
}
public static interface PageLoadedListener {
public void onPageLoaded();
}
public static class InvalidatingPageLoadedListener implements PageLoadedListener {
private final View view;
public InvalidatingPageLoadedListener(final View view) {
this.view = view;
}
@Override
public void onPageLoaded() {
view.post(new Runnable() {
@Override
public void run() {
view.invalidate();
}
});
}
@Override
public String toString() {
return "InvalidatingPageLoadedListener{}";
}
}
public static class DrawInfo {
int page;
int index;
public DrawInfo(final int page, final int index) {
this.page = page;
this.index = index;
}
@Override
public String toString() {
return "DrawInfo{" +
"page=" + page +
", index=" + index +
'}';
}
}
}

View File

@ -21,6 +21,7 @@ import android.content.Context;
import android.content.res.Resources.Theme;
import android.content.res.TypedArray;
import android.graphics.drawable.Drawable;
import android.support.annotation.ArrayRes;
import android.support.annotation.AttrRes;
import android.util.TypedValue;
@ -44,6 +45,16 @@ public class ResUtil {
}
public static Drawable getDrawable(Context c, @AttrRes int attr) {
return c.getResources().getDrawable(getDrawableRes(c, attr));
return c.getResources().getDrawable(getDrawableRes(c, attr), c.getTheme());
}
public static int[] getResourceIds(Context c, @ArrayRes int array) {
final TypedArray typedArray = c.getResources().obtainTypedArray(array);
final int[] resourceIds = new int[typedArray.length()];
for (int i = 0; i < typedArray.length(); i++) {
resourceIds[i] = typedArray.getResourceId(i, 0);
}
typedArray.recycle();
return resourceIds;
}
}