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
e91b520d
Commit
e91b520d
authored
Mar 16, 2018
by
Leonardo Aramaki
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Fix emoji parsing on messages
parent
29fc25ea
Changes
4
Hide whitespace changes
Inline
Side-by-side
Showing
4 changed files
with
79 additions
and
73 deletions
+79
-73
MessageParser.kt
...src/main/java/chat/rocket/android/helper/MessageParser.kt
+49
-55
EmojiParser.kt
...main/java/chat/rocket/android/widget/emoji/EmojiParser.kt
+19
-6
EmojiRepository.kt
.../java/chat/rocket/android/widget/emoji/EmojiRepository.kt
+6
-7
EmojiTypefaceSpan.kt
...ava/chat/rocket/android/widget/emoji/EmojiTypefaceSpan.kt
+5
-5
No files found.
app/src/main/java/chat/rocket/android/helper/MessageParser.kt
View file @
e91b520d
...
...
@@ -11,7 +11,6 @@ import android.provider.Browser
import
android.support.v4.content.ContextCompat
import
android.support.v4.content.res.ResourcesCompat
import
android.text.Layout
import
android.text.Spannable
import
android.text.Spanned
import
android.text.style.*
import
android.util.Patterns
...
...
@@ -25,6 +24,7 @@ import chat.rocket.common.model.SimpleUser
import
chat.rocket.core.model.Message
import
org.commonmark.node.AbstractVisitor
import
org.commonmark.node.BlockQuote
import
org.commonmark.node.Document
import
org.commonmark.node.Text
import
ru.noties.markwon.Markwon
import
ru.noties.markwon.SpannableBuilder
...
...
@@ -36,13 +36,6 @@ import javax.inject.Inject
class
MessageParser
@Inject
constructor
(
val
context
:
Application
,
private
val
configuration
:
SpannableConfiguration
)
{
private
val
parser
=
Markwon
.
createParser
()
private
val
othersTextColor
=
ResourcesCompat
.
getColor
(
context
.
resources
,
R
.
color
.
colorAccent
,
context
.
theme
)
private
val
othersBackgroundColor
=
ResourcesCompat
.
getColor
(
context
.
resources
,
android
.
R
.
color
.
transparent
,
context
.
theme
)
private
val
myselfTextColor
=
ResourcesCompat
.
getColor
(
context
.
resources
,
R
.
color
.
white
,
context
.
theme
)
private
val
myselfBackgroundColor
=
ResourcesCompat
.
getColor
(
context
.
resources
,
R
.
color
.
colorAccent
,
context
.
theme
)
private
val
mentionPadding
=
context
.
resources
.
getDimensionPixelSize
(
R
.
dimen
.
padding_mention
).
toFloat
()
private
val
mentionRadius
=
context
.
resources
.
getDimensionPixelSize
(
R
.
dimen
.
radius_mention
).
toFloat
()
/**
* Render a markdown text message to Spannable.
...
...
@@ -64,49 +57,16 @@ class MessageParser @Inject constructor(val context: Application, private val co
parentNode
.
appendChild
(
quoteNode
)
quoteNode
.
accept
(
QuoteMessageSenderVisitor
(
context
,
configuration
,
builder
,
senderName
.
length
))
quoteNode
=
parser
.
parse
(
"> ${toLenientMarkdown(quote.rawData.message)}"
)
quoteNode
.
accept
(
EmojiVisitor
(
builder
))
quoteNode
.
accept
(
EmojiVisitor
(
builder
,
configuration
))
quoteNode
.
accept
(
QuoteMessageBodyVisitor
(
context
,
configuration
,
builder
))
}
parentNode
.
accept
(
LinkVisitor
(
builder
))
parentNode
.
accept
(
EmojiVisitor
(
builder
))
val
result
=
builder
.
text
()
applySpans
(
result
,
message
,
selfUsername
)
return
result
}
private
fun
applySpans
(
text
:
CharSequence
,
message
:
Message
,
currentUser
:
String
?)
{
if
(
text
!
is
Spannable
||
!
containsAnyMentions
(
text
,
message
.
mentions
))
return
applyMentionSpans
(
text
,
message
.
mentions
!!
,
currentUser
)
}
private
fun
containsAnyMentions
(
text
:
CharSequence
,
mentions
:
List
<
SimpleUser
>?):
Boolean
{
return
mentions
!=
null
&&
mentions
.
isNotEmpty
()
||
text
.
contains
(
"@all"
,
true
)
||
text
.
contains
(
"@here"
,
true
)
}
private
fun
applyMentionSpans
(
text
:
Spannable
,
mentions
:
List
<
SimpleUser
>,
currentUser
:
String
?)
{
val
mentionsList
=
mentions
.
map
{
it
.
username
}.
toMutableList
()
mentionsList
.
add
(
"all"
)
mentionsList
.
add
(
"here"
)
mentionsList
.
toList
().
forEach
{
if
(
it
!=
null
)
{
val
mentionMe
=
it
==
currentUser
||
it
==
"all"
||
it
==
"here"
var
offset
=
text
.
indexOf
(
"@$it"
,
0
,
true
)
while
(
offset
>
-
1
)
{
val
textColor
=
if
(
mentionMe
)
myselfTextColor
else
othersTextColor
val
backgroundColor
=
if
(
mentionMe
)
myselfBackgroundColor
else
othersBackgroundColor
val
usernameSpan
=
MentionSpan
(
backgroundColor
,
textColor
,
mentionRadius
,
mentionPadding
,
mentionMe
)
// Add 1 to end offset to include the @.
val
end
=
offset
+
it
.
length
+
1
text
.
setSpan
(
usernameSpan
,
offset
,
end
,
0
)
offset
=
text
.
indexOf
(
"@$it"
,
end
,
true
)
}
}
if
(
message
.
mentions
!=
null
)
{
parentNode
.
accept
(
MentionVisitor
(
context
,
builder
,
message
.
mentions
!!
,
selfUsername
))
}
parentNode
.
accept
(
EmojiVisitor
(
builder
,
configuration
))
return
builder
.
text
()
}
// Convert to a lenient markdown consistent with Rocket.Chat web markdown instead of the official specs.
...
...
@@ -142,16 +102,52 @@ class MessageParser @Inject constructor(val context: Application, private val co
}
}
class
EmojiVisitor
(
private
val
builder
:
SpannableBuilder
)
:
AbstractVisitor
()
{
override
fun
visit
(
text
:
Text
)
{
val
spannable
=
EmojiParser
.
parse
(
text
.
literal
)
class
MentionVisitor
(
context
:
Context
,
private
val
builder
:
SpannableBuilder
,
private
val
mentions
:
List
<
SimpleUser
>,
private
val
currentUser
:
String
?)
:
AbstractVisitor
()
{
private
val
othersTextColor
=
ResourcesCompat
.
getColor
(
context
.
resources
,
R
.
color
.
colorAccent
,
context
.
theme
)
private
val
othersBackgroundColor
=
ResourcesCompat
.
getColor
(
context
.
resources
,
android
.
R
.
color
.
transparent
,
context
.
theme
)
private
val
myselfTextColor
=
ResourcesCompat
.
getColor
(
context
.
resources
,
R
.
color
.
white
,
context
.
theme
)
private
val
myselfBackgroundColor
=
ResourcesCompat
.
getColor
(
context
.
resources
,
R
.
color
.
colorAccent
,
context
.
theme
)
private
val
mentionPadding
=
context
.
resources
.
getDimensionPixelSize
(
R
.
dimen
.
padding_mention
).
toFloat
()
private
val
mentionRadius
=
context
.
resources
.
getDimensionPixelSize
(
R
.
dimen
.
radius_mention
).
toFloat
()
override
fun
visit
(
t
:
Text
)
{
val
text
=
t
.
literal
val
mentionsList
=
mentions
.
map
{
it
.
username
}.
toMutableList
()
mentionsList
.
add
(
"all"
)
mentionsList
.
add
(
"here"
)
mentionsList
.
toList
().
forEach
{
if
(
it
!=
null
)
{
val
mentionMe
=
it
==
currentUser
||
it
==
"all"
||
it
==
"here"
var
offset
=
text
.
indexOf
(
"@$it"
,
0
,
true
)
while
(
offset
>
-
1
)
{
val
textColor
=
if
(
mentionMe
)
myselfTextColor
else
othersTextColor
val
backgroundColor
=
if
(
mentionMe
)
myselfBackgroundColor
else
othersBackgroundColor
val
usernameSpan
=
MentionSpan
(
backgroundColor
,
textColor
,
mentionRadius
,
mentionPadding
,
mentionMe
)
// Add 1 to end offset to include the @.
val
end
=
offset
+
it
.
length
+
1
builder
.
setSpan
(
usernameSpan
,
offset
,
end
,
0
)
offset
=
text
.
indexOf
(
"@$it"
,
end
,
true
)
}
}
}
}
}
class
EmojiVisitor
(
private
val
builder
:
SpannableBuilder
,
configuration
:
SpannableConfiguration
)
:
SpannableMarkdownVisitor
(
configuration
,
builder
)
{
override
fun
visit
(
document
:
Document
)
{
val
spannable
=
EmojiParser
.
parse
(
builder
.
text
())
if
(
spannable
is
Spanned
)
{
val
spans
=
spannable
.
getSpans
(
0
,
spannable
.
length
,
EmojiTypefaceSpan
::
class
.
java
)
spans
.
forEach
{
builder
.
setSpan
(
it
,
spannable
.
getSpanStart
(
it
),
spannable
.
getSpanEnd
(
it
),
0
)
builder
.
setSpan
(
it
,
spannable
.
getSpanStart
(
it
),
spannable
.
getSpanEnd
(
it
),
0
)
}
}
visitChildren
(
text
)
}
}
...
...
@@ -272,13 +268,11 @@ class MessageParser @Inject constructor(val context: Application, private val co
bottom
:
Int
,
paint
:
Paint
)
{
val
length
=
paint
.
measureText
(
text
.
subSequence
(
start
,
end
).
toString
())
val
rect
=
RectF
(
x
,
top
.
toFloat
(),
x
+
length
+
padding
*
2
,
bottom
.
toFloat
())
val
rect
=
RectF
(
x
,
top
.
toFloat
(),
x
+
length
+
padding
*
2
,
bottom
.
toFloat
())
paint
.
color
=
backgroundColor
canvas
.
drawRoundRect
(
rect
,
radius
,
radius
,
paint
)
paint
.
color
=
textColor
canvas
.
drawText
(
text
,
start
,
end
,
x
+
padding
,
y
.
toFloat
(),
paint
)
}
}
}
\ No newline at end of file
app/src/main/java/chat/rocket/android/widget/emoji/EmojiParser.kt
View file @
e91b520d
...
...
@@ -14,15 +14,21 @@ class EmojiParser {
*/
fun
parse
(
text
:
CharSequence
):
CharSequence
{
val
unicodedText
=
EmojiRepository
.
shortnameToUnicode
(
text
,
true
)
val
spannableString
=
SpannableString
.
valueOf
(
unicodedText
)
// Look for groups of emojis, set a CustomTypefaceSpan with the emojione font
val
length
=
spannableString
.
length
var
spannable
=
SpannableString
.
valueOf
(
unicodedText
)
val
typeface
=
EmojiRepository
.
cachedTypeface
// Look for groups of emojis, set a EmojiTypefaceSpan with the emojione font.
val
length
=
spannable
.
length
var
inEmoji
=
false
var
emojiStart
=
0
var
offset
=
0
while
(
offset
<
length
)
{
val
codepoint
=
unicodedText
.
codePointAt
(
offset
)
val
count
=
Character
.
charCount
(
codepoint
)
// Skip control characters.
if
(
codepoint
==
0
x2028
)
{
offset
+=
count
continue
}
if
(
codepoint
>=
0
x200
)
{
if
(!
inEmoji
)
{
emojiStart
=
offset
...
...
@@ -30,18 +36,25 @@ class EmojiParser {
inEmoji
=
true
}
else
{
if
(
inEmoji
)
{
spannable
String
.
setSpan
(
EmojiTypefaceSpan
(
"sans-serif"
,
EmojiRepository
.
cachedT
ypeface
),
spannable
.
setSpan
(
EmojiTypefaceSpan
(
"sans-serif"
,
t
ypeface
),
emojiStart
,
offset
,
Spanned
.
SPAN_EXCLUSIVE_EXCLUSIVE
)
}
inEmoji
=
false
}
offset
+=
count
if
(
offset
>=
length
&&
inEmoji
)
{
spannable
String
.
setSpan
(
EmojiTypefaceSpan
(
"sans-serif"
,
EmojiRepository
.
cachedT
ypeface
),
spannable
.
setSpan
(
EmojiTypefaceSpan
(
"sans-serif"
,
t
ypeface
),
emojiStart
,
offset
,
Spanned
.
SPAN_EXCLUSIVE_EXCLUSIVE
)
}
}
return
spannableString
return
spannable
}
private
fun
calculateSurrogatePairs
(
scalar
:
Int
):
Pair
<
Int
,
Int
>
{
val
temp
:
Int
=
(
scalar
-
0
x10000
)
/
0
x400
val
s1
:
Int
=
Math
.
floor
(
temp
.
toDouble
()).
toInt
()
+
0
xD800
val
s2
:
Int
=
((
scalar
-
0
x10000
)
%
0
x400
)
+
0
xDC00
return
Pair
(
s1
,
s2
)
}
}
}
\ No newline at end of file
app/src/main/java/chat/rocket/android/widget/emoji/EmojiRepository.kt
View file @
e91b520d
...
...
@@ -54,6 +54,10 @@ object EmojiRepository {
*/
fun
getAll
()
=
ALL_EMOJIS
// fun findEmojiByUnicode(unicode: Int) {
// ALL_EMOJIS.find { it.unicode == }
// }
/**
* Get all emojis for a given category.
*
...
...
@@ -119,10 +123,7 @@ object EmojiRepository {
var
result
:
String
=
input
.
toString
()
while
(
matcher
.
find
())
{
val
unicode
=
shortNameToUnicode
.
get
(
":${matcher.group(1)}:"
)
if
(
unicode
==
null
)
{
continue
}
val
unicode
=
shortNameToUnicode
.
get
(
":${matcher.group(1)}:"
)
?:
continue
if
(
supported
)
{
result
=
result
.
replace
(
":"
+
matcher
.
group
(
1
)
+
":"
,
unicode
)
...
...
@@ -159,9 +160,7 @@ object EmojiRepository {
private
fun
buildStringListFromJsonArray
(
array
:
JSONArray
):
List
<
String
>
{
val
list
=
ArrayList
<
String
>(
array
.
length
())
for
(
i
in
0
..
array
.
length
()
-
1
)
{
list
.
add
(
array
.
getString
(
i
))
}
(
0
until
array
.
length
()).
mapTo
(
list
)
{
array
.
getString
(
it
)
}
return
list
}
...
...
app/src/main/java/chat/rocket/android/widget/emoji/EmojiTypefaceSpan.kt
View file @
e91b520d
...
...
@@ -17,22 +17,22 @@ class EmojiTypefaceSpan(family: String, private val newType: Typeface) : Typefac
private
fun
applyCustomTypeFace
(
paint
:
Paint
,
tf
:
Typeface
)
{
val
oldStyle
:
Int
val
old
=
paint
.
getTypeface
()
val
old
=
paint
.
typeface
if
(
old
==
null
)
{
oldStyle
=
0
}
else
{
oldStyle
=
old
.
getStyle
()
oldStyle
=
old
.
style
}
val
fake
=
oldStyle
and
tf
.
style
.
inv
()
if
(
fake
and
Typeface
.
BOLD
!=
0
)
{
paint
.
setFakeBoldText
(
true
)
paint
.
isFakeBoldText
=
true
}
if
(
fake
and
Typeface
.
ITALIC
!=
0
)
{
paint
.
setTextSkewX
(-
0.25f
)
paint
.
textSkewX
=
-
0.25f
}
paint
.
setTypeface
(
tf
)
paint
.
typeface
=
tf
}
}
\ No newline at end of file
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