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
1e4ac51e
Commit
1e4ac51e
authored
Feb 20, 2018
by
Leonardo Aramaki
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Set emoji keyboard height to the same size as the softkeyboard. Store the height for later use
parent
80cacca2
Changes
13
Hide whitespace changes
Inline
Side-by-side
Showing
13 changed files
with
424 additions
and
304 deletions
+424
-304
AndroidManifest.xml
app/src/main/AndroidManifest.xml
+1
-0
RocketChatApplication.kt
...ain/java/chat/rocket/android/app/RocketChatApplication.kt
+2
-2
ChatRoomActivity.kt
.../java/chat/rocket/android/chatroom/ui/ChatRoomActivity.kt
+9
-1
ChatRoomFragment.kt
.../java/chat/rocket/android/chatroom/ui/ChatRoomFragment.kt
+25
-8
CategoryPagerAdapter.kt
.../chat/rocket/android/widget/emoji/CategoryPagerAdapter.kt
+3
-3
EmojiCategory.kt
...in/java/chat/rocket/android/widget/emoji/EmojiCategory.kt
+1
-1
EmojiFragment.kt
...in/java/chat/rocket/android/widget/emoji/EmojiFragment.kt
+81
-34
EmojiLoader.kt
...main/java/chat/rocket/android/widget/emoji/EmojiLoader.kt
+0
-187
EmojiParser.kt
...main/java/chat/rocket/android/widget/emoji/EmojiParser.kt
+3
-3
EmojiRepository.kt
.../java/chat/rocket/android/widget/emoji/EmojiRepository.kt
+203
-0
emoji_category_layout.xml
app/src/main/res/layout/emoji_category_layout.xml
+3
-1
emoji_popup_layout.xml
app/src/main/res/layout/emoji_popup_layout.xml
+18
-5
message_composer.xml
app/src/main/res/layout/message_composer.xml
+75
-59
No files found.
app/src/main/AndroidManifest.xml
View file @
1e4ac51e
...
...
@@ -45,6 +45,7 @@
<activity
android:name=
".chatroom.ui.ChatRoomActivity"
android:windowSoftInputMode=
"adjustPan"
android:theme=
"@style/AppTheme"
/>
<activity
...
...
app/src/main/java/chat/rocket/android/app/RocketChatApplication.kt
View file @
1e4ac51e
...
...
@@ -9,7 +9,7 @@ import chat.rocket.android.helper.CrashlyticsTree
import
chat.rocket.android.server.domain.GetCurrentServerInteractor
import
chat.rocket.android.server.domain.MultiServerTokenRepository
import
chat.rocket.android.server.domain.SettingsRepository
import
chat.rocket.android.widget.emoji.Emoji
Loader
import
chat.rocket.android.widget.emoji.Emoji
Repository
import
chat.rocket.common.model.Token
import
chat.rocket.core.TokenRepository
import
com.crashlytics.android.Crashlytics
...
...
@@ -58,7 +58,7 @@ class RocketChatApplication : Application(), HasActivityInjector, HasServiceInje
initCurrentServer
()
AndroidThreeTen
.
init
(
this
)
Emoji
Loader
.
load
(
this
)
Emoji
Repository
.
load
(
this
)
setupCrashlytics
()
setupFresco
()
...
...
app/src/main/java/chat/rocket/android/chatroom/ui/ChatRoomActivity.kt
View file @
1e4ac51e
...
...
@@ -8,6 +8,7 @@ import android.support.v7.app.AppCompatActivity
import
chat.rocket.android.R
import
chat.rocket.android.util.extensions.addFragment
import
chat.rocket.android.util.extensions.textContent
import
chat.rocket.android.widget.emoji.EmojiFragment
import
dagger.android.AndroidInjection
import
dagger.android.AndroidInjector
import
dagger.android.DispatchingAndroidInjector
...
...
@@ -61,7 +62,14 @@ class ChatRoomActivity : AppCompatActivity(), HasSupportFragmentInjector {
}
}
override
fun
onBackPressed
()
=
finishActivity
()
override
fun
onBackPressed
()
{
val
frag
=
supportFragmentManager
.
findFragmentByTag
(
EmojiFragment
.
TAG
)
as
EmojiFragment
?
if
(
frag
!=
null
&&
frag
.
isShown
())
{
frag
.
hide
()
}
else
{
finishActivity
()
}
}
override
fun
supportFragmentInjector
():
AndroidInjector
<
Fragment
>
{
return
fragmentDispatchingAndroidInjector
...
...
app/src/main/java/chat/rocket/android/chatroom/ui/ChatRoomFragment.kt
View file @
1e4ac51e
...
...
@@ -12,6 +12,7 @@ import android.support.v4.app.Fragment
import
android.support.v7.widget.DefaultItemAnimator
import
android.support.v7.widget.LinearLayoutManager
import
android.support.v7.widget.RecyclerView
import
android.text.method.ScrollingMovementMethod
import
android.view.*
import
chat.rocket.android.R
import
chat.rocket.android.chatroom.presentation.ChatRoomPresenter
...
...
@@ -22,7 +23,7 @@ import chat.rocket.android.helper.KeyboardHelper
import
chat.rocket.android.helper.MessageParser
import
chat.rocket.android.util.extensions.*
import
chat.rocket.android.widget.emoji.Emoji
import
chat.rocket.android.widget.emoji.Emoji
BottomPicker
import
chat.rocket.android.widget.emoji.Emoji
Fragment
import
chat.rocket.android.widget.emoji.EmojiParser
import
dagger.android.support.AndroidSupportInjection
import
kotlinx.android.synthetic.main.fragment_chat_room.*
...
...
@@ -47,7 +48,7 @@ private const val BUNDLE_CHAT_ROOM_TYPE = "chat_room_type"
private
const
val
BUNDLE_IS_CHAT_ROOM_READ_ONLY
=
"is_chat_room_read_only"
private
const
val
REQUEST_CODE_FOR_PERFORM_SAF
=
42
class
ChatRoomFragment
:
Fragment
(),
ChatRoomView
,
Emoji
BottomPicker
.
OnEmojiClickCallback
{
class
ChatRoomFragment
:
Fragment
(),
ChatRoomView
,
Emoji
Fragment
.
OnEmojiClickCallback
{
@Inject
lateinit
var
presenter
:
ChatRoomPresenter
@Inject
lateinit
var
parser
:
MessageParser
private
lateinit
var
adapter
:
ChatRoomAdapter
...
...
@@ -225,7 +226,6 @@ class ChatRoomFragment : Fragment(), ChatRoomView, EmojiBottomPicker.OnEmojiClic
val
cursorPosition
=
text_message
.
selectionStart
text_message
.
text
.
insert
(
cursorPosition
,
EmojiParser
.
parse
(
emoji
.
shortname
))
text_message
.
setSelection
(
cursorPosition
+
emoji
.
unicode
.
length
)
KeyboardHelper
.
showSoftKeyboard
(
text_message
)
}
private
fun
setupComposer
()
{
...
...
@@ -234,6 +234,7 @@ class ChatRoomFragment : Fragment(), ChatRoomView, EmojiBottomPicker.OnEmojiClic
input_container
.
setVisible
(
false
)
}
else
{
var
playAnimation
=
true
text_message
.
movementMethod
=
ScrollingMovementMethod
()
text_message
.
asObservable
(
0
)
.
subscribe
({
t
->
if
(
t
.
isNotEmpty
()
&&
playAnimation
)
{
...
...
@@ -279,14 +280,24 @@ class ChatRoomFragment : Fragment(), ChatRoomView, EmojiBottomPicker.OnEmojiClic
button_add_reaction
.
setOnClickListener
{
activity
?.
let
{
KeyboardHelper
.
hideSoftKeyboard
(
it
)
val
emojiBottomPicker
=
EmojiBottomPicker
()
text_message
.
apply
{
addTextChangedListener
(
EmojiBottomPicker
.
EmojiTextWatcher
(
this
))
val
editor
=
text_message
val
emojiFragment
=
EmojiFragment
.
getOrAttach
(
it
,
R
.
id
.
emoji_fragment_placeholder
,
composer
)
with
(
emojiFragment
)
{
if
(!
isShown
())
{
show
()
}
else
{
if
(
softKeyboardVisible
)
{
KeyboardHelper
.
hideSoftKeyboard
(
it
)
}
else
{
KeyboardHelper
.
showSoftKeyboard
(
editor
)
}
}
}
emojiBottomPicker
.
show
(
it
.
supportFragmentManager
,
"EmojiBottomPicker"
)
}
}
addEmojiFragment
()
text_message
.
addTextChangedListener
(
EmojiFragment
.
EmojiTextWatcher
(
text_message
))
}
}
...
...
@@ -297,6 +308,12 @@ class ChatRoomFragment : Fragment(), ChatRoomView, EmojiBottomPicker.OnEmojiClic
})
}
private
fun
addEmojiFragment
()
{
activity
?.
let
{
EmojiFragment
.
getOrAttach
(
it
,
R
.
id
.
emoji_fragment_placeholder
,
composer
)
}
}
private
fun
clearActionMessage
()
{
citation
=
null
editingMessageId
=
null
...
...
app/src/main/java/chat/rocket/android/widget/emoji/CategoryPagerAdapter.kt
View file @
1e4ac51e
...
...
@@ -9,7 +9,7 @@ import android.view.View
import
android.view.ViewGroup
import
android.widget.TextView
import
chat.rocket.android.R
import
chat.rocket.android.widget.emoji.Emoji
BottomPicker
.OnEmojiClickCallback
import
chat.rocket.android.widget.emoji.Emoji
Fragment
.OnEmojiClickCallback
import
java.util.*
class
CategoryPagerAdapter
(
val
callback
:
OnEmojiClickCallback
)
:
PagerAdapter
()
{
...
...
@@ -25,9 +25,9 @@ class CategoryPagerAdapter(val callback: OnEmojiClickCallback) : PagerAdapter()
val
adapter
=
EmojiAdapter
(
layoutManager
.
spanCount
,
callback
)
val
category
=
EmojiCategory
.
values
().
get
(
position
)
val
emojis
=
if
(
category
!=
EmojiCategory
.
RECENTS
)
Emoji
Loader
.
getEmojisByCategory
(
category
)
Emoji
Repository
.
getEmojisByCategory
(
category
)
else
Emoji
Loader
.
getRecents
()
Emoji
Repository
.
getRecents
()
adapter
.
addEmojis
(
emojis
)
recycler
.
layoutManager
=
layoutManager
recycler
.
itemAnimator
=
DefaultItemAnimator
()
...
...
app/src/main/java/chat/rocket/android/widget/emoji/EmojiCategory.kt
View file @
1e4ac51e
...
...
@@ -35,7 +35,7 @@ enum class EmojiCategory {
abstract
fun
icon
():
CharSequence
protected
fun
getTextIconFor
(
text
:
String
):
CharSequence
{
val
span
=
EmojiTypefaceSpan
(
"sans-serif"
,
Emoji
Loader
.
cachedTypeface
)
val
span
=
EmojiTypefaceSpan
(
"sans-serif"
,
Emoji
Repository
.
cachedTypeface
)
return
SpannableString
.
valueOf
(
text
).
apply
{
setSpan
(
span
,
0
,
text
.
length
,
Spanned
.
SPAN_INCLUSIVE_INCLUSIVE
)
}
...
...
app/src/main/java/chat/rocket/android/widget/emoji/Emoji
BottomPicker
.kt
→
app/src/main/java/chat/rocket/android/widget/emoji/Emoji
Fragment
.kt
View file @
1e4ac51e
package
chat.rocket.android.widget.emoji
import
android.
app.Dialog
import
android.
graphics.Rect
import
android.os.Bundle
import
android.support.design.widget.BottomSheetBehavior
import
android.support.design.widget.BottomSheetDialog
import
android.support.annotation.IdRes
import
android.support.design.widget.TabLayout
import
android.support.v4.app.DialogFragment
import
android.support.v4.app.Fragment
import
android.support.v4.app.FragmentActivity
import
android.support.v4.view.ViewPager
import
android.text.Editable
import
android.text.TextWatcher
...
...
@@ -16,45 +16,44 @@ import android.view.ViewTreeObserver
import
android.widget.EditText
import
android.widget.TextView
import
chat.rocket.android.R
import
java.util.concurrent.atomic.AtomicReference
import
java.util.function.UnaryOperator
import
chat.rocket.android.util.extensions.setVisible
open
class
EmojiBottomPicker
:
Dialog
Fragment
()
{
class
EmojiFragment
:
Fragment
()
{
private
lateinit
var
viewPager
:
ViewPager
private
lateinit
var
tabLayout
:
TabLayout
private
lateinit
var
editor
:
View
internal
lateinit
var
parentContainer
:
ViewGroup
var
softKeyboardVisible
=
false
companion
object
{
const
val
PREF_EMOJI_RECENTS
=
"PREF_EMOJI_RECENTS"
const
val
PREF_KEYBOARD_HEIGHT
=
"PREF_KEYBOARD_HEIGHT"
val
TAG
:
String
=
EmojiFragment
::
class
.
java
.
simpleName
fun
newInstance
(
editor
:
View
)
=
EmojiFragment
().
apply
{
this
.
editor
=
editor
}
fun
getOrAttach
(
activity
:
FragmentActivity
,
@IdRes
containerId
:
Int
,
editor
:
View
):
EmojiFragment
{
val
fragmentManager
=
activity
.
supportFragmentManager
var
fragment
:
Fragment
?
=
fragmentManager
.
findFragmentByTag
(
TAG
)
return
if
(
fragment
==
null
)
{
fragment
=
newInstance
(
editor
)
fragment
.
parentContainer
=
activity
.
findViewById
(
containerId
)
fragmentManager
.
beginTransaction
()
.
replace
(
containerId
,
fragment
,
TAG
)
.
commit
()
fragment
}
else
{
fragment
as
EmojiFragment
}
}
}
override
fun
onCreateView
(
inflater
:
LayoutInflater
,
container
:
ViewGroup
?,
savedInstanceState
:
Bundle
?):
View
{
val
view
=
inflater
.
inflate
(
R
.
layout
.
emoji_popup_layout
,
container
,
false
)
parentContainer
=
view
.
findViewById
(
R
.
id
.
emoji_keyboard_container
)
viewPager
=
view
.
findViewById
(
R
.
id
.
pager_categories
)
tabLayout
=
view
.
findViewById
(
R
.
id
.
tabs
)
tabLayout
.
setupWithViewPager
(
viewPager
)
view
.
viewTreeObserver
.
addOnGlobalLayoutListener
(
object
:
ViewTreeObserver
.
OnGlobalLayoutListener
{
override
fun
onGlobalLayout
()
{
view
.
viewTreeObserver
.
removeOnGlobalLayoutListener
(
this
)
val
parent
=
dialog
.
findViewById
<
View
>(
R
.
id
.
design_bottom_sheet
)
parent
?.
let
{
val
bottomSheetBehavior
=
BottomSheetBehavior
.
from
(
parent
)
if
(
bottomSheetBehavior
!=
null
)
{
bottomSheetBehavior
.
setBottomSheetCallback
(
object
:
BottomSheetBehavior
.
BottomSheetCallback
()
{
override
fun
onSlide
(
bottomSheet
:
View
,
slideOffset
:
Float
)
{
}
override
fun
onStateChanged
(
bottomSheet
:
View
,
newState
:
Int
)
{
if
(
newState
==
BottomSheetBehavior
.
STATE_DRAGGING
)
{
bottomSheetBehavior
.
setState
(
BottomSheetBehavior
.
STATE_EXPANDED
);
}
}
})
bottomSheetBehavior
.
setState
(
BottomSheetBehavior
.
STATE_EXPANDED
)
}
}
}
})
return
view
}
...
...
@@ -70,10 +69,47 @@ open class EmojiBottomPicker : DialogFragment() {
}
}
activity
?.
let
{
val
decorView
=
it
.
getWindow
().
decorView
decorView
.
viewTreeObserver
.
addOnGlobalLayoutListener
(
object
:
ViewTreeObserver
.
OnGlobalLayoutListener
{
private
val
windowVisibleDisplayFrame
=
Rect
()
private
var
lastVisibleDecorViewHeight
:
Int
=
0
override
fun
onGlobalLayout
()
{
// Retrieve visible rectangle inside window.
decorView
.
getWindowVisibleDisplayFrame
(
windowVisibleDisplayFrame
)
val
visibleDecorViewHeight
=
windowVisibleDisplayFrame
.
height
()
// Decide whether keyboard is visible from changing decor view height.
if
(
lastVisibleDecorViewHeight
!=
0
)
{
if
(
lastVisibleDecorViewHeight
>
visibleDecorViewHeight
+
150
)
{
// Calculate current keyboard height (this includes also navigation bar height when in fullscreen mode).
val
currentKeyboardHeight
=
decorView
.
height
-
windowVisibleDisplayFrame
.
bottom
-
editor
.
measuredHeight
// Notify listener about keyboard being shown.
EmojiRepository
.
saveKeyboardHeight
(
currentKeyboardHeight
)
setKeyboardHeight
(
currentKeyboardHeight
)
softKeyboardVisible
=
true
show
()
}
else
if
(
lastVisibleDecorViewHeight
+
150
<
visibleDecorViewHeight
)
{
// Notify listener about keyboard being hidden.
softKeyboardVisible
=
false
hide
()
}
}
// Save current decor view height for the next call.
lastVisibleDecorViewHeight
=
visibleDecorViewHeight
}
})
}
val
storedHeight
=
EmojiRepository
.
getKeyboardHeight
()
if
(
storedHeight
>
0
)
{
setKeyboardHeight
(
storedHeight
)
}
viewPager
.
adapter
=
CategoryPagerAdapter
(
object
:
OnEmojiClickCallback
{
override
fun
onEmojiAdded
(
emoji
:
Emoji
)
{
dismiss
()
EmojiLoader
.
addToRecents
(
emoji
)
EmojiRepository
.
addToRecents
(
emoji
)
callback
.
onEmojiAdded
(
emoji
)
}
})
...
...
@@ -86,13 +122,14 @@ open class EmojiBottomPicker : DialogFragment() {
textView
.
text
=
category
.
icon
()
}
val
currentTab
=
if
(
Emoji
Loader
.
getRecents
().
isEmpty
())
EmojiCategory
.
PEOPLE
.
ordinal
else
val
currentTab
=
if
(
Emoji
Repository
.
getRecents
().
isEmpty
())
EmojiCategory
.
PEOPLE
.
ordinal
else
EmojiCategory
.
RECENTS
.
ordinal
viewPager
.
setCurrentItem
(
currentTab
)
}
override
fun
onCreateDialog
(
savedInstanceState
:
Bundle
?):
Dialog
{
return
BottomSheetDialog
(
context
!!
,
theme
)
private
fun
setKeyboardHeight
(
height
:
Int
)
{
parentContainer
.
layoutParams
.
height
=
height
parentContainer
.
requestLayout
()
}
class
EmojiTextWatcher
(
val
editor
:
EditText
)
:
TextWatcher
{
...
...
@@ -139,6 +176,16 @@ open class EmojiBottomPicker : DialogFragment() {
}
}
fun
show
()
{
parentContainer
.
setVisible
(
true
)
}
fun
hide
()
{
parentContainer
.
setVisible
(
false
)
}
fun
isShown
()
=
parentContainer
.
visibility
==
View
.
VISIBLE
interface
OnEmojiClickCallback
{
/**
* Callback triggered after an emoji is selected on the picker.
...
...
app/src/main/java/chat/rocket/android/widget/emoji/EmojiLoader.kt
deleted
100644 → 0
View file @
80cacca2
package
chat.rocket.android.widget.emoji
import
android.content.Context
import
android.content.SharedPreferences
import
android.graphics.Typeface
import
android.os.Build
import
org.json.JSONArray
import
org.json.JSONObject
import
java.io.BufferedReader
import
java.io.InputStream
import
java.io.InputStreamReader
import
java.util.*
import
java.util.regex.Pattern
class
EmojiLoader
{
companion
object
{
private
val
shortNameToUnicode
=
HashMap
<
String
,
String
>()
private
val
SHORTNAME_PATTERN
=
Pattern
.
compile
(
":([-+\\w]+):"
)
private
val
ALL_EMOJIS
=
mutableListOf
<
Emoji
>()
private
lateinit
var
preferences
:
SharedPreferences
internal
lateinit
var
cachedTypeface
:
Typeface
fun
load
(
context
:
Context
,
path
:
String
=
"emoji.json"
)
{
preferences
=
context
.
getSharedPreferences
(
"emoji"
,
Context
.
MODE_PRIVATE
)
ALL_EMOJIS
.
clear
()
cachedTypeface
=
Typeface
.
createFromAsset
(
context
.
assets
,
"fonts/emojione-android.ttf"
)
val
stream
=
context
.
assets
.
open
(
path
)
val
emojis
=
loadEmojis
(
stream
)
emojis
.
forEach
{
val
unicodeIntList
=
mutableListOf
<
Int
>()
it
.
unicode
.
split
(
"-"
).
forEach
{
val
value
=
it
.
toInt
(
16
)
if
(
value
>=
0
x10000
)
{
val
surrogatePair
=
calculateSurrogatePairs
(
value
)
unicodeIntList
.
add
(
surrogatePair
.
first
)
unicodeIntList
.
add
(
surrogatePair
.
second
)
}
else
{
unicodeIntList
.
add
(
value
)
}
}
val
unicodeIntArray
=
unicodeIntList
.
toIntArray
()
val
unicode
=
String
(
unicodeIntArray
,
0
,
unicodeIntArray
.
size
)
ALL_EMOJIS
.
add
(
it
.
copy
(
unicode
=
unicode
))
shortNameToUnicode
.
apply
{
put
(
it
.
shortname
,
unicode
)
}
}
}
/**
* Get all loaded emojis as list of Emoji objects.
*
* @return All emojis for all categories.
*/
fun
getAll
()
=
ALL_EMOJIS
/**
* Get all emojis for a given category.
*
* @param category Emoji category such as: PEOPLE, NATURE, ETC
*
* @return All emoji from specified category
*/
fun
getEmojisByCategory
(
category
:
EmojiCategory
):
List
<
Emoji
>
{
return
ALL_EMOJIS
.
filter
{
it
.
category
.
toLowerCase
()
==
category
.
name
.
toLowerCase
()
}
}
/**
* Get the emoji given by a specified shortname. Returns null if can't find any.
*
* @param shortname The emoji shortname to search for
*
* @return Emoji given by shortname or null
*/
fun
getEmojiByShortname
(
shortname
:
String
)
=
ALL_EMOJIS
.
firstOrNull
{
it
.
shortname
==
shortname
}
/**
* Add an emoji to the Recents category.
*/
fun
addToRecents
(
emoji
:
Emoji
)
{
val
emojiShortname
=
emoji
.
shortname
val
recentsJson
=
JSONObject
(
preferences
.
getString
(
EmojiBottomPicker
.
PREF_EMOJI_RECENTS
,
"{}"
))
if
(
recentsJson
.
has
(
emojiShortname
))
{
val
useCount
=
recentsJson
.
getInt
(
emojiShortname
)
recentsJson
.
put
(
emojiShortname
,
useCount
+
1
)
}
else
{
recentsJson
.
put
(
emojiShortname
,
1
)
}
preferences
.
edit
().
putString
(
EmojiBottomPicker
.
PREF_EMOJI_RECENTS
,
recentsJson
.
toString
()).
apply
()
}
/**
* Get all recently used emojis ordered by usage count.
*
* @return All recent emojis ordered by usage.
*/
fun
getRecents
():
List
<
Emoji
>
{
val
list
=
mutableListOf
<
Emoji
>()
val
recentsJson
=
JSONObject
(
preferences
.
getString
(
EmojiBottomPicker
.
PREF_EMOJI_RECENTS
,
"{}"
))
for
(
shortname
in
recentsJson
.
keys
())
{
val
emoji
=
getEmojiByShortname
(
shortname
)
emoji
?.
let
{
val
useCount
=
recentsJson
.
getInt
(
it
.
shortname
)
list
.
add
(
it
.
copy
(
count
=
useCount
))
}
}
Collections
.
sort
(
list
,
{
o1
,
o2
->
o2
.
count
-
o1
.
count
})
return
list
}
/**
* Replace shortnames to unicode characters.
*/
fun
shortnameToUnicode
(
input
:
CharSequence
,
removeIfUnsupported
:
Boolean
):
String
{
val
matcher
=
SHORTNAME_PATTERN
.
matcher
(
input
)
val
supported
=
Build
.
VERSION
.
SDK_INT
>=
16
var
result
:
String
=
input
.
toString
()
while
(
matcher
.
find
())
{
val
unicode
=
shortNameToUnicode
.
get
(
":${matcher.group(1)}:"
)
if
(
unicode
==
null
)
{
continue
}
if
(
supported
)
{
result
=
result
.
replace
(
":"
+
matcher
.
group
(
1
)
+
":"
,
unicode
)
}
else
if
(!
supported
&&
removeIfUnsupported
)
{
result
=
result
.
replace
(
":"
+
matcher
.
group
(
1
)
+
":"
,
""
)
}
}
return
result
}
private
fun
loadEmojis
(
stream
:
InputStream
):
List
<
Emoji
>
{
val
emojisJSON
=
JSONArray
(
inputStreamToString
(
stream
))
val
emojis
=
ArrayList
<
Emoji
>(
emojisJSON
.
length
());
for
(
i
in
0
until
emojisJSON
.
length
())
{
val
emoji
=
buildEmojiFromJSON
(
emojisJSON
.
getJSONObject
(
i
))
emoji
?.
let
{
emojis
.
add
(
it
)
}
}
return
emojis
}
private
fun
buildEmojiFromJSON
(
json
:
JSONObject
):
Emoji
?
{
if
(!
json
.
has
(
"shortname"
)
||
!
json
.
has
(
"unicode"
))
{
return
null
}
return
Emoji
(
shortname
=
json
.
getString
(
"shortname"
),
unicode
=
json
.
getString
(
"unicode"
),
shortnameAlternates
=
buildStringListFromJsonArray
(
json
.
getJSONArray
(
"shortnameAlternates"
)),
category
=
json
.
getString
(
"category"
),
keywords
=
buildStringListFromJsonArray
(
json
.
getJSONArray
(
"keywords"
)))
}
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
))
}
return
list
}
private
fun
inputStreamToString
(
stream
:
InputStream
):
String
{
val
sb
=
StringBuilder
()
val
isr
=
InputStreamReader
(
stream
,
Charsets
.
UTF_8
)
val
br
=
BufferedReader
(
isr
)
var
read
:
String
?
=
br
.
readLine
()
while
(
read
!=
null
)
{
sb
.
append
(
read
)
read
=
br
.
readLine
()
}
br
.
close
()
return
sb
.
toString
()
}
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/EmojiParser.kt
View file @
1e4ac51e
...
...
@@ -14,7 +14,7 @@ class EmojiParser {
* @return A rendered Spannable containing any supported emoji.
*/
fun
parse
(
text
:
CharSequence
):
CharSequence
{
val
unicodedText
=
Emoji
Loader
.
shortnameToUnicode
(
text
,
true
)
val
unicodedText
=
Emoji
Repository
.
shortnameToUnicode
(
text
,
true
)
val
spannableString
=
SpannableString
.
valueOf
(
unicodedText
)
// Look for groups of emojis, set a CustomTypefaceSpan with the emojione font
val
length
=
spannableString
.
length
...
...
@@ -31,14 +31,14 @@ class EmojiParser {
inEmoji
=
true
}
else
{
if
(
inEmoji
)
{
spannableString
.
setSpan
(
EmojiTypefaceSpan
(
"sans-serif"
,
Emoji
Loader
.
cachedTypeface
),
spannableString
.
setSpan
(
EmojiTypefaceSpan
(
"sans-serif"
,
Emoji
Repository
.
cachedTypeface
),
emojiStart
,
offset
,
Spanned
.
SPAN_EXCLUSIVE_EXCLUSIVE
)
}
inEmoji
=
false
}
offset
+=
count
if
(
offset
>=
length
&&
inEmoji
)
{
spannableString
.
setSpan
(
EmojiTypefaceSpan
(
"sans-serif"
,
Emoji
Loader
.
cachedTypeface
),
spannableString
.
setSpan
(
EmojiTypefaceSpan
(
"sans-serif"
,
Emoji
Repository
.
cachedTypeface
),
emojiStart
,
offset
,
Spanned
.
SPAN_EXCLUSIVE_EXCLUSIVE
)
}
}
...
...
app/src/main/java/chat/rocket/android/widget/emoji/EmojiRepository.kt
0 → 100644
View file @
1e4ac51e
package
chat.rocket.android.widget.emoji
import
android.content.Context
import
android.content.SharedPreferences
import
android.graphics.Typeface
import
android.os.Build
import
org.json.JSONArray
import
org.json.JSONObject
import
java.io.BufferedReader
import
java.io.InputStream
import
java.io.InputStreamReader
import
java.util.*
import
java.util.regex.Pattern
object
EmojiRepository
{
private
val
shortNameToUnicode
=
HashMap
<
String
,
String
>()
private
val
SHORTNAME_PATTERN
=
Pattern
.
compile
(
":([-+\\w]+):"
)
private
val
ALL_EMOJIS
=
mutableListOf
<
Emoji
>()
private
lateinit
var
preferences
:
SharedPreferences
internal
lateinit
var
cachedTypeface
:
Typeface
fun
load
(
context
:
Context
,
path
:
String
=
"emoji.json"
)
{
preferences
=
context
.
getSharedPreferences
(
"emoji"
,
Context
.
MODE_PRIVATE
)
ALL_EMOJIS
.
clear
()
cachedTypeface
=
Typeface
.
createFromAsset
(
context
.
assets
,
"fonts/emojione-android.ttf"
)
val
stream
=
context
.
assets
.
open
(
path
)
val
emojis
=
loadEmojis
(
stream
)
emojis
.
forEach
{
val
unicodeIntList
=
mutableListOf
<
Int
>()
it
.
unicode
.
split
(
"-"
).
forEach
{
val
value
=
it
.
toInt
(
16
)
if
(
value
>=
0
x10000
)
{
val
surrogatePair
=
calculateSurrogatePairs
(
value
)
unicodeIntList
.
add
(
surrogatePair
.
first
)
unicodeIntList
.
add
(
surrogatePair
.
second
)
}
else
{
unicodeIntList
.
add
(
value
)
}
}
val
unicodeIntArray
=
unicodeIntList
.
toIntArray
()
val
unicode
=
String
(
unicodeIntArray
,
0
,
unicodeIntArray
.
size
)
ALL_EMOJIS
.
add
(
it
.
copy
(
unicode
=
unicode
))
shortNameToUnicode
.
apply
{
put
(
it
.
shortname
,
unicode
)
}
}
}
/**
* Get all loaded emojis as list of Emoji objects.
*
* @return All emojis for all categories.
*/
fun
getAll
()
=
ALL_EMOJIS
/**
* Get all emojis for a given category.
*
* @param category Emoji category such as: PEOPLE, NATURE, ETC
*
* @return All emoji from specified category
*/
fun
getEmojisByCategory
(
category
:
EmojiCategory
):
List
<
Emoji
>
{
return
ALL_EMOJIS
.
filter
{
it
.
category
.
toLowerCase
()
==
category
.
name
.
toLowerCase
()
}
}
/**
* Get the emoji given by a specified shortname. Returns null if can't find any.
*
* @param shortname The emoji shortname to search for
*
* @return Emoji given by shortname or null
*/
fun
getEmojiByShortname
(
shortname
:
String
)
=
ALL_EMOJIS
.
firstOrNull
{
it
.
shortname
==
shortname
}
/**
* Add an emoji to the Recents category.
*/
fun
addToRecents
(
emoji
:
Emoji
)
{
val
emojiShortname
=
emoji
.
shortname
val
recentsJson
=
JSONObject
(
preferences
.
getString
(
EmojiFragment
.
PREF_EMOJI_RECENTS
,
"{}"
))
if
(
recentsJson
.
has
(
emojiShortname
))
{
val
useCount
=
recentsJson
.
getInt
(
emojiShortname
)
recentsJson
.
put
(
emojiShortname
,
useCount
+
1
)
}
else
{
recentsJson
.
put
(
emojiShortname
,
1
)
}
preferences
.
edit
().
putString
(
EmojiFragment
.
PREF_EMOJI_RECENTS
,
recentsJson
.
toString
()).
apply
()
}
/**
* Get all recently used emojis ordered by usage count.
*
* @return All recent emojis ordered by usage.
*/
fun
getRecents
():
List
<
Emoji
>
{
val
list
=
mutableListOf
<
Emoji
>()
val
recentsJson
=
JSONObject
(
preferences
.
getString
(
EmojiFragment
.
PREF_EMOJI_RECENTS
,
"{}"
))
for
(
shortname
in
recentsJson
.
keys
())
{
val
emoji
=
getEmojiByShortname
(
shortname
)
emoji
?.
let
{
val
useCount
=
recentsJson
.
getInt
(
it
.
shortname
)
list
.
add
(
it
.
copy
(
count
=
useCount
))
}
}
Collections
.
sort
(
list
,
{
o1
,
o2
->
o2
.
count
-
o1
.
count
})
return
list
}
/**
* Store current soft keyboard height for later reference.
*/
fun
saveKeyboardHeight
(
height
:
Int
)
{
if
(
height
<=
0
)
{
return
}
preferences
.
edit
()
.
putInt
(
EmojiFragment
.
PREF_KEYBOARD_HEIGHT
,
height
)
.
apply
()
}
/**
* Get stored keyboard height.
*
* @return Height of the current soft keyboard.
*/
fun
getKeyboardHeight
()
=
preferences
.
getInt
(
EmojiFragment
.
PREF_KEYBOARD_HEIGHT
,
0
)
/**
* Replace shortnames to unicode characters.
*/
fun
shortnameToUnicode
(
input
:
CharSequence
,
removeIfUnsupported
:
Boolean
):
String
{
val
matcher
=
SHORTNAME_PATTERN
.
matcher
(
input
)
val
supported
=
Build
.
VERSION
.
SDK_INT
>=
16
var
result
:
String
=
input
.
toString
()
while
(
matcher
.
find
())
{
val
unicode
=
shortNameToUnicode
.
get
(
":${matcher.group(1)}:"
)
if
(
unicode
==
null
)
{
continue
}
if
(
supported
)
{
result
=
result
.
replace
(
":"
+
matcher
.
group
(
1
)
+
":"
,
unicode
)
}
else
if
(!
supported
&&
removeIfUnsupported
)
{
result
=
result
.
replace
(
":"
+
matcher
.
group
(
1
)
+
":"
,
""
)
}
}
return
result
}
private
fun
loadEmojis
(
stream
:
InputStream
):
List
<
Emoji
>
{
val
emojisJSON
=
JSONArray
(
inputStreamToString
(
stream
))
val
emojis
=
ArrayList
<
Emoji
>(
emojisJSON
.
length
());
for
(
i
in
0
until
emojisJSON
.
length
())
{
val
emoji
=
buildEmojiFromJSON
(
emojisJSON
.
getJSONObject
(
i
))
emoji
?.
let
{
emojis
.
add
(
it
)
}
}
return
emojis
}
private
fun
buildEmojiFromJSON
(
json
:
JSONObject
):
Emoji
?
{
if
(!
json
.
has
(
"shortname"
)
||
!
json
.
has
(
"unicode"
))
{
return
null
}
return
Emoji
(
shortname
=
json
.
getString
(
"shortname"
),
unicode
=
json
.
getString
(
"unicode"
),
shortnameAlternates
=
buildStringListFromJsonArray
(
json
.
getJSONArray
(
"shortnameAlternates"
)),
category
=
json
.
getString
(
"category"
),
keywords
=
buildStringListFromJsonArray
(
json
.
getJSONArray
(
"keywords"
)))
}
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
))
}
return
list
}
private
fun
inputStreamToString
(
stream
:
InputStream
):
String
{
val
sb
=
StringBuilder
()
val
isr
=
InputStreamReader
(
stream
,
Charsets
.
UTF_8
)
val
br
=
BufferedReader
(
isr
)
var
read
:
String
?
=
br
.
readLine
()
while
(
read
!=
null
)
{
sb
.
append
(
read
)
read
=
br
.
readLine
()
}
br
.
close
()
return
sb
.
toString
()
}
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/res/layout/emoji_category_layout.xml
View file @
1e4ac51e
...
...
@@ -5,9 +5,11 @@
android:layout_height=
"wrap_content"
>
<android.support.v7.widget.RecyclerView
xmlns:android=
"http://schemas.android.com/apk/res/android"
xmlns:app=
"http://schemas.android.com/apk/res-auto"
android:id=
"@+id/emojiRecyclerView"
android:layout_width=
"match_parent"
android:layout_height=
"
wrap_cont
ent"
android:layout_height=
"
match_par
ent"
android:layout_marginEnd=
"8dp"
android:layout_marginStart=
"8dp"
app:layout_constraintBottom_toBottomOf=
"parent"
...
...
app/src/main/res/layout/emoji_popup_layout.xml
View file @
1e4ac51e
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout
xmlns:android=
"http://schemas.android.com/apk/res/android"
xmlns:app=
"http://schemas.android.com/apk/res-auto"
android:id=
"@+id/emoji_keyboard_container"
android:visibility=
"gone"
android:layout_width=
"match_parent"
android:layout_height=
"250dp"
>
android:layout_height=
"200dp"
>
<View
android:id=
"@+id/divider"
android:layout_width=
"match_parent"
android:layout_height=
"1dp"
android:background=
"@color/colorDividerMessageComposer"
app:layout_constraintTop_toTopOf=
"parent"
app:layout_constraintEnd_toEndOf=
"parent"
app:layout_constraintStart_toStartOf=
"parent"
app:layout_constraintBottom_toTopOf=
"@+id/tabs"
/>
<android.support.design.widget.TabLayout
android:id=
"@+id/tabs"
android:layout_width=
"
match_parent
"
android:layout_width=
"
0dp
"
android:layout_height=
"wrap_content"
app:layout_constraintBottom_toTopOf=
"@+id/pager_categories"
app:layout_constraintEnd_toEndOf=
"parent"
app:layout_constraintStart_toStartOf=
"parent"
app:layout_constraintTop_toTopOf=
"parent"
app:tabGravity=
"fill"
app:tabBackground=
"@color/whitesmoke"
app:tabGravity=
"fill"
app:tabMaxWidth=
"48dp"
app:tabMode=
"scrollable"
/>
<android.support.v4.view.ViewPager
android:id=
"@+id/pager_categories"
android:layout_width=
"
match_parent
"
android:layout_height=
"
20
0dp"
android:layout_width=
"
0dp
"
android:layout_height=
"0dp"
android:layout_marginEnd=
"8dp"
android:layout_marginStart=
"8dp"
android:background=
"@color/white"
...
...
app/src/main/res/layout/message_composer.xml
View file @
1e4ac51e
<?xml version="1.0" encoding="utf-8"?>
<
android.support.constraint.Constraint
Layout
xmlns:android=
"http://schemas.android.com/apk/res/android"
<
Linear
Layout
xmlns:android=
"http://schemas.android.com/apk/res/android"
xmlns:app=
"http://schemas.android.com/apk/res-auto"
android:layout_width=
"match_parent"
android:layout_height=
"wrap_content"
>
android:layout_height=
"wrap_content"
android:orientation=
"vertical"
>
<
View
android:id=
"@+id/
divid
er"
<
android.support.constraint.ConstraintLayout
android:id=
"@+id/
compos
er"
android:layout_width=
"match_parent"
android:layout_height=
"1dp"
android:background=
"@color/colorDividerMessageComposer"
/>
android:layout_height=
"wrap_content"
>
<TextView
android:id=
"@+id/text_room_is_read_only"
android:layout_width=
"match_parent"
android:layout_height=
"45dp"
android:background=
"@color/white"
android:gravity=
"center"
android:text=
"@string/msg_this_room_is_read_only"
android:textColor=
"@color/black"
android:visibility=
"gone"
app:layout_constraintTop_toBottomOf=
"@+id/divider"
/>
<View
android:id=
"@+id/divider"
android:layout_width=
"match_parent"
android:layout_height=
"1dp"
android:background=
"@color/colorDividerMessageComposer"
/>
<LinearLayout
android:id=
"@+id/input_container
"
android:layout_width=
"match_parent"
android:layout_height=
"wrap_content
"
android:layout_marginEnd=
"10dp
"
android:layout_marginStart=
"10dp
"
android:layout_marginTop=
"10dp
"
android:orientation=
"horizontal
"
android:paddingBottom=
"10dp
"
app:layout_constraintTop_toBottomOf=
"@+id/divider"
>
<TextView
android:id=
"@+id/text_room_is_read_only
"
android:layout_width=
"match_parent"
android:layout_height=
"45dp
"
android:background=
"@color/white
"
android:gravity=
"center
"
android:text=
"@string/msg_this_room_is_read_only
"
android:textColor=
"@color/black
"
android:visibility=
"gone
"
app:layout_constraintTop_toBottomOf=
"@+id/divider"
/
>
<
ImageButton
android:id=
"@+id/
button_add_reaction
"
android:layout_width=
"
wrap_cont
ent"
<
LinearLayout
android:id=
"@+id/
input_container
"
android:layout_width=
"
match_par
ent"
android:layout_height=
"wrap_content"
android:layout_marginEnd=
"16dp"
android:background=
"?attr/selectableItemBackgroundBorderless"
android:contentDescription=
"@string/msg_content_description_show_attachment_options"
android:src=
"@drawable/ic_reaction_24dp"
/>
android:layout_marginEnd=
"10dp"
android:layout_marginStart=
"10dp"
android:gravity=
"center_vertical"
android:orientation=
"horizontal"
android:paddingBottom=
"10dp"
android:paddingTop=
"10dp"
app:layout_constraintTop_toBottomOf=
"@+id/divider"
>
<EditText
android:id=
"@+id/text_message
"
android:layout_width=
"0dp
"
android:layout_height=
"wrap_content"
android:layout_marginEnd=
"16dp"
android:layout_weight=
"1
"
android:background=
"@android:color/transparent
"
android:hint=
"@string/msg_messag
e"
android:maxLines=
"4
"
/>
<ImageButton
android:id=
"@+id/button_add_reaction
"
android:layout_width=
"wrap_content
"
android:layout_height=
"wrap_content"
android:layout_marginEnd=
"16dp"
android:background=
"?attr/selectableItemBackgroundBorderless
"
android:contentDescription=
"@string/msg_content_description_show_attachment_options
"
android:clickable=
"fals
e"
android:src=
"@drawable/ic_reaction_24dp
"
/>
<ImageButton
android:id=
"@+id/button_show_attachment_options"
android:layout_width=
"wrap_content"
android:layout_height=
"wrap_content"
android:background=
"?attr/selectableItemBackgroundBorderless"
android:contentDescription=
"@string/msg_content_description_show_attachment_options"
android:src=
"@drawable/ic_add_24dp"
/>
<EditText
android:id=
"@+id/text_message"
android:layout_width=
"0dp"
android:layout_height=
"wrap_content"
android:layout_marginEnd=
"16dp"
android:layout_weight=
"1"
android:background=
"@android:color/transparent"
android:hint=
"@string/msg_message"
android:maxLines=
"4"
android:scrollbars=
"vertical"
/>
<ImageButton
android:id=
"@+id/button_send"
android:layout_width=
"wrap_content"
android:layout_height=
"wrap_content"
android:background=
"?attr/selectableItemBackgroundBorderless"
android:contentDescription=
"@string/msg_content_description_send_message"
android:src=
"@drawable/ic_send_24dp"
android:visibility=
"gone"
/>
</LinearLayout>
<ImageButton
android:id=
"@+id/button_show_attachment_options"
android:layout_width=
"wrap_content"
android:layout_height=
"wrap_content"
android:background=
"?attr/selectableItemBackgroundBorderless"
android:contentDescription=
"@string/msg_content_description_show_attachment_options"
android:src=
"@drawable/ic_add_24dp"
/>
<ImageButton
android:id=
"@+id/button_send"
android:layout_width=
"wrap_content"
android:layout_height=
"wrap_content"
android:background=
"?attr/selectableItemBackgroundBorderless"
android:contentDescription=
"@string/msg_content_description_send_message"
android:src=
"@drawable/ic_send_24dp"
android:visibility=
"gone"
/>
</LinearLayout>
</android.support.constraint.ConstraintLayout>
<FrameLayout
android:id=
"@+id/emoji_fragment_placeholder"
android:layout_width=
"match_parent"
android:layout_height=
"wrap_content"
/>
</android.support.constraint.ConstraintLayout>
\ No newline at end of file
</LinearLayout>
\ 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