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
4e3936ca
Unverified
Commit
4e3936ca
authored
Aug 29, 2018
by
Filipe de Lima Brito
Committed by
GitHub
Aug 29, 2018
Browse files
Options
Browse Files
Download
Plain Diff
Merge branch 'develop' into new/google-analytics-for-firebase
parents
8ce5d9d3
9e631abd
Changes
44
Hide whitespace changes
Inline
Side-by-side
Showing
44 changed files
with
958 additions
and
163 deletions
+958
-163
build.gradle
app/build.gradle
+2
-0
RocketChatApplication.kt
...ain/java/chat/rocket/android/app/RocketChatApplication.kt
+0
-2
ActionsAttachmentViewHolder.kt
...t/android/chatroom/adapter/ActionsAttachmentViewHolder.kt
+43
-0
ActionsListAdapter.kt
...hat/rocket/android/chatroom/adapter/ActionsListAdapter.kt
+73
-0
ChatRoomAdapter.kt
...a/chat/rocket/android/chatroom/adapter/ChatRoomAdapter.kt
+39
-3
MessageViewHolder.kt
...chat/rocket/android/chatroom/adapter/MessageViewHolder.kt
+39
-13
ChatRoomView.kt
...chat/rocket/android/chatroom/presentation/ChatRoomView.kt
+0
-5
ChatRoomFragment.kt
.../java/chat/rocket/android/chatroom/ui/ChatRoomFragment.kt
+37
-11
ActionsAttachmentUiModel.kt
...cket/android/chatroom/uimodel/ActionsAttachmentUiModel.kt
+29
-0
BaseUiModel.kt
.../java/chat/rocket/android/chatroom/uimodel/BaseUiModel.kt
+2
-1
UiModelMapper.kt
...ava/chat/rocket/android/chatroom/uimodel/UiModelMapper.kt
+18
-1
MessageParser.kt
...src/main/java/chat/rocket/android/helper/MessageParser.kt
+21
-8
MainPresenter.kt
...va/chat/rocket/android/main/presentation/MainPresenter.kt
+38
-0
MainActivity.kt
...src/main/java/chat/rocket/android/main/ui/MainActivity.kt
+1
-0
Text.kt
...src/main/java/chat/rocket/android/util/extensions/Text.kt
+3
-2
item_action_button.xml
app/src/main/res/layout/item_action_button.xml
+29
-0
item_actions_attachment.xml
app/src/main/res/layout/item_actions_attachment.xml
+57
-0
dimens.xml
app/src/main/res/values/dimens.xml
+1
-0
build.gradle
build.gradle
+1
-1
dependencies.gradle
dependencies.gradle
+4
-1
build.gradle
emoji/build.gradle
+10
-0
proguard-rules.pro
emoji/proguard-rules.pro
+9
-0
ComposerEditText.kt
...c/main/java/chat/rocket/android/emoji/ComposerEditText.kt
+22
-3
Emoji.kt
emoji/src/main/java/chat/rocket/android/emoji/Emoji.kt
+17
-9
EmojiDao.kt
emoji/src/main/java/chat/rocket/android/emoji/EmojiDao.kt
+45
-0
EmojiKeyboardPopup.kt
...main/java/chat/rocket/android/emoji/EmojiKeyboardPopup.kt
+15
-9
EmojiParser.kt
emoji/src/main/java/chat/rocket/android/emoji/EmojiParser.kt
+78
-8
EmojiPickerPopup.kt
...c/main/java/chat/rocket/android/emoji/EmojiPickerPopup.kt
+7
-3
EmojiRepository.kt
...rc/main/java/chat/rocket/android/emoji/EmojiRepository.kt
+150
-61
EmojiCategory.kt
.../java/chat/rocket/android/emoji/internal/EmojiCategory.kt
+7
-2
EmojiGlideModule.kt
...va/chat/rocket/android/emoji/internal/EmojiGlideModule.kt
+15
-0
EmojiPagerAdapter.kt
...a/chat/rocket/android/emoji/internal/EmojiPagerAdapter.kt
+58
-17
Extensions.kt
...ain/java/chat/rocket/android/emoji/internal/Extensions.kt
+5
-0
EmojiDatabase.kt
...va/chat/rocket/android/emoji/internal/db/EmojiDatabase.kt
+48
-0
StringListConverter.kt
...t/rocket/android/emoji/internal/db/StringListConverter.kt
+16
-0
ic_emoji_custom.png
emoji/src/main/res/drawable-hdpi/ic_emoji_custom.png
+0
-0
ic_emoji_custom.png
emoji/src/main/res/drawable-mdpi/ic_emoji_custom.png
+0
-0
ic_emoji_custom.png
emoji/src/main/res/drawable-xhdpi/ic_emoji_custom.png
+0
-0
ic_emoji_custom.png
emoji/src/main/res/drawable-xxhdpi/ic_emoji_custom.png
+0
-0
ic_emoji_custom.png
emoji/src/main/res/drawable-xxxhdpi/ic_emoji_custom.png
+0
-0
emoji_image_row_item.xml
emoji/src/main/res/layout/emoji_image_row_item.xml
+14
-0
emoji_row_item.xml
emoji/src/main/res/layout/emoji_row_item.xml
+1
-0
dimens.xml
emoji/src/main/res/values/dimens.xml
+3
-2
gradle-wrapper.properties
gradle/wrapper/gradle-wrapper.properties
+1
-1
No files found.
app/build.gradle
View file @
4e3936ca
...
...
@@ -134,6 +134,8 @@ dependencies {
implementation
libraries
.
frescoWebP
implementation
libraries
.
frescoAnimatedWebP
implementation
libraries
.
glide
kapt
libraries
.
kotshiCompiler
implementation
libraries
.
kotshiApi
...
...
app/src/main/java/chat/rocket/android/app/RocketChatApplication.kt
View file @
4e3936ca
...
...
@@ -18,7 +18,6 @@ import chat.rocket.android.server.domain.GetCurrentServerInteractor
import
chat.rocket.android.server.domain.GetSettingsInteractor
import
chat.rocket.android.server.domain.SITE_URL
import
chat.rocket.android.server.domain.TokenRepository
import
chat.rocket.android.emoji.EmojiRepository
import
chat.rocket.android.util.setupFabric
import
com.facebook.drawee.backends.pipeline.DraweeConfig
import
com.facebook.drawee.backends.pipeline.Fresco
...
...
@@ -84,7 +83,6 @@ class RocketChatApplication : Application(), HasActivityInjector, HasServiceInje
context
=
WeakReference
(
applicationContext
)
AndroidThreeTen
.
init
(
this
)
EmojiRepository
.
load
(
this
)
setupFabric
(
this
)
setupFresco
()
...
...
app/src/main/java/chat/rocket/android/chatroom/adapter/ActionsAttachmentViewHolder.kt
0 → 100644
View file @
4e3936ca
package
chat.rocket.android.chatroom.adapter
import
android.view.View
import
chat.rocket.android.chatroom.uimodel.ActionsAttachmentUiModel
import
chat.rocket.android.emoji.EmojiReactionListener
import
chat.rocket.core.model.attachment.actions.Action
import
chat.rocket.core.model.attachment.actions.ButtonAction
import
kotlinx.android.synthetic.main.item_actions_attachment.view.*
import
androidx.recyclerview.widget.LinearLayoutManager
import
timber.log.Timber
class
ActionsAttachmentViewHolder
(
itemView
:
View
,
listener
:
ActionsListener
,
reactionListener
:
EmojiReactionListener
?
=
null
,
var
actionAttachmentOnClickListener
:
ActionAttachmentOnClickListener
)
:
BaseViewHolder
<
ActionsAttachmentUiModel
>(
itemView
,
listener
,
reactionListener
)
{
init
{
with
(
itemView
)
{
setupActionMenu
(
actions_attachment_container
)
}
}
override
fun
bindViews
(
data
:
ActionsAttachmentUiModel
)
{
val
actions
=
data
.
actions
val
alignment
=
data
.
buttonAlignment
Timber
.
d
(
"no of actions : ${actions.size} : $actions"
)
with
(
itemView
)
{
title
.
text
=
data
.
title
?:
""
actions_list
.
layoutManager
=
LinearLayoutManager
(
itemView
.
context
,
when
(
alignment
)
{
"horizontal"
->
LinearLayoutManager
.
HORIZONTAL
else
->
LinearLayoutManager
.
VERTICAL
//Default
},
false
)
actions_list
.
adapter
=
ActionsListAdapter
(
actions
,
actionAttachmentOnClickListener
)
}
}
}
interface
ActionAttachmentOnClickListener
{
fun
onActionClicked
(
view
:
View
,
action
:
Action
)
}
\ No newline at end of file
app/src/main/java/chat/rocket/android/chatroom/adapter/ActionsListAdapter.kt
0 → 100644
View file @
4e3936ca
package
chat.rocket.android.chatroom.adapter
import
android.view.View
import
android.view.ViewGroup
import
androidx.core.view.isVisible
import
androidx.recyclerview.widget.RecyclerView
import
chat.rocket.android.R
import
chat.rocket.android.util.extensions.inflate
import
chat.rocket.core.model.attachment.actions.Action
import
chat.rocket.core.model.attachment.actions.ButtonAction
import
com.facebook.drawee.backends.pipeline.Fresco
import
kotlinx.android.synthetic.main.item_action_button.view.*
import
timber.log.Timber
class
ActionsListAdapter
(
actions
:
List
<
Action
>,
var
actionAttachmentOnClickListener
:
ActionAttachmentOnClickListener
)
:
RecyclerView
.
Adapter
<
ActionsListAdapter
.
ViewHolder
>()
{
var
actions
:
List
<
Action
>
=
actions
inner
class
ViewHolder
(
var
layout
:
View
)
:
RecyclerView
.
ViewHolder
(
layout
)
{
lateinit
var
action
:
ButtonAction
private
val
onClickListener
=
View
.
OnClickListener
{
actionAttachmentOnClickListener
.
onActionClicked
(
it
,
action
)
}
init
{
with
(
itemView
)
{
action_button
.
setOnClickListener
(
onClickListener
)
action_image_button
.
setOnClickListener
(
onClickListener
)
}
}
fun
bindAction
(
action
:
Action
)
{
with
(
itemView
)
{
Timber
.
d
(
"action : $action"
)
this
@ViewHolder
.
action
=
action
as
ButtonAction
if
(
action
.
imageUrl
!=
null
)
{
action_button
.
isVisible
=
false
action_image_button
.
isVisible
=
true
//Image button
val
controller
=
Fresco
.
newDraweeControllerBuilder
().
apply
{
setUri
(
action
.
imageUrl
)
autoPlayAnimations
=
true
oldController
=
action_image_button
.
controller
}.
build
()
action_image_button
.
controller
=
controller
}
else
if
(
action
.
text
!=
null
)
{
action_button
.
isVisible
=
true
action_image_button
.
isVisible
=
false
this
.
action_button
.
setText
(
action
.
text
)
}
}
}
}
override
fun
onCreateViewHolder
(
parent
:
ViewGroup
,
viewType
:
Int
):
ViewHolder
{
val
view
=
parent
.
inflate
(
R
.
layout
.
item_action_button
)
return
ViewHolder
(
view
)
}
override
fun
getItemCount
():
Int
{
return
actions
.
size
}
override
fun
onBindViewHolder
(
holder
:
ViewHolder
,
position
:
Int
)
{
val
action
=
actions
[
position
]
holder
.
bindAction
(
action
)
}
}
\ No newline at end of file
app/src/main/java/chat/rocket/android/chatroom/adapter/ChatRoomAdapter.kt
View file @
4e3936ca
package
chat.rocket.android.chatroom.adapter
import
android.app.AlertDialog
import
android.content.Context
import
androidx.recyclerview.widget.RecyclerView
import
android.view.MenuItem
import
android.view.View
import
android.view.ViewGroup
import
chat.rocket.android.R
import
chat.rocket.android.chatroom.presentation.ChatRoomPresenter
import
chat.rocket.android.chatroom.uimodel.*
import
chat.rocket.android.util.extensions.inflate
import
chat.rocket.android.emoji.EmojiReactionListener
import
chat.rocket.android.util.extensions.openTabbedUrl
import
chat.rocket.core.model.attachment.actions.Action
import
chat.rocket.core.model.attachment.actions.ButtonAction
import
chat.rocket.core.model.Message
import
chat.rocket.core.model.isSystemMessage
import
timber.log.Timber
import
java.security.InvalidParameterException
class
ChatRoomAdapter
(
private
val
roomId
:
String
?
=
null
,
private
val
roomType
:
String
?
=
null
,
private
val
roomName
:
String
?
=
null
,
private
val
actionSelectListener
:
OnActionSelected
?
=
null
,
...
...
@@ -72,6 +74,10 @@ class ChatRoomAdapter(
actionSelectListener
?.
openDirectMessage
(
roomName
,
permalink
)
}
}
BaseUiModel
.
ViewType
.
ACTIONS_ATTACHMENT
->
{
val
view
=
parent
.
inflate
(
R
.
layout
.
item_actions_attachment
)
ActionsAttachmentViewHolder
(
view
,
actionsListener
,
reactionListener
,
actionAttachmentOnClickListener
)
}
else
->
{
throw
InvalidParameterException
(
"TODO - implement for ${viewType.toViewType()}"
)
}
...
...
@@ -125,6 +131,8 @@ class ChatRoomAdapter(
holder
.
bind
(
dataSet
[
position
]
as
GenericFileAttachmentUiModel
)
is
MessageReplyViewHolder
->
holder
.
bind
(
dataSet
[
position
]
as
MessageReplyUiModel
)
is
ActionsAttachmentViewHolder
->
holder
.
bind
(
dataSet
[
position
]
as
ActionsAttachmentUiModel
)
}
}
...
...
@@ -203,6 +211,33 @@ class ChatRoomAdapter(
}
}
private
val
actionAttachmentOnClickListener
=
object
:
ActionAttachmentOnClickListener
{
override
fun
onActionClicked
(
view
:
View
,
action
:
Action
)
{
val
temp
=
action
as
ButtonAction
if
(
temp
.
url
!=
null
&&
temp
.
isWebView
!=
null
)
{
if
(
temp
.
isWebView
==
true
)
{
//TODO: Open in a configurable sizable webview
Timber
.
d
(
"Open in a configurable sizable webview"
)
}
else
{
//Open in chrome custom tab
temp
.
url
?.
let
{
view
.
openTabbedUrl
(
it
)
}
}
}
else
if
(
temp
.
message
!=
null
&&
temp
.
isMessageInChatWindow
!=
null
)
{
if
(
temp
.
isMessageInChatWindow
==
true
)
{
//Send to chat window
temp
.
message
?.
let
{
if
(
roomId
!=
null
)
{
actionSelectListener
?.
sendMessage
(
roomId
,
it
)
}
}
}
else
{
//TODO: Send to bot but not in chat window
Timber
.
d
(
"Send to bot but not in chat window"
)
}
}
}
}
private
val
actionsListener
=
object
:
BaseViewHolder
.
ActionsListener
{
override
fun
isActionsEnabled
():
Boolean
=
enableActions
...
...
@@ -259,5 +294,6 @@ class ChatRoomAdapter(
fun
deleteMessage
(
roomId
:
String
,
id
:
String
)
fun
showReactions
(
id
:
String
)
fun
openDirectMessage
(
roomName
:
String
,
message
:
String
)
fun
sendMessage
(
chatRoomId
:
String
,
text
:
String
)
}
}
\ No newline at end of file
app/src/main/java/chat/rocket/android/chatroom/adapter/MessageViewHolder.kt
View file @
4e3936ca
package
chat.rocket.android.chatroom.adapter
import
android.graphics.Color
import
android.graphics.drawable.Drawable
import
android.text.Spannable
import
android.text.method.LinkMovementMethod
import
android.text.style.ImageSpan
import
android.view.View
import
androidx.core.view.isVisible
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 +19,7 @@ class MessageViewHolder(
itemView
:
View
,
listener
:
ActionsListener
,
reactionListener
:
EmojiReactionListener
?
=
null
)
:
BaseViewHolder
<
MessageUiModel
>(
itemView
,
listener
,
reactionListener
)
{
)
:
BaseViewHolder
<
MessageUiModel
>(
itemView
,
listener
,
reactionListener
)
,
Drawable
.
Callback
{
init
{
with
(
itemView
)
{
...
...
@@ -26,22 +30,26 @@ class MessageViewHolder(
override
fun
bindViews
(
data
:
MessageUiModel
)
{
with
(
itemView
)
{
day_marker_layout
.
visibility
=
if
(
data
.
showDayMarker
)
{
day
.
text
=
data
.
currentDayMarkerText
View
.
VISIBLE
}
else
{
View
.
GONE
}
day
.
text
=
data
.
currentDayMarkerText
day_marker_layout
.
isVisible
=
data
.
showDayMarker
if
(
data
.
isFirstUnread
)
{
new_messages_notif
.
visibility
=
View
.
VISIBLE
}
else
{
new_messages_notif
.
visibility
=
View
.
GONE
}
new_messages_notif
.
isVisible
=
data
.
isFirstUnread
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
)
...
...
@@ -64,4 +72,22 @@ class MessageViewHolder(
}
}
}
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
)
}
}
}
app/src/main/java/chat/rocket/android/chatroom/presentation/ChatRoomView.kt
View file @
4e3936ca
...
...
@@ -148,9 +148,4 @@ interface ChatRoomView : LoadingView, MessageView {
*/
fun
onRoomUpdated
(
userCanPost
:
Boolean
,
channelIsBroadcast
:
Boolean
,
userCanMod
:
Boolean
)
/**
* Open a DM with the user in the given [chatRoom] and pass the [permalink] for the message
* to reply.
*/
fun
openDirectMessage
(
chatRoom
:
ChatRoom
,
permalink
:
String
)
}
app/src/main/java/chat/rocket/android/chatroom/ui/ChatRoomFragment.kt
View file @
4e3936ca
...
...
@@ -6,9 +6,13 @@ import android.content.ClipData
import
android.content.ClipboardManager
import
android.content.Context
import
android.content.Intent
import
android.graphics.drawable.Drawable
import
android.os.Bundle
import
android.os.Handler
import
android.os.SystemClock
import
android.text.Spannable
import
android.text.SpannableStringBuilder
import
android.text.style.ImageSpan
import
android.view.KeyEvent
import
android.view.LayoutInflater
import
android.view.Menu
...
...
@@ -53,11 +57,13 @@ import chat.rocket.android.emoji.EmojiKeyboardPopup
import
chat.rocket.android.emoji.EmojiParser
import
chat.rocket.android.emoji.EmojiPickerPopup
import
chat.rocket.android.emoji.EmojiReactionListener
import
chat.rocket.android.emoji.internal.isCustom
import
chat.rocket.android.helper.EndlessRecyclerViewScrollListener
import
chat.rocket.android.helper.ImageHelper
import
chat.rocket.android.helper.KeyboardHelper
import
chat.rocket.android.helper.MessageParser
import
chat.rocket.android.util.extension.asObservable
import
chat.rocket.android.util.extension.launchUI
import
chat.rocket.android.util.extensions.circularRevealOrUnreveal
import
chat.rocket.android.util.extensions.fadeIn
import
chat.rocket.android.util.extensions.fadeOut
...
...
@@ -71,6 +77,7 @@ import chat.rocket.common.model.RoomType
import
chat.rocket.common.model.roomTypeOf
import
chat.rocket.core.internal.realtime.socket.model.State
import
chat.rocket.core.model.ChatRoom
import
com.bumptech.glide.load.resource.gif.GifDrawable
import
dagger.android.support.AndroidSupportInjection
import
io.reactivex.Observable
import
io.reactivex.disposables.CompositeDisposable
...
...
@@ -79,6 +86,8 @@ import kotlinx.android.synthetic.main.fragment_chat_room.*
import
kotlinx.android.synthetic.main.message_attachment_options.*
import
kotlinx.android.synthetic.main.message_composer.*
import
kotlinx.android.synthetic.main.message_list.*
import
kotlinx.coroutines.experimental.android.UI
import
kotlinx.coroutines.experimental.launch
import
java.util.concurrent.TimeUnit
import
java.util.concurrent.atomic.AtomicInteger
import
javax.inject.Inject
...
...
@@ -131,7 +140,8 @@ internal const val MENU_ACTION_FAVORITE_MESSAGES = 5
internal
const
val
MENU_ACTION_FILES
=
6
class
ChatRoomFragment
:
Fragment
(),
ChatRoomView
,
EmojiKeyboardListener
,
EmojiReactionListener
,
ChatRoomAdapter
.
OnActionSelected
{
ChatRoomAdapter
.
OnActionSelected
,
Drawable
.
Callback
{
@Inject
lateinit
var
presenter
:
ChatRoomPresenter
@Inject
...
...
@@ -208,10 +218,7 @@ class ChatRoomFragment : Fragment(), ChatRoomView, EmojiKeyboardListener, EmojiR
requireNotNull
(
bundle
)
{
"no arguments supplied when the fragment was instantiated"
}
}
adapter
=
ChatRoomAdapter
(
chatRoomType
,
chatRoomName
,
this
,
reactionListener
=
this
)
adapter
=
ChatRoomAdapter
(
chatRoomId
,
chatRoomType
,
chatRoomName
,
this
,
reactionListener
=
this
)
}
override
fun
onCreateView
(
...
...
@@ -395,10 +402,6 @@ class ChatRoomFragment : Fragment(), ChatRoomView, EmojiKeyboardListener, EmojiR
}
}
override
fun
openDirectMessage
(
chatRoom
:
ChatRoom
,
permalink
:
String
)
{
}
private
val
layoutChangeListener
=
View
.
OnLayoutChangeListener
{
_
,
_
,
_
,
_
,
bottom
,
_
,
_
,
_
,
oldBottom
->
val
y
=
oldBottom
-
bottom
...
...
@@ -641,8 +644,12 @@ 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
.
setSelection
(
cursorPosition
+
emoji
.
unicode
.
length
)
context
?.
let
{
val
offset
=
if
(!
emoji
.
isCustom
())
emoji
.
unicode
.
length
else
emoji
.
shortname
.
length
val
parsed
=
if
(
emoji
.
isCustom
())
emoji
.
shortname
else
EmojiParser
.
parse
(
it
,
emoji
.
shortname
)
text_message
.
text
?.
insert
(
cursorPosition
,
parsed
)
text_message
.
setSelection
(
cursorPosition
+
offset
)
}
}
}
...
...
@@ -773,9 +780,11 @@ class ChatRoomFragment : Fragment(), ChatRoomView, EmojiKeyboardListener, EmojiR
button_send
.
isVisible
=
false
button_show_attachment_options
.
alpha
=
1f
button_show_attachment_options
.
isVisible
=
true
activity
?.
supportFragmentManager
?.
addOnBackStackChangedListener
{
println
(
"attach"
)
}
activity
?.
supportFragmentManager
?.
registerFragmentLifecycleCallbacks
(
object
:
FragmentManager
.
FragmentLifecycleCallbacks
()
{
override
fun
onFragmentAttached
(
...
...
@@ -791,6 +800,7 @@ class ChatRoomFragment : Fragment(), ChatRoomView, EmojiKeyboardListener, EmojiR
},
true
)
subscribeComposeTextMessage
()
emojiKeyboardPopup
=
EmojiKeyboardPopup
(
activity
!!
,
activity
!!
.
findViewById
(
R
.
id
.
fragment_container
))
...
...
@@ -979,6 +989,18 @@ class ChatRoomFragment : Fragment(), ChatRoomView, EmojiKeyboardListener, EmojiR
(
activity
as
ChatRoomActivity
).
showToolbarTitle
(
toolbarTitle
)
}
override
fun
unscheduleDrawable
(
who
:
Drawable
?,
what
:
Runnable
?)
{
text_message
?.
removeCallbacks
(
what
)
}
override
fun
invalidateDrawable
(
who
:
Drawable
?)
{
text_message
?.
invalidate
()
}
override
fun
scheduleDrawable
(
who
:
Drawable
?,
what
:
Runnable
?,
`when`
:
Long
)
{
text_message
?.
postDelayed
(
what
,
`when`
)
}
override
fun
showMessageInfo
(
id
:
String
)
{
presenter
.
messageInfo
(
id
)
}
...
...
@@ -1039,4 +1061,8 @@ class ChatRoomFragment : Fragment(), ChatRoomView, EmojiKeyboardListener, EmojiR
override
fun
openDirectMessage
(
roomName
:
String
,
message
:
String
)
{
presenter
.
openDirectMessage
(
roomName
,
message
)
}
override
fun
sendMessage
(
chatRoomId
:
String
,
text
:
String
)
{
presenter
.
sendMessage
(
chatRoomId
,
text
,
null
)
}
}
app/src/main/java/chat/rocket/android/chatroom/uimodel/ActionsAttachmentUiModel.kt
0 → 100644
View file @
4e3936ca
package
chat.rocket.android.chatroom.uimodel
import
chat.rocket.android.R
import
chat.rocket.core.model.Message
import
chat.rocket.core.model.attachment.actions.Action
import
chat.rocket.core.model.attachment.actions.ActionsAttachment
data class
ActionsAttachmentUiModel
(
override
val
attachmentUrl
:
String
,
val
title
:
String
?,
val
actions
:
List
<
Action
>,
val
buttonAlignment
:
String
,
override
val
message
:
Message
,
override
val
rawData
:
ActionsAttachment
,
override
val
messageId
:
String
,
override
var
reactions
:
List
<
ReactionUiModel
>,
override
var
nextDownStreamMessage
:
BaseUiModel
<*>?
=
null
,
override
var
preview
:
Message
?
=
null
,
override
var
isTemporary
:
Boolean
=
false
,
override
var
unread
:
Boolean
?
=
null
,
override
var
menuItemsToHide
:
MutableList
<
Int
>
=
mutableListOf
(),
override
var
currentDayMarkerText
:
String
,
override
var
showDayMarker
:
Boolean
)
:
BaseAttachmentUiModel
<
ActionsAttachment
>
{
override
val
viewType
:
Int
get
()
=
BaseUiModel
.
ViewType
.
ACTIONS_ATTACHMENT
.
viewType
override
val
layoutId
:
Int
get
()
=
R
.
layout
.
item_actions_attachment
}
\ No newline at end of file
app/src/main/java/chat/rocket/android/chatroom/uimodel/BaseUiModel.kt
View file @
4e3936ca
...
...
@@ -29,7 +29,8 @@ interface BaseUiModel<out T> {
AUTHOR_ATTACHMENT
(
7
),
COLOR_ATTACHMENT
(
8
),
GENERIC_FILE_ATTACHMENT
(
9
),
MESSAGE_REPLY
(
10
)
MESSAGE_REPLY
(
10
),
ACTIONS_ATTACHMENT
(
11
)
}
}
...
...
app/src/main/java/chat/rocket/android/chatroom/uimodel/UiModelMapper.kt
View file @
4e3936ca
...
...
@@ -46,6 +46,7 @@ import chat.rocket.core.model.attachment.GenericFileAttachment
import
chat.rocket.core.model.attachment.ImageAttachment
import
chat.rocket.core.model.attachment.MessageAttachment
import
chat.rocket.core.model.attachment.VideoAttachment
import
chat.rocket.core.model.attachment.actions.ActionsAttachment
import
chat.rocket.core.model.isSystemMessage
import
chat.rocket.core.model.url.Url
import
kotlinx.coroutines.experimental.CommonPool
...
...
@@ -305,10 +306,26 @@ class UiModelMapper @Inject constructor(
is
MessageAttachment
->
mapMessageAttachment
(
message
,
attachment
)
is
AuthorAttachment
->
mapAuthorAttachment
(
message
,
attachment
)
is
ColorAttachment
->
mapColorAttachment
(
message
,
attachment
)
is
ActionsAttachment
->
mapActionsAttachment
(
message
,
attachment
)
else
->
null
}
}
private
fun
mapActionsAttachment
(
message
:
Message
,
attachment
:
ActionsAttachment
):
BaseUiModel
<
*
>?
{
return
with
(
attachment
)
{
val
content
=
stripMessageQuotes
(
message
)
val
localDateTime
=
DateTimeHelper
.
getLocalDateTime
(
message
.
timestamp
)
val
dayMarkerText
=
DateTimeHelper
.
getFormattedDateForMessages
(
localDateTime
,
context
)
ActionsAttachmentUiModel
(
attachmentUrl
=
url
,
title
=
title
,
actions
=
actions
,
buttonAlignment
=
buttonAlignment
,
message
=
message
,
rawData
=
attachment
,
messageId
=
message
.
id
,
reactions
=
getReactions
(
message
),
preview
=
message
.
copy
(
message
=
content
.
message
),
unread
=
message
.
unread
,
showDayMarker
=
false
,
currentDayMarkerText
=
dayMarkerText
)
}
}
private
fun
mapColorAttachment
(
message
:
Message
,
attachment
:
ColorAttachment
):
BaseUiModel
<
*
>?
{
return
with
(
attachment
)
{
val
content
=
stripMessageQuotes
(
message
)
...
...
@@ -493,7 +510,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 @
4e3936ca
...
...
@@ -7,6 +7,7 @@ import android.graphics.Paint
import
android.graphics.RectF
import
android.text.Spanned
import
android.text.style.ClickableSpan
import
android.text.style.ImageSpan
import
android.text.style.ReplacementSpan
import
android.util.Patterns
import
android.view.View
...
...
@@ -25,7 +26,6 @@ import org.commonmark.node.Document
import
org.commonmark.node.ListItem
import
org.commonmark.node.Node
import
org.commonmark.node.OrderedList
import
org.commonmark.node.Paragraph
import
org.commonmark.node.Text
import
ru.noties.markwon.Markwon
import
ru.noties.markwon.SpannableBuilder
...
...
@@ -60,11 +60,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
))
}
...
...
@@ -126,16 +126,29 @@ class MessageParser @Inject constructor(
}
class
EmojiVisitor
(
private
val
context
:
Context
,
configuration
:
SpannableConfiguration
,
private
val
builder
:
SpannableBuilder
)
:
SpannableMarkdownVisitor
(
configuration
,
builder
)
{
private
val
emojiSize
=
context
.
resources
.
getDimensionPixelSize
(
R
.
dimen
.
radius_mention
)
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
)
spans
.
forEach
{
builder
.
setSpan
(
it
,
spannable
.
getSpanStart
(
it
),
spannable
.
getSpanEnd
(
it
),
0
)
val
emojiOneTypefaceSpans
=
spannable
.
getSpans
(
0
,
spannable
.
length
,
EmojiTypefaceSpan
::
class
.
java
)
val
emojiImageSpans
=
spannable
.
getSpans
(
0
,
spannable
.
length
,
ImageSpan
::
class
.
java
)
emojiOneTypefaceSpans
.
forEach
{
builder
.
setSpan
(
it
,
spannable
.
getSpanStart
(
it
),
spannable
.
getSpanEnd
(
it
),
Spanned
.
SPAN_EXCLUSIVE_EXCLUSIVE
)
}
emojiImageSpans
.
forEach
{
it
.
drawable
?.
setBounds
(
0
,
0
,
emojiSize
,
emojiSize
)
builder
.
setSpan
(
it
,
spannable
.
getSpanStart
(
it
),
spannable
.
getSpanEnd
(
it
),
Spanned
.
SPAN_EXCLUSIVE_EXCLUSIVE
)
}
}
}
...
...
@@ -230,4 +243,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/main/presentation/MainPresenter.kt
View file @
4e3936ca
package
chat.rocket.android.main.presentation
import
android.content.Context
import
chat.rocket.android.core.lifecycle.CancelStrategy
import
chat.rocket.android.db.DatabaseManagerFactory
import
chat.rocket.android.emoji.Emoji
import
chat.rocket.android.emoji.EmojiRepository
import
chat.rocket.android.emoji.Fitzpatrick
import
chat.rocket.android.emoji.internal.EmojiCategory
import
chat.rocket.android.infrastructure.LocalRepository
import
chat.rocket.android.main.uimodel.NavHeaderUiModel
import
chat.rocket.android.main.uimodel.NavHeaderUiModelMapper
...
...
@@ -30,6 +35,7 @@ import chat.rocket.common.RocketChatException
import
chat.rocket.common.model.UserStatus
import
chat.rocket.common.util.ifNull
import
chat.rocket.core.RocketChatClient
import
chat.rocket.core.internal.rest.getCustomEmojis
import
chat.rocket.core.internal.rest.logout
import
chat.rocket.core.internal.rest.me
import
chat.rocket.core.internal.rest.unregisterPushToken
...
...
@@ -125,6 +131,38 @@ class MainPresenter @Inject constructor(
}
}
/**
* Load all emojis for the current server. Simple emojis are always the same for every server,
* but custom emojis vary according to the its url.
*/
fun
loadEmojis
()
{
launchUI
(
strategy
)
{
EmojiRepository
.
setCurrentServerUrl
(
currentServer
)
val
customEmojiList
=
mutableListOf
<
Emoji
>()
try
{
for
(
customEmoji
in
retryIO
(
"getCustomEmojis()"
)
{
client
.
getCustomEmojis
()
})
{
customEmojiList
.
add
(
Emoji
(
shortname
=
":${customEmoji.name}:"
,
category
=
EmojiCategory
.
CUSTOM
.
name
,
url
=
"$currentServer/emoji-custom/${customEmoji.name}.${customEmoji.extension}"
,
count
=
0
,
fitzpatrick
=
Fitzpatrick
.
Default
.
type
,
keywords
=
customEmoji
.
aliases
,
shortnameAlternates
=
customEmoji
.
aliases
,
siblings
=
mutableListOf
(),
unicode
=
""
,
isDefault
=
true
))
}
EmojiRepository
.
load
(
view
as
Context
,
customEmojis
=
customEmojiList
)
}
catch
(
ex
:
RocketChatException
)
{
Timber
.
e
(
ex
)
EmojiRepository
.
load
(
view
as
Context
)
}
}
}
/**
* Logout from current server.
*/
...
...
app/src/main/java/chat/rocket/android/main/ui/MainActivity.kt
View file @
4e3936ca
...
...
@@ -76,6 +76,7 @@ class MainActivity : AppCompatActivity(), MainView, HasActivityInjector,
presenter
.
connect
()
presenter
.
loadServerAccounts
()
presenter
.
loadCurrentInfo
()
presenter
.
loadEmojis
()
setupToolbar
()
setupNavigationView
()
}
...
...
app/src/main/java/chat/rocket/android/util/extensions/Text.kt
View file @
4e3936ca
...
...
@@ -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
)
...
...
app/src/main/res/layout/item_action_button.xml
0 → 100644
View file @
4e3936ca
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
xmlns:android=
"http://schemas.android.com/apk/res/android"
xmlns:fresco=
"http://schemas.android.com/apk/res-auto"
android:layout_width=
"wrap_content"
android:layout_height=
"wrap_content"
android:orientation=
"horizontal"
>
<com.google.android.material.button.MaterialButton
android:id=
"@+id/action_button"
style=
"@style/Widget.MaterialComponents.Button"
android:layout_width=
"wrap_content"
android:layout_height=
"wrap_content"
android:layout_marginEnd=
"2dp"
android:layout_marginStart=
"2dp"
android:textAppearance=
"@style/TextAppearance.AppCompat.Body2"
android:textSize=
"12sp"
/>
<com.facebook.drawee.view.SimpleDraweeView
android:id=
"@+id/action_image_button"
android:layout_width=
"match_parent"
android:layout_height=
"75dp"
android:layout_marginBottom=
"10dp"
android:layout_marginEnd=
"2dp"
android:layout_marginStart=
"2dp"
android:visibility=
"gone"
fresco:actualImageScaleType=
"fitStart"
fresco:placeholderImage=
"@drawable/image_dummy"
/>
</RelativeLayout>
\ No newline at end of file
app/src/main/res/layout/item_actions_attachment.xml
0 → 100644
View file @
4e3936ca
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android=
"http://schemas.android.com/apk/res/android"
xmlns:app=
"http://schemas.android.com/apk/res-auto"
xmlns:tools=
"http://schemas.android.com/tools"
android:id=
"@+id/actions_attachment_container"
android:layout_width=
"match_parent"
android:layout_height=
"wrap_content"
android:background=
"?android:attr/selectableItemBackground"
android:clickable=
"true"
android:focusable=
"true"
android:paddingBottom=
"@dimen/message_item_top_and_bottom_padding"
android:paddingEnd=
"@dimen/screen_edge_left_and_right_padding"
android:paddingStart=
"@dimen/screen_edge_left_and_right_padding"
>
<TextView
android:id=
"@+id/title"
style=
"@style/Message.TextView"
android:layout_width=
"0dp"
android:layout_height=
"wrap_content"
android:layout_marginBottom=
"4dp"
android:layout_marginStart=
"56dp"
android:layout_marginTop=
"2dp"
android:textDirection=
"locale"
app:layout_constraintEnd_toEndOf=
"parent"
app:layout_constraintStart_toStartOf=
"parent"
tools:text=
"This is a multiline chat message from Bertie that will take more than just one line of text. I have sure that everything is amazing!"
/>
<View
android:id=
"@+id/quote_bar"
android:layout_width=
"4dp"
android:layout_height=
"0dp"
android:background=
"@drawable/quote_vertical_gray_bar"
app:layout_constraintBottom_toTopOf=
"@id/recycler_view_reactions"
app:layout_constraintStart_toStartOf=
"@id/title"
app:layout_constraintTop_toBottomOf=
"@id/title"
/>
<androidx.recyclerview.widget.RecyclerView
android:id=
"@+id/actions_list"
android:layout_width=
"0dp"
android:layout_height=
"wrap_content"
android:layout_marginStart=
"8dp"
android:textAppearance=
"@style/TextAppearance.AppCompat.Body2"
android:textColor=
"@color/colorAccent"
android:textDirection=
"locale"
app:layout_constraintEnd_toEndOf=
"@id/title"
app:layout_constraintStart_toEndOf=
"@id/quote_bar"
app:layout_constraintTop_toBottomOf=
"@id/title"
/>
<include
layout=
"@layout/layout_reactions"
android:layout_width=
"0dp"
android:layout_height=
"wrap_content"
app:layout_constraintStart_toStartOf=
"@id/quote_bar"
app:layout_constraintTop_toBottomOf=
"@id/actions_list"
/>
</androidx.constraintlayout.widget.ConstraintLayout>
\ No newline at end of file
app/src/main/res/values/dimens.xml
View file @
4e3936ca
...
...
@@ -31,6 +31,7 @@
<dimen
name=
"supposed_keyboard_height"
>
252dp
</dimen>
<dimen
name=
"picker_popup_height"
>
250dp
</dimen>
<dimen
name=
"picker_popup_width"
>
300dp
</dimen>
<dimen
name=
"emoji_size"
>
22dp
</dimen>
<!--Toolbar-->
<dimen
name=
"toolbar_height"
>
56dp
</dimen>
...
...
build.gradle
View file @
4e3936ca
...
...
@@ -10,7 +10,7 @@ buildscript {
}
dependencies
{
classpath
'com.android.tools.build:gradle:3.
3.0-alpha05
'
classpath
'com.android.tools.build:gradle:3.
2.0-rc02
'
classpath
"org.jetbrains.kotlin:kotlin-gradle-plugin:${versions.kotlin}"
classpath
"org.jetbrains.dokka:dokka-gradle-plugin:${versions.dokka}"
classpath
'com.google.gms:google-services:4.0.2'
...
...
dependencies.gradle
View file @
4e3936ca
...
...
@@ -5,7 +5,7 @@ ext {
compileSdk
:
28
,
targetSdk
:
28
,
minSdk
:
21
,
buildTools
:
'28.0.
1
'
,
buildTools
:
'28.0.
2
'
,
dokka
:
'0.9.16'
,
// For app
...
...
@@ -47,6 +47,7 @@ ext {
frescoImageViewer
:
'0.5.1'
,
markwon
:
'1.1.0'
,
aVLoadingIndicatorView:
'2.1.3'
,
glide
:
'4.8.0-SNAPSHOT'
,
// For wearable
wear
:
'2.3.0'
,
...
...
@@ -106,6 +107,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 @
4e3936ca
apply
plugin:
'com.android.library'
apply
plugin:
'kotlin-android'
apply
plugin:
'kotlin-android-extensions'
apply
plugin:
'kotlin-kapt'
android
{
compileSdkVersion
versions
.
compileSdk
...
...
@@ -14,6 +15,11 @@ android {
testInstrumentationRunner
"androidx.test.runner.AndroidJUnitRunner"
javaCompileOptions
{
annotationProcessorOptions
{
arguments
=
[
"room.schemaLocation"
:
"$projectDir/schemas"
.
toString
()]
}
}
}
buildTypes
{
...
...
@@ -34,6 +40,10 @@ dependencies {
implementation
libraries
.
constraintlayout
implementation
libraries
.
recyclerview
implementation
libraries
.
material
implementation
libraries
.
glide
kapt
libraries
.
glideProcessor
implementation
libraries
.
room
kapt
libraries
.
roomProcessor
}
kotlin
{
...
...
emoji/proguard-rules.pro
View file @
4e3936ca
...
...
@@ -19,3 +19,12 @@
#
If
you
keep
the
line
number
information
,
uncomment
this
to
#
hide
the
original
source
file
name
.
#-
renamesourcefileattribute
SourceFile
-
keep
public
class
*
implements
com
.
bumptech
.
glide
.
module
.
GlideModule
-
keep
public
class
*
extends
com
.
bumptech
.
glide
.
module
.
AppGlideModule
-
keep
public
enum
com
.
bumptech
.
glide
.
load
.
ImageHeaderParser
$
**
{
**
[]
$
VALUES
;
public
*
;
}
#
for
DexGuard
only
-
keepresourcexmlelements
manifest
/
application
/
meta
-
data
@
value
=
GlideModule
emoji/src/main/java/chat/rocket/android/emoji/ComposerEditText.kt
View file @
4e3936ca
package
chat.rocket.android.emoji
import
android.content.Context
import
androidx.appcompat.widget.AppCompatEditText
import
android.text.Spanned
import
android.text.style.ImageSpan
import
android.util.AttributeSet
import
android.view.KeyEvent
import
androidx.appcompat.widget.AppCompatEditText
import
androidx.core.text.getSpans
class
ComposerEditText
:
AppCompatEditText
{
var
listener
:
ComposerEditTextListener
?
=
null
constructor
(
context
:
Context
,
attrs
:
AttributeSet
?,
defStyleAttr
:
Int
)
:
super
(
context
,
attrs
,
defStyleAttr
)
{
super
(
context
,
attrs
,
defStyleAttr
)
{
isFocusable
=
true
isFocusableInTouchMode
=
true
isClickable
=
true
...
...
@@ -20,6 +24,21 @@ class ComposerEditText : AppCompatEditText {
constructor
(
context
:
Context
)
:
this
(
context
,
null
)
override
fun
onSelectionChanged
(
selStart
:
Int
,
selEnd
:
Int
)
{
super
.
onSelectionChanged
(
selStart
,
selEnd
)
text
?.
getSpans
<
ImageSpan
>()
?.
forEach
{
val
s
=
text
?.
getSpanStart
(
it
)
?:
-
1
val
e
=
text
?.
getSpanEnd
(
it
)
?:
-
1
val
flags
=
if
(
selStart
in
s
..
e
)
{
Spanned
.
SPAN_EXCLUSIVE_EXCLUSIVE
or
Spanned
.
SPAN_COMPOSING
}
else
{
Spanned
.
SPAN_EXCLUSIVE_EXCLUSIVE
}
text
?.
setSpan
(
it
,
s
,
e
,
flags
)
}
}
override
fun
dispatchKeyEventPreIme
(
event
:
KeyEvent
):
Boolean
{
if
(
event
.
keyCode
==
KeyEvent
.
KEYCODE_BACK
)
{
val
state
=
keyDispatcherState
...
...
@@ -43,4 +62,4 @@ class ComposerEditText : AppCompatEditText {
fun
onKeyboardClosed
()
fun
onKeyboardOpened
()
}
}
\ No newline at end of file
}
emoji/src/main/java/chat/rocket/android/emoji/Emoji.kt
View file @
4e3936ca
package
chat.rocket.android.emoji
import
androidx.room.Entity
import
androidx.room.Ignore
import
androidx.room.PrimaryKey
@Entity
data class
Emoji
(
val
shortname
:
String
,
val
shortnameAlternates
:
List
<
String
>,
val
unicode
:
String
,
val
keywords
:
List
<
String
>,
val
category
:
String
,
val
count
:
Int
=
0
,
val
siblings
:
MutableCollection
<
Emoji
>
=
mutableListOf
(),
val
fitzpatrick
:
Fitzpatrick
=
Fitzpatrick
.
Default
)
\ No newline at end of file
@PrimaryKey
var
shortname
:
String
=
""
,
var
shortnameAlternates
:
List
<
String
>
=
listOf
(),
var
unicode
:
String
=
""
,
@Ignore
val
keywords
:
List
<
String
>
=
listOf
(),
var
category
:
String
=
""
,
var
count
:
Int
=
0
,
var
siblings
:
MutableList
<
String
>
=
mutableListOf
(),
// Siblings are the same emoji with different skin tones.
var
fitzpatrick
:
String
=
Fitzpatrick
.
Default
.
type
,
var
url
:
String
?
=
null
,
// Filled for custom emojis
var
isDefault
:
Boolean
=
true
// Tell if this is the default emoji if it has siblings (usually a yellow-toned one).
)
emoji/src/main/java/chat/rocket/android/emoji/EmojiDao.kt
0 → 100644
View file @
4e3936ca
package
chat.rocket.android.emoji
import
androidx.room.Dao
import
androidx.room.Delete
import
androidx.room.Insert
import
androidx.room.OnConflictStrategy.IGNORE
import
androidx.room.Query
import
androidx.room.Update
@Dao
interface
EmojiDao
{
@Query
(
"SELECT * FROM emoji"
)
fun
loadAllEmojis
():
List
<
Emoji
>
@Query
(
"SELECT * FROM emoji WHERE url IS NULL"
)
fun
loadSimpleEmojis
():
List
<
Emoji
>
@Query
(
"SELECT * FROM emoji WHERE url IS NOT NULL"
)
fun
loadAllCustomEmojis
():
List
<
Emoji
>
@Query
(
"SELECT * FROM emoji WHERE shortname=:shortname"
)
fun
loadEmojiByShortname
(
shortname
:
String
):
List
<
Emoji
>
@Query
(
"SELECT * FROM emoji WHERE UPPER(category)=UPPER(:category)"
)
fun
loadEmojisByCategory
(
category
:
String
):
List
<
Emoji
>
@Query
(
"SELECT * FROM emoji WHERE UPPER(category)=UPPER(:category) AND url LIKE :url"
)
fun
loadEmojisByCategoryAndUrl
(
category
:
String
,
url
:
String
):
List
<
Emoji
>
@Insert
(
onConflict
=
IGNORE
)
fun
insertEmoji
(
emoji
:
Emoji
)
@Insert
(
onConflict
=
IGNORE
)
fun
insertAllEmojis
(
vararg
emojis
:
Emoji
)
@Update
fun
updateEmoji
(
emoji
:
Emoji
)
@Delete
fun
deleteEmoji
(
emoji
:
Emoji
)
@Query
(
"DELETE FROM emoji"
)
fun
deleteAll
()
}
emoji/src/main/java/chat/rocket/android/emoji/EmojiKeyboardPopup.kt
View file @
4e3936ca
...
...
@@ -22,6 +22,8 @@ import chat.rocket.android.emoji.internal.EmojiCategory
import
chat.rocket.android.emoji.internal.EmojiPagerAdapter
import
chat.rocket.android.emoji.internal.PREF_EMOJI_SKIN_TONE
import
com.google.android.material.tabs.TabLayout
import
kotlinx.coroutines.experimental.android.UI
import
kotlinx.coroutines.experimental.launch
class
EmojiKeyboardPopup
(
context
:
Context
,
view
:
View
)
:
OverKeyboardPopupWindow
(
context
,
view
)
{
...
...
@@ -49,8 +51,10 @@ class EmojiKeyboardPopup(context: Context, view: View) : OverKeyboardPopupWindow
}
override
fun
onViewCreated
(
view
:
View
)
{
setupViewPager
()
setupBottomBar
()
launch
(
UI
)
{
setupViewPager
()
setupBottomBar
()
}
}
private
fun
setupBottomBar
()
{
...
...
@@ -81,42 +85,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
)
...
...
@@ -148,7 +152,7 @@ class EmojiKeyboardPopup(context: Context, view: View) : OverKeyboardPopupWindow
}
}
private
fun
setupViewPager
()
{
private
suspend
fun
setupViewPager
()
{
context
.
let
{
val
callback
=
when
(
it
)
{
is
EmojiKeyboardListener
->
it
...
...
@@ -167,6 +171,7 @@ class EmojiKeyboardPopup(context: Context, view: View) : OverKeyboardPopupWindow
callback
.
onEmojiAdded
(
emoji
)
}
})
viewPager
.
offscreenPageLimit
=
EmojiCategory
.
values
().
size
viewPager
.
adapter
=
adapter
...
...
@@ -183,6 +188,7 @@ class EmojiKeyboardPopup(context: Context, view: View) : OverKeyboardPopupWindow
}
else
{
EmojiCategory
.
RECENTS
.
ordinal
}
viewPager
.
currentItem
=
currentTab
}
}
...
...
emoji/src/main/java/chat/rocket/android/emoji/EmojiParser.kt
View file @
4e3936ca
package
chat.rocket.android.emoji
import
android.content.Context
import
android.graphics.Bitmap
import
android.graphics.Typeface
import
android.text.Spannable
import
android.text.SpannableString
import
android.text.Spanned
import
android.text.style.ImageSpan
import
android.util.Log
import
chat.rocket.android.emoji.internal.GlideApp
import
com.bumptech.glide.load.engine.DiskCacheStrategy
import
com.bumptech.glide.load.resource.gif.GifDrawable
import
kotlinx.coroutines.experimental.CommonPool
import
kotlinx.coroutines.experimental.Deferred
import
kotlinx.coroutines.experimental.async
class
EmojiParser
{
companion
object
{
private
val
regex
=
":[\\w]+:"
.
toRegex
()
/**
* Parses a text string containing unicode characters and/or shortnames to a rendered
* Spannable.
...
...
@@ -15,10 +29,18 @@ 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
)
val
typeface
=
EmojiRepository
.
cachedTypeface
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
=
try
{
EmojiRepository
.
cachedTypeface
}
catch
(
ex
:
UninitializedPropertyAccessException
)
{
// swallow this exception and create typeface now
Typeface
.
createFromAsset
(
context
.
assets
,
"fonts/emojione-android.ttf"
)
}
// Look for groups of emojis, set a EmojiTypefaceSpan with the emojione font.
val
length
=
spannable
.
length
var
inEmoji
=
false
...
...
@@ -32,6 +54,7 @@ class EmojiParser {
offset
+=
count
continue
}
if
(
codepoint
>=
0
x200
)
{
if
(!
inEmoji
)
{
emojiStart
=
offset
...
...
@@ -40,17 +63,64 @@ 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
)
}
}
return
spannable
val
customEmojis
=
EmojiRepository
.
getCustomEmojis
()
val
px
=
context
.
resources
.
getDimensionPixelSize
(
R
.
dimen
.
custom_emoji_small
)
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
))
{
GlideApp
.
with
(
context
).
asGif
()
}
else
{
GlideApp
.
with
(
context
).
asBitmap
()
}
val
futureTarget
=
glideRequest
.
diskCacheStrategy
(
DiskCacheStrategy
.
ALL
)
.
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
,
image
.
intrinsicWidth
,
image
.
intrinsicHeight
)
spannable
.
setSpan
(
ImageSpan
(
image
),
range
.
start
,
range
.
endInclusive
+
1
,
Spanned
.
SPAN_EXCLUSIVE_EXCLUSIVE
)
}
}
}
catch
(
ex
:
Throwable
)
{
Log
.
e
(
"EmojiParser"
,
""
,
ex
)
}
}
}
}
}
}
fun
parseAsync
(
context
:
Context
,
text
:
CharSequence
,
factory
:
Spannable
.
Factory
?
=
null
):
Deferred
<
CharSequence
>
{
return
async
(
CommonPool
)
{
parse
(
context
,
text
,
factory
)
}
}
}
}
\ No newline at end of file
}
emoji/src/main/java/chat/rocket/android/emoji/EmojiPickerPopup.kt
View file @
4e3936ca
...
...
@@ -14,6 +14,8 @@ import chat.rocket.android.emoji.internal.EmojiCategory
import
chat.rocket.android.emoji.internal.EmojiPagerAdapter
import
chat.rocket.android.emoji.internal.PREF_EMOJI_SKIN_TONE
import
kotlinx.android.synthetic.main.emoji_picker.*
import
kotlinx.coroutines.experimental.android.UI
import
kotlinx.coroutines.experimental.launch
class
EmojiPickerPopup
(
context
:
Context
)
:
Dialog
(
context
)
{
...
...
@@ -27,8 +29,10 @@ class EmojiPickerPopup(context: Context) : Dialog(context) {
setContentView
(
R
.
layout
.
emoji_picker
)
tabs
.
setupWithViewPager
(
pager_categories
)
setupViewPager
()
setSize
()
launch
(
UI
)
{
setupViewPager
()
setSize
()
}
}
private
fun
setSize
()
{
...
...
@@ -39,7 +43,7 @@ class EmojiPickerPopup(context: Context) : Dialog(context) {
window
.
setLayout
(
dialogWidth
,
dialogHeight
)
}
private
fun
setupViewPager
()
{
private
suspend
fun
setupViewPager
()
{
adapter
=
EmojiPagerAdapter
(
object
:
EmojiKeyboardListener
{
override
fun
onEmojiAdded
(
emoji
:
Emoji
)
{
EmojiRepository
.
addToRecents
(
emoji
)
...
...
emoji/src/main/java/chat/rocket/android/emoji/EmojiRepository.kt
View file @
4e3936ca
...
...
@@ -3,12 +3,14 @@ package chat.rocket.android.emoji
import
android.content.Context
import
android.content.SharedPreferences
import
android.graphics.Typeface
import
android.os.SystemClock
import
chat.rocket.android.emoji.internal.EmojiCategory
import
chat.rocket.android.emoji.internal.PREF_EMOJI_RECENTS
import
chat.rocket.android.emoji.internal.db.EmojiDatabase
import
chat.rocket.android.emoji.internal.isCustom
import
com.bumptech.glide.Glide
import
kotlinx.coroutines.experimental.CommonPool
import
kotlinx.coroutines.experimental.launch
import
kotlinx.coroutines.experimental.withContext
import
kotlinx.coroutines.experimental.yield
import
org.json.JSONArray
import
org.json.JSONObject
import
java.io.BufferedReader
...
...
@@ -16,61 +18,112 @@ import java.io.InputStream
import
java.io.InputStreamReader
import
java.util.*
import
java.util.regex.Pattern
import
kotlin.collections.ArrayList
import
kotlin.coroutines.experimental.buildSequence
object
EmojiRepository
{
private
val
FITZPATRICK_REGEX
=
"(.*)_(tone[0-9]):"
.
toRegex
(
RegexOption
.
IGNORE_CASE
)
private
val
shortNameToUnicode
=
HashMap
<
String
,
String
>()
private
val
SHORTNAME_PATTERN
=
Pattern
.
compile
(
":([-+\\w]+):"
)
private
va
l
ALL_EMOJIS
=
mutableL
istOf
<
Emoji
>()
private
va
r
customEmojis
=
l
istOf
<
Emoji
>()
private
lateinit
var
preferences
:
SharedPreferences
internal
lateinit
var
cachedTypeface
:
Typeface
private
lateinit
var
db
:
EmojiDatabase
private
lateinit
var
currentServerUrl
:
String
fun
setCurrentServerUrl
(
url
:
String
)
{
currentServerUrl
=
url
}
fun
getCurrentServerUrl
():
String
?
{
return
if
(
::
currentServerUrl
.
isInitialized
)
currentServerUrl
else
null
}
fun
load
(
context
:
Context
,
customEmojis
:
List
<
Emoji
>
=
emptyList
(),
path
:
String
=
"emoji.json"
)
{
launch
(
CommonPool
)
{
this
@EmojiRepository
.
customEmojis
=
customEmojis
val
allEmojis
=
mutableListOf
<
Emoji
>()
db
=
EmojiDatabase
.
getInstance
(
context
)
cachedTypeface
=
Typeface
.
createFromAsset
(
context
.
assets
,
"fonts/emojione-android.ttf"
)
preferences
=
context
.
getSharedPreferences
(
"emoji"
,
Context
.
MODE_PRIVATE
)
val
stream
=
context
.
assets
.
open
(
path
)
// Load emojis from emojione ttf file temporarily here. We still need to work on them.
val
emojis
=
loadEmojis
(
stream
).
also
{
it
.
addAll
(
customEmojis
)
}.
toList
()
for
(
emoji
in
emojis
)
{
val
unicodeIntList
=
mutableListOf
<
Int
>()
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
{
emoji
->
val
unicodeIntList
=
mutableListOf
<
Int
>()
emoji
.
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
)
emoji
.
category
=
emoji
.
category
if
(
emoji
.
isCustom
())
{
allEmojis
.
add
(
emoji
)
continue
}
}
val
unicodeIntArray
=
unicodeIntList
.
toIntArray
()
val
unicode
=
String
(
unicodeIntArray
,
0
,
unicodeIntArray
.
size
)
val
emojiWithUnicode
=
emoji
.
copy
(
unicode
=
unicode
)
if
(
hasFitzpatrick
(
emoji
.
shortname
))
{
val
matchResult
=
FITZPATRICK_REGEX
.
find
(
emoji
.
shortname
)
val
prefix
=
matchResult
!!
.
groupValues
[
1
]
+
":"
val
fitzpatrick
=
Fitzpatrick
.
valueOf
(
matchResult
.
groupValues
[
2
])
val
defaultEmoji
=
ALL_EMOJIS
.
firstOrNull
{
it
.
shortname
==
prefix
}
val
emojiWithFitzpatrick
=
emojiWithUnicode
.
copy
(
fitzpatrick
=
fitzpatrick
)
if
(
defaultEmoji
!=
null
)
{
defaultEmoji
.
siblings
.
add
(
emojiWithFitzpatrick
)
}
else
{
// This emoji doesn't have a default tone, ie. :man_in_business_suit_levitating_tone1:
// In this case, the default emoji becomes the first toned one.
ALL_EMOJIS
.
add
(
emojiWithFitzpatrick
)
emoji
.
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
)
emoji
.
unicode
=
unicode
if
(
hasFitzpatrick
(
emoji
.
shortname
))
{
val
matchResult
=
FITZPATRICK_REGEX
.
find
(
emoji
.
shortname
)
val
prefix
=
matchResult
!!
.
groupValues
[
1
]
+
":"
val
fitzpatrick
=
Fitzpatrick
.
valueOf
(
matchResult
.
groupValues
[
2
])
val
defaultEmoji
=
allEmojis
.
firstOrNull
{
it
.
shortname
==
prefix
}
emoji
.
fitzpatrick
=
fitzpatrick
.
type
emoji
.
isDefault
=
if
(
defaultEmoji
!=
null
)
{
defaultEmoji
.
siblings
.
add
(
emoji
.
shortname
)
false
}
else
{
true
}
emoji
.
isDefault
=
false
}
allEmojis
.
add
(
emoji
)
shortNameToUnicode
.
apply
{
put
(
emoji
.
shortname
,
unicode
)
emoji
.
shortnameAlternates
.
forEach
{
alternate
->
put
(
alternate
,
unicode
)
}
}
}
else
{
ALL_EMOJIS
.
add
(
emojiWithUnicode
)
}
shortNameToUnicode
.
apply
{
put
(
emoji
.
shortname
,
unicode
)
emoji
.
shortnameAlternates
.
forEach
{
alternate
->
put
(
alternate
,
unicode
)
}
saveEmojisToDatabase
(
allEmojis
.
toList
())
// Prefetch all custom emojis to make cache.
val
px
=
context
.
resources
.
getDimensionPixelSize
(
R
.
dimen
.
custom_emoji_large
)
customEmojis
.
forEach
{
val
future
=
Glide
.
with
(
context
)
.
load
(
it
.
url
)
.
submit
(
px
,
px
)
future
.
get
()
}
}
}
private
suspend
fun
saveEmojisToDatabase
(
emojis
:
List
<
Emoji
>)
{
withContext
(
CommonPool
)
{
db
.
emojiDao
().
insertAllEmojis
(*
emojis
.
toTypedArray
())
}
}
private
fun
hasFitzpatrick
(
shortname
:
String
):
Boolean
{
return
FITZPATRICK_REGEX
matches
shortname
}
...
...
@@ -80,22 +133,28 @@ object EmojiRepository {
*
* @return All emojis for all categories.
*/
internal
fun
getAll
()
=
ALL_EMOJIS
internal
suspend
fun
getAll
():
List
<
Emoji
>
=
withContext
(
CommonPool
)
{
return
@withContext
db
.
emojiDao
().
loadAllEmojis
()
}
/**
* Get all emojis for a given category.
*
* @param category Emoji category such as: PEOPLE, NATURE, ETC
*
* @return All emoji from specified category
*/
internal
fun
getEmojisByCategory
(
category
:
EmojiCategory
):
List
<
Emoji
>
{
return
ALL_EMOJIS
.
filter
{
it
.
category
.
toLowerCase
()
==
category
.
name
.
toLowerCase
()
}
internal
suspend
fun
getEmojiSequenceByCategory
(
category
:
EmojiCategory
):
Sequence
<
Emoji
>
{
val
list
=
withContext
(
CommonPool
)
{
db
.
emojiDao
().
loadEmojisByCategory
(
category
.
name
)
}
return
buildSequence
{
list
.
forEach
{
yield
(
it
)
}
}
}
internal
fun
getEmojiSequenceByCategory
(
category
:
EmojiCategory
):
Sequence
<
Emoji
>
{
val
list
=
ALL_EMOJIS
.
filter
{
it
.
category
.
toLowerCase
()
==
category
.
name
.
toLowerCase
()
}
return
buildSequence
{
internal
suspend
fun
getEmojiSequenceByCategoryAndUrl
(
category
:
EmojiCategory
,
url
:
String
):
Sequence
<
Emoji
>
{
val
list
=
withContext
(
CommonPool
)
{
db
.
emojiDao
().
loadEmojisByCategoryAndUrl
(
category
.
name
,
"$url%"
)
}
return
buildSequence
{
list
.
forEach
{
yield
(
it
)
}
...
...
@@ -109,7 +168,9 @@ object EmojiRepository {
*
* @return Emoji given by shortname or null
*/
internal
fun
getEmojiByShortname
(
shortname
:
String
)
=
ALL_EMOJIS
.
firstOrNull
{
it
.
shortname
==
shortname
}
private
suspend
fun
getEmojiByShortname
(
shortname
:
String
):
Emoji
?
=
withContext
(
CommonPool
)
{
return
@withContext
db
.
emojiDao
().
loadAllCustomEmojis
().
firstOrNull
()
}
/**
* Add an emoji to the Recents category.
...
...
@@ -117,40 +178,68 @@ object EmojiRepository {
internal
fun
addToRecents
(
emoji
:
Emoji
)
{
val
emojiShortname
=
emoji
.
shortname
val
recentsJson
=
JSONObject
(
preferences
.
getString
(
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
(
PREF_EMOJI_RECENTS
,
recentsJson
.
toString
()).
apply
()
}
internal
suspend
fun
getCustomEmojisAsync
():
List
<
Emoji
>
{
return
withContext
(
CommonPool
)
{
db
.
emojiDao
().
loadAllCustomEmojis
().
also
{
this
.
customEmojis
=
it
}
}
}
internal
fun
getCustomEmojis
():
List
<
Emoji
>
=
customEmojis
/**
* Get all recently used emojis ordered by usage count.
*
* @return All recent emojis ordered by usage.
*/
internal
fun
getRecents
():
List
<
Emoji
>
{
internal
suspend
fun
getRecents
():
List
<
Emoji
>
=
withContext
(
CommonPool
)
{
val
list
=
mutableListOf
<
Emoji
>()
val
recentsJson
=
JSONObject
(
preferences
.
getString
(
PREF_EMOJI_RECENTS
,
"{}"
))
for
(
shortname
in
recentsJson
.
keys
())
{
val
emoji
=
getEmojiByShortname
(
shortname
)
emoji
?.
let
{
val
allEmojis
=
db
.
emojiDao
().
loadAllEmojis
()
val
len
=
recentsJson
.
length
()
val
recentShortnames
=
recentsJson
.
keys
()
for
(
i
in
0
until
len
)
{
val
shortname
=
recentShortnames
.
next
()
allEmojis
.
firstOrNull
{
if
(
it
.
shortname
==
shortname
)
{
if
(
it
.
isCustom
())
{
return
@firstOrNull
getCurrentServerUrl
()
?.
let
{
url
->
it
.
url
?.
startsWith
(
url
)
}
?:
false
}
return
@firstOrNull
true
}
false
}
?.
let
{
val
useCount
=
recentsJson
.
getInt
(
it
.
shortname
)
list
.
add
(
it
.
copy
(
count
=
useCount
))
}
}
list
.
sortWith
(
Comparator
{
o1
,
o2
->
o2
.
count
-
o1
.
count
})
return
list
return
@withContext
list
}
/**
* 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
()
...
...
@@ -163,7 +252,7 @@ object EmojiRepository {
return
result
}
private
fun
loadEmojis
(
stream
:
InputStream
):
List
<
Emoji
>
{
private
fun
loadEmojis
(
stream
:
InputStream
):
Mutable
List
<
Emoji
>
{
val
emojisJSON
=
JSONArray
(
inputStreamToString
(
stream
))
val
emojis
=
ArrayList
<
Emoji
>(
emojisJSON
.
length
());
for
(
i
in
0
until
emojisJSON
.
length
())
{
...
...
emoji/src/main/java/chat/rocket/android/emoji/internal/EmojiCategory.kt
View file @
4e3936ca
...
...
@@ -7,12 +7,17 @@ import chat.rocket.android.emoji.EmojiRepository
import
chat.rocket.android.emoji.EmojiTypefaceSpan
import
chat.rocket.android.emoji.R
internal
enum
class
EmojiCategory
{
enum
class
EmojiCategory
{
RECENTS
{
override
fun
resourceIcon
()
=
R
.
drawable
.
ic_emoji_recents
override
fun
textIcon
()
=
getTextIconFor
(
"\uD83D\uDD58"
)
},
CUSTOM
{
override
fun
resourceIcon
()
=
R
.
drawable
.
ic_emoji_custom
override
fun
textIcon
()
=
getTextIconFor
(
"\uD83D\uDD58"
)
},
PEOPLE
()
{
override
fun
resourceIcon
()
=
R
.
drawable
.
ic_emoji_people
...
...
@@ -65,4 +70,4 @@ internal enum class EmojiCategory {
setSpan
(
span
,
0
,
text
.
length
,
Spanned
.
SPAN_INCLUSIVE_INCLUSIVE
)
}
}
}
\ No newline at end of file
}
emoji/src/main/java/chat/rocket/android/emoji/internal/EmojiGlideModule.kt
0 → 100644
View file @
4e3936ca
package
chat.rocket.android.emoji.internal
import
android.content.Context
import
com.bumptech.glide.GlideBuilder
import
com.bumptech.glide.annotation.GlideModule
import
com.bumptech.glide.load.engine.cache.ExternalPreferredCacheDiskCacheFactory
import
com.bumptech.glide.module.AppGlideModule
@GlideModule
class
EmojiGlideModule
:
AppGlideModule
()
{
override
fun
applyOptions
(
context
:
Context
,
builder
:
GlideBuilder
)
{
builder
.
setDiskCache
(
ExternalPreferredCacheDiskCacheFactory
(
context
))
}
}
emoji/src/main/java/chat/rocket/android/emoji/internal/EmojiPagerAdapter.kt
View file @
4e3936ca
...
...
@@ -14,7 +14,9 @@ import chat.rocket.android.emoji.EmojiParser
import
chat.rocket.android.emoji.EmojiRepository
import
chat.rocket.android.emoji.Fitzpatrick
import
chat.rocket.android.emoji.R
import
com.bumptech.glide.load.engine.DiskCacheStrategy
import
kotlinx.android.synthetic.main.emoji_category_layout.view.*
import
kotlinx.android.synthetic.main.emoji_image_row_item.view.*
import
kotlinx.android.synthetic.main.emoji_row_item.view.*
import
kotlinx.coroutines.experimental.CommonPool
import
kotlinx.coroutines.experimental.android.UI
...
...
@@ -43,22 +45,33 @@ internal class EmojiPagerAdapter(private val listener: EmojiKeyboardListener) :
container
.
addView
(
view
)
launch
(
UI
)
{
val
currentServerUrl
=
EmojiRepository
.
getCurrentServerUrl
()
val
emojis
=
if
(
category
!=
EmojiCategory
.
RECENTS
)
{
EmojiRepository
.
getEmojiSequenceByCategory
(
category
)
if
(
category
==
EmojiCategory
.
CUSTOM
)
{
currentServerUrl
?.
let
{
url
->
EmojiRepository
.
getEmojiSequenceByCategoryAndUrl
(
category
,
url
)
}
?:
emptySequence
()
}
else
{
EmojiRepository
.
getEmojiSequenceByCategory
(
category
)
}
}
else
{
sequenceOf
(*
EmojiRepository
.
getRecents
().
toTypedArray
())
}
val
recentEmojiSize
=
EmojiRepository
.
getRecents
().
size
text_no_recent_emoji
.
isVisible
=
category
==
EmojiCategory
.
RECENTS
&&
recentEmojiSize
==
0
if
(
adapters
[
category
]
==
null
)
{
val
adapter
=
EmojiAdapter
(
listener
=
listener
)
emoji_recycler_view
.
adapter
=
adapter
adapters
[
category
]
=
adapter
adapter
.
addEmojisFromSequence
(
emojis
)
}
adapters
[
category
]
!!
.
setFitzpatrick
(
fitzpatrick
)
}
}
return
view
}
...
...
@@ -88,20 +101,24 @@ internal class EmojiPagerAdapter(private val listener: EmojiKeyboardListener) :
private
val
listener
:
EmojiKeyboardListener
)
:
RecyclerView
.
Adapter
<
EmojiRowViewHolder
>()
{
private
val
CUSTOM
=
1
private
val
NORMAL
=
2
private
val
allEmojis
=
mutableListOf
<
Emoji
>()
private
val
emojis
=
mutableListOf
<
Emoji
>()
fun
addEmojis
(
emojis
:
List
<
Emoji
>)
{
this
.
emojis
.
clear
()
this
.
emojis
.
addAll
(
emojis
)
notifyDataSetChanged
()
override
fun
getItemViewType
(
position
:
Int
):
Int
{
return
if
(
emojis
[
position
].
isCustom
())
CUSTOM
else
NORMAL
}
suspend
fun
addEmojisFromSequence
(
emojiSequence
:
Sequence
<
Emoji
>)
{
withContext
(
CommonPool
)
{
emojiSequence
.
forEachIndexed
{
index
,
emoji
->
withContext
(
UI
)
{
emojis
.
add
(
emoji
)
notifyItemInserted
(
index
)
allEmojis
.
add
(
emoji
)
if
(
emoji
.
isDefault
)
{
emojis
.
add
(
emoji
)
notifyItemInserted
(
emojis
.
size
-
1
)
}
}
}
}
...
...
@@ -115,12 +132,26 @@ internal class EmojiPagerAdapter(private val listener: EmojiKeyboardListener) :
override
fun
onBindViewHolder
(
holder
:
EmojiRowViewHolder
,
position
:
Int
)
{
val
emoji
=
emojis
[
position
]
holder
.
bind
(
emoji
.
siblings
.
find
{
it
.
fitzpatrick
==
fitzpatrick
}
?:
emoji
if
(
fitzpatrick
!=
Fitzpatrick
.
Default
)
{
emoji
.
siblings
.
find
{
it
.
endsWith
(
"${fitzpatrick.type}:"
)
}
?.
let
{
shortname
->
allEmojis
.
firstOrNull
{
it
.
shortname
==
shortname
}
}
?:
emoji
}
else
{
emoji
}
)
}
override
fun
onCreateViewHolder
(
parent
:
ViewGroup
,
viewType
:
Int
):
EmojiRowViewHolder
{
val
view
=
LayoutInflater
.
from
(
parent
.
context
).
inflate
(
R
.
layout
.
emoji_row_item
,
parent
,
false
)
val
view
=
if
(
viewType
==
CUSTOM
)
{
LayoutInflater
.
from
(
parent
.
context
).
inflate
(
R
.
layout
.
emoji_image_row_item
,
parent
,
false
)
}
else
{
LayoutInflater
.
from
(
parent
.
context
).
inflate
(
R
.
layout
.
emoji_row_item
,
parent
,
false
)
}
return
EmojiRowViewHolder
(
view
,
listener
)
}
...
...
@@ -134,16 +165,26 @@ internal class EmojiPagerAdapter(private val listener: EmojiKeyboardListener) :
fun
bind
(
emoji
:
Emoji
)
{
with
(
itemView
)
{
val
parsedUnicode
=
unicodeCache
[
emoji
.
unicode
]
emoji_view
.
setSpannableFactory
(
spannableFactory
)
emoji_view
.
text
=
if
(
parsedUnicode
==
null
)
{
EmojiParser
.
parse
(
emoji
.
unicode
,
spannableFactory
).
let
{
unicodeCache
[
emoji
.
unicode
]
=
it
it
if
(
emoji
.
unicode
.
isNotEmpty
())
{
// Handle simple emoji.
val
parsedUnicode
=
unicodeCache
[
emoji
.
unicode
]
emoji_view
.
setSpannableFactory
(
spannableFactory
)
emoji_view
.
text
=
if
(
parsedUnicode
==
null
)
{
EmojiParser
.
parse
(
itemView
.
context
,
emoji
.
unicode
,
spannableFactory
).
let
{
unicodeCache
[
emoji
.
unicode
]
=
it
it
}
}
else
{
parsedUnicode
}
}
else
{
parsedUnicode
// Handle custom emoji.
GlideApp
.
with
(
context
)
.
load
(
emoji
.
url
)
.
diskCacheStrategy
(
DiskCacheStrategy
.
ALL
)
.
into
(
emoji_image_view
)
}
itemView
.
setOnClickListener
{
listener
.
onEmojiAdded
(
emoji
)
}
...
...
@@ -155,4 +196,4 @@ internal class EmojiPagerAdapter(private val listener: EmojiKeyboardListener) :
private
val
unicodeCache
=
mutableMapOf
<
CharSequence
,
CharSequence
>()
}
}
}
\ No newline at end of file
}
emoji/src/main/java/chat/rocket/android/emoji/internal/Extensions.kt
0 → 100644
View file @
4e3936ca
package
chat.rocket.android.emoji.internal
import
chat.rocket.android.emoji.Emoji
fun
Emoji
.
isCustom
():
Boolean
=
this
.
url
!=
null
emoji/src/main/java/chat/rocket/android/emoji/internal/db/EmojiDatabase.kt
0 → 100644
View file @
4e3936ca
package
chat.rocket.android.emoji.internal.db
import
android.content.Context
import
androidx.room.Database
import
androidx.room.Room
import
androidx.room.RoomDatabase
import
androidx.room.TypeConverters
import
chat.rocket.android.emoji.Emoji
import
chat.rocket.android.emoji.EmojiDao
@Database
(
entities
=
[
Emoji
::
class
],
version
=
1
)
@TypeConverters
(
StringListConverter
::
class
)
abstract
class
EmojiDatabase
:
RoomDatabase
()
{
abstract
fun
emojiDao
():
EmojiDao
companion
object
:
SingletonHolder
<
EmojiDatabase
,
Context
>({
Room
.
databaseBuilder
(
it
.
applicationContext
,
EmojiDatabase
::
class
.
java
,
"emoji.db"
)
.
fallbackToDestructiveMigration
()
.
build
()
})
}
open
class
SingletonHolder
<
out
T
,
in
A
>(
creator
:
(
A
)
->
T
)
{
private
var
creator
:
((
A
)
->
T
)?
=
creator
@kotlin
.
jvm
.
Volatile
private
var
instance
:
T
?
=
null
fun
getInstance
(
arg
:
A
):
T
{
val
i
=
instance
if
(
i
!=
null
)
{
return
i
}
return
synchronized
(
this
)
{
val
i2
=
instance
if
(
i2
!=
null
)
{
i2
}
else
{
val
created
=
creator
!!
(
arg
)
instance
=
created
creator
=
null
created
}
}
}
}
emoji/src/main/java/chat/rocket/android/emoji/internal/db/StringListConverter.kt
0 → 100644
View file @
4e3936ca
package
chat.rocket.android.emoji.internal.db
import
androidx.room.TypeConverter
class
StringListConverter
{
@TypeConverter
fun
fromStringList
(
list
:
List
<
String
>?):
String
{
return
list
?.
joinToString
(
separator
=
","
)
?:
""
}
@TypeConverter
fun
fromString
(
value
:
String
?):
List
<
String
>
{
return
value
?.
split
(
","
)
?:
emptyList
()
}
}
emoji/src/main/res/drawable-hdpi/ic_emoji_custom.png
0 → 100644
View file @
4e3936ca
1.61 KB
emoji/src/main/res/drawable-mdpi/ic_emoji_custom.png
0 → 100644
View file @
4e3936ca
1.09 KB
emoji/src/main/res/drawable-xhdpi/ic_emoji_custom.png
0 → 100644
View file @
4e3936ca
2.42 KB
emoji/src/main/res/drawable-xxhdpi/ic_emoji_custom.png
0 → 100644
View file @
4e3936ca
3.23 KB
emoji/src/main/res/drawable-xxxhdpi/ic_emoji_custom.png
0 → 100644
View file @
4e3936ca
6.84 KB
emoji/src/main/res/layout/emoji_image_row_item.xml
0 → 100644
View file @
4e3936ca
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout
xmlns:android=
"http://schemas.android.com/apk/res/android"
xmlns:tools=
"http://schemas.android.com/tools"
android:layout_width=
"48dp"
android:layout_height=
"48dp"
android:layout_gravity=
"center"
>
<ImageView
android:id=
"@+id/emoji_image_view"
android:layout_width=
"32dp"
android:layout_height=
"32dp"
android:layout_gravity=
"center"
tools:src=
"@tools:sample/avatars"
/>
</FrameLayout>
emoji/src/main/res/layout/emoji_row_item.xml
View file @
4e3936ca
...
...
@@ -6,6 +6,7 @@
android:layout_width=
"48dp"
android:layout_height=
"48dp"
android:foreground=
"?selectableItemBackground"
android:gravity=
"center"
android:textColor=
"#000000"
android:textSize=
"26sp"
tools:text=
"😀"
/>
emoji/src/main/res/values/dimens.xml
View file @
4e3936ca
...
...
@@ -5,5 +5,6 @@
<dimen
name=
"supposed_keyboard_height"
>
252dp
</dimen>
<dimen
name=
"picker_popup_height"
>
250dp
</dimen>
<dimen
name=
"picker_popup_width"
>
300dp
</dimen>
</resources>
\ No newline at end of file
<dimen
name=
"custom_emoji_large"
>
32dp
</dimen>
<dimen
name=
"custom_emoji_small"
>
22dp
</dimen>
</resources>
gradle/wrapper/gradle-wrapper.properties
View file @
4e3936ca
#
Wed Aug 01 22:00:00 ED
T 2018
#
Mon Aug 06 11:30:07 BR
T 2018
distributionBase
=
GRADLE_USER_HOME
distributionPath
=
wrapper/dists
zipStoreBase
=
GRADLE_USER_HOME
...
...
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