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
8685b5dd
Commit
8685b5dd
authored
Mar 30, 2018
by
Filipe de Lima Brito
Browse files
Options
Browse Files
Download
Plain Diff
Merge branch 'develop-2.x' of github.com:RocketChat/Rocket.Chat.Android into fix/login-with-oauth
parents
29355692
2176c0ce
Changes
38
Show whitespace changes
Inline
Side-by-side
Showing
38 changed files
with
538 additions
and
379 deletions
+538
-379
RocketChatApplication.kt
...ain/java/chat/rocket/android/app/RocketChatApplication.kt
+0
-1
ChatRoomAdapter.kt
...a/chat/rocket/android/chatroom/adapter/ChatRoomAdapter.kt
+16
-8
CommandSuggestionsAdapter.kt
...ket/android/chatroom/adapter/CommandSuggestionsAdapter.kt
+1
-1
MessageAttachmentViewHolder.kt
...t/android/chatroom/adapter/MessageAttachmentViewHolder.kt
+29
-0
PeopleSuggestionsAdapter.kt
...cket/android/chatroom/adapter/PeopleSuggestionsAdapter.kt
+33
-6
ChatRoomPresenter.kt
...rocket/android/chatroom/presentation/ChatRoomPresenter.kt
+1
-2
ActionSnackbar.kt
...in/java/chat/rocket/android/chatroom/ui/ActionSnackbar.kt
+8
-12
ChatRoomFragment.kt
.../java/chat/rocket/android/chatroom/ui/ChatRoomFragment.kt
+49
-7
AudioAttachmentViewModel.kt
...et/android/chatroom/viewmodel/AudioAttachmentViewModel.kt
+3
-2
BaseViewModel.kt
...a/chat/rocket/android/chatroom/viewmodel/BaseViewModel.kt
+1
-0
ImageAttachmentViewModel.kt
...et/android/chatroom/viewmodel/ImageAttachmentViewModel.kt
+2
-1
MessageAttachmentViewModel.kt
.../android/chatroom/viewmodel/MessageAttachmentViewModel.kt
+24
-0
MessageViewModel.kt
...hat/rocket/android/chatroom/viewmodel/MessageViewModel.kt
+1
-0
UrlPreviewViewModel.kt
.../rocket/android/chatroom/viewmodel/UrlPreviewViewModel.kt
+2
-1
VideoAttachmentViewModel.kt
...et/android/chatroom/viewmodel/VideoAttachmentViewModel.kt
+2
-1
ViewModelMapper.kt
...chat/rocket/android/chatroom/viewmodel/ViewModelMapper.kt
+50
-108
PeopleSuggestionViewModel.kt
...hatroom/viewmodel/suggestion/PeopleSuggestionViewModel.kt
+1
-1
ChatRoomsPresenter.kt
...cket/android/chatrooms/presentation/ChatRoomsPresenter.kt
+21
-6
ChatRoomsAdapter.kt
...java/chat/rocket/android/chatrooms/ui/ChatRoomsAdapter.kt
+23
-9
ChatRoomsFragment.kt
...ava/chat/rocket/android/chatrooms/ui/ChatRoomsFragment.kt
+12
-5
MessageParser.kt
...src/main/java/chat/rocket/android/helper/MessageParser.kt
+58
-159
LocalRepository.kt
...ava/chat/rocket/android/infrastructure/LocalRepository.kt
+3
-1
ProfileFragment.kt
...in/java/chat/rocket/android/profile/ui/ProfileFragment.kt
+5
-3
CompletionStrategy.kt
...roid/widget/autocompletion/strategy/CompletionStrategy.kt
+1
-0
StringMatchingCompletionStrategy.kt
...letion/strategy/regex/StringMatchingCompletionStrategy.kt
+19
-4
TrieCompletionStrategy.kt
...et/autocompletion/strategy/trie/TrieCompletionStrategy.kt
+4
-0
SuggestionsAdapter.kt
...et/android/widget/autocompletion/ui/SuggestionsAdapter.kt
+14
-3
SuggestionsView.kt
...ocket/android/widget/autocompletion/ui/SuggestionsView.kt
+17
-16
EmojiParser.kt
...main/java/chat/rocket/android/widget/emoji/EmojiParser.kt
+19
-6
EmojiRepository.kt
.../java/chat/rocket/android/widget/emoji/EmojiRepository.kt
+6
-7
EmojiTypefaceSpan.kt
...ava/chat/rocket/android/widget/emoji/EmojiTypefaceSpan.kt
+5
-5
quote_vertical_bar.xml
app/src/main/res/drawable/quote_vertical_bar.xml
+3
-1
item_message_attachment.xml
app/src/main/res/layout/item_message_attachment.xml
+70
-0
message_action_bar.xml
app/src/main/res/layout/message_action_bar.xml
+11
-0
suggestion_member_item.xml
app/src/main/res/layout/suggestion_member_item.xml
+4
-3
strings.xml
app/src/main/res/values-pt-rBR/strings.xml
+8
-0
strings.xml
app/src/main/res/values/strings.xml
+8
-0
styles.xml
app/src/main/res/values/styles.xml
+4
-0
No files found.
app/src/main/java/chat/rocket/android/app/RocketChatApplication.kt
View file @
8685b5dd
...
@@ -24,7 +24,6 @@ import chat.rocket.android.server.domain.*
...
@@ -24,7 +24,6 @@ import chat.rocket.android.server.domain.*
import
chat.rocket.android.server.domain.model.Account
import
chat.rocket.android.server.domain.model.Account
import
chat.rocket.android.widget.emoji.EmojiRepository
import
chat.rocket.android.widget.emoji.EmojiRepository
import
chat.rocket.common.model.Token
import
chat.rocket.common.model.Token
import
chat.rocket.common.util.ifNull
import
chat.rocket.core.model.Value
import
chat.rocket.core.model.Value
import
com.crashlytics.android.Crashlytics
import
com.crashlytics.android.Crashlytics
import
com.crashlytics.android.core.CrashlyticsCore
import
com.crashlytics.android.core.CrashlyticsCore
...
...
app/src/main/java/chat/rocket/android/chatroom/adapter/ChatRoomAdapter.kt
View file @
8685b5dd
...
@@ -49,6 +49,10 @@ class ChatRoomAdapter(
...
@@ -49,6 +49,10 @@ class ChatRoomAdapter(
val
view
=
parent
.
inflate
(
R
.
layout
.
message_url_preview
)
val
view
=
parent
.
inflate
(
R
.
layout
.
message_url_preview
)
UrlPreviewViewHolder
(
view
,
actionsListener
,
reactionListener
)
UrlPreviewViewHolder
(
view
,
actionsListener
,
reactionListener
)
}
}
BaseViewModel
.
ViewType
.
MESSAGE_ATTACHMENT
->
{
val
view
=
parent
.
inflate
(
R
.
layout
.
item_message_attachment
)
MessageAttachmentViewHolder
(
view
,
actionsListener
,
reactionListener
)
}
else
->
{
else
->
{
throw
InvalidParameterException
(
"TODO - implement for ${viewType.toViewType()}"
)
throw
InvalidParameterException
(
"TODO - implement for ${viewType.toViewType()}"
)
}
}
...
@@ -87,6 +91,7 @@ class ChatRoomAdapter(
...
@@ -87,6 +91,7 @@ class ChatRoomAdapter(
is
AudioAttachmentViewHolder
->
holder
.
bind
(
dataSet
[
position
]
as
AudioAttachmentViewModel
)
is
AudioAttachmentViewHolder
->
holder
.
bind
(
dataSet
[
position
]
as
AudioAttachmentViewModel
)
is
VideoAttachmentViewHolder
->
holder
.
bind
(
dataSet
[
position
]
as
VideoAttachmentViewModel
)
is
VideoAttachmentViewHolder
->
holder
.
bind
(
dataSet
[
position
]
as
VideoAttachmentViewModel
)
is
UrlPreviewViewHolder
->
holder
.
bind
(
dataSet
[
position
]
as
UrlPreviewViewModel
)
is
UrlPreviewViewHolder
->
holder
.
bind
(
dataSet
[
position
]
as
UrlPreviewViewModel
)
is
MessageAttachmentViewHolder
->
holder
.
bind
(
dataSet
[
position
]
as
MessageAttachmentViewModel
)
}
}
}
}
...
@@ -117,19 +122,22 @@ class ChatRoomAdapter(
...
@@ -117,19 +122,22 @@ class ChatRoomAdapter(
fun
updateItem
(
message
:
BaseViewModel
<
*
>)
{
fun
updateItem
(
message
:
BaseViewModel
<
*
>)
{
var
index
=
dataSet
.
indexOfLast
{
it
.
messageId
==
message
.
messageId
}
var
index
=
dataSet
.
indexOfLast
{
it
.
messageId
==
message
.
messageId
}
val
indexOf
Firs
t
=
dataSet
.
indexOfFirst
{
it
.
messageId
==
message
.
messageId
}
val
indexOf
Nex
t
=
dataSet
.
indexOfFirst
{
it
.
messageId
==
message
.
messageId
}
Timber
.
d
(
"index: $index"
)
Timber
.
d
(
"index: $index"
)
if
(
index
>
-
1
)
{
if
(
index
>
-
1
)
{
dataSet
[
index
]
=
message
dataSet
[
index
]
=
message
dataSet
.
forEachIndexed
{
index
,
viewModel
->
if
(
viewModel
.
messageId
==
message
.
messageId
)
{
if
(
viewModel
.
nextDownStreamMessage
==
null
)
{
viewModel
.
reactions
=
message
.
reactions
}
notifyItemChanged
(
index
)
notifyItemChanged
(
index
)
while
(
dataSet
[
index
].
nextDownStreamMessage
!=
null
)
{
}
dataSet
[
index
].
nextDownStreamMessage
!!
.
reactions
=
message
.
reactions
notifyItemChanged
(--
index
)
}
}
// Delete message only if current is a system message update, i.e.: Message Removed
// Delete message only if current is a system message update, i.e.: Message Removed
if
(
message
.
message
.
isSystemMessage
()
&&
indexOf
First
>
-
1
&&
indexOfFirs
t
!=
index
)
{
if
(
message
.
message
.
isSystemMessage
()
&&
indexOf
Next
>
-
1
&&
indexOfNex
t
!=
index
)
{
dataSet
.
removeAt
(
indexOf
Firs
t
)
dataSet
.
removeAt
(
indexOf
Nex
t
)
notifyItemRemoved
(
indexOf
Firs
t
)
notifyItemRemoved
(
indexOf
Nex
t
)
}
}
}
}
}
}
...
...
app/src/main/java/chat/rocket/android/chatroom/adapter/CommandSuggestionsAdapter.kt
View file @
8685b5dd
...
@@ -12,7 +12,7 @@ import chat.rocket.android.widget.autocompletion.ui.BaseSuggestionViewHolder
...
@@ -12,7 +12,7 @@ import chat.rocket.android.widget.autocompletion.ui.BaseSuggestionViewHolder
import
chat.rocket.android.widget.autocompletion.ui.SuggestionsAdapter
import
chat.rocket.android.widget.autocompletion.ui.SuggestionsAdapter
class
CommandSuggestionsAdapter
:
SuggestionsAdapter
<
CommandSuggestionsViewHolder
>(
token
=
"/"
,
class
CommandSuggestionsAdapter
:
SuggestionsAdapter
<
CommandSuggestionsViewHolder
>(
token
=
"/"
,
constraint
=
CONSTRAINT_BOUND_TO_START
,
threshold
=
UNLIMITED_RESULT_COUNT
)
{
constraint
=
CONSTRAINT_BOUND_TO_START
,
threshold
=
RESULT_COUNT_UNLIMITED
)
{
override
fun
onCreateViewHolder
(
parent
:
ViewGroup
,
viewType
:
Int
):
CommandSuggestionsViewHolder
{
override
fun
onCreateViewHolder
(
parent
:
ViewGroup
,
viewType
:
Int
):
CommandSuggestionsViewHolder
{
val
view
=
LayoutInflater
.
from
(
parent
.
context
).
inflate
(
R
.
layout
.
suggestion_command_item
,
parent
,
val
view
=
LayoutInflater
.
from
(
parent
.
context
).
inflate
(
R
.
layout
.
suggestion_command_item
,
parent
,
...
...
app/src/main/java/chat/rocket/android/chatroom/adapter/MessageAttachmentViewHolder.kt
0 → 100644
View file @
8685b5dd
package
chat.rocket.android.chatroom.adapter
import
android.text.method.LinkMovementMethod
import
android.view.View
import
chat.rocket.android.chatroom.viewmodel.MessageAttachmentViewModel
import
chat.rocket.android.widget.emoji.EmojiReactionListener
import
kotlinx.android.synthetic.main.item_message.view.*
class
MessageAttachmentViewHolder
(
itemView
:
View
,
listener
:
ActionsListener
,
reactionListener
:
EmojiReactionListener
?
=
null
)
:
BaseViewHolder
<
MessageAttachmentViewModel
>(
itemView
,
listener
,
reactionListener
)
{
init
{
with
(
itemView
)
{
text_content
.
movementMethod
=
LinkMovementMethod
()
setupActionMenu
(
text_content
)
}
}
override
fun
bindViews
(
data
:
MessageAttachmentViewModel
)
{
with
(
itemView
)
{
text_message_time
.
text
=
data
.
time
text_sender
.
text
=
data
.
senderName
text_content
.
text
=
data
.
content
}
}
}
\ No newline at end of file
app/src/main/java/chat/rocket/android/chatroom/adapter/PeopleSuggestionsAdapter.kt
View file @
8685b5dd
package
chat.rocket.android.chatroom.adapter
package
chat.rocket.android.chatroom.adapter
import
DrawableHelper
import
DrawableHelper
import
android.content.Context
import
android.view.LayoutInflater
import
android.view.LayoutInflater
import
android.view.View
import
android.view.View
import
android.view.ViewGroup
import
android.view.ViewGroup
...
@@ -13,10 +14,32 @@ import chat.rocket.android.util.extensions.setVisible
...
@@ -13,10 +14,32 @@ import chat.rocket.android.util.extensions.setVisible
import
chat.rocket.android.widget.autocompletion.model.SuggestionModel
import
chat.rocket.android.widget.autocompletion.model.SuggestionModel
import
chat.rocket.android.widget.autocompletion.ui.BaseSuggestionViewHolder
import
chat.rocket.android.widget.autocompletion.ui.BaseSuggestionViewHolder
import
chat.rocket.android.widget.autocompletion.ui.SuggestionsAdapter
import
chat.rocket.android.widget.autocompletion.ui.SuggestionsAdapter
import
chat.rocket.common.model.UserStatus
import
com.facebook.drawee.view.SimpleDraweeView
import
com.facebook.drawee.view.SimpleDraweeView
class
PeopleSuggestionsAdapter
:
SuggestionsAdapter
<
PeopleSuggestionViewHolder
>(
"@"
)
{
class
PeopleSuggestionsAdapter
(
context
:
Context
)
:
SuggestionsAdapter
<
PeopleSuggestionViewHolder
>(
"@"
)
{
init
{
val
allDescription
=
context
.
getString
(
R
.
string
.
suggest_all_description
)
val
hereDescription
=
context
.
getString
(
R
.
string
.
suggest_here_description
)
val
pinnedList
=
listOf
(
PeopleSuggestionViewModel
(
imageUri
=
null
,
text
=
"all"
,
username
=
"all"
,
name
=
allDescription
,
status
=
null
,
pinned
=
false
,
searchList
=
listOf
(
"all"
)),
PeopleSuggestionViewModel
(
imageUri
=
null
,
text
=
"here"
,
username
=
"here"
,
name
=
hereDescription
,
status
=
null
,
pinned
=
false
,
searchList
=
listOf
(
"here"
))
)
setPinnedSuggestions
(
pinnedList
)
}
override
fun
onCreateViewHolder
(
parent
:
ViewGroup
,
viewType
:
Int
):
PeopleSuggestionViewHolder
{
override
fun
onCreateViewHolder
(
parent
:
ViewGroup
,
viewType
:
Int
):
PeopleSuggestionViewHolder
{
val
view
=
LayoutInflater
.
from
(
parent
.
context
).
inflate
(
R
.
layout
.
suggestion_member_item
,
parent
,
val
view
=
LayoutInflater
.
from
(
parent
.
context
).
inflate
(
R
.
layout
.
suggestion_member_item
,
parent
,
false
)
false
)
...
@@ -34,15 +57,19 @@ class PeopleSuggestionsAdapter : SuggestionsAdapter<PeopleSuggestionViewHolder>(
...
@@ -34,15 +57,19 @@ class PeopleSuggestionsAdapter : SuggestionsAdapter<PeopleSuggestionViewHolder>(
val
statusView
=
itemView
.
findViewById
<
ImageView
>(
R
.
id
.
image_status
)
val
statusView
=
itemView
.
findViewById
<
ImageView
>(
R
.
id
.
image_status
)
username
.
text
=
item
.
username
username
.
text
=
item
.
username
name
.
text
=
item
.
name
name
.
text
=
item
.
name
if
(
item
.
imageUri
.
isEmpty
()
)
{
if
(
item
.
imageUri
?.
isEmpty
()
!=
false
)
{
avatar
.
setVisible
(
false
)
avatar
.
setVisible
(
false
)
}
else
{
}
else
{
avatar
.
setVisible
(
true
)
avatar
.
setVisible
(
true
)
avatar
.
setImageURI
(
item
.
imageUri
)
avatar
.
setImageURI
(
item
.
imageUri
)
}
}
val
status
=
item
.
status
?:
UserStatus
.
Offline
()
val
status
=
item
.
status
if
(
status
!=
null
)
{
val
statusDrawable
=
DrawableHelper
.
getUserStatusDrawable
(
status
,
itemView
.
context
)
val
statusDrawable
=
DrawableHelper
.
getUserStatusDrawable
(
status
,
itemView
.
context
)
statusView
.
setImageDrawable
(
statusDrawable
)
statusView
.
setImageDrawable
(
statusDrawable
)
}
else
{
statusView
.
setVisible
(
false
)
}
setOnClickListener
{
setOnClickListener
{
itemClickListener
?.
onClick
(
item
)
itemClickListener
?.
onClick
(
item
)
}
}
...
...
app/src/main/java/chat/rocket/android/chatroom/presentation/ChatRoomPresenter.kt
View file @
8685b5dd
...
@@ -291,7 +291,7 @@ class ChatRoomPresenter @Inject constructor(private val view: ChatRoomView,
...
@@ -291,7 +291,7 @@ class ChatRoomPresenter @Inject constructor(private val view: ChatRoomView,
view
.
showReplyingAction
(
view
.
showReplyingAction
(
username
=
user
,
username
=
user
,
replyMarkdown
=
"[ ]($serverUrl/$room/$roomName?msg=$id) $mention "
,
replyMarkdown
=
"[ ]($serverUrl/$room/$roomName?msg=$id) $mention "
,
quotedMessage
=
m
.
message
quotedMessage
=
m
apper
.
map
(
message
).
last
().
preview
?.
message
?:
""
)
)
}
}
}
}
...
@@ -499,7 +499,6 @@ class ChatRoomPresenter @Inject constructor(private val view: ChatRoomView,
...
@@ -499,7 +499,6 @@ class ChatRoomPresenter @Inject constructor(private val view: ChatRoomView,
//TODO: cache the commands
//TODO: cache the commands
val
commands
=
client
.
commands
(
0
,
100
).
result
val
commands
=
client
.
commands
(
0
,
100
).
result
view
.
populateCommandSuggestions
(
commands
.
map
{
view
.
populateCommandSuggestions
(
commands
.
map
{
println
(
"${it.command} - ${it.description}"
)
CommandSuggestionViewModel
(
it
.
command
,
it
.
description
?:
""
,
listOf
(
it
.
command
))
CommandSuggestionViewModel
(
it
.
command
,
it
.
description
?:
""
,
listOf
(
it
.
command
))
})
})
}
catch
(
ex
:
RocketChatException
)
{
}
catch
(
ex
:
RocketChatException
)
{
...
...
app/src/main/java/chat/rocket/android/chatroom/ui/ActionSnackbar.kt
View file @
8685b5dd
package
chat.rocket.android.chatroom.ui
package
chat.rocket.android.chatroom.ui
import
android.graphics.drawable.Drawable
import
android.support.design.widget.BaseTransientBottomBar
import
android.support.design.widget.BaseTransientBottomBar
import
android.support.v4.view.ViewCompat
import
android.support.v4.view.ViewCompat
import
android.text.Spannable
import
android.text.Spannable
import
android.text.SpannableStringBuilder
import
android.view.LayoutInflater
import
android.view.LayoutInflater
import
android.view.View
import
android.view.View
import
android.view.ViewGroup
import
android.view.ViewGroup
...
@@ -27,7 +27,6 @@ class ActionSnackbar : BaseTransientBottomBar<ActionSnackbar> {
...
@@ -27,7 +27,6 @@ class ActionSnackbar : BaseTransientBottomBar<ActionSnackbar> {
actionSnackbar
.
cancelView
=
view
.
findViewById
(
R
.
id
.
image_view_action_cancel_quote
)
as
ImageView
actionSnackbar
.
cancelView
=
view
.
findViewById
(
R
.
id
.
image_view_action_cancel_quote
)
as
ImageView
actionSnackbar
.
duration
=
BaseTransientBottomBar
.
LENGTH_INDEFINITE
actionSnackbar
.
duration
=
BaseTransientBottomBar
.
LENGTH_INDEFINITE
val
spannable
=
Markwon
.
markdown
(
context
,
content
).
trim
()
val
spannable
=
Markwon
.
markdown
(
context
,
content
).
trim
()
actionSnackbar
.
marginDrawable
=
context
.
getDrawable
(
R
.
drawable
.
quote
)
actionSnackbar
.
messageTextView
.
content
=
spannable
actionSnackbar
.
messageTextView
.
content
=
spannable
return
actionSnackbar
return
actionSnackbar
}
}
...
@@ -37,19 +36,16 @@ class ActionSnackbar : BaseTransientBottomBar<ActionSnackbar> {
...
@@ -37,19 +36,16 @@ class ActionSnackbar : BaseTransientBottomBar<ActionSnackbar> {
lateinit
var
cancelView
:
View
lateinit
var
cancelView
:
View
private
lateinit
var
messageTextView
:
TextView
private
lateinit
var
messageTextView
:
TextView
private
lateinit
var
titleTextView
:
TextView
private
lateinit
var
titleTextView
:
TextView
private
lateinit
var
marginDrawable
:
Drawable
var
text
:
String
=
""
var
text
:
String
=
""
set
(
value
)
{
set
(
value
)
{
val
spannable
=
parser
.
renderMarkdown
(
value
)
as
Spannable
val
spannable
=
SpannableStringBuilder
.
valueOf
(
value
)
spannable
.
setSpan
(
MessageParser
.
QuoteMarginSpan
(
marginDrawable
,
10
),
0
,
spannable
.
length
,
0
)
messageTextView
.
content
=
spannable
messageTextView
.
content
=
spannable
}
}
var
title
:
String
=
""
var
title
:
String
=
""
set
(
value
)
{
set
(
value
)
{
val
spannable
=
Markwon
.
markdown
(
this
.
context
,
value
)
as
Spannable
val
spannable
=
Markwon
.
markdown
(
this
.
context
,
value
)
as
Spannable
spannable
.
setSpan
(
MessageParser
.
QuoteMarginSpan
(
marginDrawable
,
10
),
0
,
spannable
.
length
,
0
)
titleTextView
.
content
=
spannable
titleTextView
.
content
=
spannable
}
}
...
...
app/src/main/java/chat/rocket/android/chatroom/ui/ChatRoomFragment.kt
View file @
8685b5dd
...
@@ -36,7 +36,9 @@ import kotlinx.android.synthetic.main.message_attachment_options.*
...
@@ -36,7 +36,9 @@ import kotlinx.android.synthetic.main.message_attachment_options.*
import
kotlinx.android.synthetic.main.message_composer.*
import
kotlinx.android.synthetic.main.message_composer.*
import
kotlinx.android.synthetic.main.message_list.*
import
kotlinx.android.synthetic.main.message_list.*
import
timber.log.Timber
import
timber.log.Timber
import
java.util.concurrent.atomic.AtomicInteger
import
javax.inject.Inject
import
javax.inject.Inject
import
kotlin.math.absoluteValue
fun
newInstance
(
chatRoomId
:
String
,
fun
newInstance
(
chatRoomId
:
String
,
chatRoomName
:
String
,
chatRoomName
:
String
,
...
@@ -88,6 +90,7 @@ class ChatRoomFragment : Fragment(), ChatRoomView, EmojiKeyboardListener, EmojiR
...
@@ -88,6 +90,7 @@ class ChatRoomFragment : Fragment(), ChatRoomView, EmojiKeyboardListener, EmojiR
private
val
centerX
by
lazy
{
recycler_view
.
right
}
private
val
centerX
by
lazy
{
recycler_view
.
right
}
private
val
centerY
by
lazy
{
recycler_view
.
bottom
}
private
val
centerY
by
lazy
{
recycler_view
.
bottom
}
private
val
handler
=
Handler
()
private
val
handler
=
Handler
()
private
var
verticalScrollOffset
=
AtomicInteger
(
0
)
override
fun
onCreate
(
savedInstanceState
:
Bundle
?)
{
override
fun
onCreate
(
savedInstanceState
:
Bundle
?)
{
super
.
onCreate
(
savedInstanceState
)
super
.
onCreate
(
savedInstanceState
)
...
@@ -207,13 +210,54 @@ class ChatRoomFragment : Fragment(), ChatRoomView, EmojiKeyboardListener, EmojiR
...
@@ -207,13 +210,54 @@ class ChatRoomFragment : Fragment(), ChatRoomView, EmojiKeyboardListener, EmojiR
}
}
})
})
}
}
recycler_view
.
addOnLayoutChangeListener
{
_
,
_
,
_
,
_
,
bottom
,
_
,
_
,
_
,
oldBottom
->
val
y
=
oldBottom
-
bottom
if
(
y
.
absoluteValue
>
0
)
{
// if y is positive the keyboard is up else it's down
recycler_view
.
post
{
if
(
y
>
0
||
verticalScrollOffset
.
get
().
absoluteValue
>=
y
.
absoluteValue
)
{
recycler_view
.
scrollBy
(
0
,
y
)
}
else
{
recycler_view
.
scrollBy
(
0
,
verticalScrollOffset
.
get
())
}
}
}
}
recycler_view
.
addOnScrollListener
(
object
:
RecyclerView
.
OnScrollListener
()
{
var
state
=
AtomicInteger
(
RecyclerView
.
SCROLL_STATE_IDLE
)
override
fun
onScrollStateChanged
(
recyclerView
:
RecyclerView
,
newState
:
Int
)
{
state
.
compareAndSet
(
RecyclerView
.
SCROLL_STATE_IDLE
,
newState
)
when
(
newState
)
{
RecyclerView
.
SCROLL_STATE_IDLE
->
{
if
(!
state
.
compareAndSet
(
RecyclerView
.
SCROLL_STATE_SETTLING
,
newState
))
{
state
.
compareAndSet
(
RecyclerView
.
SCROLL_STATE_DRAGGING
,
newState
)
}
}
RecyclerView
.
SCROLL_STATE_DRAGGING
->
{
state
.
compareAndSet
(
RecyclerView
.
SCROLL_STATE_IDLE
,
newState
)
}
RecyclerView
.
SCROLL_STATE_SETTLING
->
{
state
.
compareAndSet
(
RecyclerView
.
SCROLL_STATE_DRAGGING
,
newState
)
}
}
}
override
fun
onScrolled
(
recyclerView
:
RecyclerView
,
dx
:
Int
,
dy
:
Int
)
{
if
(
state
.
get
()
!=
RecyclerView
.
SCROLL_STATE_IDLE
)
{
verticalScrollOffset
.
getAndAdd
(
dy
)
}
}
})
}
}
val
oldMessagesCount
=
adapter
.
itemCount
val
oldMessagesCount
=
adapter
.
itemCount
adapter
.
appendData
(
dataSet
)
adapter
.
appendData
(
dataSet
)
recycler_view
.
scrollToPosition
(
92
)
if
(
oldMessagesCount
==
0
&&
dataSet
.
isNotEmpty
())
{
if
(
oldMessagesCount
==
0
&&
dataSet
.
isNotEmpty
())
{
recycler_view
.
scrollToPosition
(
0
)
recycler_view
.
scrollToPosition
(
0
)
verticalScrollOffset
.
set
(
0
)
}
}
presenter
.
loadActiveMembers
(
chatRoomId
,
chatRoomType
,
filterSelfOut
=
true
)
presenter
.
loadActiveMembers
(
chatRoomId
,
chatRoomType
,
filterSelfOut
=
true
)
}
}
...
@@ -241,6 +285,7 @@ class ChatRoomFragment : Fragment(), ChatRoomView, EmojiKeyboardListener, EmojiR
...
@@ -241,6 +285,7 @@ class ChatRoomFragment : Fragment(), ChatRoomView, EmojiKeyboardListener, EmojiR
override
fun
showNewMessage
(
message
:
List
<
BaseViewModel
<*
>>)
{
override
fun
showNewMessage
(
message
:
List
<
BaseViewModel
<*
>>)
{
adapter
.
prependData
(
message
)
adapter
.
prependData
(
message
)
recycler_view
.
scrollToPosition
(
0
)
recycler_view
.
scrollToPosition
(
0
)
verticalScrollOffset
.
set
(
0
)
}
}
override
fun
disableSendMessageButton
()
{
override
fun
disableSendMessageButton
()
{
...
@@ -281,6 +326,7 @@ class ChatRoomFragment : Fragment(), ChatRoomView, EmojiKeyboardListener, EmojiR
...
@@ -281,6 +326,7 @@ class ChatRoomFragment : Fragment(), ChatRoomView, EmojiKeyboardListener, EmojiR
if
(!
recycler_view
.
isAtBottom
())
{
if
(!
recycler_view
.
isAtBottom
())
{
if
(
adapter
.
itemCount
>
0
)
{
if
(
adapter
.
itemCount
>
0
)
{
recycler_view
.
scrollToPosition
(
0
)
recycler_view
.
scrollToPosition
(
0
)
verticalScrollOffset
.
set
(
0
)
}
}
}
}
}
}
...
@@ -430,6 +476,7 @@ class ChatRoomFragment : Fragment(), ChatRoomView, EmojiKeyboardListener, EmojiR
...
@@ -430,6 +476,7 @@ class ChatRoomFragment : Fragment(), ChatRoomView, EmojiKeyboardListener, EmojiR
private
fun
setupFab
()
{
private
fun
setupFab
()
{
button_fab
.
setOnClickListener
{
button_fab
.
setOnClickListener
{
recycler_view
.
scrollToPosition
(
0
)
recycler_view
.
scrollToPosition
(
0
)
verticalScrollOffset
.
set
(
0
)
button_fab
.
hide
()
button_fab
.
hide
()
}
}
}
}
...
@@ -453,11 +500,6 @@ class ChatRoomFragment : Fragment(), ChatRoomView, EmojiKeyboardListener, EmojiR
...
@@ -453,11 +500,6 @@ class ChatRoomFragment : Fragment(), ChatRoomView, EmojiKeyboardListener, EmojiR
emojiKeyboardPopup
.
listener
=
this
emojiKeyboardPopup
.
listener
=
this
text_message
.
listener
=
object
:
ComposerEditText
.
ComposerEditTextListener
{
text_message
.
listener
=
object
:
ComposerEditText
.
ComposerEditTextListener
{
override
fun
onKeyboardOpened
()
{
override
fun
onKeyboardOpened
()
{
if
(
recycler_view
.
isAtBottom
())
{
if
(
adapter
.
itemCount
>
0
)
{
recycler_view
.
scrollToPosition
(
0
)
}
}
}
}
override
fun
onKeyboardClosed
()
{
override
fun
onKeyboardClosed
()
{
...
@@ -511,7 +553,7 @@ class ChatRoomFragment : Fragment(), ChatRoomView, EmojiKeyboardListener, EmojiR
...
@@ -511,7 +553,7 @@ class ChatRoomFragment : Fragment(), ChatRoomView, EmojiKeyboardListener, EmojiR
private
fun
setupSuggestionsView
()
{
private
fun
setupSuggestionsView
()
{
suggestions_view
.
anchorTo
(
text_message
)
suggestions_view
.
anchorTo
(
text_message
)
.
setMaximumHeight
(
resources
.
getDimensionPixelSize
(
R
.
dimen
.
suggestions_box_max_height
))
.
setMaximumHeight
(
resources
.
getDimensionPixelSize
(
R
.
dimen
.
suggestions_box_max_height
))
.
addTokenAdapter
(
PeopleSuggestionsAdapter
())
.
addTokenAdapter
(
PeopleSuggestionsAdapter
(
context
!!
))
.
addTokenAdapter
(
CommandSuggestionsAdapter
())
.
addTokenAdapter
(
CommandSuggestionsAdapter
())
.
addTokenAdapter
(
RoomSuggestionsAdapter
())
.
addTokenAdapter
(
RoomSuggestionsAdapter
())
.
addSuggestionProviderAction
(
"@"
)
{
query
->
.
addSuggestionProviderAction
(
"@"
)
{
query
->
...
...
app/src/main/java/chat/rocket/android/chatroom/viewmodel/AudioAttachmentViewModel.kt
View file @
8685b5dd
...
@@ -12,8 +12,9 @@ data class AudioAttachmentViewModel(
...
@@ -12,8 +12,9 @@ data class AudioAttachmentViewModel(
override
val
attachmentTitle
:
CharSequence
,
override
val
attachmentTitle
:
CharSequence
,
override
val
id
:
Long
,
override
val
id
:
Long
,
override
var
reactions
:
List
<
ReactionViewModel
>,
override
var
reactions
:
List
<
ReactionViewModel
>,
override
var
nextDownStreamMessage
:
BaseViewModel
<*>?
=
null
override
var
nextDownStreamMessage
:
BaseViewModel
<*>?
=
null
,
)
:
BaseFileAttachmentViewModel
<
AudioAttachment
>
{
override
var
preview
:
Message
?
=
null
)
:
BaseFileAttachmentViewModel
<
AudioAttachment
>
{
override
val
viewType
:
Int
override
val
viewType
:
Int
get
()
=
BaseViewModel
.
ViewType
.
AUDIO_ATTACHMENT
.
viewType
get
()
=
BaseViewModel
.
ViewType
.
AUDIO_ATTACHMENT
.
viewType
override
val
layoutId
:
Int
override
val
layoutId
:
Int
...
...
app/src/main/java/chat/rocket/android/chatroom/viewmodel/BaseViewModel.kt
View file @
8685b5dd
...
@@ -11,6 +11,7 @@ interface BaseViewModel<out T> {
...
@@ -11,6 +11,7 @@ interface BaseViewModel<out T> {
val
layoutId
:
Int
val
layoutId
:
Int
var
reactions
:
List
<
ReactionViewModel
>
var
reactions
:
List
<
ReactionViewModel
>
var
nextDownStreamMessage
:
BaseViewModel
<*>?
var
nextDownStreamMessage
:
BaseViewModel
<*>?
var
preview
:
Message
?
enum
class
ViewType
(
val
viewType
:
Int
)
{
enum
class
ViewType
(
val
viewType
:
Int
)
{
MESSAGE
(
0
),
MESSAGE
(
0
),
...
...
app/src/main/java/chat/rocket/android/chatroom/viewmodel/ImageAttachmentViewModel.kt
View file @
8685b5dd
...
@@ -12,7 +12,8 @@ data class ImageAttachmentViewModel(
...
@@ -12,7 +12,8 @@ data class ImageAttachmentViewModel(
override
val
attachmentTitle
:
CharSequence
,
override
val
attachmentTitle
:
CharSequence
,
override
val
id
:
Long
,
override
val
id
:
Long
,
override
var
reactions
:
List
<
ReactionViewModel
>,
override
var
reactions
:
List
<
ReactionViewModel
>,
override
var
nextDownStreamMessage
:
BaseViewModel
<*>?
=
null
override
var
nextDownStreamMessage
:
BaseViewModel
<*>?
=
null
,
override
var
preview
:
Message
?
=
null
)
:
BaseFileAttachmentViewModel
<
ImageAttachment
>
{
)
:
BaseFileAttachmentViewModel
<
ImageAttachment
>
{
override
val
viewType
:
Int
override
val
viewType
:
Int
get
()
=
BaseViewModel
.
ViewType
.
IMAGE_ATTACHMENT
.
viewType
get
()
=
BaseViewModel
.
ViewType
.
IMAGE_ATTACHMENT
.
viewType
...
...
app/src/main/java/chat/rocket/android/chatroom/viewmodel/MessageAttachmentViewModel.kt
0 → 100644
View file @
8685b5dd
package
chat.rocket.android.chatroom.viewmodel
import
chat.rocket.android.R
import
chat.rocket.core.model.Message
data class
MessageAttachmentViewModel
(
override
val
message
:
Message
,
override
val
rawData
:
Message
,
override
val
messageId
:
String
,
var
senderName
:
String
,
val
time
:
CharSequence
,
val
content
:
CharSequence
,
val
isPinned
:
Boolean
,
override
var
reactions
:
List
<
ReactionViewModel
>,
override
var
nextDownStreamMessage
:
BaseViewModel
<*>?
=
null
,
var
messageLink
:
String
?
=
null
,
override
var
preview
:
Message
?
=
null
)
:
BaseViewModel
<
Message
>
{
override
val
viewType
:
Int
get
()
=
BaseViewModel
.
ViewType
.
MESSAGE_ATTACHMENT
.
viewType
override
val
layoutId
:
Int
get
()
=
R
.
layout
.
item_message_attachment
}
\ No newline at end of file
app/src/main/java/chat/rocket/android/chatroom/viewmodel/MessageViewModel.kt
View file @
8685b5dd
...
@@ -14,6 +14,7 @@ data class MessageViewModel(
...
@@ -14,6 +14,7 @@ data class MessageViewModel(
override
val
isPinned
:
Boolean
,
override
val
isPinned
:
Boolean
,
override
var
reactions
:
List
<
ReactionViewModel
>,
override
var
reactions
:
List
<
ReactionViewModel
>,
override
var
nextDownStreamMessage
:
BaseViewModel
<*>?
=
null
,
override
var
nextDownStreamMessage
:
BaseViewModel
<*>?
=
null
,
override
var
preview
:
Message
?
=
null
,
var
isFirstUnread
:
Boolean
var
isFirstUnread
:
Boolean
)
:
BaseMessageViewModel
<
Message
>
{
)
:
BaseMessageViewModel
<
Message
>
{
override
val
viewType
:
Int
override
val
viewType
:
Int
...
...
app/src/main/java/chat/rocket/android/chatroom/viewmodel/UrlPreviewViewModel.kt
View file @
8685b5dd
...
@@ -13,7 +13,8 @@ data class UrlPreviewViewModel(
...
@@ -13,7 +13,8 @@ data class UrlPreviewViewModel(
val
description
:
CharSequence
?,
val
description
:
CharSequence
?,
val
thumbUrl
:
String
?,
val
thumbUrl
:
String
?,
override
var
reactions
:
List
<
ReactionViewModel
>,
override
var
reactions
:
List
<
ReactionViewModel
>,
override
var
nextDownStreamMessage
:
BaseViewModel
<*>?
=
null
override
var
nextDownStreamMessage
:
BaseViewModel
<*>?
=
null
,
override
var
preview
:
Message
?
=
null
)
:
BaseViewModel
<
Url
>
{
)
:
BaseViewModel
<
Url
>
{
override
val
viewType
:
Int
override
val
viewType
:
Int
get
()
=
BaseViewModel
.
ViewType
.
URL_PREVIEW
.
viewType
get
()
=
BaseViewModel
.
ViewType
.
URL_PREVIEW
.
viewType
...
...
app/src/main/java/chat/rocket/android/chatroom/viewmodel/VideoAttachmentViewModel.kt
View file @
8685b5dd
...
@@ -12,7 +12,8 @@ data class VideoAttachmentViewModel(
...
@@ -12,7 +12,8 @@ data class VideoAttachmentViewModel(
override
val
attachmentTitle
:
CharSequence
,
override
val
attachmentTitle
:
CharSequence
,
override
val
id
:
Long
,
override
val
id
:
Long
,
override
var
reactions
:
List
<
ReactionViewModel
>,
override
var
reactions
:
List
<
ReactionViewModel
>,
override
var
nextDownStreamMessage
:
BaseViewModel
<*>?
=
null
override
var
nextDownStreamMessage
:
BaseViewModel
<*>?
=
null
,
override
var
preview
:
Message
?
=
null
)
:
BaseFileAttachmentViewModel
<
VideoAttachment
>
{
)
:
BaseFileAttachmentViewModel
<
VideoAttachment
>
{
override
val
viewType
:
Int
override
val
viewType
:
Int
get
()
=
BaseViewModel
.
ViewType
.
VIDEO_ATTACHMENT
.
viewType
get
()
=
BaseViewModel
.
ViewType
.
VIDEO_ATTACHMENT
.
viewType
...
...
app/src/main/java/chat/rocket/android/chatroom/viewmodel/ViewModelMapper.kt
View file @
8685b5dd
...
@@ -4,9 +4,7 @@ import DateTimeHelper
...
@@ -4,9 +4,7 @@ import DateTimeHelper
import
android.content.Context
import
android.content.Context
import
android.graphics.Color
import
android.graphics.Color
import
android.graphics.Typeface
import
android.graphics.Typeface
import
android.text.SpannableString
import
android.text.SpannableStringBuilder
import
android.text.SpannableStringBuilder
import
android.text.style.AbsoluteSizeSpan
import
android.text.style.ForegroundColorSpan
import
android.text.style.ForegroundColorSpan
import
android.text.style.StyleSpan
import
android.text.style.StyleSpan
import
chat.rocket.android.R
import
chat.rocket.android.R
...
@@ -22,10 +20,8 @@ import chat.rocket.core.model.attachment.*
...
@@ -22,10 +20,8 @@ import chat.rocket.core.model.attachment.*
import
chat.rocket.core.model.isSystemMessage
import
chat.rocket.core.model.isSystemMessage
import
chat.rocket.core.model.url.Url
import
chat.rocket.core.model.url.Url
import
kotlinx.coroutines.experimental.CommonPool
import
kotlinx.coroutines.experimental.CommonPool
import
kotlinx.coroutines.experimental.launch
import
kotlinx.coroutines.experimental.withContext
import
kotlinx.coroutines.experimental.withContext
import
okhttp3.HttpUrl
import
okhttp3.HttpUrl
import
timber.log.Timber
import
java.security.InvalidParameterException
import
java.security.InvalidParameterException
import
javax.inject.Inject
import
javax.inject.Inject
...
@@ -72,9 +68,17 @@ class ViewModelMapper @Inject constructor(private val context: Context,
...
@@ -72,9 +68,17 @@ class ViewModelMapper @Inject constructor(private val context: Context,
}
}
mapMessage
(
message
).
let
{
mapMessage
(
message
).
let
{
if
(
list
.
size
>
0
)
{
it
.
preview
=
list
[
0
].
preview
}
list
.
add
(
it
)
list
.
add
(
it
)
}
}
for
(
i
in
list
.
size
-
1
downTo
0
)
{
val
next
=
if
(
i
-
1
<
0
)
null
else
list
[
i
-
1
]
list
[
i
].
nextDownStreamMessage
=
next
}
return
@withContext
list
return
@withContext
list
}
}
...
@@ -87,27 +91,47 @@ class ViewModelMapper @Inject constructor(private val context: Context,
...
@@ -87,27 +91,47 @@ class ViewModelMapper @Inject constructor(private val context: Context,
val
description
=
url
.
meta
?.
description
val
description
=
url
.
meta
?.
description
return
UrlPreviewViewModel
(
message
,
url
,
message
.
id
,
title
,
hostname
,
description
,
thumb
,
return
UrlPreviewViewModel
(
message
,
url
,
message
.
id
,
title
,
hostname
,
description
,
thumb
,
getReactions
(
message
))
getReactions
(
message
)
,
preview
=
message
.
copy
(
message
=
url
.
url
)
)
}
}
private
fun
mapAttachment
(
message
:
Message
,
attachment
:
Attachment
):
BaseViewModel
<
*
>?
{
private
suspend
fun
mapAttachment
(
message
:
Message
,
attachment
:
Attachment
):
BaseViewModel
<
*
>?
{
return
when
(
attachment
)
{
return
when
(
attachment
)
{
is
FileAttachment
->
mapFileAttachment
(
message
,
attachment
)
is
FileAttachment
->
mapFileAttachment
(
message
,
attachment
)
is
MessageAttachment
->
mapMessageAttachment
(
message
,
attachment
)
else
->
null
else
->
null
}
}
}
}
private
suspend
fun
mapMessageAttachment
(
message
:
Message
,
attachment
:
MessageAttachment
):
MessageAttachmentViewModel
{
val
attachmentAuthor
=
attachment
.
author
!!
val
time
=
getTime
(
attachment
.
timestamp
!!
)
val
attachmentText
=
when
(
attachment
.
attachments
.
orEmpty
().
firstOrNull
())
{
is
ImageAttachment
->
context
.
getString
(
R
.
string
.
msg_preview_photo
)
is
VideoAttachment
->
context
.
getString
(
R
.
string
.
msg_preview_video
)
is
AudioAttachment
->
context
.
getString
(
R
.
string
.
msg_preview_audio
)
else
->
attachment
.
text
?:
""
}
val
content
=
stripMessageQuotes
(
message
)
return
MessageAttachmentViewModel
(
message
=
content
,
rawData
=
message
,
messageId
=
message
.
id
,
time
=
time
,
senderName
=
attachmentAuthor
,
content
=
attachmentText
,
isPinned
=
message
.
pinned
,
reactions
=
getReactions
(
message
),
preview
=
message
.
copy
(
message
=
content
.
message
))
}
private
fun
mapFileAttachment
(
message
:
Message
,
attachment
:
FileAttachment
):
BaseViewModel
<
*
>?
{
private
fun
mapFileAttachment
(
message
:
Message
,
attachment
:
FileAttachment
):
BaseViewModel
<
*
>?
{
val
attachmentUrl
=
attachmentUrl
(
attachment
)
val
attachmentUrl
=
attachmentUrl
(
attachment
)
val
attachmentTitle
=
attachmentTitle
(
attachment
)
val
attachmentTitle
=
attachmentTitle
(
attachment
)
val
id
=
attachmentId
(
message
,
attachment
)
val
id
=
attachmentId
(
message
,
attachment
)
return
when
(
attachment
)
{
return
when
(
attachment
)
{
is
ImageAttachment
->
ImageAttachmentViewModel
(
message
,
attachment
,
message
.
id
,
is
ImageAttachment
->
ImageAttachmentViewModel
(
message
,
attachment
,
message
.
id
,
attachmentUrl
,
attachmentTitle
,
id
,
getReactions
(
message
))
attachmentUrl
,
attachmentTitle
,
id
,
getReactions
(
message
),
preview
=
message
.
copy
(
message
=
context
.
getString
(
R
.
string
.
msg_preview_photo
)))
is
VideoAttachment
->
VideoAttachmentViewModel
(
message
,
attachment
,
message
.
id
,
is
VideoAttachment
->
VideoAttachmentViewModel
(
message
,
attachment
,
message
.
id
,
attachmentUrl
,
attachmentTitle
,
id
,
getReactions
(
message
))
attachmentUrl
,
attachmentTitle
,
id
,
getReactions
(
message
),
preview
=
message
.
copy
(
message
=
context
.
getString
(
R
.
string
.
msg_preview_video
)))
is
AudioAttachment
->
AudioAttachmentViewModel
(
message
,
attachment
,
message
.
id
,
is
AudioAttachment
->
AudioAttachmentViewModel
(
message
,
attachment
,
message
.
id
,
attachmentUrl
,
attachmentTitle
,
id
,
getReactions
(
message
))
attachmentUrl
,
attachmentTitle
,
id
,
getReactions
(
message
),
preview
=
message
.
copy
(
message
=
context
.
getString
(
R
.
string
.
msg_preview_audio
)))
else
->
null
else
->
null
}
}
}
}
...
@@ -151,32 +175,20 @@ class ViewModelMapper @Inject constructor(private val context: Context,
...
@@ -151,32 +175,20 @@ class ViewModelMapper @Inject constructor(private val context: Context,
val
sender
=
getSenderName
(
message
)
val
sender
=
getSenderName
(
message
)
val
time
=
getTime
(
message
.
timestamp
)
val
time
=
getTime
(
message
.
timestamp
)
val
avatar
=
getUserAvatar
(
message
)
val
avatar
=
getUserAvatar
(
message
)
val
preview
=
mapMessagePreview
(
message
)
val
baseUrl
=
settings
.
baseUrl
()
val
content
=
getContent
(
stripMessageQuotes
(
message
))
var
quote
:
Message
?
=
null
MessageViewModel
(
message
=
stripMessageQuotes
(
message
),
rawData
=
message
,
val
urls
=
ArrayList
<
Url
>()
message
.
urls
?.
let
{
if
(
it
.
isEmpty
())
return
@let
for
(
url
in
it
)
{
urls
.
add
(
url
)
baseUrl
?.
let
{
val
quoteUrl
=
HttpUrl
.
parse
(
url
.
url
)
val
serverUrl
=
HttpUrl
.
parse
(
baseUrl
)
if
(
quoteUrl
!=
null
&&
serverUrl
!=
null
)
{
quote
=
makeQuote
(
quoteUrl
,
serverUrl
)
?.
let
{
getMessageWithoutQuoteMarkdown
(
it
)
}
}
}
}
}
val
content
=
getContent
(
context
,
getMessageWithoutQuoteMarkdown
(
message
),
quote
)
MessageViewModel
(
message
=
getMessageWithoutQuoteMarkdown
(
message
),
rawData
=
message
,
messageId
=
message
.
id
,
avatar
=
avatar
!!
,
time
=
time
,
senderName
=
sender
,
messageId
=
message
.
id
,
avatar
=
avatar
!!
,
time
=
time
,
senderName
=
sender
,
content
=
content
,
isPinned
=
message
.
pinned
,
reactions
=
getReactions
(
message
),
content
=
content
,
isPinned
=
message
.
pinned
,
reactions
=
getReactions
(
message
),
isFirstUnread
=
false
)
isFirstUnread
=
false
,
preview
=
preview
)
}
private
suspend
fun
mapMessagePreview
(
message
:
Message
):
Message
{
return
when
(
message
.
isSystemMessage
())
{
false
->
stripMessageQuotes
(
message
)
true
->
message
.
copy
(
message
=
getSystemMessage
(
message
).
toString
())
}
}
}
private
fun
getReactions
(
message
:
Message
):
List
<
ReactionViewModel
>
{
private
fun
getReactions
(
message
:
Message
):
List
<
ReactionViewModel
>
{
...
@@ -198,7 +210,7 @@ class ViewModelMapper @Inject constructor(private val context: Context,
...
@@ -198,7 +210,7 @@ class ViewModelMapper @Inject constructor(private val context: Context,
return
reactions
?:
emptyList
()
return
reactions
?:
emptyList
()
}
}
private
fun
getMessageWithoutQuoteMarkdown
(
message
:
Message
):
Message
{
private
suspend
fun
stripMessageQuotes
(
message
:
Message
):
Message
{
val
baseUrl
=
settings
.
baseUrl
()
val
baseUrl
=
settings
.
baseUrl
()
return
message
.
copy
(
return
message
.
copy
(
message
=
message
.
message
.
replace
(
"\\[\\s\\]\\($baseUrl.*\\)"
.
toRegex
(),
""
).
trim
()
message
=
message
.
message
.
replace
(
"\\[\\s\\]\\($baseUrl.*\\)"
.
toRegex
(),
""
).
trim
()
...
@@ -229,33 +241,14 @@ class ViewModelMapper @Inject constructor(private val context: Context,
...
@@ -229,33 +241,14 @@ class ViewModelMapper @Inject constructor(private val context: Context,
private
fun
getTime
(
timestamp
:
Long
)
=
DateTimeHelper
.
getTime
(
DateTimeHelper
.
getLocalDateTime
(
timestamp
))
private
fun
getTime
(
timestamp
:
Long
)
=
DateTimeHelper
.
getTime
(
DateTimeHelper
.
getLocalDateTime
(
timestamp
))
private
fun
makeQuote
(
quoteUrl
:
HttpUrl
,
serverUrl
:
HttpUrl
):
Message
?
{
private
suspend
fun
getContent
(
message
:
Message
):
CharSequence
{
if
(
quoteUrl
.
host
()
==
serverUrl
.
host
())
{
val
msgIdToQuote
=
quoteUrl
.
queryParameter
(
"msg"
)
Timber
.
d
(
"Will quote message Id: $msgIdToQuote"
)
return
if
(
msgIdToQuote
!=
null
)
messagesRepository
.
getById
(
msgIdToQuote
)
else
null
}
return
null
}
private
suspend
fun
getContent
(
context
:
Context
,
message
:
Message
,
quote
:
Message
?):
CharSequence
{
return
when
(
message
.
isSystemMessage
())
{
return
when
(
message
.
isSystemMessage
())
{
true
->
getSystemMessage
(
message
,
context
)
true
->
getSystemMessage
(
message
)
false
->
getNormalMessage
(
message
,
quot
e
)
false
->
parser
.
renderMarkdown
(
message
,
currentUsernam
e
)
}
}
}
}
private
suspend
fun
getNormalMessage
(
message
:
Message
,
quote
:
Message
?):
CharSequence
{
private
fun
getSystemMessage
(
message
:
Message
):
CharSequence
{
var
quoteViewModel
:
MessageViewModel
?
=
null
if
(
quote
!=
null
)
{
val
quoteMessage
:
Message
=
quote
quoteViewModel
=
mapMessage
(
quoteMessage
)
}
return
parser
.
renderMarkdown
(
message
.
message
,
quoteViewModel
,
currentUsername
)
}
private
fun
getSystemMessage
(
message
:
Message
,
context
:
Context
):
CharSequence
{
val
content
=
when
(
message
.
type
)
{
val
content
=
when
(
message
.
type
)
{
//TODO: Add implementation for Welcome type.
//TODO: Add implementation for Welcome type.
is
MessageType
.
MessageRemoved
->
context
.
getString
(
R
.
string
.
message_removed
)
is
MessageType
.
MessageRemoved
->
context
.
getString
(
R
.
string
.
message_removed
)
...
@@ -264,68 +257,17 @@ class ViewModelMapper @Inject constructor(private val context: Context,
...
@@ -264,68 +257,17 @@ class ViewModelMapper @Inject constructor(private val context: Context,
is
MessageType
.
UserAdded
->
context
.
getString
(
R
.
string
.
message_user_added_by
,
message
.
message
,
message
.
sender
?.
username
)
is
MessageType
.
UserAdded
->
context
.
getString
(
R
.
string
.
message_user_added_by
,
message
.
message
,
message
.
sender
?.
username
)
is
MessageType
.
RoomNameChanged
->
context
.
getString
(
R
.
string
.
message_room_name_changed
,
message
.
message
,
message
.
sender
?.
username
)
is
MessageType
.
RoomNameChanged
->
context
.
getString
(
R
.
string
.
message_room_name_changed
,
message
.
message
,
message
.
sender
?.
username
)
is
MessageType
.
UserRemoved
->
context
.
getString
(
R
.
string
.
message_user_removed_by
,
message
.
message
,
message
.
sender
?.
username
)
is
MessageType
.
UserRemoved
->
context
.
getString
(
R
.
string
.
message_user_removed_by
,
message
.
message
,
message
.
sender
?.
username
)
is
MessageType
.
MessagePinned
->
{
is
MessageType
.
MessagePinned
->
context
.
getString
(
R
.
string
.
message_pinned
)
val
attachment
=
message
.
attachments
?.
get
(
0
)
val
pinnedSystemMessage
=
context
.
getString
(
R
.
string
.
message_pinned
)
if
(
attachment
!=
null
&&
attachment
is
MessageAttachment
)
{
return
SpannableStringBuilder
(
pinnedSystemMessage
)
.
apply
{
setSpan
(
StyleSpan
(
Typeface
.
ITALIC
),
0
,
length
,
0
)
setSpan
(
ForegroundColorSpan
(
Color
.
GRAY
),
0
,
length
,
0
)
}
.
append
(
quoteMessage
(
attachment
.
author
!!
,
attachment
.
text
!!
,
attachment
.
timestamp
!!
))
}
return
pinnedSystemMessage
}
else
->
{
else
->
{
throw
InvalidParameterException
(
"Invalid message type: ${message.type}"
)
throw
InvalidParameterException
(
"Invalid message type: ${message.type}"
)
}
}
}
}
//isSystemMessage = true
val
spannableMsg
=
SpannableStringBuilder
(
content
)
val
spannableMsg
=
SpannableStringBuilder
(
content
)
spannableMsg
.
setSpan
(
StyleSpan
(
Typeface
.
ITALIC
),
0
,
spannableMsg
.
length
,
spannableMsg
.
setSpan
(
StyleSpan
(
Typeface
.
ITALIC
),
0
,
spannableMsg
.
length
,
0
)
0
)
spannableMsg
.
setSpan
(
ForegroundColorSpan
(
Color
.
GRAY
),
0
,
spannableMsg
.
length
,
spannableMsg
.
setSpan
(
ForegroundColorSpan
(
Color
.
GRAY
),
0
,
spannableMsg
.
length
,
0
)
0
)
/*if (attachmentType == null) {
val username = message.sender?.username
val message = message.message
val usernameTextStartIndex = if (username != null) content.indexOf(username) else -1
val usernameTextEndIndex = if (username != null) usernameTextStartIndex + username.length else -1
val messageTextStartIndex = if (message.isNotEmpty()) content.indexOf(message) else -1
val messageTextEndIndex = messageTextStartIndex + message.length
if (usernameTextStartIndex > -1) {
spannableMsg.setSpan(StyleSpan(Typeface.BOLD_ITALIC), usernameTextStartIndex, usernameTextEndIndex,
0)
}
if (messageTextStartIndex > -1) {
spannableMsg.setSpan(StyleSpan(Typeface.BOLD_ITALIC), messageTextStartIndex, messageTextEndIndex,
0)
}
} else if (attachmentType == AttachmentType.Message) {
spannableMsg.append(quoteMessage(attachmentMessageAuthor!!, attachmentMessageText!!, attachmentTimestamp!!))
}*/
return
spannableMsg
return
spannableMsg
}
}
private
fun
quoteMessage
(
author
:
String
,
text
:
String
,
timestamp
:
Long
):
CharSequence
{
return
SpannableStringBuilder
().
apply
{
val
header
=
"\n$author ${getTime(timestamp)}\n"
append
(
SpannableString
(
header
).
apply
{
setSpan
(
StyleSpan
(
Typeface
.
BOLD
),
1
,
author
.
length
+
1
,
0
)
setSpan
(
MessageParser
.
QuoteMarginSpan
(
context
.
getDrawable
(
R
.
drawable
.
quote
),
10
),
1
,
length
,
0
)
setSpan
(
AbsoluteSizeSpan
(
context
.
resources
.
getDimensionPixelSize
(
R
.
dimen
.
message_time_text_size
)),
author
.
length
+
1
,
length
,
0
)
})
append
(
SpannableString
(
parser
.
renderMarkdown
(
text
)).
apply
{
setSpan
(
MessageParser
.
QuoteMarginSpan
(
context
.
getDrawable
(
R
.
drawable
.
quote
),
10
),
0
,
length
,
0
)
})
}
}
}
}
\ No newline at end of file
app/src/main/java/chat/rocket/android/chatroom/viewmodel/suggestion/PeopleSuggestionViewModel.kt
View file @
8685b5dd
...
@@ -3,7 +3,7 @@ package chat.rocket.android.chatroom.viewmodel.suggestion
...
@@ -3,7 +3,7 @@ package chat.rocket.android.chatroom.viewmodel.suggestion
import
chat.rocket.android.widget.autocompletion.model.SuggestionModel
import
chat.rocket.android.widget.autocompletion.model.SuggestionModel
import
chat.rocket.common.model.UserStatus
import
chat.rocket.common.model.UserStatus
class
PeopleSuggestionViewModel
(
val
imageUri
:
String
,
class
PeopleSuggestionViewModel
(
val
imageUri
:
String
?
,
text
:
String
,
text
:
String
,
val
username
:
String
,
val
username
:
String
,
val
name
:
String
,
val
name
:
String
,
...
...
app/src/main/java/chat/rocket/android/chatrooms/presentation/ChatRoomsPresenter.kt
View file @
8685b5dd
package
chat.rocket.android.chatrooms.presentation
package
chat.rocket.android.chatrooms.presentation
import
chat.rocket.android.chatroom.viewmodel.ViewModelMapper
import
chat.rocket.android.core.lifecycle.CancelStrategy
import
chat.rocket.android.core.lifecycle.CancelStrategy
import
chat.rocket.android.main.presentation.MainNavigator
import
chat.rocket.android.main.presentation.MainNavigator
import
chat.rocket.android.server.domain.*
import
chat.rocket.android.server.domain.*
...
@@ -9,7 +10,10 @@ import chat.rocket.android.server.infraestructure.chatRooms
...
@@ -9,7 +10,10 @@ import chat.rocket.android.server.infraestructure.chatRooms
import
chat.rocket.android.server.infraestructure.state
import
chat.rocket.android.server.infraestructure.state
import
chat.rocket.android.util.extensions.launchUI
import
chat.rocket.android.util.extensions.launchUI
import
chat.rocket.common.RocketChatException
import
chat.rocket.common.RocketChatException
import
chat.rocket.common.model.*
import
chat.rocket.common.model.BaseRoom
import
chat.rocket.common.model.RoomType
import
chat.rocket.common.model.SimpleUser
import
chat.rocket.common.model.User
import
chat.rocket.core.internal.model.Subscription
import
chat.rocket.core.internal.model.Subscription
import
chat.rocket.core.internal.realtime.State
import
chat.rocket.core.internal.realtime.State
import
chat.rocket.core.internal.realtime.StreamMessage
import
chat.rocket.core.internal.realtime.StreamMessage
...
@@ -30,6 +34,7 @@ class ChatRoomsPresenter @Inject constructor(private val view: ChatRoomsView,
...
@@ -30,6 +34,7 @@ class ChatRoomsPresenter @Inject constructor(private val view: ChatRoomsView,
private
val
getChatRoomsInteractor
:
GetChatRoomsInteractor
,
private
val
getChatRoomsInteractor
:
GetChatRoomsInteractor
,
private
val
saveChatRoomsInteractor
:
SaveChatRoomsInteractor
,
private
val
saveChatRoomsInteractor
:
SaveChatRoomsInteractor
,
private
val
refreshSettingsInteractor
:
RefreshSettingsInteractor
,
private
val
refreshSettingsInteractor
:
RefreshSettingsInteractor
,
private
val
viewModelMapper
:
ViewModelMapper
,
settingsRepository
:
SettingsRepository
,
settingsRepository
:
SettingsRepository
,
factory
:
ConnectionManagerFactory
)
{
factory
:
ConnectionManagerFactory
)
{
private
val
manager
:
ConnectionManager
=
factory
.
create
(
serverInteractor
.
get
()
!!
)
private
val
manager
:
ConnectionManager
=
factory
.
create
(
serverInteractor
.
get
()
!!
)
...
@@ -89,9 +94,9 @@ class ChatRoomsPresenter @Inject constructor(private val view: ChatRoomsView,
...
@@ -89,9 +94,9 @@ class ChatRoomsPresenter @Inject constructor(private val view: ChatRoomsView,
val
chatRoomsCombined
=
mutableListOf
<
ChatRoom
>()
val
chatRoomsCombined
=
mutableListOf
<
ChatRoom
>()
chatRoomsCombined
.
addAll
(
usersToChatRooms
(
users
))
chatRoomsCombined
.
addAll
(
usersToChatRooms
(
users
))
chatRoomsCombined
.
addAll
(
roomsToChatRooms
(
rooms
))
chatRoomsCombined
.
addAll
(
roomsToChatRooms
(
rooms
))
view
.
updateChatRooms
(
chatRoomsCombined
)
view
.
updateChatRooms
(
getChatRoomsWithPreviews
(
chatRoomsCombined
.
toList
())
)
}
else
{
}
else
{
view
.
updateChatRooms
(
roomList
)
view
.
updateChatRooms
(
getChatRoomsWithPreviews
(
roomList
)
)
}
}
}
catch
(
ex
:
RocketChatException
)
{
}
catch
(
ex
:
RocketChatException
)
{
Timber
.
e
(
ex
)
Timber
.
e
(
ex
)
...
@@ -156,7 +161,7 @@ class ChatRoomsPresenter @Inject constructor(private val view: ChatRoomsView,
...
@@ -156,7 +161,7 @@ class ChatRoomsPresenter @Inject constructor(private val view: ChatRoomsView,
val
sortedRooms
=
sortRooms
(
chatRooms
)
val
sortedRooms
=
sortRooms
(
chatRooms
)
Timber
.
d
(
"Loaded rooms: ${sortedRooms.size}"
)
Timber
.
d
(
"Loaded rooms: ${sortedRooms.size}"
)
saveChatRoomsInteractor
.
save
(
currentServer
,
sortedRooms
)
saveChatRoomsInteractor
.
save
(
currentServer
,
sortedRooms
)
return
sortedRooms
return
getChatRoomsWithPreviews
(
sortedRooms
)
}
}
private
fun
sortRooms
(
chatRooms
:
List
<
ChatRoom
>):
List
<
ChatRoom
>
{
private
fun
sortRooms
(
chatRooms
:
List
<
ChatRoom
>):
List
<
ChatRoom
>
{
...
@@ -167,7 +172,17 @@ class ChatRoomsPresenter @Inject constructor(private val view: ChatRoomsView,
...
@@ -167,7 +172,17 @@ class ChatRoomsPresenter @Inject constructor(private val view: ChatRoomsView,
private
fun
updateRooms
()
{
private
fun
updateRooms
()
{
Timber
.
d
(
"Updating Rooms"
)
Timber
.
d
(
"Updating Rooms"
)
launch
(
strategy
.
jobs
)
{
launch
(
strategy
.
jobs
)
{
view
.
updateChatRooms
(
getChatRoomsInteractor
.
get
(
currentServer
))
view
.
updateChatRooms
(
getChatRoomsWithPreviews
(
getChatRoomsInteractor
.
get
(
currentServer
)))
}
}
private
suspend
fun
getChatRoomsWithPreviews
(
chatRooms
:
List
<
ChatRoom
>):
List
<
ChatRoom
>
{
return
chatRooms
.
map
{
if
(
it
.
lastMessage
!=
null
)
{
it
.
copy
(
lastMessage
=
viewModelMapper
.
map
(
it
.
lastMessage
!!
).
last
().
preview
)
}
else
{
it
}
}
}
}
}
...
@@ -304,7 +319,7 @@ class ChatRoomsPresenter @Inject constructor(private val view: ChatRoomsView,
...
@@ -304,7 +319,7 @@ class ChatRoomsPresenter @Inject constructor(private val view: ChatRoomsView,
// Update a ChatRoom with a Subscription information
// Update a ChatRoom with a Subscription information
private
fun
updateSubscription
(
subscription
:
Subscription
)
{
private
fun
updateSubscription
(
subscription
:
Subscription
)
{
Timber
.
d
(
"Updating subscrition: ${subscription.id} - ${subscription.name}"
)
Timber
.
d
(
"Updating subscri
p
tion: ${subscription.id} - ${subscription.name}"
)
val
chatRooms
=
getChatRoomsInteractor
.
get
(
currentServer
).
toMutableList
()
val
chatRooms
=
getChatRoomsInteractor
.
get
(
currentServer
).
toMutableList
()
val
chatRoom
=
chatRooms
.
find
{
chatRoom
->
chatRoom
.
id
==
subscription
.
roomId
}
val
chatRoom
=
chatRooms
.
find
{
chatRoom
->
chatRoom
.
id
==
subscription
.
roomId
}
chatRoom
?.
apply
{
chatRoom
?.
apply
{
...
...
app/src/main/java/chat/rocket/android/chatrooms/ui/ChatRoomsAdapter.kt
View file @
8685b5dd
...
@@ -3,14 +3,18 @@ package chat.rocket.android.chatrooms.ui
...
@@ -3,14 +3,18 @@ package chat.rocket.android.chatrooms.ui
import
DateTimeHelper
import
DateTimeHelper
import
DrawableHelper
import
DrawableHelper
import
android.content.Context
import
android.content.Context
import
android.graphics.
drawable.Drawable
import
android.graphics.
Color
import
android.support.v4.content.ContextCompat
import
android.support.v4.content.ContextCompat
import
android.support.v7.widget.RecyclerView
import
android.support.v7.widget.RecyclerView
import
android.text.SpannableStringBuilder
import
android.text.style.ForegroundColorSpan
import
android.view.View
import
android.view.View
import
android.view.ViewGroup
import
android.view.ViewGroup
import
android.widget.TextView
import
android.widget.TextView
import
chat.rocket.android.R
import
chat.rocket.android.R
import
chat.rocket.android.helper.UrlHelper
import
chat.rocket.android.helper.UrlHelper
import
chat.rocket.android.infrastructure.LocalRepository
import
chat.rocket.android.infrastructure.checkIfMyself
import
chat.rocket.android.server.domain.PublicSettings
import
chat.rocket.android.server.domain.PublicSettings
import
chat.rocket.android.server.domain.useRealName
import
chat.rocket.android.server.domain.useRealName
import
chat.rocket.android.util.extensions.content
import
chat.rocket.android.util.extensions.content
...
@@ -26,6 +30,7 @@ import kotlinx.android.synthetic.main.unread_messages_badge.view.*
...
@@ -26,6 +30,7 @@ import kotlinx.android.synthetic.main.unread_messages_badge.view.*
class
ChatRoomsAdapter
(
private
val
context
:
Context
,
class
ChatRoomsAdapter
(
private
val
context
:
Context
,
private
val
settings
:
PublicSettings
,
private
val
settings
:
PublicSettings
,
private
val
localRepository
:
LocalRepository
,
private
val
listener
:
(
ChatRoom
)
->
Unit
)
:
RecyclerView
.
Adapter
<
ChatRoomsAdapter
.
ViewHolder
>()
{
private
val
listener
:
(
ChatRoom
)
->
Unit
)
:
RecyclerView
.
Adapter
<
ChatRoomsAdapter
.
ViewHolder
>()
{
var
dataSet
:
MutableList
<
ChatRoom
>
=
ArrayList
()
var
dataSet
:
MutableList
<
ChatRoom
>
=
ArrayList
()
...
@@ -116,21 +121,30 @@ class ChatRoomsAdapter(private val context: Context,
...
@@ -116,21 +121,30 @@ class ChatRoomsAdapter(private val context: Context,
val
lastMessageSender
=
lastMessage
?.
sender
val
lastMessageSender
=
lastMessage
?.
sender
if
(
lastMessage
!=
null
&&
lastMessageSender
!=
null
)
{
if
(
lastMessage
!=
null
&&
lastMessageSender
!=
null
)
{
val
message
=
lastMessage
.
message
val
message
=
lastMessage
.
message
val
senderUsername
=
lastMessageSender
.
username
val
senderUsername
=
if
(
settings
.
useRealName
())
{
lastMessageSender
.
name
?:
lastMessageSender
.
username
}
else
{
lastMessageSender
.
username
}
when
(
senderUsername
)
{
when
(
senderUsername
)
{
chatRoom
.
name
->
{
chatRoom
.
name
->
{
textView
.
content
=
message
textView
.
content
=
message
}
}
// TODO Change to MySelf
// chatRoom.user?.username -> {
// holder.lastMessage.textContent = context.getString(R.string.msg_you) + ": $message"
// }
else
->
{
else
->
{
textView
.
content
=
"@$senderUsername: $message"
val
user
=
if
(
localRepository
.
checkIfMyself
(
lastMessageSender
.
username
!!
))
{
"${context.getString(R.string.msg_you)}: "
}
else
{
"$senderUsername: "
}
val
spannable
=
SpannableStringBuilder
(
user
)
val
len
=
spannable
.
length
spannable
.
setSpan
(
ForegroundColorSpan
(
Color
.
BLACK
),
0
,
len
-
1
,
0
)
spannable
.
append
(
message
)
textView
.
content
=
spannable
}
}
}
}
}
else
{
}
else
{
textView
.
content
=
""
textView
.
content
=
context
.
getText
(
R
.
string
.
msg_no_messages_yet
)
}
}
}
}
...
...
app/src/main/java/chat/rocket/android/chatrooms/ui/ChatRoomsFragment.kt
View file @
8685b5dd
...
@@ -12,6 +12,7 @@ import android.view.*
...
@@ -12,6 +12,7 @@ import android.view.*
import
chat.rocket.android.R
import
chat.rocket.android.R
import
chat.rocket.android.chatrooms.presentation.ChatRoomsPresenter
import
chat.rocket.android.chatrooms.presentation.ChatRoomsPresenter
import
chat.rocket.android.chatrooms.presentation.ChatRoomsView
import
chat.rocket.android.chatrooms.presentation.ChatRoomsView
import
chat.rocket.android.infrastructure.LocalRepository
import
chat.rocket.android.server.domain.GetCurrentServerInteractor
import
chat.rocket.android.server.domain.GetCurrentServerInteractor
import
chat.rocket.android.server.domain.SettingsRepository
import
chat.rocket.android.server.domain.SettingsRepository
import
chat.rocket.android.util.extensions.*
import
chat.rocket.android.util.extensions.*
...
@@ -31,6 +32,7 @@ class ChatRoomsFragment : Fragment(), ChatRoomsView {
...
@@ -31,6 +32,7 @@ class ChatRoomsFragment : Fragment(), ChatRoomsView {
@Inject
lateinit
var
presenter
:
ChatRoomsPresenter
@Inject
lateinit
var
presenter
:
ChatRoomsPresenter
@Inject
lateinit
var
serverInteractor
:
GetCurrentServerInteractor
@Inject
lateinit
var
serverInteractor
:
GetCurrentServerInteractor
@Inject
lateinit
var
settingsRepository
:
SettingsRepository
@Inject
lateinit
var
settingsRepository
:
SettingsRepository
@Inject
lateinit
var
localRepository
:
LocalRepository
private
var
searchView
:
SearchView
?
=
null
private
var
searchView
:
SearchView
?
=
null
private
val
handler
=
Handler
()
private
val
handler
=
Handler
()
...
@@ -108,7 +110,11 @@ class ChatRoomsFragment : Fragment(), ChatRoomsView {
...
@@ -108,7 +110,11 @@ class ChatRoomsFragment : Fragment(), ChatRoomsView {
override
fun
showLoading
()
=
view_loading
.
setVisible
(
true
)
override
fun
showLoading
()
=
view_loading
.
setVisible
(
true
)
override
fun
hideLoading
()
=
view_loading
.
setVisible
(
false
)
override
fun
hideLoading
()
{
if
(
view_loading
!=
null
)
{
view_loading
.
setVisible
(
false
)
}
}
override
fun
showMessage
(
resId
:
Int
)
{
override
fun
showMessage
(
resId
:
Int
)
{
showToast
(
resId
)
showToast
(
resId
)
...
@@ -156,8 +162,9 @@ class ChatRoomsFragment : Fragment(), ChatRoomsView {
...
@@ -156,8 +162,9 @@ class ChatRoomsFragment : Fragment(), ChatRoomsView {
resources
.
getDimensionPixelSize
(
R
.
dimen
.
divider_item_decorator_bound_end
)))
resources
.
getDimensionPixelSize
(
R
.
dimen
.
divider_item_decorator_bound_end
)))
recycler_view
.
itemAnimator
=
DefaultItemAnimator
()
recycler_view
.
itemAnimator
=
DefaultItemAnimator
()
// TODO - use a ViewModel Mapper instead of using settings on the adapter
// TODO - use a ViewModel Mapper instead of using settings on the adapter
recycler_view
.
adapter
=
ChatRoomsAdapter
(
this
,
recycler_view
.
adapter
=
ChatRoomsAdapter
(
settingsRepository
.
get
(
serverInteractor
.
get
()
!!
))
{
chatRoom
->
this
,
settingsRepository
.
get
(
serverInteractor
.
get
()
!!
),
localRepository
)
{
chatRoom
->
presenter
.
loadChatRoom
(
chatRoom
)
presenter
.
loadChatRoom
(
chatRoom
)
}
}
}
}
...
...
app/src/main/java/chat/rocket/android/helper/MessageParser.kt
View file @
8685b5dd
...
@@ -4,158 +4,113 @@ import android.app.Application
...
@@ -4,158 +4,113 @@ import android.app.Application
import
android.content.ActivityNotFoundException
import
android.content.ActivityNotFoundException
import
android.content.Context
import
android.content.Context
import
android.content.Intent
import
android.content.Intent
import
android.graphics.*
import
android.graphics.Canvas
import
android.graphics.drawable.Drawable
import
android.graphics.Paint
import
android.graphics.RectF
import
android.net.Uri
import
android.net.Uri
import
android.support.customtabs.CustomTabsIntent
import
android.support.customtabs.CustomTabsIntent
import
android.provider.Browser
import
android.provider.Browser
import
android.support.v4.content.ContextCompat
import
android.support.v4.content.res.ResourcesCompat
import
android.support.v4.content.res.ResourcesCompat
import
android.text.Layout
import
android.text.Spannable
import
android.text.Spanned
import
android.text.Spanned
import
android.text.
TextUtils
import
android.text.
style.ClickableSpan
import
android.text.style.
*
import
android.text.style.
ReplacementSpan
import
android.util.Patterns
import
android.util.Patterns
import
android.view.View
import
android.view.View
import
chat.rocket.android.R
import
chat.rocket.android.R
import
chat.rocket.android.chatroom.viewmodel.MessageViewModel
import
chat.rocket.android.widget.emoji.EmojiParser
import
chat.rocket.android.widget.emoji.EmojiParser
import
chat.rocket.android.widget.emoji.EmojiRepository
import
chat.rocket.android.widget.emoji.EmojiRepository
import
chat.rocket.android.widget.emoji.EmojiTypefaceSpan
import
chat.rocket.android.widget.emoji.EmojiTypefaceSpan
import
chat.rocket.common.model.SimpleUser
import
chat.rocket.core.model.Message
import
org.commonmark.node.AbstractVisitor
import
org.commonmark.node.AbstractVisitor
import
org.commonmark.node.
BlockQuote
import
org.commonmark.node.
Document
import
org.commonmark.node.Text
import
org.commonmark.node.Text
import
ru.noties.markwon.Markwon
import
ru.noties.markwon.Markwon
import
ru.noties.markwon.SpannableBuilder
import
ru.noties.markwon.SpannableBuilder
import
ru.noties.markwon.SpannableConfiguration
import
ru.noties.markwon.SpannableConfiguration
import
ru.noties.markwon.renderer.SpannableMarkdownVisitor
import
ru.noties.markwon.renderer.SpannableMarkdownVisitor
import
timber.log.Timber
import
timber.log.Timber
import
java.util.regex.Pattern
import
javax.inject.Inject
import
javax.inject.Inject
class
MessageParser
@Inject
constructor
(
val
context
:
Application
,
private
val
configuration
:
SpannableConfiguration
)
{
class
MessageParser
@Inject
constructor
(
val
context
:
Application
,
private
val
configuration
:
SpannableConfiguration
)
{
private
val
parser
=
Markwon
.
createParser
()
private
val
parser
=
Markwon
.
createParser
()
private
val
regexUsername
=
Pattern
.
compile
(
"([^\\S]|^)+(@[\\w.\\-]+)"
,
Pattern
.
MULTILINE
or
Pattern
.
CASE_INSENSITIVE
)
private
val
selfReferList
=
listOf
(
"@all"
,
"@here"
)
/**
/**
* Render a markdown text message to Spannable.
* Render a markdown text message to Spannable.
*
*
* @param text The text message containing markdown syntax.
* @param message The [Message] object we're interested on rendering.
* @param quote An optional message to be quoted either by a quote or reply action.
* @param selfUsername This user username.
* @param urls A list of urls to convert to markdown link syntax.
*
*
* @return A Spannable with the parsed markdown.
* @return A Spannable with the parsed markdown.
*/
*/
fun
renderMarkdown
(
text
:
String
,
quote
:
MessageViewModel
?
=
null
,
selfUsername
:
String
?
=
null
):
CharSequence
{
fun
renderMarkdown
(
message
:
Message
,
selfUsername
:
String
?
=
null
):
CharSequence
{
val
text
=
message
.
message
val
builder
=
SpannableBuilder
()
val
builder
=
SpannableBuilder
()
val
content
=
EmojiRepository
.
shortnameToUnicode
(
text
,
true
)
val
content
=
EmojiRepository
.
shortnameToUnicode
(
text
,
true
)
val
parentNode
=
parser
.
parse
(
toLenientMarkdown
(
content
))
val
parentNode
=
parser
.
parse
(
toLenientMarkdown
(
content
))
parentNode
.
accept
(
QuoteMessageBodyVisitor
(
context
,
configuration
,
builder
))
parentNode
.
accept
(
SpannableMarkdownVisitor
(
configuration
,
builder
))
quote
?.
apply
{
var
quoteNode
=
parser
.
parse
(
"> $senderName $time"
)
parentNode
.
appendChild
(
quoteNode
)
quoteNode
.
accept
(
QuoteMessageSenderVisitor
(
context
,
configuration
,
builder
,
senderName
.
length
))
quoteNode
=
parser
.
parse
(
"> ${toLenientMarkdown(quote.rawData.message)}"
)
quoteNode
.
accept
(
EmojiVisitor
(
builder
))
quoteNode
.
accept
(
QuoteMessageBodyVisitor
(
context
,
configuration
,
builder
))
}
parentNode
.
accept
(
LinkVisitor
(
builder
))
parentNode
.
accept
(
LinkVisitor
(
builder
))
parentNode
.
accept
(
EmojiVisitor
(
builder
))
parentNode
.
accept
(
EmojiVisitor
(
configuration
,
builder
))
val
result
=
builder
.
text
()
message
.
mentions
?.
let
{
applySpans
(
result
,
selfUsername
)
parentNode
.
accept
(
MentionVisitor
(
context
,
builder
,
it
,
selfUsername
))
return
result
}
private
fun
applySpans
(
text
:
CharSequence
,
currentUser
:
String
?)
{
if
(
text
!
is
Spannable
)
return
applyMentionSpans
(
text
,
currentUser
)
}
private
fun
applyMentionSpans
(
text
:
CharSequence
,
currentUser
:
String
?)
{
val
matcher
=
regexUsername
.
matcher
(
text
)
val
result
=
text
as
Spannable
while
(
matcher
.
find
())
{
val
user
=
matcher
.
group
(
2
)
val
start
=
matcher
.
start
(
2
)
//TODO: should check if username actually exists prior to applying.
with
(
context
)
{
val
referSelf
=
when
(
user
)
{
in
selfReferList
->
true
"@$currentUser"
->
true
else
->
false
}
val
mentionTextColor
:
Int
val
mentionBgColor
:
Int
if
(
referSelf
)
{
mentionTextColor
=
ResourcesCompat
.
getColor
(
resources
,
R
.
color
.
white
,
theme
)
mentionBgColor
=
ResourcesCompat
.
getColor
(
context
.
resources
,
R
.
color
.
colorAccent
,
theme
)
}
else
{
mentionTextColor
=
ResourcesCompat
.
getColor
(
resources
,
R
.
color
.
colorAccent
,
theme
)
mentionBgColor
=
ResourcesCompat
.
getColor
(
resources
,
android
.
R
.
color
.
transparent
,
theme
)
}
}
val
padding
=
resources
.
getDimensionPixelSize
(
R
.
dimen
.
padding_mention
).
toFloat
()
return
builder
.
text
()
val
radius
=
resources
.
getDimensionPixelSize
(
R
.
dimen
.
radius_mention
).
toFloat
()
val
usernameSpan
=
MentionSpan
(
mentionBgColor
,
mentionTextColor
,
radius
,
padding
,
referSelf
)
result
.
setSpan
(
usernameSpan
,
start
,
start
+
user
.
length
,
0
)
}
}
}
}
/**
// Convert to a lenient markdown consistent with Rocket.Chat web markdown instead of the official specs.
* Convert to a lenient markdown consistent with Rocket.Chat web markdown instead of the official specs.
*/
private
fun
toLenientMarkdown
(
text
:
String
):
String
{
private
fun
toLenientMarkdown
(
text
:
String
):
String
{
return
text
.
trim
().
replace
(
"\\*(.+)\\*"
.
toRegex
())
{
"**${it.groupValues[1].trim()}**"
}
return
text
.
trim
().
replace
(
"\\*(.+)\\*"
.
toRegex
())
{
"**${it.groupValues[1].trim()}**"
}
.
replace
(
"\\~(.+)\\~"
.
toRegex
())
{
"~~${it.groupValues[1].trim()}~~"
}
.
replace
(
"\\~(.+)\\~"
.
toRegex
())
{
"~~${it.groupValues[1].trim()}~~"
}
.
replace
(
"\\_(.+)\\_"
.
toRegex
())
{
"_${it.groupValues[1].trim()}_"
}
.
replace
(
"\\_(.+)\\_"
.
toRegex
())
{
"_${it.groupValues[1].trim()}_"
}
}
}
class
QuoteMessageSenderVisitor
(
private
val
context
:
Context
,
class
MentionVisitor
(
context
:
Context
,
configuration
:
SpannableConfiguration
,
private
val
builder
:
SpannableBuilder
,
private
val
builder
:
SpannableBuilder
,
private
val
senderNameLength
:
Int
)
:
SpannableMarkdownVisitor
(
configuration
,
builder
)
{
private
val
mentions
:
List
<
SimpleUser
>,
private
val
currentUser
:
String
?)
:
AbstractVisitor
()
{
override
fun
visit
(
blockQuote
:
BlockQuote
)
{
private
val
othersTextColor
=
ResourcesCompat
.
getColor
(
context
.
resources
,
R
.
color
.
colorAccent
,
context
.
theme
)
private
val
othersBackgroundColor
=
ResourcesCompat
.
getColor
(
context
.
resources
,
android
.
R
.
color
.
transparent
,
context
.
theme
)
// mark current length
private
val
myselfTextColor
=
ResourcesCompat
.
getColor
(
context
.
resources
,
R
.
color
.
white
,
context
.
theme
)
val
length
=
builder
.
length
()
private
val
myselfBackgroundColor
=
ResourcesCompat
.
getColor
(
context
.
resources
,
R
.
color
.
colorAccent
,
context
.
theme
)
private
val
mentionPadding
=
context
.
resources
.
getDimensionPixelSize
(
R
.
dimen
.
padding_mention
).
toFloat
()
// pass to super to apply markdown
private
val
mentionRadius
=
context
.
resources
.
getDimensionPixelSize
(
R
.
dimen
.
radius_mention
).
toFloat
()
super
.
visit
(
blockQuote
)
override
fun
visit
(
t
:
Text
)
{
val
text
=
t
.
literal
val
res
=
context
.
resources
val
mentionsList
=
mentions
.
map
{
it
.
username
}.
toMutableList
()
val
timeOffsetStart
=
length
+
senderNameLength
+
1
mentionsList
.
add
(
"all"
)
builder
.
setSpan
(
QuoteMarginSpan
(
context
.
getDrawable
(
R
.
drawable
.
quote
),
10
),
length
,
builder
.
length
())
mentionsList
.
add
(
"here"
)
builder
.
setSpan
(
StyleSpan
(
Typeface
.
BOLD
),
length
,
length
+
senderNameLength
)
builder
.
setSpan
(
ForegroundColorSpan
(
Color
.
BLACK
),
length
,
builder
.
length
())
mentionsList
.
toList
().
forEach
{
// set time spans
if
(
it
!=
null
)
{
builder
.
setSpan
(
AbsoluteSizeSpan
(
res
.
getDimensionPixelSize
(
R
.
dimen
.
message_time_text_size
)),
val
mentionMe
=
it
==
currentUser
||
it
==
"all"
||
it
==
"here"
timeOffsetStart
,
builder
.
length
())
var
offset
=
text
.
indexOf
(
"@$it"
,
0
,
true
)
builder
.
setSpan
(
ForegroundColorSpan
(
ContextCompat
.
getColor
(
context
,
R
.
color
.
darkGray
)),
while
(
offset
>
-
1
)
{
timeOffsetStart
,
builder
.
length
())
val
textColor
=
if
(
mentionMe
)
myselfTextColor
else
othersTextColor
}
val
backgroundColor
=
if
(
mentionMe
)
myselfBackgroundColor
else
othersBackgroundColor
}
val
usernameSpan
=
MentionSpan
(
backgroundColor
,
textColor
,
mentionRadius
,
mentionPadding
,
mentionMe
)
class
EmojiVisitor
(
private
val
builder
:
SpannableBuilder
)
:
AbstractVisitor
()
{
// Add 1 to end offset to include the @.
override
fun
visit
(
text
:
Text
)
{
val
end
=
offset
+
it
.
length
+
1
val
spannable
=
EmojiParser
.
parse
(
text
.
literal
)
builder
.
setSpan
(
usernameSpan
,
offset
,
end
,
0
)
offset
=
text
.
indexOf
(
"@$it"
,
end
,
true
)
}
}
}
}
}
class
EmojiVisitor
(
configuration
:
SpannableConfiguration
,
private
val
builder
:
SpannableBuilder
)
:
SpannableMarkdownVisitor
(
configuration
,
builder
)
{
override
fun
visit
(
document
:
Document
)
{
val
spannable
=
EmojiParser
.
parse
(
builder
.
text
())
if
(
spannable
is
Spanned
)
{
if
(
spannable
is
Spanned
)
{
val
spans
=
spannable
.
getSpans
(
0
,
spannable
.
length
,
EmojiTypefaceSpan
::
class
.
java
)
val
spans
=
spannable
.
getSpans
(
0
,
spannable
.
length
,
EmojiTypefaceSpan
::
class
.
java
)
spans
.
forEach
{
spans
.
forEach
{
builder
.
setSpan
(
it
,
spannable
.
getSpanStart
(
it
),
spannable
.
getSpanEnd
(
it
),
0
)
builder
.
setSpan
(
it
,
spannable
.
getSpanStart
(
it
),
spannable
.
getSpanEnd
(
it
),
0
)
}
}
}
}
visitChildren
(
text
)
}
}
}
}
...
@@ -195,60 +150,6 @@ class MessageParser @Inject constructor(val context: Application, private val co
...
@@ -195,60 +150,6 @@ class MessageParser @Inject constructor(val context: Application, private val co
}
}
}
}
class
QuoteMessageBodyVisitor
(
private
val
context
:
Context
,
configuration
:
SpannableConfiguration
,
private
val
builder
:
SpannableBuilder
)
:
SpannableMarkdownVisitor
(
configuration
,
builder
)
{
override
fun
visit
(
blockQuote
:
BlockQuote
)
{
// mark current length
val
length
=
builder
.
length
()
// pass to super to apply markdown
super
.
visit
(
blockQuote
)
val
padding
=
context
.
resources
.
getDimensionPixelSize
(
R
.
dimen
.
padding_quote
)
builder
.
setSpan
(
QuoteMarginSpan
(
context
.
getDrawable
(
R
.
drawable
.
quote
),
padding
),
length
,
builder
.
length
())
}
}
class
QuoteMarginSpan
(
quoteDrawable
:
Drawable
,
private
var
pad
:
Int
)
:
LeadingMarginSpan
,
LineHeightSpan
{
private
val
drawable
:
Drawable
=
quoteDrawable
override
fun
getLeadingMargin
(
first
:
Boolean
):
Int
{
return
drawable
.
intrinsicWidth
+
pad
}
override
fun
drawLeadingMargin
(
c
:
Canvas
,
p
:
Paint
,
x
:
Int
,
dir
:
Int
,
top
:
Int
,
baseline
:
Int
,
bottom
:
Int
,
text
:
CharSequence
,
start
:
Int
,
end
:
Int
,
first
:
Boolean
,
layout
:
Layout
)
{
val
st
=
(
text
as
Spanned
).
getSpanStart
(
this
)
val
ix
=
x
val
itop
=
layout
.
getLineTop
(
layout
.
getLineForOffset
(
st
))
val
dw
=
drawable
.
intrinsicWidth
val
dh
=
drawable
.
intrinsicHeight
// XXX What to do about Paint?
drawable
.
setBounds
(
ix
,
itop
,
ix
+
dw
,
itop
+
layout
.
height
)
drawable
.
draw
(
c
)
}
override
fun
chooseHeight
(
text
:
CharSequence
,
start
:
Int
,
end
:
Int
,
spanstartv
:
Int
,
v
:
Int
,
fm
:
Paint
.
FontMetricsInt
)
{
if
(
end
==
(
text
as
Spanned
).
getSpanEnd
(
this
))
{
val
ht
=
drawable
.
intrinsicHeight
var
need
=
ht
-
(
v
+
fm
.
descent
-
fm
.
ascent
-
spanstartv
)
if
(
need
>
0
)
fm
.
descent
+=
need
need
=
ht
-
(
v
+
fm
.
bottom
-
fm
.
top
-
spanstartv
)
if
(
need
>
0
)
fm
.
bottom
+=
need
}
}
}
class
MentionSpan
(
private
val
backgroundColor
:
Int
,
class
MentionSpan
(
private
val
backgroundColor
:
Int
,
private
val
textColor
:
Int
,
private
val
textColor
:
Int
,
private
val
radius
:
Float
,
private
val
radius
:
Float
,
...
@@ -274,13 +175,11 @@ class MessageParser @Inject constructor(val context: Application, private val co
...
@@ -274,13 +175,11 @@ class MessageParser @Inject constructor(val context: Application, private val co
bottom
:
Int
,
bottom
:
Int
,
paint
:
Paint
)
{
paint
:
Paint
)
{
val
length
=
paint
.
measureText
(
text
.
subSequence
(
start
,
end
).
toString
())
val
length
=
paint
.
measureText
(
text
.
subSequence
(
start
,
end
).
toString
())
val
rect
=
RectF
(
x
,
top
.
toFloat
(),
x
+
length
+
padding
*
2
,
val
rect
=
RectF
(
x
,
top
.
toFloat
(),
x
+
length
+
padding
*
2
,
bottom
.
toFloat
())
bottom
.
toFloat
())
paint
.
color
=
backgroundColor
paint
.
setColor
(
backgroundColor
)
canvas
.
drawRoundRect
(
rect
,
radius
,
radius
,
paint
)
canvas
.
drawRoundRect
(
rect
,
radius
,
radius
,
paint
)
paint
.
setColor
(
textColor
)
paint
.
color
=
textColor
canvas
.
drawText
(
text
,
start
,
end
,
x
+
padding
,
y
.
toFloat
(),
paint
)
canvas
.
drawText
(
text
,
start
,
end
,
x
+
padding
,
y
.
toFloat
(),
paint
)
}
}
}
}
}
}
\ No newline at end of file
app/src/main/java/chat/rocket/android/infrastructure/LocalRepository.kt
View file @
8685b5dd
...
@@ -23,3 +23,5 @@ interface LocalRepository {
...
@@ -23,3 +23,5 @@ interface LocalRepository {
const
val
CURRENT_USERNAME_KEY
=
"username_"
const
val
CURRENT_USERNAME_KEY
=
"username_"
}
}
}
}
fun
LocalRepository
.
checkIfMyself
(
username
:
String
)
=
get
(
LocalRepository
.
CURRENT_USERNAME_KEY
)
==
username
\ No newline at end of file
app/src/main/java/chat/rocket/android/profile/ui/ProfileFragment.kt
View file @
8685b5dd
...
@@ -56,8 +56,8 @@ class ProfileFragment : Fragment(), ProfileView, ActionMode.Callback {
...
@@ -56,8 +56,8 @@ class ProfileFragment : Fragment(), ProfileView, ActionMode.Callback {
text_email
.
textContent
=
email
text_email
.
textContent
=
email
text_avatar_url
.
textContent
=
""
text_avatar_url
.
textContent
=
""
currentName
=
user
name
currentName
=
name
currentUsername
=
name
currentUsername
=
user
name
currentEmail
=
email
currentEmail
=
email
currentAvatar
=
avatarUrl
currentAvatar
=
avatarUrl
...
@@ -76,7 +76,9 @@ class ProfileFragment : Fragment(), ProfileView, ActionMode.Callback {
...
@@ -76,7 +76,9 @@ class ProfileFragment : Fragment(), ProfileView, ActionMode.Callback {
}
}
override
fun
hideLoading
()
{
override
fun
hideLoading
()
{
if
(
view_loading
!=
null
)
{
view_loading
.
setVisible
(
false
)
view_loading
.
setVisible
(
false
)
}
enableUserInput
(
true
)
enableUserInput
(
true
)
}
}
...
...
app/src/main/java/chat/rocket/android/widget/autocompletion/strategy/CompletionStrategy.kt
View file @
8685b5dd
...
@@ -6,5 +6,6 @@ interface CompletionStrategy {
...
@@ -6,5 +6,6 @@ interface CompletionStrategy {
fun
getItem
(
prefix
:
String
,
position
:
Int
):
SuggestionModel
fun
getItem
(
prefix
:
String
,
position
:
Int
):
SuggestionModel
fun
autocompleteItems
(
prefix
:
String
):
List
<
SuggestionModel
>
fun
autocompleteItems
(
prefix
:
String
):
List
<
SuggestionModel
>
fun
addAll
(
list
:
List
<
SuggestionModel
>)
fun
addAll
(
list
:
List
<
SuggestionModel
>)
fun
addPinned
(
list
:
List
<
SuggestionModel
>)
fun
size
():
Int
fun
size
():
Int
}
}
\ No newline at end of file
app/src/main/java/chat/rocket/android/widget/autocompletion/strategy/regex/StringMatchingCompletionStrategy.kt
View file @
8685b5dd
...
@@ -2,14 +2,19 @@ package chat.rocket.android.widget.autocompletion.strategy.regex
...
@@ -2,14 +2,19 @@ package chat.rocket.android.widget.autocompletion.strategy.regex
import
chat.rocket.android.widget.autocompletion.model.SuggestionModel
import
chat.rocket.android.widget.autocompletion.model.SuggestionModel
import
chat.rocket.android.widget.autocompletion.strategy.CompletionStrategy
import
chat.rocket.android.widget.autocompletion.strategy.CompletionStrategy
import
chat.rocket.android.widget.autocompletion.ui.SuggestionsAdapter
import
chat.rocket.android.widget.autocompletion.ui.SuggestionsAdapter
.Companion.RESULT_COUNT_UNLIMITED
import
java.util.concurrent.CopyOnWriteArrayList
import
java.util.concurrent.CopyOnWriteArrayList
internal
class
StringMatchingCompletionStrategy
(
private
val
threshold
:
Int
=
-
1
)
:
CompletionStrategy
{
internal
class
StringMatchingCompletionStrategy
(
private
val
threshold
:
Int
=
RESULT_COUNT_UNLIMITED
)
:
CompletionStrategy
{
private
val
list
=
CopyOnWriteArrayList
<
SuggestionModel
>()
private
val
list
=
CopyOnWriteArrayList
<
SuggestionModel
>()
private
val
pinnedList
=
mutableListOf
<
SuggestionModel
>()
init
{
check
(
threshold
>=
RESULT_COUNT_UNLIMITED
)
}
override
fun
autocompleteItems
(
prefix
:
String
):
List
<
SuggestionModel
>
{
override
fun
autocompleteItems
(
prefix
:
String
):
List
<
SuggestionModel
>
{
val
r
esult
=
list
.
filter
{
val
partialR
esult
=
list
.
filter
{
it
.
searchList
.
forEach
{
word
->
it
.
searchList
.
forEach
{
word
->
if
(
word
.
contains
(
prefix
,
ignoreCase
=
true
))
{
if
(
word
.
contains
(
prefix
,
ignoreCase
=
true
))
{
return
@filter
true
return
@filter
true
...
@@ -17,13 +22,23 @@ internal class StringMatchingCompletionStrategy(private val threshold: Int = -1)
...
@@ -17,13 +22,23 @@ internal class StringMatchingCompletionStrategy(private val threshold: Int = -1)
}
}
false
false
}.
sortedByDescending
{
it
.
pinned
}
}.
sortedByDescending
{
it
.
pinned
}
return
if
(
threshold
==
SuggestionsAdapter
.
UNLIMITED_RESULT_COUNT
)
result
else
result
.
take
(
threshold
)
return
if
(
threshold
==
RESULT_COUNT_UNLIMITED
)
partialResult
.
toList
()
else
{
val
result
=
partialResult
.
take
(
threshold
).
toMutableList
()
result
.
addAll
(
pinnedList
)
result
.
toList
()
}
}
}
override
fun
addAll
(
list
:
List
<
SuggestionModel
>)
{
override
fun
addAll
(
list
:
List
<
SuggestionModel
>)
{
this
.
list
.
addAllAbsent
(
list
)
this
.
list
.
addAllAbsent
(
list
)
}
}
override
fun
addPinned
(
list
:
List
<
SuggestionModel
>)
{
this
.
pinnedList
.
addAll
(
list
)
}
override
fun
getItem
(
prefix
:
String
,
position
:
Int
):
SuggestionModel
{
override
fun
getItem
(
prefix
:
String
,
position
:
Int
):
SuggestionModel
{
return
list
[
position
]
return
list
[
position
]
}
}
...
...
app/src/main/java/chat/rocket/android/widget/autocompletion/strategy/trie/TrieCompletionStrategy.kt
View file @
8685b5dd
...
@@ -27,5 +27,9 @@ class TrieCompletionStrategy : CompletionStrategy {
...
@@ -27,5 +27,9 @@ class TrieCompletionStrategy : CompletionStrategy {
}
}
}
}
override
fun
addPinned
(
list
:
List
<
SuggestionModel
>)
{
}
override
fun
size
()
=
items
.
size
override
fun
size
()
=
items
.
size
}
}
\ No newline at end of file
app/src/main/java/chat/rocket/android/widget/autocompletion/ui/SuggestionsAdapter.kt
View file @
8685b5dd
...
@@ -13,7 +13,7 @@ abstract class SuggestionsAdapter<VH : BaseSuggestionViewHolder>(
...
@@ -13,7 +13,7 @@ abstract class SuggestionsAdapter<VH : BaseSuggestionViewHolder>(
threshold
:
Int
=
MAX_RESULT_COUNT
)
:
RecyclerView
.
Adapter
<
VH
>()
{
threshold
:
Int
=
MAX_RESULT_COUNT
)
:
RecyclerView
.
Adapter
<
VH
>()
{
companion
object
{
companion
object
{
// Any number of results.
// Any number of results.
const
val
UNLIMITED_RESULT_COUNT
=
-
1
const
val
RESULT_COUNT_UNLIMITED
=
-
1
// Trigger suggestions only if on the line start.
// Trigger suggestions only if on the line start.
const
val
CONSTRAINT_BOUND_TO_START
=
0
const
val
CONSTRAINT_BOUND_TO_START
=
0
// Trigger suggestions from anywhere.
// Trigger suggestions from anywhere.
...
@@ -21,12 +21,14 @@ abstract class SuggestionsAdapter<VH : BaseSuggestionViewHolder>(
...
@@ -21,12 +21,14 @@ abstract class SuggestionsAdapter<VH : BaseSuggestionViewHolder>(
// Maximum number of results to display by default.
// Maximum number of results to display by default.
private
const
val
MAX_RESULT_COUNT
=
5
private
const
val
MAX_RESULT_COUNT
=
5
}
}
private
var
itemType
:
Type
?
=
null
private
var
itemType
:
Type
?
=
null
private
var
itemClickListener
:
ItemClickListener
?
=
null
private
var
itemClickListener
:
ItemClickListener
?
=
null
// Called to gather results when no results have previously matched.
// Called to gather results when no results have previously matched.
private
var
providerExternal
:
((
query
:
String
)
->
Unit
)?
=
null
private
var
providerExternal
:
((
query
:
String
)
->
Unit
)?
=
null
private
var
pinnedSuggestions
:
List
<
SuggestionModel
>?
=
null
// Maximum number of results/suggestions to display.
// Maximum number of results/suggestions to display.
private
var
resultsThreshold
:
Int
=
if
(
threshold
>
0
)
threshold
else
UNLIMITED_RESULT_COUNT
private
var
resultsThreshold
:
Int
=
if
(
threshold
>
0
)
threshold
else
RESULT_COUNT_UNLIMITED
// The strategy used for suggesting completions.
// The strategy used for suggesting completions.
private
val
strategy
:
CompletionStrategy
=
StringMatchingCompletionStrategy
(
resultsThreshold
)
private
val
strategy
:
CompletionStrategy
=
StringMatchingCompletionStrategy
(
resultsThreshold
)
// Current input term to look up for suggestions.
// Current input term to look up for suggestions.
...
@@ -53,6 +55,15 @@ abstract class SuggestionsAdapter<VH : BaseSuggestionViewHolder>(
...
@@ -53,6 +55,15 @@ abstract class SuggestionsAdapter<VH : BaseSuggestionViewHolder>(
return
strategy
.
autocompleteItems
(
currentTerm
)[
position
]
return
strategy
.
autocompleteItems
(
currentTerm
)[
position
]
}
}
/**
* Set suggestions that should always appear when prompted.
*
* @param suggestions The list of suggestions that will be pinned.
*/
fun
setPinnedSuggestions
(
suggestions
:
List
<
SuggestionModel
>)
{
this
.
strategy
.
addPinned
(
suggestions
)
}
fun
autocomplete
(
newTerm
:
String
)
{
fun
autocomplete
(
newTerm
:
String
)
{
this
.
currentTerm
=
newTerm
.
toLowerCase
().
trim
()
this
.
currentTerm
=
newTerm
.
toLowerCase
().
trim
()
}
}
...
...
app/src/main/java/chat/rocket/android/widget/autocompletion/ui/SuggestionsView.kt
View file @
8685b5dd
...
@@ -23,23 +23,19 @@ import chat.rocket.android.R
...
@@ -23,23 +23,19 @@ import chat.rocket.android.R
import
chat.rocket.android.widget.autocompletion.model.SuggestionModel
import
chat.rocket.android.widget.autocompletion.model.SuggestionModel
import
chat.rocket.android.widget.autocompletion.ui.SuggestionsAdapter.Companion.CONSTRAINT_BOUND_TO_START
import
chat.rocket.android.widget.autocompletion.ui.SuggestionsAdapter.Companion.CONSTRAINT_BOUND_TO_START
import
java.lang.ref.WeakReference
import
java.lang.ref.WeakReference
import
java.util.concurrent.CopyOnWriteArrayList
import
java.util.concurrent.atomic.AtomicInteger
import
java.util.concurrent.atomic.AtomicInteger
/**
// This is a special index that means we're not at an autocompleting state.
* This is a special index that means we're not at an autocompleting state.
*/
private
const
val
NO_STATE_INDEX
=
0
private
const
val
NO_STATE_INDEX
=
0
class
SuggestionsView
:
FrameLayout
,
TextWatcher
{
class
SuggestionsView
:
FrameLayout
,
TextWatcher
{
private
val
recyclerView
:
RecyclerView
private
val
recyclerView
:
RecyclerView
private
val
registeredTokens
=
CopyOnWriteArrayList
<
String
>()
// Maps tokens to their respective adapters.
// Maps tokens to their respective adapters.
private
val
adaptersByToken
=
hashMapOf
<
String
,
SuggestionsAdapter
<
out
BaseSuggestionViewHolder
>>()
private
val
adaptersByToken
=
hashMapOf
<
String
,
SuggestionsAdapter
<
out
BaseSuggestionViewHolder
>>()
private
val
externalProvidersByToken
=
hashMapOf
<
String
,
((
query
:
String
)
->
Unit
)>()
private
val
externalProvidersByToken
=
hashMapOf
<
String
,
((
query
:
String
)
->
Unit
)>()
private
val
localProvidersByToken
=
hashMapOf
<
String
,
HashMap
<
String
,
List
<
SuggestionModel
>>>()
private
val
localProvidersByToken
=
hashMapOf
<
String
,
HashMap
<
String
,
List
<
SuggestionModel
>>>()
private
var
editor
:
WeakReference
<
EditText
>?
=
null
private
var
editor
:
WeakReference
<
EditText
>?
=
null
private
var
completion
StartIndex
=
AtomicInteger
(
NO_STATE_INDEX
)
private
var
completion
Offset
=
AtomicInteger
(
NO_STATE_INDEX
)
private
var
maxHeight
:
Int
=
0
private
var
maxHeight
:
Int
=
0
companion
object
{
companion
object
{
...
@@ -66,7 +62,7 @@ class SuggestionsView : FrameLayout, TextWatcher {
...
@@ -66,7 +62,7 @@ class SuggestionsView : FrameLayout, TextWatcher {
// If we have a deletion.
// If we have a deletion.
if
(
after
==
0
)
{
if
(
after
==
0
)
{
val
deleted
=
s
.
subSequence
(
start
,
start
+
count
).
toString
()
val
deleted
=
s
.
subSequence
(
start
,
start
+
count
).
toString
()
if
(
adaptersByToken
.
containsKey
(
deleted
)
&&
completion
StartIndex
.
get
()
>
NO_STATE_INDEX
)
{
if
(
adaptersByToken
.
containsKey
(
deleted
)
&&
completion
Offset
.
get
()
>
NO_STATE_INDEX
)
{
// We have removed the '@', '#' or any other action token so halt completion.
// We have removed the '@', '#' or any other action token so halt completion.
cancelSuggestions
(
true
)
cancelSuggestions
(
true
)
}
}
...
@@ -77,6 +73,11 @@ class SuggestionsView : FrameLayout, TextWatcher {
...
@@ -77,6 +73,11 @@ class SuggestionsView : FrameLayout, TextWatcher {
// If we don't have any adapter bound to any token bail out.
// If we don't have any adapter bound to any token bail out.
if
(
adaptersByToken
.
isEmpty
())
return
if
(
adaptersByToken
.
isEmpty
())
return
if
(
editor
?.
get
()
!=
null
&&
editor
?.
get
()
?.
selectionStart
?:
0
<=
completionOffset
.
get
())
{
completionOffset
.
set
(
NO_STATE_INDEX
)
collapse
()
}
val
new
=
s
.
subSequence
(
start
,
start
+
count
).
toString
()
val
new
=
s
.
subSequence
(
start
,
start
+
count
).
toString
()
if
(
adaptersByToken
.
containsKey
(
new
))
{
if
(
adaptersByToken
.
containsKey
(
new
))
{
val
constraint
=
adapter
(
new
).
constraint
val
constraint
=
adapter
(
new
).
constraint
...
@@ -84,8 +85,8 @@ class SuggestionsView : FrameLayout, TextWatcher {
...
@@ -84,8 +85,8 @@ class SuggestionsView : FrameLayout, TextWatcher {
return
return
}
}
swapAdapter
(
getAdapterForToken
(
new
)
!!
)
swapAdapter
(
getAdapterForToken
(
new
)
!!
)
completion
StartIndex
.
compareAndSet
(
NO_STATE_INDEX
,
start
+
1
)
completion
Offset
.
compareAndSet
(
NO_STATE_INDEX
,
start
+
1
)
editor
?.
let
{
this
.
editor
?.
let
{
// Disable keyboard suggestions when autocompleting.
// Disable keyboard suggestions when autocompleting.
val
editText
=
it
.
get
()
val
editText
=
it
.
get
()
if
(
editText
!=
null
)
{
if
(
editText
!=
null
)
{
...
@@ -97,13 +98,13 @@ class SuggestionsView : FrameLayout, TextWatcher {
...
@@ -97,13 +98,13 @@ class SuggestionsView : FrameLayout, TextWatcher {
if
(
new
.
startsWith
(
" "
))
{
if
(
new
.
startsWith
(
" "
))
{
// just halts the completion execution
// just halts the completion execution
cancelSuggestions
(
fals
e
)
cancelSuggestions
(
tru
e
)
return
return
}
}
val
prefixEndIndex
=
editor
?.
get
()
?.
selectionStart
?:
NO_STATE_INDEX
val
prefixEndIndex
=
this
.
editor
?.
get
()
?.
selectionStart
?:
NO_STATE_INDEX
if
(
prefixEndIndex
==
NO_STATE_INDEX
||
prefixEndIndex
<
completion
StartIndex
.
get
())
return
if
(
prefixEndIndex
==
NO_STATE_INDEX
||
prefixEndIndex
<
completion
Offset
.
get
())
return
val
prefix
=
s
.
subSequence
(
completion
StartIndex
.
get
(),
editor
?.
get
()
?.
selectionStart
?:
completionStartIndex
.
get
()).
toString
()
val
prefix
=
s
.
subSequence
(
completion
Offset
.
get
(),
this
.
editor
?.
get
()
?.
selectionStart
?:
completionOffset
.
get
()).
toString
()
recyclerView
.
adapter
?.
let
{
recyclerView
.
adapter
?.
let
{
it
as
SuggestionsAdapter
it
as
SuggestionsAdapter
// we need to look up only after the '@'
// we need to look up only after the '@'
...
@@ -157,7 +158,7 @@ class SuggestionsView : FrameLayout, TextWatcher {
...
@@ -157,7 +158,7 @@ class SuggestionsView : FrameLayout, TextWatcher {
val
adapter
=
adapter
(
token
)
val
adapter
=
adapter
(
token
)
localProvidersByToken
.
getOrPut
(
token
,
{
hashMapOf
()
})
localProvidersByToken
.
getOrPut
(
token
,
{
hashMapOf
()
})
.
put
(
adapter
.
term
(),
list
)
.
put
(
adapter
.
term
(),
list
)
if
(
completion
StartIndex
.
get
()
>
NO_STATE_INDEX
&&
adapter
.
itemCount
==
0
)
expand
()
if
(
completion
Offset
.
get
()
>
NO_STATE_INDEX
&&
adapter
.
itemCount
==
0
)
expand
()
adapter
.
addItems
(
list
)
adapter
.
addItems
(
list
)
}
}
return
this
return
this
...
@@ -199,7 +200,7 @@ class SuggestionsView : FrameLayout, TextWatcher {
...
@@ -199,7 +200,7 @@ class SuggestionsView : FrameLayout, TextWatcher {
// Reset completion start index only if we've deleted the token that triggered completion or
// Reset completion start index only if we've deleted the token that triggered completion or
// we finished the completion process.
// we finished the completion process.
if
(
haltCompletion
)
{
if
(
haltCompletion
)
{
completion
StartIndex
.
set
(
NO_STATE_INDEX
)
completion
Offset
.
set
(
NO_STATE_INDEX
)
}
}
collapse
()
collapse
()
// Re-enable keyboard suggestions.
// Re-enable keyboard suggestions.
...
@@ -212,7 +213,7 @@ class SuggestionsView : FrameLayout, TextWatcher {
...
@@ -212,7 +213,7 @@ class SuggestionsView : FrameLayout, TextWatcher {
private
fun
insertSuggestionOnEditor
(
item
:
SuggestionModel
)
{
private
fun
insertSuggestionOnEditor
(
item
:
SuggestionModel
)
{
editor
?.
get
()
?.
let
{
editor
?.
get
()
?.
let
{
val
suggestionText
=
item
.
text
val
suggestionText
=
item
.
text
it
.
text
.
replace
(
completion
StartIndex
.
get
(),
it
.
selectionStart
,
"$suggestionText "
)
it
.
text
.
replace
(
completion
Offset
.
get
(),
it
.
selectionStart
,
"$suggestionText "
)
}
}
}
}
...
...
app/src/main/java/chat/rocket/android/widget/emoji/EmojiParser.kt
View file @
8685b5dd
...
@@ -14,15 +14,21 @@ class EmojiParser {
...
@@ -14,15 +14,21 @@ class EmojiParser {
*/
*/
fun
parse
(
text
:
CharSequence
):
CharSequence
{
fun
parse
(
text
:
CharSequence
):
CharSequence
{
val
unicodedText
=
EmojiRepository
.
shortnameToUnicode
(
text
,
true
)
val
unicodedText
=
EmojiRepository
.
shortnameToUnicode
(
text
,
true
)
val
spannableString
=
SpannableString
.
valueOf
(
unicodedText
)
var
spannable
=
SpannableString
.
valueOf
(
unicodedText
)
// Look for groups of emojis, set a CustomTypefaceSpan with the emojione font
val
typeface
=
EmojiRepository
.
cachedTypeface
val
length
=
spannableString
.
length
// Look for groups of emojis, set a EmojiTypefaceSpan with the emojione font.
val
length
=
spannable
.
length
var
inEmoji
=
false
var
inEmoji
=
false
var
emojiStart
=
0
var
emojiStart
=
0
var
offset
=
0
var
offset
=
0
while
(
offset
<
length
)
{
while
(
offset
<
length
)
{
val
codepoint
=
unicodedText
.
codePointAt
(
offset
)
val
codepoint
=
unicodedText
.
codePointAt
(
offset
)
val
count
=
Character
.
charCount
(
codepoint
)
val
count
=
Character
.
charCount
(
codepoint
)
// Skip control characters.
if
(
codepoint
==
0
x2028
)
{
offset
+=
count
continue
}
if
(
codepoint
>=
0
x200
)
{
if
(
codepoint
>=
0
x200
)
{
if
(!
inEmoji
)
{
if
(!
inEmoji
)
{
emojiStart
=
offset
emojiStart
=
offset
...
@@ -30,18 +36,25 @@ class EmojiParser {
...
@@ -30,18 +36,25 @@ class EmojiParser {
inEmoji
=
true
inEmoji
=
true
}
else
{
}
else
{
if
(
inEmoji
)
{
if
(
inEmoji
)
{
spannable
String
.
setSpan
(
EmojiTypefaceSpan
(
"sans-serif"
,
EmojiRepository
.
cachedT
ypeface
),
spannable
.
setSpan
(
EmojiTypefaceSpan
(
"sans-serif"
,
t
ypeface
),
emojiStart
,
offset
,
Spanned
.
SPAN_EXCLUSIVE_EXCLUSIVE
)
emojiStart
,
offset
,
Spanned
.
SPAN_EXCLUSIVE_EXCLUSIVE
)
}
}
inEmoji
=
false
inEmoji
=
false
}
}
offset
+=
count
offset
+=
count
if
(
offset
>=
length
&&
inEmoji
)
{
if
(
offset
>=
length
&&
inEmoji
)
{
spannable
String
.
setSpan
(
EmojiTypefaceSpan
(
"sans-serif"
,
EmojiRepository
.
cachedT
ypeface
),
spannable
.
setSpan
(
EmojiTypefaceSpan
(
"sans-serif"
,
t
ypeface
),
emojiStart
,
offset
,
Spanned
.
SPAN_EXCLUSIVE_EXCLUSIVE
)
emojiStart
,
offset
,
Spanned
.
SPAN_EXCLUSIVE_EXCLUSIVE
)
}
}
}
}
return
spannableString
return
spannable
}
private
fun
calculateSurrogatePairs
(
scalar
:
Int
):
Pair
<
Int
,
Int
>
{
val
temp
:
Int
=
(
scalar
-
0
x10000
)
/
0
x400
val
s1
:
Int
=
Math
.
floor
(
temp
.
toDouble
()).
toInt
()
+
0
xD800
val
s2
:
Int
=
((
scalar
-
0
x10000
)
%
0
x400
)
+
0
xDC00
return
Pair
(
s1
,
s2
)
}
}
}
}
}
}
\ No newline at end of file
app/src/main/java/chat/rocket/android/widget/emoji/EmojiRepository.kt
View file @
8685b5dd
...
@@ -54,6 +54,10 @@ object EmojiRepository {
...
@@ -54,6 +54,10 @@ object EmojiRepository {
*/
*/
fun
getAll
()
=
ALL_EMOJIS
fun
getAll
()
=
ALL_EMOJIS
// fun findEmojiByUnicode(unicode: Int) {
// ALL_EMOJIS.find { it.unicode == }
// }
/**
/**
* Get all emojis for a given category.
* Get all emojis for a given category.
*
*
...
@@ -119,10 +123,7 @@ object EmojiRepository {
...
@@ -119,10 +123,7 @@ object EmojiRepository {
var
result
:
String
=
input
.
toString
()
var
result
:
String
=
input
.
toString
()
while
(
matcher
.
find
())
{
while
(
matcher
.
find
())
{
val
unicode
=
shortNameToUnicode
.
get
(
":${matcher.group(1)}:"
)
val
unicode
=
shortNameToUnicode
.
get
(
":${matcher.group(1)}:"
)
?:
continue
if
(
unicode
==
null
)
{
continue
}
if
(
supported
)
{
if
(
supported
)
{
result
=
result
.
replace
(
":"
+
matcher
.
group
(
1
)
+
":"
,
unicode
)
result
=
result
.
replace
(
":"
+
matcher
.
group
(
1
)
+
":"
,
unicode
)
...
@@ -159,9 +160,7 @@ object EmojiRepository {
...
@@ -159,9 +160,7 @@ object EmojiRepository {
private
fun
buildStringListFromJsonArray
(
array
:
JSONArray
):
List
<
String
>
{
private
fun
buildStringListFromJsonArray
(
array
:
JSONArray
):
List
<
String
>
{
val
list
=
ArrayList
<
String
>(
array
.
length
())
val
list
=
ArrayList
<
String
>(
array
.
length
())
for
(
i
in
0
..
array
.
length
()
-
1
)
{
(
0
until
array
.
length
()).
mapTo
(
list
)
{
array
.
getString
(
it
)
}
list
.
add
(
array
.
getString
(
i
))
}
return
list
return
list
}
}
...
...
app/src/main/java/chat/rocket/android/widget/emoji/EmojiTypefaceSpan.kt
View file @
8685b5dd
...
@@ -17,22 +17,22 @@ class EmojiTypefaceSpan(family: String, private val newType: Typeface) : Typefac
...
@@ -17,22 +17,22 @@ class EmojiTypefaceSpan(family: String, private val newType: Typeface) : Typefac
private
fun
applyCustomTypeFace
(
paint
:
Paint
,
tf
:
Typeface
)
{
private
fun
applyCustomTypeFace
(
paint
:
Paint
,
tf
:
Typeface
)
{
val
oldStyle
:
Int
val
oldStyle
:
Int
val
old
=
paint
.
getTypeface
()
val
old
=
paint
.
typeface
if
(
old
==
null
)
{
if
(
old
==
null
)
{
oldStyle
=
0
oldStyle
=
0
}
else
{
}
else
{
oldStyle
=
old
.
getStyle
()
oldStyle
=
old
.
style
}
}
val
fake
=
oldStyle
and
tf
.
style
.
inv
()
val
fake
=
oldStyle
and
tf
.
style
.
inv
()
if
(
fake
and
Typeface
.
BOLD
!=
0
)
{
if
(
fake
and
Typeface
.
BOLD
!=
0
)
{
paint
.
setFakeBoldText
(
true
)
paint
.
isFakeBoldText
=
true
}
}
if
(
fake
and
Typeface
.
ITALIC
!=
0
)
{
if
(
fake
and
Typeface
.
ITALIC
!=
0
)
{
paint
.
setTextSkewX
(-
0.25f
)
paint
.
textSkewX
=
-
0.25f
}
}
paint
.
setTypeface
(
tf
)
paint
.
typeface
=
tf
}
}
}
}
\ No newline at end of file
app/src/main/res/drawable/quote.xml
→
app/src/main/res/drawable/quote
_vertical_bar
.xml
View file @
8685b5dd
...
@@ -2,9 +2,11 @@
...
@@ -2,9 +2,11 @@
<shape
xmlns:android=
"http://schemas.android.com/apk/res/android"
<shape
xmlns:android=
"http://schemas.android.com/apk/res/android"
android:shape=
"rectangle"
>
android:shape=
"rectangle"
>
<solid
android:color=
"@color/
darkGra
y"
/>
<solid
android:color=
"@color/
colorPrimar
y"
/>
<size
<size
android:width=
"4dp"
android:width=
"4dp"
android:height=
"4dp"
/>
android:height=
"4dp"
/>
<corners
android:radius=
"8dp"
/>
</shape>
</shape>
\ No newline at end of file
app/src/main/res/layout/item_message_attachment.xml
0 → 100644
View file @
8685b5dd
<?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"
xmlns:tools=
"http://schemas.android.com/tools"
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"
android:paddingTop=
"@dimen/message_item_top_and_bottom_padding"
>
<View
android:id=
"@+id/quote_bar"
android:layout_width=
"4dp"
android:layout_height=
"0dp"
android:layout_marginStart=
"56dp"
android:background=
"@drawable/quote_vertical_bar"
app:layout_constraintBottom_toTopOf=
"@+id/recycler_view_reactions"
app:layout_constraintStart_toStartOf=
"parent"
app:layout_constraintTop_toTopOf=
"parent"
/>
<LinearLayout
android:id=
"@+id/top_container"
android:layout_width=
"0dp"
android:layout_height=
"wrap_content"
android:layout_marginStart=
"8dp"
android:orientation=
"horizontal"
app:layout_constraintLeft_toRightOf=
"@+id/quote_bar"
app:layout_constraintTop_toBottomOf=
"@id/new_messages_notif"
>
<TextView
android:id=
"@+id/text_sender"
style=
"@style/Sender.Name.TextView"
android:layout_width=
"wrap_content"
android:layout_height=
"wrap_content"
android:textColor=
"@color/colorPrimary"
tools:text=
"Ronald Perkins"
/>
<TextView
android:id=
"@+id/text_message_time"
style=
"@style/Timestamp.TextView"
android:layout_width=
"wrap_content"
android:layout_height=
"wrap_content"
android:layout_marginStart=
"10dp"
tools:text=
"11:45 PM"
/>
</LinearLayout>
<TextView
android:id=
"@+id/text_content"
style=
"@style/Message.Quote.TextView"
android:layout_width=
"0dp"
android:layout_height=
"wrap_content"
android:ellipsize=
"end"
android:singleLine=
"true"
app:layout_constraintEnd_toEndOf=
"parent"
app:layout_constraintStart_toStartOf=
"@+id/top_container"
app:layout_constraintTop_toBottomOf=
"@+id/top_container"
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!"
/>
<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/text_content"
/>
</android.support.constraint.ConstraintLayout>
\ No newline at end of file
app/src/main/res/layout/message_action_bar.xml
View file @
8685b5dd
...
@@ -6,6 +6,17 @@
...
@@ -6,6 +6,17 @@
android:layout_height=
"wrap_content"
android:layout_height=
"wrap_content"
android:background=
"@color/colorPrimary"
>
android:background=
"@color/colorPrimary"
>
<View
android:id=
"@+id/quote_bar"
android:layout_width=
"4dp"
android:layout_height=
"0dp"
android:background=
"@drawable/quote_vertical_bar"
android:layout_marginTop=
"4dp"
android:layout_marginBottom=
"4dp"
app:layout_constraintBottom_toBottomOf=
"parent"
app:layout_constraintStart_toEndOf=
"@+id/image_view_action_cancel_quote"
app:layout_constraintTop_toTopOf=
"parent"
/>
<TextView
<TextView
android:id=
"@+id/text_view_action_text"
android:id=
"@+id/text_view_action_text"
android:layout_width=
"0dp"
android:layout_width=
"0dp"
...
...
app/src/main/res/layout/suggestion_member_item.xml
View file @
8685b5dd
...
@@ -6,9 +6,9 @@
...
@@ -6,9 +6,9 @@
android:layout_height=
"wrap_content"
android:layout_height=
"wrap_content"
android:layout_marginBottom=
"2dp"
android:layout_marginBottom=
"2dp"
android:layout_marginEnd=
"2dp"
android:layout_marginEnd=
"2dp"
android:layout_marginLeft=
"
4
dp"
android:layout_marginLeft=
"
8
dp"
android:layout_marginRight=
"2dp"
android:layout_marginRight=
"2dp"
android:layout_marginStart=
"
4
dp"
android:layout_marginStart=
"
8
dp"
android:layout_marginTop=
"2dp"
android:layout_marginTop=
"2dp"
android:background=
"@color/suggestion_background_color"
>
android:background=
"@color/suggestion_background_color"
>
...
@@ -22,7 +22,8 @@
...
@@ -22,7 +22,8 @@
android:id=
"@+id/image_avatar"
android:id=
"@+id/image_avatar"
android:layout_width=
"24dp"
android:layout_width=
"24dp"
android:layout_height=
"24dp"
android:layout_height=
"24dp"
android:layout_margin=
"4dp"
android:layout_marginTop=
"4dp"
android:layout_marginBottom=
"4dp"
app:roundedCornerRadius=
"3dp"
app:roundedCornerRadius=
"3dp"
tools:src=
"@tools:sample/avatars"
/>
tools:src=
"@tools:sample/avatars"
/>
...
...
app/src/main/res/values-pt-rBR/strings.xml
View file @
8685b5dd
...
@@ -70,6 +70,10 @@
...
@@ -70,6 +70,10 @@
<string
name=
"msg_new_password"
>
Informe a nova senha
</string>
<string
name=
"msg_new_password"
>
Informe a nova senha
</string>
<string
name=
"msg_confirm_password"
>
Confirme a nova senha
</string>
<string
name=
"msg_confirm_password"
>
Confirme a nova senha
</string>
<string
name=
"msg_unread_messages"
>
Mensagens não lidas
</string>
<string
name=
"msg_unread_messages"
>
Mensagens não lidas
</string>
<string
name=
"msg_preview_video"
>
Vídeo
</string>
<string
name=
"msg_preview_audio"
>
Audio
</string>
<string
name=
"msg_preview_photo"
>
Foto
</string>
<string
name=
"msg_no_messages_yet"
>
Nenhuma mensagem ainda
</string>
<!-- System messages -->
<!-- System messages -->
<string
name=
"message_room_name_changed"
>
Nome da sala alterado para: %1$s por %2$s
</string>
<string
name=
"message_room_name_changed"
>
Nome da sala alterado para: %1$s por %2$s
</string>
...
@@ -116,6 +120,10 @@
...
@@ -116,6 +120,10 @@
<string
name=
"status_disconnecting"
>
desconectando
</string>
<string
name=
"status_disconnecting"
>
desconectando
</string>
<string
name=
"status_waiting"
>
conectando em %d segundos
</string>
<string
name=
"status_waiting"
>
conectando em %d segundos
</string>
<!--Suggestions-->
<string
name=
"suggest_all_description"
>
Notifica todos nesta sala
</string>
<string
name=
"suggest_here_description"
>
Notifica usuários ativos nesta sala
</string>
<!-- Slash Commands -->
<!-- Slash Commands -->
<string
name=
"Slash_Gimme_Description"
>
Exibir ༼ つ ◕_◕ ༽つ antes de sua mensagem
</string>
<string
name=
"Slash_Gimme_Description"
>
Exibir ༼ つ ◕_◕ ༽つ antes de sua mensagem
</string>
<string
name=
"Slash_LennyFace_Description"
>
Exibir ( ͡° ͜ʖ ͡°) depois de sua mensagem
</string>
<string
name=
"Slash_LennyFace_Description"
>
Exibir ( ͡° ͜ʖ ͡°) depois de sua mensagem
</string>
...
...
app/src/main/res/values/strings.xml
View file @
8685b5dd
...
@@ -71,6 +71,10 @@
...
@@ -71,6 +71,10 @@
<string
name=
"msg_new_password"
>
Enter New Password
</string>
<string
name=
"msg_new_password"
>
Enter New Password
</string>
<string
name=
"msg_confirm_password"
>
Confirm New Password
</string>
<string
name=
"msg_confirm_password"
>
Confirm New Password
</string>
<string
name=
"msg_unread_messages"
>
Unread messages
</string>
<string
name=
"msg_unread_messages"
>
Unread messages
</string>
<string
name=
"msg_preview_video"
>
Video
</string>
<string
name=
"msg_preview_audio"
>
Audio
</string>
<string
name=
"msg_preview_photo"
>
Photo
</string>
<string
name=
"msg_no_messages_yet"
>
No messages yet
</string>
<!-- System messages -->
<!-- System messages -->
<string
name=
"message_room_name_changed"
>
Room name changed to: %1$s by %2$s
</string>
<string
name=
"message_room_name_changed"
>
Room name changed to: %1$s by %2$s
</string>
...
@@ -117,6 +121,10 @@
...
@@ -117,6 +121,10 @@
<string
name=
"status_disconnecting"
>
disconnecting
</string>
<string
name=
"status_disconnecting"
>
disconnecting
</string>
<string
name=
"status_waiting"
>
connecting in %d seconds
</string>
<string
name=
"status_waiting"
>
connecting in %d seconds
</string>
<!--Suggestions-->
<string
name=
"suggest_all_description"
>
Notify all in this room
</string>
<string
name=
"suggest_here_description"
>
Notify active users in this room
</string>
<!-- Slash Commands -->
<!-- Slash Commands -->
<string
name=
"Slash_Gimme_Description"
>
Displays ༼ つ ◕_◕ ༽つ before your message
</string>
<string
name=
"Slash_Gimme_Description"
>
Displays ༼ つ ◕_◕ ༽つ before your message
</string>
<string
name=
"Slash_LennyFace_Description"
>
Displays ( ͡° ͜ʖ ͡°) after your message
</string>
<string
name=
"Slash_LennyFace_Description"
>
Displays ( ͡° ͜ʖ ͡°) after your message
</string>
...
...
app/src/main/res/values/styles.xml
View file @
8685b5dd
...
@@ -88,6 +88,10 @@
...
@@ -88,6 +88,10 @@
<item
name=
"android:textColor"
>
@color/colorPrimaryText
</item>
<item
name=
"android:textColor"
>
@color/colorPrimaryText
</item>
</style>
</style>
<style
name=
"Message.Quote.TextView"
parent=
"Message.TextView"
>
<item
name=
"android:textColor"
>
@color/colorPrimaryText
</item>
</style>
<style
name=
"Timestamp.TextView"
parent=
"TextAppearance.AppCompat.Caption"
>
<style
name=
"Timestamp.TextView"
parent=
"TextAppearance.AppCompat.Caption"
>
<item
name=
"android:textSize"
>
10sp
</item>
<item
name=
"android:textSize"
>
10sp
</item>
</style>
</style>
...
...
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