Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
A
AloqaIM-Android
Project
Project
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Boards
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
Administrator
AloqaIM-Android
Commits
e2c4ebf6
Commit
e2c4ebf6
authored
Jul 25, 2018
by
Leonardo Aramaki
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Show custom emojis on messages (including animated gifs)
parent
cfcc4f62
Changes
12
Hide whitespace changes
Inline
Side-by-side
Showing
12 changed files
with
134 additions
and
27 deletions
+134
-27
build.gradle
app/build.gradle
+2
-0
MessageViewHolder.kt
...chat/rocket/android/chatroom/adapter/MessageViewHolder.kt
+40
-2
ChatRoomFragment.kt
.../java/chat/rocket/android/chatroom/ui/ChatRoomFragment.kt
+1
-1
UiModelMapper.kt
...ava/chat/rocket/android/chatroom/uimodel/UiModelMapper.kt
+1
-1
MessageParser.kt
...src/main/java/chat/rocket/android/helper/MessageParser.kt
+10
-4
Text.kt
...src/main/java/chat/rocket/android/util/extensions/Text.kt
+3
-2
dependencies.gradle
dependencies.gradle
+3
-0
build.gradle
emoji/build.gradle
+2
-2
EmojiKeyboardPopup.kt
...main/java/chat/rocket/android/emoji/EmojiKeyboardPopup.kt
+6
-6
EmojiParser.kt
emoji/src/main/java/chat/rocket/android/emoji/EmojiParser.kt
+60
-7
EmojiRepository.kt
...rc/main/java/chat/rocket/android/emoji/EmojiRepository.kt
+5
-1
EmojiPagerAdapter.kt
...a/chat/rocket/android/emoji/internal/EmojiPagerAdapter.kt
+1
-1
No files found.
app/build.gradle
View file @
e2c4ebf6
...
...
@@ -115,6 +115,8 @@ dependencies {
implementation
libraries
.
frescoWebP
implementation
libraries
.
frescoAnimatedWebP
implementation
libraries
.
glide
kapt
libraries
.
kotshiCompiler
implementation
libraries
.
kotshiApi
...
...
app/src/main/java/chat/rocket/android/chatroom/adapter/MessageViewHolder.kt
View file @
e2c4ebf6
package
chat.rocket.android.chatroom.adapter
import
android.graphics.Color
import
android.graphics.drawable.Drawable
import
android.text.Spannable
import
android.text.Spanned
import
android.text.method.LinkMovementMethod
import
android.text.style.ImageSpan
import
android.view.View
import
androidx.core.view.isVisible
import
androidx.core.view.postDelayed
import
chat.rocket.android.R
import
chat.rocket.android.chatroom.uimodel.MessageUiModel
import
chat.rocket.android.emoji.EmojiReactionListener
import
chat.rocket.core.model.isSystemMessage
import
com.bumptech.glide.load.resource.gif.GifDrawable
import
kotlinx.android.synthetic.main.avatar.view.*
import
kotlinx.android.synthetic.main.item_message.view.*
...
...
@@ -15,7 +21,24 @@ class MessageViewHolder(
itemView
:
View
,
listener
:
ActionsListener
,
reactionListener
:
EmojiReactionListener
?
=
null
)
:
BaseViewHolder
<
MessageUiModel
>(
itemView
,
listener
,
reactionListener
)
{
)
:
BaseViewHolder
<
MessageUiModel
>(
itemView
,
listener
,
reactionListener
),
Drawable
.
Callback
{
override
fun
unscheduleDrawable
(
who
:
Drawable
?,
what
:
Runnable
?)
{
with
(
itemView
)
{
text_content
.
removeCallbacks
(
what
)
}
}
override
fun
invalidateDrawable
(
p0
:
Drawable
?)
{
with
(
itemView
)
{
text_content
.
invalidate
()
}
}
override
fun
scheduleDrawable
(
who
:
Drawable
?,
what
:
Runnable
?,
w
:
Long
)
{
with
(
itemView
)
{
text_content
.
postDelayed
(
what
,
w
)
}
}
init
{
with
(
itemView
)
{
...
...
@@ -31,15 +54,30 @@ class MessageViewHolder(
text_message_time
.
text
=
data
.
time
text_sender
.
text
=
data
.
senderName
text_content
.
text
=
data
.
content
if
(
data
.
content
is
Spannable
)
{
val
spans
=
data
.
content
.
getSpans
(
0
,
data
.
content
.
length
,
ImageSpan
::
class
.
java
)
spans
.
forEach
{
if
(
it
.
drawable
is
GifDrawable
)
{
it
.
drawable
.
callback
=
this
@MessageViewHolder
(
it
.
drawable
as
GifDrawable
).
start
()
}
}
}
text_content
.
text_content
.
text
=
data
.
content
image_avatar
.
setImageURI
(
data
.
avatar
)
text_content
.
setTextColor
(
if
(
data
.
isTemporary
)
Color
.
GRAY
else
Color
.
BLACK
)
data
.
message
.
let
{
text_edit_indicator
.
isVisible
=
!
it
.
isSystemMessage
()
&&
it
.
editedBy
!=
null
image_star_indicator
.
isVisible
=
it
.
starred
?.
isNotEmpty
()
?:
false
}
if
(
data
.
unread
==
null
)
{
read_receipt_view
.
isVisible
=
false
}
else
{
...
...
app/src/main/java/chat/rocket/android/chatroom/ui/ChatRoomFragment.kt
View file @
e2c4ebf6
...
...
@@ -618,7 +618,7 @@ class ChatRoomFragment : Fragment(), ChatRoomView, EmojiKeyboardListener, EmojiR
override
fun
onEmojiAdded
(
emoji
:
Emoji
)
{
val
cursorPosition
=
text_message
.
selectionStart
if
(
cursorPosition
>
-
1
)
{
text_message
.
text
?.
insert
(
cursorPosition
,
EmojiParser
.
parse
(
emoji
.
shortname
))
text_message
.
text
?.
insert
(
cursorPosition
,
EmojiParser
.
parse
(
context
!!
,
emoji
.
shortname
))
text_message
.
setSelection
(
cursorPosition
+
emoji
.
unicode
.
length
)
}
}
...
...
app/src/main/java/chat/rocket/android/chatroom/uimodel/UiModelMapper.kt
View file @
e2c4ebf6
...
...
@@ -457,7 +457,7 @@ class UiModelMapper @Inject constructor(
list
.
add
(
ReactionUiModel
(
messageId
=
message
.
id
,
shortname
=
shortname
,
unicode
=
EmojiParser
.
parse
(
shortname
),
unicode
=
EmojiParser
.
parse
(
context
,
shortname
),
count
=
count
,
usernames
=
usernames
)
)
...
...
app/src/main/java/chat/rocket/android/helper/MessageParser.kt
View file @
e2c4ebf6
...
...
@@ -9,6 +9,7 @@ import android.net.Uri
import
androidx.core.content.res.ResourcesCompat
import
android.text.Spanned
import
android.text.style.ClickableSpan
import
android.text.style.ImageSpan
import
android.text.style.ReplacementSpan
import
android.text.style.StyleSpan
import
android.util.Patterns
...
...
@@ -62,11 +63,11 @@ class MessageParser @Inject constructor(
}
}
val
builder
=
SpannableBuilder
()
val
content
=
EmojiRepository
.
shortnameToUnicode
(
text
,
true
)
val
content
=
EmojiRepository
.
shortnameToUnicode
(
text
)
val
parentNode
=
parser
.
parse
(
toLenientMarkdown
(
content
))
parentNode
.
accept
(
MarkdownVisitor
(
configuration
,
builder
))
parentNode
.
accept
(
LinkVisitor
(
builder
))
parentNode
.
accept
(
EmojiVisitor
(
configuration
,
builder
))
parentNode
.
accept
(
EmojiVisitor
(
con
text
,
con
figuration
,
builder
))
message
.
mentions
?.
let
{
parentNode
.
accept
(
MentionVisitor
(
context
,
builder
,
mentions
,
selfUsername
))
}
...
...
@@ -128,17 +129,22 @@ class MessageParser @Inject constructor(
}
class
EmojiVisitor
(
private
val
context
:
Context
,
configuration
:
SpannableConfiguration
,
private
val
builder
:
SpannableBuilder
)
:
SpannableMarkdownVisitor
(
configuration
,
builder
)
{
override
fun
visit
(
document
:
Document
)
{
val
spannable
=
EmojiParser
.
parse
(
builder
.
text
())
val
spannable
=
EmojiParser
.
parse
(
context
,
builder
.
text
())
if
(
spannable
is
Spanned
)
{
val
spans
=
spannable
.
getSpans
(
0
,
spannable
.
length
,
EmojiTypefaceSpan
::
class
.
java
)
val
spans2
=
spannable
.
getSpans
(
0
,
spannable
.
length
,
ImageSpan
::
class
.
java
)
spans
.
forEach
{
builder
.
setSpan
(
it
,
spannable
.
getSpanStart
(
it
),
spannable
.
getSpanEnd
(
it
),
0
)
}
spans2
.
forEach
{
builder
.
setSpan
(
it
,
spannable
.
getSpanStart
(
it
),
spannable
.
getSpanEnd
(
it
),
0
)
}
}
}
}
...
...
@@ -240,4 +246,4 @@ class MessageParser @Inject constructor(
canvas
.
drawText
(
text
,
start
,
end
,
x
+
padding
,
y
.
toFloat
(),
paint
)
}
}
}
\ No newline at end of file
}
app/src/main/java/chat/rocket/android/util/extensions/Text.kt
View file @
e2c4ebf6
...
...
@@ -66,12 +66,13 @@ var TextView.content: CharSequence?
Markwon
.
unscheduleDrawables
(
this
)
Markwon
.
unscheduleTableRows
(
this
)
if
(
value
is
Spanned
)
{
val
result
=
EmojiParser
.
parse
(
value
.
toString
())
as
Spannable
val
context
=
this
.
context
val
result
=
EmojiParser
.
parse
(
context
,
value
.
toString
())
as
Spannable
val
end
=
if
(
value
.
length
>
result
.
length
)
result
.
length
else
value
.
length
TextUtils
.
copySpansFrom
(
value
,
0
,
end
,
Any
::
class
.
java
,
result
,
0
)
text
=
result
}
else
{
val
result
=
EmojiParser
.
parse
(
value
.
toString
())
as
Spannable
val
result
=
EmojiParser
.
parse
(
context
,
value
.
toString
())
as
Spannable
text
=
result
}
Markwon
.
scheduleDrawables
(
this
)
...
...
dependencies.gradle
View file @
e2c4ebf6
...
...
@@ -46,6 +46,7 @@ ext {
frescoImageViewer
:
'0.5.1'
,
markwon
:
'1.0.6'
,
aVLoadingIndicatorView:
'2.1.3'
,
glide
:
'4.7.1'
,
// For wearable
wear
:
'2.3.0'
,
...
...
@@ -107,6 +108,8 @@ ext {
kotshiCompiler
:
"se.ansman.kotshi:compiler:${versions.kotshi}"
,
frescoImageViewer
:
"com.github.luciofm:FrescoImageViewer:${versions.frescoImageViewer}"
,
glide
:
"com.github.bumptech.glide:glide:${versions.glide}"
,
glideProcessor
:
"com.github.bumptech.glide:compiler:${versions.glide}"
,
markwon
:
"ru.noties:markwon:${versions.markwon}"
,
...
...
emoji/build.gradle
View file @
e2c4ebf6
...
...
@@ -34,8 +34,8 @@ dependencies {
implementation
libraries
.
constraintlayout
implementation
libraries
.
recyclerview
implementation
libraries
.
material
implementation
'com.github.bumptech.glide:glide:4.7.1'
annotationProcessor
'com.github.bumptech.glide:compiler:4.7.1'
implementation
libraries
.
glide
annotationProcessor
libraries
.
glideProcessor
}
kotlin
{
...
...
emoji/src/main/java/chat/rocket/android/emoji/EmojiKeyboardPopup.kt
View file @
e2c4ebf6
...
...
@@ -81,42 +81,42 @@ class EmojiKeyboardPopup(context: Context, view: View) : OverKeyboardPopupWindow
.
create
()
view
.
findViewById
<
TextView
>(
R
.
id
.
default_tone_text
).
also
{
it
.
text
=
EmojiParser
.
parse
(
it
.
text
)
it
.
text
=
EmojiParser
.
parse
(
context
,
it
.
text
)
}.
setOnClickListener
{
dialog
.
dismiss
()
changeSkinTone
(
Fitzpatrick
.
Default
)
}
view
.
findViewById
<
TextView
>(
R
.
id
.
light_tone_text
).
also
{
it
.
text
=
EmojiParser
.
parse
(
it
.
text
)
it
.
text
=
EmojiParser
.
parse
(
context
,
it
.
text
)
}.
setOnClickListener
{
dialog
.
dismiss
()
changeSkinTone
(
Fitzpatrick
.
LightTone
)
}
view
.
findViewById
<
TextView
>(
R
.
id
.
medium_light_text
).
also
{
it
.
text
=
EmojiParser
.
parse
(
it
.
text
)
it
.
text
=
EmojiParser
.
parse
(
context
,
it
.
text
)
}.
setOnClickListener
{
dialog
.
dismiss
()
changeSkinTone
(
Fitzpatrick
.
MediumLightTone
)
}
view
.
findViewById
<
TextView
>(
R
.
id
.
medium_tone_text
).
also
{
it
.
text
=
EmojiParser
.
parse
(
it
.
text
)
it
.
text
=
EmojiParser
.
parse
(
context
,
it
.
text
)
}.
setOnClickListener
{
dialog
.
dismiss
()
changeSkinTone
(
Fitzpatrick
.
MediumTone
)
}
view
.
findViewById
<
TextView
>(
R
.
id
.
medium_dark_tone_text
).
also
{
it
.
text
=
EmojiParser
.
parse
(
it
.
text
)
it
.
text
=
EmojiParser
.
parse
(
context
,
it
.
text
)
}.
setOnClickListener
{
dialog
.
dismiss
()
changeSkinTone
(
Fitzpatrick
.
MediumDarkTone
)
}
view
.
findViewById
<
TextView
>(
R
.
id
.
dark_tone_text
).
also
{
it
.
text
=
EmojiParser
.
parse
(
it
.
text
)
it
.
text
=
EmojiParser
.
parse
(
context
,
it
.
text
)
}.
setOnClickListener
{
dialog
.
dismiss
()
changeSkinTone
(
Fitzpatrick
.
DarkTone
)
...
...
emoji/src/main/java/chat/rocket/android/emoji/EmojiParser.kt
View file @
e2c4ebf6
package
chat.rocket.android.emoji
import
android.content.Context
import
android.graphics.Bitmap
import
android.graphics.drawable.Drawable
import
android.text.Spannable
import
android.text.SpannableString
import
android.text.Spanned
import
android.text.style.ImageSpan
import
android.util.Log
import
com.bumptech.glide.Glide
import
com.bumptech.glide.load.DataSource
import
com.bumptech.glide.load.engine.GlideException
import
com.bumptech.glide.load.resource.gif.GifDrawable
import
com.bumptech.glide.request.RequestListener
import
com.bumptech.glide.request.RequestOptions
import
com.bumptech.glide.request.target.Target
class
EmojiParser
{
companion
object
{
private
val
regex
=
":[\\w]+:"
.
toRegex
()
/**
* Parses a text string containing unicode characters and/or shortnames to a rendered
* Spannable.
...
...
@@ -15,9 +30,11 @@ class EmojiParser {
* @param factory Optional. A [Spannable.Factory] instance to reuse when creating [Spannable].
* @return A rendered Spannable containing any supported emoji.
*/
fun
parse
(
text
:
CharSequence
,
factory
:
Spannable
.
Factory
?
=
null
):
CharSequence
{
val
unicodedText
=
EmojiRepository
.
shortnameToUnicode
(
text
,
true
)
val
spannable
=
factory
?.
newSpannable
(
unicodedText
)
?:
SpannableString
.
valueOf
(
unicodedText
)
fun
parse
(
context
:
Context
,
text
:
CharSequence
,
factory
:
Spannable
.
Factory
?
=
null
):
CharSequence
{
val
unicodedText
=
EmojiRepository
.
shortnameToUnicode
(
text
)
val
spannable
=
factory
?.
newSpannable
(
unicodedText
)
?:
SpannableString
.
valueOf
(
unicodedText
)
val
typeface
=
EmojiRepository
.
cachedTypeface
// Look for groups of emojis, set a EmojiTypefaceSpan with the emojione font.
val
length
=
spannable
.
length
...
...
@@ -40,17 +57,53 @@ class EmojiParser {
}
else
{
if
(
inEmoji
)
{
spannable
.
setSpan
(
EmojiTypefaceSpan
(
"sans-serif"
,
typeface
),
emojiStart
,
offset
,
Spanned
.
SPAN_EXCLUSIVE_EXCLUSIVE
)
emojiStart
,
offset
,
Spanned
.
SPAN_EXCLUSIVE_EXCLUSIVE
)
}
inEmoji
=
false
}
offset
+=
count
if
(
offset
>=
length
&&
inEmoji
)
{
spannable
.
setSpan
(
EmojiTypefaceSpan
(
"sans-serif"
,
typeface
),
emojiStart
,
offset
,
Spanned
.
SPAN_EXCLUSIVE_EXCLUSIVE
)
emojiStart
,
offset
,
Spanned
.
SPAN_EXCLUSIVE_EXCLUSIVE
)
}
}
val
customEmojis
=
EmojiRepository
.
getCustomEmojis
()
val
density
=
context
.
resources
.
displayMetrics
.
density
val
px
=
(
24
*
density
).
toInt
()
return
spannable
.
also
{
regex
.
findAll
(
spannable
).
iterator
().
forEach
{
match
->
customEmojis
.
find
{
it
.
shortname
.
toLowerCase
()
==
match
.
value
.
toLowerCase
()
}
?.
let
{
it
.
url
?.
let
{
url
->
try
{
val
glideRequest
=
if
(
url
.
endsWith
(
"gif"
,
true
))
{
Glide
.
with
(
context
).
asGif
()
}
else
{
Glide
.
with
(
context
).
asBitmap
()
}
val
futureTarget
=
glideRequest
.
load
(
url
).
submit
(
px
,
px
)
val
range
=
match
.
range
futureTarget
.
get
()
?.
let
{
image
->
if
(
image
is
Bitmap
)
{
spannable
.
setSpan
(
ImageSpan
(
context
,
image
),
range
.
start
,
range
.
endInclusive
+
1
,
Spanned
.
SPAN_EXCLUSIVE_EXCLUSIVE
)
}
else
if
(
image
is
GifDrawable
)
{
image
.
setBounds
(
0
,
0
,
px
,
px
)
spannable
.
setSpan
(
ImageSpan
(
image
),
range
.
start
,
range
.
endInclusive
+
1
,
Spanned
.
SPAN_EXCLUSIVE_EXCLUSIVE
)
}
}
}
catch
(
ex
:
Throwable
)
{
Log
.
e
(
"EmojiParser"
,
""
,
ex
)
}
}
}
}
}
return
spannable
}
}
}
\ No newline at end of file
}
emoji/src/main/java/chat/rocket/android/emoji/EmojiRepository.kt
View file @
e2c4ebf6
...
...
@@ -21,10 +21,12 @@ object EmojiRepository {
private
val
shortNameToUnicode
=
HashMap
<
String
,
String
>()
private
val
SHORTNAME_PATTERN
=
Pattern
.
compile
(
":([-+\\w]+):"
)
private
val
ALL_EMOJIS
=
mutableListOf
<
Emoji
>()
private
var
customEmojis
:
List
<
Emoji
>
=
emptyList
()
private
lateinit
var
preferences
:
SharedPreferences
internal
lateinit
var
cachedTypeface
:
Typeface
fun
load
(
context
:
Context
,
customEmojis
:
List
<
Emoji
>
=
emptyList
(),
path
:
String
=
"emoji.json"
)
{
this
.
customEmojis
=
customEmojis
preferences
=
context
.
getSharedPreferences
(
"emoji"
,
Context
.
MODE_PRIVATE
)
ALL_EMOJIS
.
clear
()
cachedTypeface
=
Typeface
.
createFromAsset
(
context
.
assets
,
"fonts/emojione-android.ttf"
)
...
...
@@ -133,6 +135,8 @@ object EmojiRepository {
preferences
.
edit
().
putString
(
PREF_EMOJI_RECENTS
,
recentsJson
.
toString
()).
apply
()
}
internal
fun
getCustomEmojis
():
List
<
Emoji
>
=
customEmojis
/**
* Get all recently used emojis ordered by usage count.
*
...
...
@@ -157,7 +161,7 @@ object EmojiRepository {
/**
* Replace shortnames to unicode characters.
*/
fun
shortnameToUnicode
(
input
:
CharSequence
,
removeIfUnsupported
:
Boolean
):
String
{
fun
shortnameToUnicode
(
input
:
CharSequence
):
String
{
val
matcher
=
SHORTNAME_PATTERN
.
matcher
(
input
)
var
result
:
String
=
input
.
toString
()
...
...
emoji/src/main/java/chat/rocket/android/emoji/internal/EmojiPagerAdapter.kt
View file @
e2c4ebf6
...
...
@@ -151,7 +151,7 @@ internal class EmojiPagerAdapter(private val listener: EmojiKeyboardListener) :
val
parsedUnicode
=
unicodeCache
[
emoji
.
unicode
]
emoji_view
.
setSpannableFactory
(
spannableFactory
)
emoji_view
.
text
=
if
(
parsedUnicode
==
null
)
{
EmojiParser
.
parse
(
emoji
.
unicode
,
spannableFactory
).
let
{
EmojiParser
.
parse
(
itemView
.
context
,
emoji
.
unicode
,
spannableFactory
).
let
{
unicodeCache
[
emoji
.
unicode
]
=
it
it
}
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment