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
7dcb3b46
Unverified
Commit
7dcb3b46
authored
Feb 28, 2018
by
Lucio Maciel
Committed by
GitHub
Feb 28, 2018
Browse files
Options
Browse Files
Download
Plain Diff
Merge branch 'release/2.0.0-beta7' into feature/member-details
parents
13161ca1
1cb3f896
Changes
42
Hide whitespace changes
Inline
Side-by-side
Showing
42 changed files
with
812 additions
and
308 deletions
+812
-308
build.gradle
app/build.gradle
+4
-2
AudioAttachmentViewHolder.kt
...ket/android/chatroom/adapter/AudioAttachmentViewHolder.kt
+5
-1
BaseViewHolder.kt
...va/chat/rocket/android/chatroom/adapter/BaseViewHolder.kt
+50
-1
ChatRoomAdapter.kt
...a/chat/rocket/android/chatroom/adapter/ChatRoomAdapter.kt
+33
-5
ImageAttachmentViewHolder.kt
...ket/android/chatroom/adapter/ImageAttachmentViewHolder.kt
+10
-1
MessageViewHolder.kt
...chat/rocket/android/chatroom/adapter/MessageViewHolder.kt
+5
-54
UrlPreviewViewHolder.kt
...t/rocket/android/chatroom/adapter/UrlPreviewViewHolder.kt
+9
-1
VideoAttachmentViewHolder.kt
...ket/android/chatroom/adapter/VideoAttachmentViewHolder.kt
+5
-1
ChatRoomPresenter.kt
...rocket/android/chatroom/presentation/ChatRoomPresenter.kt
+70
-59
ChatRoomView.kt
...chat/rocket/android/chatroom/presentation/ChatRoomView.kt
+3
-0
PinnedMessagesPresenter.kt
.../android/chatroom/presentation/PinnedMessagesPresenter.kt
+2
-3
ChatRoomActivity.kt
.../java/chat/rocket/android/chatroom/ui/ChatRoomActivity.kt
+10
-0
ChatRoomFragment.kt
.../java/chat/rocket/android/chatroom/ui/ChatRoomFragment.kt
+37
-1
AudioAttachmentViewModel.kt
...et/android/chatroom/viewmodel/AudioAttachmentViewModel.kt
+2
-0
BaseViewModel.kt
...a/chat/rocket/android/chatroom/viewmodel/BaseViewModel.kt
+2
-0
ImageAttachmentViewModel.kt
...et/android/chatroom/viewmodel/ImageAttachmentViewModel.kt
+2
-0
MessageViewModel.kt
...hat/rocket/android/chatroom/viewmodel/MessageViewModel.kt
+2
-2
UrlPreviewViewModel.kt
.../rocket/android/chatroom/viewmodel/UrlPreviewViewModel.kt
+2
-0
VideoAttachmentViewModel.kt
...et/android/chatroom/viewmodel/VideoAttachmentViewModel.kt
+2
-0
ViewModelMapper.kt
...chat/rocket/android/chatroom/viewmodel/ViewModelMapper.kt
+49
-29
ChatRoomsPresenter.kt
...cket/android/chatrooms/presentation/ChatRoomsPresenter.kt
+102
-85
ChatRoomsView.kt
...at/rocket/android/chatrooms/presentation/ChatRoomsView.kt
+3
-0
ChatRoomsAdapter.kt
...java/chat/rocket/android/chatrooms/ui/ChatRoomsAdapter.kt
+8
-1
ChatRoomsFragment.kt
...ava/chat/rocket/android/chatrooms/ui/ChatRoomsFragment.kt
+45
-7
MessageParser.kt
...src/main/java/chat/rocket/android/helper/MessageParser.kt
+19
-1
MainPresenter.kt
...va/chat/rocket/android/main/presentation/MainPresenter.kt
+12
-1
MainActivity.kt
...src/main/java/chat/rocket/android/main/ui/MainActivity.kt
+8
-0
SettingsRepository.kt
...a/chat/rocket/android/server/domain/SettingsRepository.kt
+18
-18
ConnectionManager.kt
...ocket/android/server/infraestructure/ConnectionManager.kt
+155
-0
ConnectionManagerFactory.kt
...ndroid/server/infraestructure/ConnectionManagerFactory.kt
+22
-0
RocketChatClientFactory.kt
...android/server/infraestructure/RocketChatClientFactory.kt
+1
-1
SharedPreferencesSettingsRepository.kt
...er/infraestructure/SharedPreferencesSettingsRepository.kt
+3
-3
Animation.kt
...ain/java/chat/rocket/android/util/extensions/Animation.kt
+47
-25
Ui.kt
app/src/main/java/chat/rocket/android/util/extensions/Ui.kt
+12
-1
emoji_popup_layout.xml
app/src/main/res/layout/emoji_popup_layout.xml
+1
-1
fragment_chat_room.xml
app/src/main/res/layout/fragment_chat_room.xml
+15
-0
fragment_chat_rooms.xml
app/src/main/res/layout/fragment_chat_rooms.xml
+15
-0
message_attachment.xml
app/src/main/res/layout/message_attachment.xml
+2
-2
message_url_preview.xml
app/src/main/res/layout/message_url_preview.xml
+2
-2
strings.xml
app/src/main/res/values-pt-rBR/strings.xml
+7
-0
strings.xml
app/src/main/res/values/strings.xml
+8
-0
dependencies.gradle
dependencies.gradle
+3
-0
No files found.
app/build.gradle
View file @
7dcb3b46
...
...
@@ -12,8 +12,8 @@ android {
applicationId
"chat.rocket.android"
minSdkVersion
21
targetSdkVersion
versions
.
targetSdk
versionCode
10
09
versionName
"2.0.0-dev
7
"
versionCode
10
10
versionName
"2.0.0-dev
8
"
testInstrumentationRunner
"android.support.test.runner.AndroidJUnitRunner"
multiDexEnabled
true
}
...
...
@@ -60,6 +60,8 @@ dependencies {
implementation
libraries
.
constraintLayout
implementation
libraries
.
cardView
implementation
libraries
.
androidKtx
implementation
libraries
.
dagger
implementation
libraries
.
daggerSupport
kapt
libraries
.
daggerProcessor
...
...
app/src/main/java/chat/rocket/android/chatroom/adapter/AudioAttachmentViewHolder.kt
View file @
7dcb3b46
...
...
@@ -6,11 +6,15 @@ import chat.rocket.android.player.PlayerActivity
import
chat.rocket.android.util.extensions.setVisible
import
kotlinx.android.synthetic.main.message_attachment.view.*
class
AudioAttachmentViewHolder
(
itemView
:
View
)
:
BaseViewHolder
<
AudioAttachmentViewModel
>(
itemView
)
{
class
AudioAttachmentViewHolder
(
itemView
:
View
,
listener
:
ActionsListener
)
:
BaseViewHolder
<
AudioAttachmentViewModel
>(
itemView
,
listener
)
{
init
{
with
(
itemView
)
{
image_attachment
.
setVisible
(
false
)
audio_video_attachment
.
setVisible
(
true
)
setupActionMenu
(
attachment_container
)
setupActionMenu
(
audio_video_attachment
)
}
}
...
...
app/src/main/java/chat/rocket/android/chatroom/adapter/BaseViewHolder.kt
View file @
7dcb3b46
package
chat.rocket.android.chatroom.adapter
import
android.support.v7.widget.RecyclerView
import
android.view.MenuItem
import
android.view.View
import
chat.rocket.android.R
import
chat.rocket.android.chatroom.ui.bottomsheet.BottomSheetMenu
import
chat.rocket.android.chatroom.ui.bottomsheet.adapter.ActionListAdapter
import
chat.rocket.android.chatroom.viewmodel.BaseViewModel
import
chat.rocket.core.model.Message
import
chat.rocket.core.model.isSystemMessage
import
ru.whalemare.sheetmenu.extension.inflate
import
ru.whalemare.sheetmenu.extension.toList
abstract
class
BaseViewHolder
<
T
>(
itemView
:
View
)
:
RecyclerView
.
ViewHolder
(
itemView
)
{
abstract
class
BaseViewHolder
<
T
:
BaseViewModel
<*>>(
itemView
:
View
,
private
val
listener
:
ActionsListener
)
:
RecyclerView
.
ViewHolder
(
itemView
),
MenuItem
.
OnMenuItemClickListener
{
var
data
:
T
?
=
null
init
{
setupActionMenu
(
itemView
)
}
fun
bind
(
data
:
T
)
{
this
.
data
=
data
bindViews
(
data
)
}
abstract
fun
bindViews
(
data
:
T
)
interface
ActionsListener
{
fun
isActionsEnabled
():
Boolean
fun
onActionSelected
(
item
:
MenuItem
,
message
:
Message
)
}
val
longClickListener
=
{
view
:
View
->
if
(
data
?.
message
?.
isSystemMessage
()
==
false
)
{
val
menuItems
=
view
.
context
.
inflate
(
R
.
menu
.
message_actions
).
toList
()
menuItems
.
find
{
it
.
itemId
==
R
.
id
.
action_menu_msg_pin_unpin
}
?.
apply
{
val
isPinned
=
data
?.
message
?.
pinned
?:
false
setTitle
(
if
(
isPinned
)
R
.
string
.
action_msg_unpin
else
R
.
string
.
action_msg_pin
)
isChecked
=
isPinned
}
val
adapter
=
ActionListAdapter
(
menuItems
,
this
@BaseViewHolder
)
BottomSheetMenu
(
adapter
).
show
(
view
.
context
)
}
true
}
internal
fun
setupActionMenu
(
view
:
View
)
{
if
(
listener
.
isActionsEnabled
())
{
view
.
setOnLongClickListener
(
longClickListener
)
}
}
override
fun
onMenuItemClick
(
item
:
MenuItem
):
Boolean
{
data
?.
let
{
listener
.
onActionSelected
(
item
,
it
.
message
)
}
return
true
}
}
\ No newline at end of file
app/src/main/java/chat/rocket/android/chatroom/adapter/ChatRoomAdapter.kt
View file @
7dcb3b46
package
chat.rocket.android.chatroom.adapter
import
android.support.v7.widget.RecyclerView
import
android.view.MenuItem
import
android.view.ViewGroup
import
chat.rocket.android.R
import
chat.rocket.android.chatroom.presentation.ChatRoomPresenter
import
chat.rocket.android.chatroom.viewmodel.*
import
chat.rocket.android.util.extensions.inflate
import
chat.rocket.core.model.Message
import
timber.log.Timber
import
java.security.InvalidParameterException
...
...
@@ -26,23 +28,23 @@ class ChatRoomAdapter(
return
when
(
viewType
.
toViewType
())
{
BaseViewModel
.
ViewType
.
MESSAGE
->
{
val
view
=
parent
.
inflate
(
R
.
layout
.
item_message
)
MessageViewHolder
(
view
,
roomName
,
roomType
,
presenter
,
enableActions
)
MessageViewHolder
(
view
,
actionsListener
)
}
BaseViewModel
.
ViewType
.
IMAGE_ATTACHMENT
->
{
val
view
=
parent
.
inflate
(
R
.
layout
.
message_attachment
)
ImageAttachmentViewHolder
(
view
)
ImageAttachmentViewHolder
(
view
,
actionsListener
)
}
BaseViewModel
.
ViewType
.
AUDIO_ATTACHMENT
->
{
val
view
=
parent
.
inflate
(
R
.
layout
.
message_attachment
)
AudioAttachmentViewHolder
(
view
)
AudioAttachmentViewHolder
(
view
,
actionsListener
)
}
BaseViewModel
.
ViewType
.
VIDEO_ATTACHMENT
->
{
val
view
=
parent
.
inflate
(
R
.
layout
.
message_attachment
)
VideoAttachmentViewHolder
(
view
)
VideoAttachmentViewHolder
(
view
,
actionsListener
)
}
BaseViewModel
.
ViewType
.
URL_PREVIEW
->
{
val
view
=
parent
.
inflate
(
R
.
layout
.
message_url_preview
)
UrlPreviewViewHolder
(
view
)
UrlPreviewViewHolder
(
view
,
actionsListener
)
}
else
->
{
throw
InvalidParameterException
(
"TODO - implement for ${viewType.toViewType()}"
)
...
...
@@ -108,4 +110,30 @@ class ChatRoomAdapter(
notifyItemRangeRemoved
(
index
,
oldSize
-
newSize
)
}
}
val
actionsListener
=
object
:
BaseViewHolder
.
ActionsListener
{
override
fun
isActionsEnabled
():
Boolean
=
enableActions
override
fun
onActionSelected
(
item
:
MenuItem
,
message
:
Message
)
{
message
.
apply
{
when
(
item
.
itemId
)
{
R
.
id
.
action_menu_msg_delete
->
presenter
?.
deleteMessage
(
roomId
,
id
)
R
.
id
.
action_menu_msg_quote
->
presenter
?.
citeMessage
(
roomType
,
roomName
,
id
,
false
)
R
.
id
.
action_menu_msg_reply
->
presenter
?.
citeMessage
(
roomType
,
roomName
,
id
,
true
)
R
.
id
.
action_menu_msg_copy
->
presenter
?.
copyMessage
(
id
)
R
.
id
.
action_menu_msg_edit
->
presenter
?.
editMessage
(
roomId
,
id
,
message
.
message
)
R
.
id
.
action_menu_msg_pin_unpin
->
{
with
(
item
)
{
if
(!
isChecked
)
{
presenter
?.
pinMessage
(
id
)
}
else
{
presenter
?.
unpinMessage
(
id
)
}
}
}
else
->
TODO
(
"Not implemented"
)
}
}
}
}
}
\ No newline at end of file
app/src/main/java/chat/rocket/android/chatroom/adapter/ImageAttachmentViewHolder.kt
View file @
7dcb3b46
...
...
@@ -5,7 +5,16 @@ import chat.rocket.android.chatroom.viewmodel.ImageAttachmentViewModel
import
com.stfalcon.frescoimageviewer.ImageViewer
import
kotlinx.android.synthetic.main.message_attachment.view.*
class
ImageAttachmentViewHolder
(
itemView
:
View
)
:
BaseViewHolder
<
ImageAttachmentViewModel
>(
itemView
)
{
class
ImageAttachmentViewHolder
(
itemView
:
View
,
listener
:
ActionsListener
)
:
BaseViewHolder
<
ImageAttachmentViewModel
>(
itemView
,
listener
)
{
init
{
with
(
itemView
)
{
setupActionMenu
(
attachment_container
)
setupActionMenu
(
image_attachment
)
}
}
override
fun
bindViews
(
data
:
ImageAttachmentViewModel
)
{
with
(
itemView
)
{
image_attachment
.
setImageURI
(
data
.
attachmentUrl
)
...
...
app/src/main/java/chat/rocket/android/chatroom/adapter/MessageViewHolder.kt
View file @
7dcb3b46
package
chat.rocket.android.chatroom.adapter
import
android.text.method.LinkMovementMethod
import
android.view.MenuItem
import
android.view.View
import
chat.rocket.android.R
import
chat.rocket.android.chatroom.presentation.ChatRoomPresenter
import
chat.rocket.android.chatroom.ui.bottomsheet.BottomSheetMenu
import
chat.rocket.android.chatroom.ui.bottomsheet.adapter.ActionListAdapter
import
chat.rocket.android.chatroom.viewmodel.MessageViewModel
import
kotlinx.android.synthetic.main.avatar.view.*
import
kotlinx.android.synthetic.main.item_message.view.*
import
ru.whalemare.sheetmenu.extension.inflate
import
ru.whalemare.sheetmenu.extension.toList
class
MessageViewHolder
(
itemView
:
View
,
private
val
roomType
:
String
,
private
val
roomName
:
String
,
private
val
presenter
:
ChatRoomPresenter
?,
enableActions
:
Boolean
)
:
BaseViewHolder
<
MessageViewModel
>(
itemView
),
MenuItem
.
OnMenuItemClickListener
{
listener
:
ActionsListener
)
:
BaseViewHolder
<
MessageViewModel
>(
itemView
,
listener
)
{
init
{
itemView
.
text_content
.
movementMethod
=
LinkMovementMethod
()
if
(
enableActions
)
{
itemView
.
setOnLongClickListener
{
if
(
data
?.
isSystemMessage
==
false
)
{
val
menuItems
=
it
.
context
.
inflate
(
R
.
menu
.
message_actions
).
toList
()
menuItems
.
find
{
it
.
itemId
==
R
.
id
.
action_menu_msg_pin_unpin
}
?.
apply
{
val
isPinned
=
data
?.
isPinned
?:
false
setTitle
(
if
(
isPinned
)
R
.
string
.
action_msg_unpin
else
R
.
string
.
action_msg_pin
)
isChecked
=
isPinned
}
val
adapter
=
ActionListAdapter
(
menuItems
,
this
@MessageViewHolder
)
BottomSheetMenu
(
adapter
).
apply
{
}.
show
(
it
.
context
)
}
true
}
with
(
itemView
)
{
text_content
.
movementMethod
=
LinkMovementMethod
()
setupActionMenu
(
text_content
)
}
}
...
...
@@ -52,27 +26,4 @@ class MessageViewHolder(
image_avatar
.
setImageURI
(
data
.
avatar
)
}
}
override
fun
onMenuItemClick
(
item
:
MenuItem
):
Boolean
{
data
?.
rawData
?.
apply
{
when
(
item
.
itemId
)
{
R
.
id
.
action_menu_msg_delete
->
presenter
?.
deleteMessage
(
roomId
,
id
)
R
.
id
.
action_menu_msg_quote
->
presenter
?.
citeMessage
(
roomType
,
roomName
,
id
,
false
)
R
.
id
.
action_menu_msg_reply
->
presenter
?.
citeMessage
(
roomType
,
roomName
,
id
,
true
)
R
.
id
.
action_menu_msg_copy
->
presenter
?.
copyMessage
(
id
)
R
.
id
.
action_menu_msg_edit
->
presenter
?.
editMessage
(
roomId
,
id
,
message
)
R
.
id
.
action_menu_msg_pin_unpin
->
{
with
(
item
)
{
if
(!
isChecked
)
{
presenter
?.
pinMessage
(
id
)
}
else
{
presenter
?.
unpinMessage
(
id
)
}
}
}
else
->
TODO
(
"Not implemented"
)
}
}
return
true
}
}
\ No newline at end of file
app/src/main/java/chat/rocket/android/chatroom/adapter/UrlPreviewViewHolder.kt
View file @
7dcb3b46
...
...
@@ -8,7 +8,15 @@ import chat.rocket.android.util.extensions.content
import
chat.rocket.android.util.extensions.setVisible
import
kotlinx.android.synthetic.main.message_url_preview.view.*
class
UrlPreviewViewHolder
(
itemView
:
View
)
:
BaseViewHolder
<
UrlPreviewViewModel
>(
itemView
)
{
class
UrlPreviewViewHolder
(
itemView
:
View
,
listener
:
ActionsListener
)
:
BaseViewHolder
<
UrlPreviewViewModel
>(
itemView
,
listener
)
{
init
{
with
(
itemView
)
{
setupActionMenu
(
url_preview_layout
)
}
}
override
fun
bindViews
(
data
:
UrlPreviewViewModel
)
{
with
(
itemView
)
{
if
(
data
.
thumbUrl
.
isNullOrEmpty
())
{
...
...
app/src/main/java/chat/rocket/android/chatroom/adapter/VideoAttachmentViewHolder.kt
View file @
7dcb3b46
...
...
@@ -6,11 +6,15 @@ import chat.rocket.android.player.PlayerActivity
import
chat.rocket.android.util.extensions.setVisible
import
kotlinx.android.synthetic.main.message_attachment.view.*
class
VideoAttachmentViewHolder
(
itemView
:
View
)
:
BaseViewHolder
<
VideoAttachmentViewModel
>(
itemView
)
{
class
VideoAttachmentViewHolder
(
itemView
:
View
,
listener
:
ActionsListener
)
:
BaseViewHolder
<
VideoAttachmentViewModel
>(
itemView
,
listener
)
{
init
{
with
(
itemView
)
{
image_attachment
.
setVisible
(
false
)
audio_video_attachment
.
setVisible
(
true
)
setupActionMenu
(
attachment_container
)
setupActionMenu
(
audio_video_attachment
)
}
}
...
...
app/src/main/java/chat/rocket/android/chatroom/presentation/ChatRoomPresenter.kt
View file @
7dcb3b46
...
...
@@ -6,23 +6,23 @@ import chat.rocket.android.chatroom.domain.UriInteractor
import
chat.rocket.android.chatroom.viewmodel.ViewModelMapper
import
chat.rocket.android.core.lifecycle.CancelStrategy
import
chat.rocket.android.server.domain.*
import
chat.rocket.android.server.infraestructure.RocketChatClientFactory
import
chat.rocket.android.server.infraestructure.ConnectionManagerFactory
import
chat.rocket.android.server.infraestructure.state
import
chat.rocket.android.util.extensions.launchUI
import
chat.rocket.common.RocketChatException
import
chat.rocket.common.model.RoomType
import
chat.rocket.common.model.roomTypeOf
import
chat.rocket.common.util.ifNull
import
chat.rocket.core.internal.realtime.State
import
chat.rocket.core.internal.realtime.connect
import
chat.rocket.core.internal.realtime.subscribeRoomMessages
import
chat.rocket.core.internal.realtime.unsubscribe
import
chat.rocket.core.internal.rest.*
import
chat.rocket.core.model.Message
import
chat.rocket.core.model.Value
import
kotlinx.coroutines.experimental.CommonPool
import
kotlinx.coroutines.experimental.android.UI
import
kotlinx.coroutines.experimental.async
import
kotlinx.coroutines.experimental.channels.Channel
import
kotlinx.coroutines.experimental.launch
import
org.threeten.bp.Instant
import
timber.log.Timber
import
javax.inject.Inject
...
...
@@ -34,14 +34,22 @@ class ChatRoomPresenter @Inject constructor(private val view: ChatRoomView,
private
val
permissions
:
GetPermissionsInteractor
,
private
val
uriInteractor
:
UriInteractor
,
private
val
messagesRepository
:
MessagesRepository
,
factory
:
RocketChatClient
Factory
,
factory
:
ConnectionManager
Factory
,
private
val
mapper
:
ViewModelMapper
)
{
private
val
client
=
factory
.
create
(
serverInteractor
.
get
()
!!
)
private
var
subId
:
String
?
=
null
private
val
currentServer
=
serverInteractor
.
get
()
!!
private
val
manager
=
factory
.
create
(
currentServer
)
private
val
client
=
manager
.
client
private
var
settings
:
Map
<
String
,
Value
<
Any
>>
=
getSettingsInteractor
.
get
(
serverInteractor
.
get
()
!!
)
!!
private
val
messagesChannel
=
Channel
<
Message
>()
private
var
chatRoomId
:
String
?
=
null
private
var
chatRoomType
:
String
?
=
null
private
val
stateChannel
=
Channel
<
State
>()
private
var
lastState
=
manager
.
state
fun
loadMessages
(
chatRoomId
:
String
,
chatRoomType
:
String
,
offset
:
Long
=
0
)
{
this
.
chatRoomId
=
chatRoomId
this
.
chatRoomType
=
chatRoomType
launchUI
(
strategy
)
{
view
.
showLoading
()
try
{
...
...
@@ -56,10 +64,7 @@ class ChatRoomPresenter @Inject constructor(private val view: ChatRoomView,
val
messagesViewModels
=
mapper
.
map
(
messages
)
view
.
showMessages
(
messagesViewModels
)
// Subscribe after getting the first page of messages from REST
if
(
offset
==
0L
)
{
subscribeMessages
(
chatRoomId
)
}
subscribeMessages
(
chatRoomId
)
}
catch
(
ex
:
Exception
)
{
ex
.
printStackTrace
()
ex
.
message
?.
let
{
...
...
@@ -70,6 +75,10 @@ class ChatRoomPresenter @Inject constructor(private val view: ChatRoomView,
}
finally
{
view
.
hideLoading
()
}
if
(
offset
==
0L
)
{
subscribeState
()
}
}
}
...
...
@@ -132,7 +141,7 @@ class ChatRoomPresenter @Inject constructor(private val view: ChatRoomView,
}
}
fun
markRoomAsRead
(
roomId
:
String
)
{
private
fun
markRoomAsRead
(
roomId
:
String
)
{
launchUI
(
strategy
)
{
try
{
client
.
markAsRead
(
roomId
)
...
...
@@ -143,57 +152,70 @@ class ChatRoomPresenter @Inject constructor(private val view: ChatRoomView,
}
}
private
fun
subscribeMessages
(
roomId
:
String
)
{
client
.
addStateChannel
(
stateChannel
)
private
fun
subscribeState
()
{
Timber
.
d
(
"Subscribing to Status changes"
)
lastState
=
manager
.
state
manager
.
addStatusChannel
(
stateChannel
)
launch
(
CommonPool
+
strategy
.
jobs
)
{
for
(
status
in
stateChannel
)
{
Timber
.
d
(
"Changing status to: $status"
)
when
(
status
)
{
State
.
Authenticating
->
Timber
.
d
(
"Authenticating"
)
State
.
Connected
->
{
Timber
.
d
(
"Connected"
)
subId
=
client
.
subscribeRoomMessages
(
roomId
)
{
Timber
.
d
(
"subscribe messages for $roomId: $it"
)
}
for
(
state
in
stateChannel
)
{
Timber
.
d
(
"Got new state: $state - last: $lastState"
)
if
(
state
!=
lastState
)
{
launch
(
UI
)
{
view
.
showConnectionState
(
state
)
}
}
}
Timber
.
d
(
"Done on statusChannel"
)
}
when
(
client
.
state
)
{
State
.
Connected
->
{
Timber
.
d
(
"Already connected"
)
subId
=
client
.
subscribeRoomMessages
(
roomId
)
{
Timber
.
d
(
"subscribe messages for $roomId: $it"
)
if
(
state
is
State
.
Connected
)
{
loadMissingMessages
()
}
}
lastState
=
state
}
else
->
client
.
connect
()
}
}
launchUI
(
strategy
)
{
listenMessages
(
roomId
)
}
private
fun
subscribeMessages
(
roomId
:
String
)
{
manager
.
subscribeRoomMessages
(
roomId
,
messagesChannel
)
// TODO - when we have a proper service, we won't need to take care of connection, just
// subscribe and listen...
/*launchUI(strategy) {
subId = client.subscribeRoomMessages(roomId) {
Timber.d("subscribe messages for $roomId: $it")
launch
(
CommonPool
+
strategy
.
jobs
)
{
for
(
message
in
messagesChannel
)
{
Timber
.
d
(
"New message for room ${message.roomId}"
)
updateMessage
(
message
)
}
listenMessages(roomId)
}*/
}
}
fun
unsubscribeMessages
()
{
launch
(
CommonPool
)
{
client
.
removeStateChannel
(
stateChannel
)
subId
?.
let
{
subscriptionId
->
client
.
unsubscribe
(
subscriptionId
)
private
fun
loadMissingMessages
()
{
launch
(
parent
=
strategy
.
jobs
)
{
if
(
chatRoomId
!=
null
&&
chatRoomType
!=
null
)
{
val
roomType
=
roomTypeOf
(
chatRoomType
!!
)
val
lastMessage
=
messagesRepository
.
getByRoomId
(
chatRoomId
!!
).
sortedByDescending
{
it
.
timestamp
}.
first
()
val
instant
=
Instant
.
ofEpochMilli
(
lastMessage
.
timestamp
)
val
messages
=
client
.
history
(
chatRoomId
!!
,
roomType
,
count
=
50
,
oldest
=
instant
.
toString
())
Timber
.
d
(
"History: $messages"
)
if
(
messages
.
result
.
isNotEmpty
())
{
val
models
=
mapper
.
map
(
messages
.
result
)
messagesRepository
.
saveAll
(
messages
.
result
)
launchUI
(
strategy
)
{
view
.
showNewMessage
(
models
)
}
if
(
messages
.
result
.
size
==
50
)
{
// we loade at least count messages, try one more to fetch more messages
loadMissingMessages
()
}
}
}
}
}
fun
unsubscribeMessages
(
chatRoomId
:
String
)
{
manager
.
removeStatusChannel
(
stateChannel
)
manager
.
unsubscribeRoomMessages
(
chatRoomId
)
}
/**
* Delete the message with the given id.
*
...
...
@@ -320,17 +342,6 @@ class ChatRoomPresenter @Inject constructor(private val view: ChatRoomView,
fun
toMembersList
(
chatRoomId
:
String
,
chatRoomType
:
String
)
=
navigator
.
toMembersList
(
chatRoomId
,
chatRoomType
)
private
suspend
fun
listenMessages
(
roomId
:
String
)
{
launch
(
CommonPool
+
strategy
.
jobs
)
{
for
(
message
in
client
.
messagesChannel
)
{
if
(
message
.
roomId
!=
roomId
)
{
Timber
.
d
(
"Ignoring message for room ${message.roomId}, expecting $roomId"
)
}
updateMessage
(
message
)
}
}
}
private
fun
updateMessage
(
streamedMessage
:
Message
)
{
launchUI
(
strategy
)
{
val
viewModelStreamedMessage
=
mapper
.
map
(
streamedMessage
)
...
...
app/src/main/java/chat/rocket/android/chatroom/presentation/ChatRoomView.kt
View file @
7dcb3b46
...
...
@@ -4,6 +4,7 @@ import android.net.Uri
import
chat.rocket.android.chatroom.viewmodel.BaseViewModel
import
chat.rocket.android.core.behaviours.LoadingView
import
chat.rocket.android.core.behaviours.MessageView
import
chat.rocket.core.internal.realtime.State
interface
ChatRoomView
:
LoadingView
,
MessageView
{
...
...
@@ -97,4 +98,6 @@ interface ChatRoomView : LoadingView, MessageView {
fun
clearMessageComposition
()
fun
showInvalidFileSize
(
fileSize
:
Int
,
maxFileSize
:
Int
)
fun
showConnectionState
(
state
:
State
)
}
\ No newline at end of file
app/src/main/java/chat/rocket/android/chatroom/presentation/PinnedMessagesPresenter.kt
View file @
7dcb3b46
package
chat.rocket.android.chatroom.presentation
import
chat.rocket.android.chatroom.viewmodel.MessageViewModel
import
chat.rocket.android.chatroom.viewmodel.ViewModelMapper
import
chat.rocket.android.core.lifecycle.CancelStrategy
import
chat.rocket.android.server.domain.GetChatRoomsInteractor
...
...
@@ -12,6 +11,7 @@ import chat.rocket.common.RocketChatException
import
chat.rocket.common.util.ifNull
import
chat.rocket.core.internal.rest.getRoomPinnedMessages
import
chat.rocket.core.model.Value
import
chat.rocket.core.model.isSystemMessage
import
timber.log.Timber
import
javax.inject.Inject
...
...
@@ -42,8 +42,7 @@ class PinnedMessagesPresenter @Inject constructor(private val view: PinnedMessag
val
pinnedMessages
=
client
.
getRoomPinnedMessages
(
roomId
,
room
.
type
,
pinnedMessagesListOffset
)
pinnedMessagesListOffset
=
pinnedMessages
.
offset
.
toInt
()
val
messageList
=
mapper
.
map
(
pinnedMessages
.
result
)
.
filter
{
it
is
MessageViewModel
}.
filterNot
{
(
it
as
MessageViewModel
).
isSystemMessage
}
val
messageList
=
mapper
.
map
(
pinnedMessages
.
result
.
filterNot
{
it
.
isSystemMessage
()
})
view
.
showPinnedMessages
(
messageList
)
view
.
hideLoading
()
}.
ifNull
{
...
...
app/src/main/java/chat/rocket/android/chatroom/ui/ChatRoomActivity.kt
View file @
7dcb3b46
...
...
@@ -6,6 +6,8 @@ import android.os.Bundle
import
android.support.v4.app.Fragment
import
android.support.v7.app.AppCompatActivity
import
chat.rocket.android.R
import
chat.rocket.android.server.domain.GetCurrentServerInteractor
import
chat.rocket.android.server.infraestructure.ConnectionManagerFactory
import
chat.rocket.android.util.extensions.addFragment
import
chat.rocket.android.util.extensions.textContent
import
dagger.android.AndroidInjection
...
...
@@ -32,6 +34,11 @@ private const val INTENT_IS_CHAT_ROOM_READ_ONLY = "is_chat_room_read_only"
class
ChatRoomActivity
:
AppCompatActivity
(),
HasSupportFragmentInjector
{
@Inject
lateinit
var
fragmentDispatchingAndroidInjector
:
DispatchingAndroidInjector
<
Fragment
>
// TODO - workaround for now... We will move to a single activity
@Inject
lateinit
var
serverInteractor
:
GetCurrentServerInteractor
@Inject
lateinit
var
managerFactory
:
ConnectionManagerFactory
private
lateinit
var
chatRoomId
:
String
private
lateinit
var
chatRoomName
:
String
private
lateinit
var
chatRoomType
:
String
...
...
@@ -42,6 +49,9 @@ class ChatRoomActivity : AppCompatActivity(), HasSupportFragmentInjector {
super
.
onCreate
(
savedInstanceState
)
setContentView
(
R
.
layout
.
activity_chat_room
)
// Workaround for when we are coming to the app via the recents app and the app was killed.
managerFactory
.
create
(
serverInteractor
.
get
()
!!
).
connect
()
chatRoomId
=
intent
.
getStringExtra
(
INTENT_CHAT_ROOM_ID
)
requireNotNull
(
chatRoomId
)
{
"no chat_room_id provided in Intent extras"
}
...
...
app/src/main/java/chat/rocket/android/chatroom/ui/ChatRoomFragment.kt
View file @
7dcb3b46
...
...
@@ -27,6 +27,7 @@ import chat.rocket.android.widget.emoji.ComposerEditText
import
chat.rocket.android.widget.emoji.Emoji
import
chat.rocket.android.widget.emoji.EmojiKeyboardPopup
import
chat.rocket.android.widget.emoji.EmojiParser
import
chat.rocket.core.internal.realtime.State
import
dagger.android.support.AndroidSupportInjection
import
io.reactivex.disposables.CompositeDisposable
import
kotlinx.android.synthetic.main.fragment_chat_room.*
...
...
@@ -114,7 +115,7 @@ class ChatRoomFragment : Fragment(), ChatRoomView, EmojiKeyboardPopup.Listener {
}
override
fun
onDestroyView
()
{
presenter
.
unsubscribeMessages
()
presenter
.
unsubscribeMessages
(
chatRoomId
)
handler
.
removeCallbacksAndMessages
(
null
)
unsubscribeTextMessage
()
super
.
onDestroyView
()
...
...
@@ -225,6 +226,12 @@ class ChatRoomFragment : Fragment(), ChatRoomView, EmojiKeyboardPopup.Listener {
actionSnackbar
.
title
=
username
actionSnackbar
.
text
=
quotedMessage
actionSnackbar
.
show
()
KeyboardHelper
.
showSoftKeyboard
(
text_message
)
if
(!
recycler_view
.
isAtBottom
())
{
if
(
adapter
.
itemCount
>
0
)
{
recycler_view
.
scrollToPosition
(
0
)
}
}
}
}
...
...
@@ -252,6 +259,7 @@ class ChatRoomFragment : Fragment(), ChatRoomView, EmojiKeyboardPopup.Listener {
actionSnackbar
.
show
()
text_message
.
textContent
=
text
editingMessageId
=
messageId
KeyboardHelper
.
showSoftKeyboard
(
text_message
)
}
}
...
...
@@ -288,6 +296,28 @@ class ChatRoomFragment : Fragment(), ChatRoomView, EmojiKeyboardPopup.Listener {
showMessage
(
getString
(
R
.
string
.
max_file_size_exceeded
,
fileSize
,
maxFileSize
))
}
override
fun
showConnectionState
(
state
:
State
)
{
activity
?.
apply
{
connection_status_text
.
fadeIn
()
handler
.
removeCallbacks
(
dismissStatus
)
when
(
state
)
{
is
State
.
Connected
->
{
connection_status_text
.
text
=
getString
(
R
.
string
.
status_connected
)
handler
.
postDelayed
(
dismissStatus
,
2000
)
}
is
State
.
Disconnected
->
connection_status_text
.
text
=
getString
(
R
.
string
.
status_disconnected
)
is
State
.
Connecting
->
connection_status_text
.
text
=
getString
(
R
.
string
.
status_connecting
)
is
State
.
Authenticating
->
connection_status_text
.
text
=
getString
(
R
.
string
.
status_authenticating
)
is
State
.
Disconnecting
->
connection_status_text
.
text
=
getString
(
R
.
string
.
status_disconnecting
)
is
State
.
Waiting
->
connection_status_text
.
text
=
getString
(
R
.
string
.
status_waiting
,
state
.
seconds
)
}
}
}
private
val
dismissStatus
=
{
connection_status_text
.
fadeOut
()
}
private
fun
setupRecyclerView
()
{
recycler_view
.
addOnScrollListener
(
object
:
RecyclerView
.
OnScrollListener
()
{
override
fun
onScrolled
(
recyclerView
:
RecyclerView
,
dx
:
Int
,
dy
:
Int
)
{
...
...
@@ -320,6 +350,11 @@ class ChatRoomFragment : Fragment(), ChatRoomView, EmojiKeyboardPopup.Listener {
emojiKeyboardPopup
.
listener
=
this
text_message
.
listener
=
object
:
ComposerEditText
.
ComposerEditTextListener
{
override
fun
onKeyboardOpened
()
{
if
(
recycler_view
.
isAtBottom
())
{
if
(
adapter
.
itemCount
>
0
)
{
recycler_view
.
scrollToPosition
(
0
)
}
}
}
override
fun
onKeyboardClosed
()
{
...
...
@@ -395,6 +430,7 @@ class ChatRoomFragment : Fragment(), ChatRoomView, EmojiKeyboardPopup.Listener {
actionSnackbar
=
ActionSnackbar
.
make
(
message_list_container
,
parser
=
parser
)
actionSnackbar
.
cancelView
.
setOnClickListener
({
clearMessageComposition
()
KeyboardHelper
.
showSoftKeyboard
(
text_message
)
})
}
...
...
app/src/main/java/chat/rocket/android/chatroom/viewmodel/AudioAttachmentViewModel.kt
View file @
7dcb3b46
package
chat.rocket.android.chatroom.viewmodel
import
chat.rocket.android.R
import
chat.rocket.core.model.Message
import
chat.rocket.core.model.attachment.AudioAttachment
data class
AudioAttachmentViewModel
(
override
val
message
:
Message
,
override
val
rawData
:
AudioAttachment
,
override
val
messageId
:
String
,
override
val
attachmentUrl
:
String
,
...
...
app/src/main/java/chat/rocket/android/chatroom/viewmodel/BaseViewModel.kt
View file @
7dcb3b46
package
chat.rocket.android.chatroom.viewmodel
import
chat.rocket.core.model.Message
import
java.security.InvalidParameterException
interface
BaseViewModel
<
out
T
>
{
val
message
:
Message
val
rawData
:
T
val
messageId
:
String
val
viewType
:
Int
...
...
app/src/main/java/chat/rocket/android/chatroom/viewmodel/ImageAttachmentViewModel.kt
View file @
7dcb3b46
package
chat.rocket.android.chatroom.viewmodel
import
chat.rocket.android.R
import
chat.rocket.core.model.Message
import
chat.rocket.core.model.attachment.ImageAttachment
data class
ImageAttachmentViewModel
(
override
val
message
:
Message
,
override
val
rawData
:
ImageAttachment
,
override
val
messageId
:
String
,
override
val
attachmentUrl
:
String
,
...
...
app/src/main/java/chat/rocket/android/chatroom/viewmodel/MessageViewModel.kt
View file @
7dcb3b46
...
...
@@ -4,14 +4,14 @@ import chat.rocket.android.R
import
chat.rocket.core.model.Message
data class
MessageViewModel
(
override
val
message
:
Message
,
override
val
rawData
:
Message
,
override
val
messageId
:
String
,
override
val
avatar
:
String
,
override
val
time
:
CharSequence
,
override
val
senderName
:
CharSequence
,
override
val
content
:
CharSequence
,
override
val
isPinned
:
Boolean
,
val
isSystemMessage
:
Boolean
override
val
isPinned
:
Boolean
)
:
BaseMessageViewModel
<
Message
>
{
override
val
viewType
:
Int
get
()
=
BaseViewModel
.
ViewType
.
MESSAGE
.
viewType
...
...
app/src/main/java/chat/rocket/android/chatroom/viewmodel/UrlPreviewViewModel.kt
View file @
7dcb3b46
package
chat.rocket.android.chatroom.viewmodel
import
chat.rocket.android.R
import
chat.rocket.core.model.Message
import
chat.rocket.core.model.url.Url
data class
UrlPreviewViewModel
(
override
val
message
:
Message
,
override
val
rawData
:
Url
,
override
val
messageId
:
String
,
val
title
:
CharSequence
?,
...
...
app/src/main/java/chat/rocket/android/chatroom/viewmodel/VideoAttachmentViewModel.kt
View file @
7dcb3b46
package
chat.rocket.android.chatroom.viewmodel
import
chat.rocket.android.R
import
chat.rocket.core.model.Message
import
chat.rocket.core.model.attachment.VideoAttachment
data class
VideoAttachmentViewModel
(
override
val
message
:
Message
,
override
val
rawData
:
VideoAttachment
,
override
val
messageId
:
String
,
override
val
attachmentUrl
:
String
,
...
...
app/src/main/java/chat/rocket/android/chatroom/viewmodel/ViewModelMapper.kt
View file @
7dcb3b46
...
...
@@ -19,11 +19,13 @@ import chat.rocket.core.model.Message
import
chat.rocket.core.model.MessageType
import
chat.rocket.core.model.Value
import
chat.rocket.core.model.attachment.*
import
chat.rocket.core.model.isSystemMessage
import
chat.rocket.core.model.url.Url
import
kotlinx.coroutines.experimental.CommonPool
import
kotlinx.coroutines.experimental.withContext
import
okhttp3.HttpUrl
import
timber.log.Timber
import
java.security.InvalidParameterException
import
javax.inject.Inject
class
ViewModelMapper
@Inject
constructor
(
private
val
context
:
Context
,
...
...
@@ -53,7 +55,7 @@ class ViewModelMapper @Inject constructor(private val context: Context,
return
@withContext
list
}
private
suspend
fun
translate
(
message
:
Message
):
List
<
BaseViewModel
<*
>>
=
withContext
(
CommonPool
)
{
private
suspend
fun
translate
(
message
:
Message
):
List
<
BaseViewModel
<*
>>
=
withContext
(
CommonPool
)
{
val
list
=
ArrayList
<
BaseViewModel
<*>>()
message
.
urls
?.
forEach
{
...
...
@@ -81,7 +83,7 @@ class ViewModelMapper @Inject constructor(private val context: Context,
val
title
=
url
.
meta
?.
title
val
description
=
url
.
meta
?.
description
return
UrlPreviewViewModel
(
url
,
message
.
id
,
title
,
hostname
,
description
,
thumb
)
return
UrlPreviewViewModel
(
message
,
url
,
message
.
id
,
title
,
hostname
,
description
,
thumb
)
}
private
fun
mapAttachment
(
message
:
Message
,
attachment
:
Attachment
):
BaseViewModel
<
*
>?
{
...
...
@@ -96,12 +98,12 @@ class ViewModelMapper @Inject constructor(private val context: Context,
val
attachmentTitle
=
attachment
.
title
val
id
=
"${message.id}_${attachment.titleLink}"
.
hashCode
().
toLong
()
return
when
(
attachment
)
{
is
ImageAttachment
->
ImageAttachmentViewModel
(
attachment
,
message
.
id
,
attachmentUrl
,
attachmentTitle
?:
""
,
id
)
is
VideoAttachment
->
VideoAttachmentViewModel
(
attachment
,
message
.
id
,
is
ImageAttachment
->
ImageAttachmentViewModel
(
message
,
attachment
,
message
.
id
,
attachmentUrl
,
attachmentTitle
?:
""
,
id
)
is
VideoAttachment
->
VideoAttachmentViewModel
(
message
,
attachment
,
message
.
id
,
attachmentUrl
,
attachmentTitle
?:
""
,
id
)
is
AudioAttachment
->
AudioAttachmentViewModel
(
message
,
attachment
,
message
.
id
,
attachmentUrl
,
attachmentTitle
?:
""
,
id
)
is
AudioAttachment
->
AudioAttachmentViewModel
(
attachment
,
message
.
id
,
attachmentUrl
,
attachmentTitle
?:
""
,
id
)
else
->
null
}
}
...
...
@@ -143,13 +145,16 @@ class ViewModelMapper @Inject constructor(private val context: Context,
}
val
content
=
getContent
(
context
,
message
,
quote
)
MessageViewModel
(
rawData
=
message
,
messageId
=
message
.
id
,
MessageViewModel
(
message
=
message
,
rawData
=
message
,
messageId
=
message
.
id
,
avatar
=
avatar
!!
,
time
=
time
,
senderName
=
sender
,
content
=
content
.
first
,
isPinned
=
message
.
pinned
,
isSystemMessage
=
content
.
second
)
content
=
content
,
isPinned
=
message
.
pinned
)
}
private
fun
getSenderName
(
message
:
Message
):
CharSequence
{
if
(!
message
.
senderAlias
.
isNullOrEmpty
())
{
return
message
.
senderAlias
!!
}
val
username
=
message
.
sender
?.
username
val
realName
=
message
.
sender
?.
name
val
senderName
=
if
(
settings
.
useRealName
())
realName
else
username
...
...
@@ -157,6 +162,10 @@ class ViewModelMapper @Inject constructor(private val context: Context,
}
private
fun
getUserAvatar
(
message
:
Message
):
String
?
{
message
.
avatar
?.
let
{
return
it
// Always give preference for overridden avatar from message
}
val
username
=
message
.
sender
?.
username
?:
"?"
return
baseUrl
?.
let
{
UrlHelper
.
getAvatarUrl
(
baseUrl
,
username
)
...
...
@@ -171,28 +180,14 @@ class ViewModelMapper @Inject constructor(private val context: Context,
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
?):
Pair
<
CharSequence
,
Boolean
>
{
var
systemMessage
=
true
val
content
=
when
(
message
.
type
)
{
//TODO: Add implementation for Welcome type.
is
MessageType
.
MessageRemoved
->
getSystemMessage
(
context
.
getString
(
R
.
string
.
message_removed
))
is
MessageType
.
UserJoined
->
getSystemMessage
(
context
.
getString
(
R
.
string
.
message_user_joined_channel
))
is
MessageType
.
UserLeft
->
getSystemMessage
(
context
.
getString
(
R
.
string
.
message_user_left
))
is
MessageType
.
UserAdded
->
getSystemMessage
(
context
.
getString
(
R
.
string
.
message_user_added_by
,
message
.
message
,
message
.
sender
?.
username
))
is
MessageType
.
RoomNameChanged
->
getSystemMessage
(
context
.
getString
(
R
.
string
.
message_room_name_changed
,
message
.
message
,
message
.
sender
?.
username
))
is
MessageType
.
UserRemoved
->
getSystemMessage
(
context
.
getString
(
R
.
string
.
message_user_removed_by
,
message
.
message
,
message
.
sender
?.
username
))
is
MessageType
.
MessagePinned
->
getSystemMessage
(
context
.
getString
(
R
.
string
.
message_pinned
))
else
->
{
systemMessage
=
false
getNormalMessage
(
message
,
quote
)
}
private
suspend
fun
getContent
(
context
:
Context
,
message
:
Message
,
quote
:
Message
?):
CharSequence
{
return
when
(
message
.
isSystemMessage
())
{
true
->
getSystemMessage
(
message
,
context
)
false
->
getNormalMessage
(
message
,
quote
)
}
return
Pair
(
content
,
systemMessage
)
}
private
suspend
fun
getNormalMessage
(
message
:
Message
,
quote
:
Message
?):
CharSequence
{
...
...
@@ -204,7 +199,32 @@ class ViewModelMapper @Inject constructor(private val context: Context,
return
parser
.
renderMarkdown
(
message
.
message
,
quoteViewModel
,
currentUsername
)
}
private
fun
getSystemMessage
(
content
:
String
):
CharSequence
{
private
fun
getSystemMessage
(
message
:
Message
,
context
:
Context
):
CharSequence
{
val
content
=
when
(
message
.
type
)
{
//TODO: Add implementation for Welcome type.
is
MessageType
.
MessageRemoved
->
context
.
getString
(
R
.
string
.
message_removed
)
is
MessageType
.
UserJoined
->
context
.
getString
(
R
.
string
.
message_user_joined_channel
)
is
MessageType
.
UserLeft
->
context
.
getString
(
R
.
string
.
message_user_left
)
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
.
UserRemoved
->
context
.
getString
(
R
.
string
.
message_user_removed_by
,
message
.
message
,
message
.
sender
?.
username
)
is
MessageType
.
MessagePinned
->
{
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
->
{
throw
InvalidParameterException
(
"Invalid message type: ${message.type}"
)
}
}
//isSystemMessage = true
val
spannableMsg
=
SpannableStringBuilder
(
content
)
spannableMsg
.
setSpan
(
StyleSpan
(
Typeface
.
ITALIC
),
0
,
spannableMsg
.
length
,
...
...
app/src/main/java/chat/rocket/android/chatrooms/presentation/ChatRoomsPresenter.kt
View file @
7dcb3b46
package
chat.rocket.android.chatrooms.presentation
import
chat.rocket.android.core.lifecycle.CancelStrategy
import
chat.rocket.android.server.domain.
GetChatRoomsInteractor
import
chat.rocket.android.server.
domain.GetCurrentServerInteracto
r
import
chat.rocket.android.server.
domain.RefreshSettingsInteractor
import
chat.rocket.android.server.
domain.SaveChatRoomsInteractor
import
chat.rocket.android.server.infraestructure.
RocketChatClientFactory
import
chat.rocket.android.server.domain.
*
import
chat.rocket.android.server.
infraestructure.ConnectionManage
r
import
chat.rocket.android.server.
infraestructure.ConnectionManagerFactory
import
chat.rocket.android.server.
infraestructure.chatRooms
import
chat.rocket.android.server.infraestructure.
state
import
chat.rocket.android.util.extensions.launchUI
import
chat.rocket.common.RocketChatException
import
chat.rocket.core.RocketChatClient
import
chat.rocket.common.model.BaseRoom
import
chat.rocket.common.model.RoomType
import
chat.rocket.core.internal.model.Subscription
import
chat.rocket.core.internal.realtime.*
import
chat.rocket.core.internal.rest.chatRooms
import
chat.rocket.core.internal.realtime.State
import
chat.rocket.core.internal.realtime.StreamMessage
import
chat.rocket.core.internal.realtime.Type
import
chat.rocket.core.model.ChatRoom
import
chat.rocket.core.model.Room
import
kotlinx.coroutines.experimental.*
import
kotlinx.coroutines.experimental.android.UI
import
kotlinx.coroutines.experimental.channels.Channel
import
timber.log.Timber
import
javax.inject.Inject
...
...
@@ -26,31 +29,47 @@ class ChatRoomsPresenter @Inject constructor(private val view: ChatRoomsView,
private
val
getChatRoomsInteractor
:
GetChatRoomsInteractor
,
private
val
saveChatRoomsInteractor
:
SaveChatRoomsInteractor
,
private
val
refreshSettingsInteractor
:
RefreshSettingsInteractor
,
factory
:
RocketChatClientFactory
)
{
private
val
client
:
RocketChatClient
=
factory
.
create
(
serverInteractor
.
get
()
!!
)
settingsRepository
:
SettingsRepository
,
factory
:
ConnectionManagerFactory
)
{
private
val
manager
:
ConnectionManager
=
factory
.
create
(
serverInteractor
.
get
()
!!
)
private
val
currentServer
=
serverInteractor
.
get
()
!!
private
var
reloadJob
:
Deferred
<
List
<
ChatRoom
>>?
=
null
private
val
settings
=
settingsRepository
.
get
(
currentServer
)
!!
private
val
subscriptionsChannel
=
Channel
<
StreamMessage
<
BaseRoom
>>()
private
val
stateChannel
=
Channel
<
State
>()
private
var
lastState
=
manager
.
state
fun
loadChatRooms
()
{
refreshSettingsInteractor
.
refreshAsync
(
currentServer
)
launchUI
(
strategy
)
{
view
.
showLoading
()
subscribeStatusChange
()
try
{
view
.
updateChatRooms
(
loadRooms
())
subscribeRoomUpdates
()
}
catch
(
e
:
RocketChatException
)
{
Timber
.
e
(
e
)
view
.
showMessage
(
e
.
message
!!
)
}
finally
{
view
.
hideLoading
()
}
subscribeRoomUpdates
()
}
}
fun
loadChatRoom
(
chatRoom
:
ChatRoom
)
=
navigator
.
toChatRoom
(
chatRoom
.
id
,
chatRoom
.
name
,
chatRoom
.
type
.
toString
(),
chatRoom
.
readonly
?:
false
)
fun
loadChatRoom
(
chatRoom
:
ChatRoom
)
{
val
roomName
=
if
(
chatRoom
.
type
is
RoomType
.
DirectMessage
&&
chatRoom
.
fullName
!=
null
&&
settings
.
useRealName
())
{
chatRoom
.
fullName
!!
}
else
{
chatRoom
.
name
}
navigator
.
toChatRoom
(
chatRoom
.
id
,
roomName
,
chatRoom
.
type
.
toString
(),
chatRoom
.
readonly
?:
false
)
}
/**
* Gets a [ChatRoom] list from local repository.
...
...
@@ -65,8 +84,9 @@ class ChatRoomsPresenter @Inject constructor(private val view: ChatRoomsView,
}
private
suspend
fun
loadRooms
():
List
<
ChatRoom
>
{
val
chatRooms
=
client
.
chatRooms
().
update
val
chatRooms
=
manager
.
chatRooms
().
update
val
sortedRooms
=
sortRooms
(
chatRooms
)
Timber
.
d
(
"Loaded rooms: ${sortedRooms.size}"
)
saveChatRoomsInteractor
.
save
(
currentServer
,
sortedRooms
)
return
sortedRooms
}
...
...
@@ -77,6 +97,7 @@ class ChatRoomsPresenter @Inject constructor(private val view: ChatRoomsView,
}
private
fun
updateRooms
()
{
Timber
.
d
(
"Updating Rooms"
)
launch
{
view
.
updateChatRooms
(
getChatRoomsInteractor
.
get
(
currentServer
))
}
...
...
@@ -92,104 +113,98 @@ class ChatRoomsPresenter @Inject constructor(private val view: ChatRoomsView,
}
}
// TODO - Temporary stuff, remove when adding DB support
private
suspend
fun
subscribeRoomUpdates
()
{
client
.
addStateChannel
(
stateChannel
)
private
suspend
fun
subscribeStatusChange
()
{
lastState
=
manager
.
state
launch
(
CommonPool
+
strategy
.
jobs
)
{
for
(
status
in
stateChannel
)
{
Timber
.
d
(
"Changing status to: $status"
)
when
(
status
)
{
State
.
Authenticating
->
Timber
.
d
(
"Authenticating"
)
State
.
Connected
->
{
Timber
.
d
(
"Connected"
)
client
.
subscribeSubscriptions
{
Timber
.
d
(
"subscriptions: $it"
)
}
client
.
subscribeRooms
{
Timber
.
d
(
"rooms: $it"
)
}
for
(
state
in
stateChannel
)
{
Timber
.
d
(
"Got new state: $state - last: $lastState"
)
if
(
state
!=
lastState
)
{
launch
(
UI
)
{
view
.
showConnectionState
(
state
)
}
}
}
Timber
.
d
(
"Done on statusChannel"
)
}
when
(
client
.
state
)
{
State
.
Connected
->
{
Timber
.
d
(
"Already connected"
)
}
else
->
client
.
connect
()
}
launch
(
CommonPool
+
strategy
.
jobs
)
{
for
(
message
in
client
.
roomsChannel
)
{
Timber
.
d
(
"Got message: $message"
)
updateRoom
(
message
)
if
(
state
is
State
.
Connected
)
{
reloadRooms
()
updateRooms
()
}
}
lastState
=
state
}
}
}
// TODO - Temporary stuff, remove when adding DB support
private
suspend
fun
subscribeRoomUpdates
()
{
manager
.
addStatusChannel
(
stateChannel
)
manager
.
addRoomsAndSubscriptionsChannel
(
subscriptionsChannel
)
launch
(
CommonPool
+
strategy
.
jobs
)
{
for
(
message
in
client
.
subscriptionsChannel
)
{
for
(
message
in
subscriptionsChannel
)
{
Timber
.
d
(
"Got message: $message"
)
updateSubscription
(
message
)
when
(
message
.
data
)
{
is
Room
->
updateRoom
(
message
as
StreamMessage
<
Room
>)
is
Subscription
->
updateSubscription
(
message
as
StreamMessage
<
Subscription
>)
}
}
}
}
private
fun
updateRoom
(
message
:
StreamMessage
<
Room
>)
{
launchUI
(
strategy
)
{
when
(
message
.
type
)
{
Type
.
Removed
->
{
removeRoom
(
message
.
data
.
id
)
}
Type
.
Updated
->
{
updateRoom
(
message
.
data
)
}
Type
.
Inserted
->
{
// On insertion, just get all chatrooms again, since we can't create one just
// from a Room
reloadRooms
()
}
private
suspend
fun
updateRoom
(
message
:
StreamMessage
<
Room
>)
{
Timber
.
d
(
"Update Room: ${message.type} - ${message.data.id} - ${message.data.name}"
)
when
(
message
.
type
)
{
Type
.
Removed
->
{
removeRoom
(
message
.
data
.
id
)
}
Type
.
Updated
->
{
updateRoom
(
message
.
data
)
}
Type
.
Inserted
->
{
// On insertion, just get all chatrooms again, since we can't create one just
// from a Room
reloadRooms
()
}
updateRooms
()
}
updateRooms
()
}
private
fun
updateSubscription
(
message
:
StreamMessage
<
Subscription
>)
{
launchUI
(
strategy
)
{
when
(
message
.
type
)
{
Type
.
Removed
->
{
removeRoom
(
message
.
data
.
roomId
)
}
Type
.
Updated
->
{
updateSubscription
(
message
.
data
)
}
Type
.
Inserted
->
{
// On insertion, just get all chatrooms again, since we can't create one just
// from a Subscription
reloadRooms
()
}
private
suspend
fun
updateSubscription
(
message
:
StreamMessage
<
Subscription
>)
{
Timber
.
d
(
"Update Subscription: ${message.type} - ${message.data.id} - ${message.data.name}"
)
when
(
message
.
type
)
{
Type
.
Removed
->
{
removeRoom
(
message
.
data
.
roomId
)
}
Type
.
Updated
->
{
updateSubscription
(
message
.
data
)
}
Type
.
Inserted
->
{
// On insertion, just get all chatrooms again, since we can't create one just
// from a Subscription
reloadRooms
()
}
updateRooms
()
}
updateRooms
()
}
private
suspend
fun
reloadRooms
()
{
Timber
.
d
(
"realoadRooms()"
)
reloadJob
?.
cancel
()
reloadJob
=
async
(
CommonPool
+
strategy
.
jobs
)
{
delay
(
1000
)
Timber
.
d
(
"reloading rooms after wait"
)
loadRooms
()
try
{
reloadJob
=
async
(
CommonPool
+
strategy
.
jobs
)
{
delay
(
1000
)
Timber
.
d
(
"reloading rooms after wait"
)
loadRooms
()
}
reloadJob
?.
await
()
}
catch
(
ex
:
Exception
)
{
ex
.
printStackTrace
()
}
reloadJob
?.
await
()
}
// Update a ChatRoom with a Room information
private
fun
updateRoom
(
room
:
Room
)
{
Timber
.
d
(
"Updating Room: ${room.id} - ${room.name}"
)
val
chatRooms
=
getChatRoomsInteractor
.
get
(
currentServer
).
toMutableList
()
val
chatRoom
=
chatRooms
.
find
{
chatRoom
->
chatRoom
.
id
==
room
.
id
}
chatRoom
?.
apply
{
...
...
@@ -220,6 +235,7 @@ class ChatRoomsPresenter @Inject constructor(private val view: ChatRoomsView,
// Update a ChatRoom with a Subscription information
private
fun
updateSubscription
(
subscription
:
Subscription
)
{
Timber
.
d
(
"Updating subscrition: ${subscription.id} - ${subscription.name}"
)
val
chatRooms
=
getChatRoomsInteractor
.
get
(
currentServer
).
toMutableList
()
val
chatRoom
=
chatRooms
.
find
{
chatRoom
->
chatRoom
.
id
==
subscription
.
roomId
}
chatRoom
?.
apply
{
...
...
@@ -251,6 +267,7 @@ class ChatRoomsPresenter @Inject constructor(private val view: ChatRoomsView,
private
fun
removeRoom
(
id
:
String
,
chatRooms
:
MutableList
<
ChatRoom
>
=
getChatRoomsInteractor
.
get
(
currentServer
).
toMutableList
())
{
Timber
.
d
(
"Removing ROOM: $id"
)
synchronized
(
this
)
{
chatRooms
.
removeAll
{
chatRoom
->
chatRoom
.
id
==
id
}
}
...
...
@@ -258,7 +275,7 @@ class ChatRoomsPresenter @Inject constructor(private val view: ChatRoomsView,
}
fun
disconnect
()
{
client
.
removeState
Channel
(
stateChannel
)
client
.
disconnect
(
)
manager
.
removeStatus
Channel
(
stateChannel
)
manager
.
removeRoomsAndSubscriptionsChannel
(
subscriptionsChannel
)
}
}
\ No newline at end of file
app/src/main/java/chat/rocket/android/chatrooms/presentation/ChatRoomsView.kt
View file @
7dcb3b46
...
...
@@ -2,6 +2,7 @@ package chat.rocket.android.chatrooms.presentation
import
chat.rocket.android.core.behaviours.LoadingView
import
chat.rocket.android.core.behaviours.MessageView
import
chat.rocket.core.internal.realtime.State
import
chat.rocket.core.model.ChatRoom
interface
ChatRoomsView
:
LoadingView
,
MessageView
{
...
...
@@ -17,4 +18,6 @@ interface ChatRoomsView : LoadingView, MessageView {
* Shows no chat rooms to display.
*/
fun
showNoChatRoomsToDisplay
()
fun
showConnectionState
(
state
:
State
)
}
\ No newline at end of file
app/src/main/java/chat/rocket/android/chatrooms/ui/ChatRoomsAdapter.kt
View file @
7dcb3b46
...
...
@@ -9,6 +9,8 @@ import android.view.ViewGroup
import
android.widget.TextView
import
chat.rocket.android.R
import
chat.rocket.android.helper.UrlHelper
import
chat.rocket.android.server.domain.PublicSettings
import
chat.rocket.android.server.domain.useRealName
import
chat.rocket.android.util.extensions.content
import
chat.rocket.android.util.extensions.inflate
import
chat.rocket.android.util.extensions.setVisible
...
...
@@ -21,6 +23,7 @@ import kotlinx.android.synthetic.main.item_chat.view.*
import
kotlinx.android.synthetic.main.unread_messages_badge.view.*
class
ChatRoomsAdapter
(
private
val
context
:
Context
,
private
val
settings
:
PublicSettings
,
private
val
listener
:
(
ChatRoom
)
->
Unit
)
:
RecyclerView
.
Adapter
<
ChatRoomsAdapter
.
ViewHolder
>()
{
var
dataSet
:
MutableList
<
ChatRoom
>
=
ArrayList
()
...
...
@@ -69,7 +72,11 @@ class ChatRoomsAdapter(private val context: Context,
}
private
fun
bindName
(
chatRoom
:
ChatRoom
,
textView
:
TextView
)
{
textView
.
content
=
chatRoom
.
name
if
(
chatRoom
.
type
is
RoomType
.
DirectMessage
&&
settings
.
useRealName
())
{
textView
.
content
=
chatRoom
.
fullName
?:
chatRoom
.
name
}
else
{
textView
.
content
=
chatRoom
.
name
}
}
private
fun
bindLastMessageDateTime
(
chatRoom
:
ChatRoom
,
textView
:
TextView
)
{
...
...
app/src/main/java/chat/rocket/android/chatrooms/ui/ChatRoomsFragment.kt
View file @
7dcb3b46
package
chat.rocket.android.chatrooms.ui
import
android.os.Bundle
import
android.os.Handler
import
android.support.v4.app.Fragment
import
android.support.v7.app.AppCompatActivity
import
android.support.v7.util.DiffUtil
...
...
@@ -11,14 +12,16 @@ import android.view.*
import
chat.rocket.android.R
import
chat.rocket.android.chatrooms.presentation.ChatRoomsPresenter
import
chat.rocket.android.chatrooms.presentation.ChatRoomsView
import
chat.rocket.android.
util.extensions.inflate
import
chat.rocket.android.
util.extensions.setVisible
import
chat.rocket.android.util.extensions.
showToast
import
chat.rocket.android.
server.domain.GetCurrentServerInteractor
import
chat.rocket.android.
server.domain.SettingsRepository
import
chat.rocket.android.util.extensions.
*
import
chat.rocket.android.widget.DividerItemDecoration
import
chat.rocket.core.internal.realtime.State
import
chat.rocket.core.model.ChatRoom
import
dagger.android.support.AndroidSupportInjection
import
kotlinx.android.synthetic.main.fragment_chat_rooms.*
import
kotlinx.coroutines.experimental.CommonPool
import
kotlinx.coroutines.experimental.Job
import
kotlinx.coroutines.experimental.android.UI
import
kotlinx.coroutines.experimental.async
import
kotlinx.coroutines.experimental.launch
...
...
@@ -26,7 +29,12 @@ import javax.inject.Inject
class
ChatRoomsFragment
:
Fragment
(),
ChatRoomsView
{
@Inject
lateinit
var
presenter
:
ChatRoomsPresenter
@Inject
lateinit
var
serverInteractor
:
GetCurrentServerInteractor
@Inject
lateinit
var
settingsRepository
:
SettingsRepository
private
var
searchView
:
SearchView
?
=
null
private
val
handler
=
Handler
()
private
var
listJob
:
Job
?
=
null
companion
object
{
fun
newInstance
()
=
ChatRoomsFragment
()
...
...
@@ -39,6 +47,7 @@ class ChatRoomsFragment : Fragment(), ChatRoomsView {
}
override
fun
onDestroy
()
{
handler
.
removeCallbacks
(
dismissStatus
)
presenter
.
disconnect
()
super
.
onDestroy
()
}
...
...
@@ -72,14 +81,19 @@ class ChatRoomsFragment : Fragment(), ChatRoomsView {
override
suspend
fun
updateChatRooms
(
newDataSet
:
List
<
ChatRoom
>)
{
activity
.
apply
{
launch
(
UI
)
{
listJob
?.
cancel
()
listJob
=
launch
(
UI
)
{
val
adapter
=
recycler_view
.
adapter
as
ChatRoomsAdapter
// FIXME https://fabric.io/rocketchat3/android/apps/chat.rocket.android.dev/issues/5a90d4718cb3c2fa63b3f557?time=last-seven-days
// TODO - fix this bug to reenable DiffUtil
val
diff
=
async
(
CommonPool
)
{
DiffUtil
.
calculateDiff
(
RoomsDiffCallback
(
adapter
.
dataSet
,
newDataSet
))
}.
await
()
adapter
.
updateRooms
(
newDataSet
)
diff
.
dispatchUpdatesTo
(
adapter
)
if
(
isActive
)
{
adapter
.
updateRooms
(
newDataSet
)
diff
.
dispatchUpdatesTo
(
adapter
)
}
}
}
}
...
...
@@ -96,6 +110,28 @@ class ChatRoomsFragment : Fragment(), ChatRoomsView {
override
fun
showGenericErrorMessage
()
=
showMessage
(
getString
(
R
.
string
.
msg_generic_error
))
override
fun
showConnectionState
(
state
:
State
)
{
activity
?.
apply
{
connection_status_text
.
fadeIn
()
handler
.
removeCallbacks
(
dismissStatus
)
when
(
state
)
{
is
State
.
Connected
->
{
connection_status_text
.
text
=
getString
(
R
.
string
.
status_connected
)
handler
.
postDelayed
(
dismissStatus
,
2000
)
}
is
State
.
Disconnected
->
connection_status_text
.
text
=
getString
(
R
.
string
.
status_disconnected
)
is
State
.
Connecting
->
connection_status_text
.
text
=
getString
(
R
.
string
.
status_connecting
)
is
State
.
Authenticating
->
connection_status_text
.
text
=
getString
(
R
.
string
.
status_authenticating
)
is
State
.
Disconnecting
->
connection_status_text
.
text
=
getString
(
R
.
string
.
status_disconnecting
)
is
State
.
Waiting
->
connection_status_text
.
text
=
getString
(
R
.
string
.
status_waiting
,
state
.
seconds
)
}
}
}
private
val
dismissStatus
=
{
connection_status_text
.
fadeOut
()
}
private
fun
setupToolbar
()
{
(
activity
as
AppCompatActivity
).
supportActionBar
?.
title
=
getString
(
R
.
string
.
title_chats
)
}
...
...
@@ -107,7 +143,9 @@ class ChatRoomsFragment : Fragment(), ChatRoomsView {
resources
.
getDimensionPixelSize
(
R
.
dimen
.
divider_item_decorator_bound_start
),
resources
.
getDimensionPixelSize
(
R
.
dimen
.
divider_item_decorator_bound_end
)))
recycler_view
.
itemAnimator
=
DefaultItemAnimator
()
recycler_view
.
adapter
=
ChatRoomsAdapter
(
this
)
{
chatRoom
->
// TODO - use a ViewModel Mapper instead of using settings on the adapter
recycler_view
.
adapter
=
ChatRoomsAdapter
(
this
,
settingsRepository
.
get
(
serverInteractor
.
get
()
!!
)
!!
)
{
chatRoom
->
presenter
.
loadChatRoom
(
chatRoom
)
}
}
...
...
app/src/main/java/chat/rocket/android/helper/MessageParser.kt
View file @
7dcb3b46
...
...
@@ -13,11 +13,15 @@ import android.support.v4.content.res.ResourcesCompat
import
android.text.Layout
import
android.text.Spannable
import
android.text.Spanned
import
android.text.TextUtils
import
android.text.style.*
import
android.util.Patterns
import
android.view.View
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.EmojiRepository
import
chat.rocket.android.widget.emoji.EmojiTypefaceSpan
import
org.commonmark.node.AbstractVisitor
import
org.commonmark.node.BlockQuote
import
org.commonmark.node.Text
...
...
@@ -47,7 +51,7 @@ class MessageParser @Inject constructor(val context: Application, private val co
*/
fun
renderMarkdown
(
text
:
String
,
quote
:
MessageViewModel
?
=
null
,
selfUsername
:
String
?
=
null
):
CharSequence
{
val
builder
=
SpannableBuilder
()
val
content
=
text
val
content
=
EmojiRepository
.
shortnameToUnicode
(
text
,
true
)
val
parentNode
=
parser
.
parse
(
toLenientMarkdown
(
content
))
parentNode
.
accept
(
QuoteMessageBodyVisitor
(
context
,
configuration
,
builder
))
quote
?.
apply
{
...
...
@@ -58,6 +62,7 @@ class MessageParser @Inject constructor(val context: Application, private val co
quoteNode
.
accept
(
QuoteMessageBodyVisitor
(
context
,
configuration
,
builder
))
}
parentNode
.
accept
(
LinkVisitor
(
builder
))
parentNode
.
accept
(
EmojiVisitor
(
builder
))
val
result
=
builder
.
text
()
applySpans
(
result
,
selfUsername
)
...
...
@@ -139,6 +144,19 @@ class MessageParser @Inject constructor(val context: Application, private val co
}
}
class
EmojiVisitor
(
private
val
builder
:
SpannableBuilder
)
:
AbstractVisitor
()
{
override
fun
visit
(
text
:
Text
)
{
val
spannable
=
EmojiParser
.
parse
(
text
.
literal
)
if
(
spannable
is
Spanned
)
{
val
spans
=
spannable
.
getSpans
(
0
,
spannable
.
length
,
EmojiTypefaceSpan
::
class
.
java
)
spans
.
forEach
{
builder
.
setSpan
(
it
,
spannable
.
getSpanStart
(
it
),
spannable
.
getSpanEnd
(
it
),
0
)
}
}
visitChildren
(
text
)
}
}
class
LinkVisitor
(
private
val
builder
:
SpannableBuilder
)
:
AbstractVisitor
()
{
override
fun
visit
(
text
:
Text
)
{
...
...
app/src/main/java/chat/rocket/android/main/presentation/MainPresenter.kt
View file @
7dcb3b46
...
...
@@ -2,6 +2,7 @@ package chat.rocket.android.main.presentation
import
chat.rocket.android.infrastructure.LocalRepository
import
chat.rocket.android.server.domain.GetCurrentServerInteractor
import
chat.rocket.android.server.infraestructure.ConnectionManagerFactory
import
chat.rocket.android.server.infraestructure.RocketChatClientFactory
import
chat.rocket.common.RocketChatException
import
chat.rocket.core.RocketChatClient
...
...
@@ -13,9 +14,11 @@ import javax.inject.Inject
class
MainPresenter
@Inject
constructor
(
private
val
navigator
:
MainNavigator
,
private
val
serverInteractor
:
GetCurrentServerInteractor
,
private
val
localRepository
:
LocalRepository
,
managerFactory
:
ConnectionManagerFactory
,
factory
:
RocketChatClientFactory
)
{
private
val
client
:
RocketChatClient
=
factory
.
create
(
serverInteractor
.
get
()
!!
)
private
val
currentServer
=
serverInteractor
.
get
()
!!
private
val
manager
=
managerFactory
.
create
(
currentServer
)
private
val
client
:
RocketChatClient
=
factory
.
create
(
currentServer
)
fun
toChatList
()
=
navigator
.
toChatList
()
...
...
@@ -49,4 +52,12 @@ class MainPresenter @Inject constructor(private val navigator: MainNavigator,
}
localRepository
.
clearAllFromServer
(
currentServer
)
}
fun
connect
()
{
manager
.
connect
()
}
fun
disconnect
()
{
manager
.
disconnect
()
}
}
\ No newline at end of file
app/src/main/java/chat/rocket/android/main/ui/MainActivity.kt
View file @
7dcb3b46
...
...
@@ -29,6 +29,7 @@ class MainActivity : AppCompatActivity(), MainView, HasSupportFragmentInjector {
super
.
onCreate
(
savedInstanceState
)
setContentView
(
R
.
layout
.
activity_main
)
presenter
.
connect
()
setupToolbar
()
setupNavigationView
()
}
...
...
@@ -41,6 +42,13 @@ class MainActivity : AppCompatActivity(), MainView, HasSupportFragmentInjector {
}
}
override
fun
onDestroy
()
{
super
.
onDestroy
()
if
(
isFinishing
)
{
presenter
.
disconnect
()
}
}
override
fun
onLogout
()
{
finish
()
val
intent
=
Intent
(
this
,
AuthenticationActivity
::
class
.
java
)
...
...
app/src/main/java/chat/rocket/android/server/domain/SettingsRepository.kt
View file @
7dcb3b46
...
...
@@ -48,30 +48,30 @@ const val ALLOW_MESSAGE_PINNING = "Message_AllowPinning"
* If you need to access a Setting, add a const val key above, add it to the filter on
* ServerPresenter.kt and a extension function to access it
*/
fun
Map
<
String
,
Value
<
Any
>>
.
googleEnabled
():
Boolean
=
this
[
ACCOUNT_GOOGLE
]
?.
value
==
true
fun
Map
<
String
,
Value
<
Any
>>
.
facebookEnabled
():
Boolean
=
this
[
ACCOUNT_FACEBOOK
]
?.
value
==
true
fun
Map
<
String
,
Value
<
Any
>>
.
githubEnabled
():
Boolean
=
this
[
ACCOUNT_GITHUB
]
?.
value
==
true
fun
Map
<
String
,
Value
<
Any
>>
.
linkedinEnabled
():
Boolean
=
this
[
ACCOUNT_LINKEDIN
]
?.
value
==
true
fun
Map
<
String
,
Value
<
Any
>>
.
meteorEnabled
():
Boolean
=
this
[
ACCOUNT_METEOR
]
?.
value
==
true
fun
Map
<
String
,
Value
<
Any
>>
.
twitterEnabled
():
Boolean
=
this
[
ACCOUNT_TWITTER
]
?.
value
==
true
fun
Map
<
String
,
Value
<
Any
>>
.
gitlabEnabled
():
Boolean
=
this
[
ACCOUNT_GITLAB
]
?.
value
==
true
fun
Map
<
String
,
Value
<
Any
>>
.
wordpressEnabled
():
Boolean
=
this
[
ACCOUNT_WORDPRESS
]
?.
value
==
true
fun
PublicSettings
.
googleEnabled
():
Boolean
=
this
[
ACCOUNT_GOOGLE
]
?.
value
==
true
fun
PublicSettings
.
facebookEnabled
():
Boolean
=
this
[
ACCOUNT_FACEBOOK
]
?.
value
==
true
fun
PublicSettings
.
githubEnabled
():
Boolean
=
this
[
ACCOUNT_GITHUB
]
?.
value
==
true
fun
PublicSettings
.
linkedinEnabled
():
Boolean
=
this
[
ACCOUNT_LINKEDIN
]
?.
value
==
true
fun
PublicSettings
.
meteorEnabled
():
Boolean
=
this
[
ACCOUNT_METEOR
]
?.
value
==
true
fun
PublicSettings
.
twitterEnabled
():
Boolean
=
this
[
ACCOUNT_TWITTER
]
?.
value
==
true
fun
PublicSettings
.
gitlabEnabled
():
Boolean
=
this
[
ACCOUNT_GITLAB
]
?.
value
==
true
fun
PublicSettings
.
wordpressEnabled
():
Boolean
=
this
[
ACCOUNT_WORDPRESS
]
?.
value
==
true
fun
Map
<
String
,
Value
<
Any
>>
.
useRealName
():
Boolean
=
this
[
USE_REALNAME
]
?.
value
==
true
fun
PublicSettings
.
useRealName
():
Boolean
=
this
[
USE_REALNAME
]
?.
value
==
true
// Message settings
fun
Map
<
String
,
Value
<
Any
>>
.
showDeletedStatus
():
Boolean
=
this
[
SHOW_DELETED_STATUS
]
?.
value
==
true
fun
Map
<
String
,
Value
<
Any
>>
.
showEditedStatus
():
Boolean
=
this
[
SHOW_EDITED_STATUS
]
?.
value
==
true
fun
Map
<
String
,
Value
<
Any
>>
.
allowedMessagePinning
():
Boolean
=
this
[
ALLOW_MESSAGE_PINNING
]
?.
value
==
true
fun
Map
<
String
,
Value
<
Any
>>
.
allowedMessageEditing
():
Boolean
=
this
[
ALLOW_MESSAGE_EDITING
]
?.
value
==
true
fun
Map
<
String
,
Value
<
Any
>>
.
allowedMessageDeleting
():
Boolean
=
this
[
ALLOW_MESSAGE_DELETING
]
?.
value
==
true
fun
PublicSettings
.
showDeletedStatus
():
Boolean
=
this
[
SHOW_DELETED_STATUS
]
?.
value
==
true
fun
PublicSettings
.
showEditedStatus
():
Boolean
=
this
[
SHOW_EDITED_STATUS
]
?.
value
==
true
fun
PublicSettings
.
allowedMessagePinning
():
Boolean
=
this
[
ALLOW_MESSAGE_PINNING
]
?.
value
==
true
fun
PublicSettings
.
allowedMessageEditing
():
Boolean
=
this
[
ALLOW_MESSAGE_EDITING
]
?.
value
==
true
fun
PublicSettings
.
allowedMessageDeleting
():
Boolean
=
this
[
ALLOW_MESSAGE_DELETING
]
?.
value
==
true
fun
Map
<
String
,
Value
<
Any
>>
.
registrationEnabled
():
Boolean
{
fun
PublicSettings
.
registrationEnabled
():
Boolean
{
val
value
=
this
[
ACCOUNT_REGISTRATION
]
return
value
?.
value
==
"Public"
}
fun
Map
<
String
,
Value
<
Any
>>
.
uploadMimeTypeFilter
():
Array
<
String
>
{
fun
PublicSettings
.
uploadMimeTypeFilter
():
Array
<
String
>
{
val
values
=
this
[
UPLOAD_WHITELIST_MIMETYPES
]
?.
value
values
?.
let
{
it
as
String
}
?.
split
(
","
)
?.
let
{
return
it
.
mapToTypedArray
{
it
.
trim
()
}
...
...
@@ -80,8 +80,8 @@ fun Map<String, Value<Any>>.uploadMimeTypeFilter(): Array<String> {
return
arrayOf
(
"*/*"
)
}
fun
Map
<
String
,
Value
<
Any
>>
.
uploadMaxFileSize
():
Int
{
fun
PublicSettings
.
uploadMaxFileSize
():
Int
{
return
this
[
UPLOAD_MAX_FILE_SIZE
]
?.
value
?.
let
{
it
as
Int
}
?:
Int
.
MAX_VALUE
}
fun
Map
<
String
,
Value
<
Any
>>.
baseUrl
()
:
String
?
=
this
[
SITE_URL
]
?.
value
as
String
\ No newline at end of file
fun
PublicSettings
.
baseUrl
():
String
?
=
this
[
SITE_URL
]
?.
value
as
String
\ No newline at end of file
app/src/main/java/chat/rocket/android/server/infraestructure/ConnectionManager.kt
0 → 100644
View file @
7dcb3b46
package
chat.rocket.android.server.infraestructure
import
chat.rocket.common.model.BaseRoom
import
chat.rocket.core.RocketChatClient
import
chat.rocket.core.internal.realtime.*
import
chat.rocket.core.internal.rest.chatRooms
import
chat.rocket.core.model.Message
import
kotlinx.coroutines.experimental.Job
import
kotlinx.coroutines.experimental.channels.Channel
import
kotlinx.coroutines.experimental.launch
import
timber.log.Timber
class
ConnectionManager
(
internal
val
client
:
RocketChatClient
)
{
private
val
statusChannelList
=
ArrayList
<
Channel
<
State
>>()
private
val
statusChannel
=
Channel
<
State
>()
private
var
connectJob
:
Job
?
=
null
private
val
roomAndSubscriptionChannels
=
ArrayList
<
Channel
<
StreamMessage
<
BaseRoom
>>>()
private
val
roomMessagesChannels
=
LinkedHashMap
<
String
,
Channel
<
Message
>>()
private
val
subscriptionIdMap
=
HashMap
<
String
,
String
>()
private
var
subscriptionId
:
String
?
=
null
private
var
roomsId
:
String
?
=
null
fun
connect
()
{
if
(
connectJob
?.
isActive
==
true
&&
(
state
!
is
State
.
Disconnected
))
{
Timber
.
d
(
"Already connected, just returning..."
)
return
}
// cleanup first
client
.
removeStateChannel
(
statusChannel
)
client
.
disconnect
()
connectJob
?.
cancel
()
// Connect and setup
client
.
addStateChannel
(
statusChannel
)
connectJob
=
launch
{
for
(
status
in
statusChannel
)
{
Timber
.
d
(
"Changing status to: $status"
)
when
(
status
)
{
is
State
.
Connected
->
{
client
.
subscribeSubscriptions
{
_
,
id
->
Timber
.
d
(
"Subscribed to subscriptions: $id"
)
subscriptionId
=
id
}
client
.
subscribeRooms
{
_
,
id
->
Timber
.
d
(
"Subscribed to rooms: $id"
)
roomsId
=
id
}
resubscribeRooms
()
}
is
State
.
Waiting
->
{
Timber
.
d
(
"Connection in: ${status.seconds}"
)
}
}
for
(
channel
in
statusChannelList
)
{
channel
.
send
(
status
)
}
}
}
launch
(
parent
=
connectJob
)
{
for
(
room
in
client
.
roomsChannel
)
{
Timber
.
d
(
"GOT Room streamed"
)
for
(
channel
in
roomAndSubscriptionChannels
)
{
channel
.
send
(
room
)
}
}
}
launch
(
parent
=
connectJob
)
{
for
(
subscription
in
client
.
subscriptionsChannel
)
{
Timber
.
d
(
"GOT Subscription streamed"
)
for
(
channel
in
roomAndSubscriptionChannels
)
{
channel
.
send
(
subscription
)
}
}
}
launch
(
parent
=
connectJob
)
{
for
(
message
in
client
.
messagesChannel
)
{
Timber
.
d
(
"Received new Message for room ${message.roomId}"
)
val
channel
=
roomMessagesChannels
[
message
.
roomId
]
channel
?.
send
(
message
)
}
}
client
.
connect
()
// Broadcast initial state...
val
state
=
client
.
state
for
(
channel
in
statusChannelList
)
{
channel
.
offer
(
state
)
}
}
private
fun
resubscribeRooms
()
{
roomMessagesChannels
.
toList
().
map
{
(
roomId
,
channel
)
->
client
.
subscribeRoomMessages
(
roomId
)
{
_
,
id
->
Timber
.
d
(
"Subscribed to $roomId: $id"
)
subscriptionIdMap
[
roomId
]
=
id
}
}
}
fun
disconnect
()
{
Timber
.
d
(
"ConnectionManager DISCONNECT"
)
client
.
removeStateChannel
(
statusChannel
)
client
.
disconnect
()
connectJob
?.
cancel
()
}
fun
addStatusChannel
(
channel
:
Channel
<
State
>)
=
statusChannelList
.
add
(
channel
)
fun
removeStatusChannel
(
channel
:
Channel
<
State
>)
=
statusChannelList
.
remove
(
channel
)
fun
addRoomsAndSubscriptionsChannel
(
channel
:
Channel
<
StreamMessage
<
BaseRoom
>>)
=
roomAndSubscriptionChannels
.
add
(
channel
)
fun
removeRoomsAndSubscriptionsChannel
(
channel
:
Channel
<
StreamMessage
<
BaseRoom
>>)
=
roomAndSubscriptionChannels
.
remove
(
channel
)
fun
subscribeRoomMessages
(
roomId
:
String
,
channel
:
Channel
<
Message
>)
{
val
oldSub
=
roomMessagesChannels
.
put
(
roomId
,
channel
)
if
(
oldSub
!=
null
)
{
Timber
.
d
(
"Room $roomId already subscribed..."
)
return
}
if
(
client
.
state
is
State
.
Connected
)
{
client
.
subscribeRoomMessages
(
roomId
)
{
_
,
id
->
Timber
.
d
(
"Subscribed to $roomId: $id"
)
subscriptionIdMap
[
roomId
]
=
id
}
}
}
fun
unsubscribeRoomMessages
(
roomId
:
String
)
{
val
sub
=
roomMessagesChannels
.
remove
(
roomId
)
if
(
sub
!=
null
)
{
val
id
=
subscriptionIdMap
.
remove
(
roomId
)
id
?.
let
{
client
.
unsubscribe
(
it
)
}
}
}
}
suspend
fun
ConnectionManager
.
chatRooms
(
timestamp
:
Long
=
0
,
filterCustom
:
Boolean
=
true
)
=
client
.
chatRooms
(
timestamp
,
filterCustom
)
val
ConnectionManager
.
state
:
State
get
()
=
client
.
state
\ No newline at end of file
app/src/main/java/chat/rocket/android/server/infraestructure/ConnectionManagerFactory.kt
0 → 100644
View file @
7dcb3b46
package
chat.rocket.android.server.infraestructure
import
timber.log.Timber
import
javax.inject.Inject
import
javax.inject.Singleton
@Singleton
class
ConnectionManagerFactory
@Inject
constructor
(
private
val
factory
:
RocketChatClientFactory
)
{
private
val
cache
=
HashMap
<
String
,
ConnectionManager
>()
fun
create
(
url
:
String
):
ConnectionManager
{
cache
[
url
]
?.
let
{
Timber
.
d
(
"Returning CACHED Manager for: $url"
)
return
it
}
Timber
.
d
(
"Returning FRESH Manager for: $url"
)
val
manager
=
ConnectionManager
(
factory
.
create
(
url
))
cache
[
url
]
=
manager
return
manager
}
}
\ No newline at end of file
app/src/main/java/chat/rocket/android/server/infraestructure/RocketChatClientFactory.kt
View file @
7dcb3b46
...
...
@@ -28,7 +28,7 @@ class RocketChatClientFactory @Inject constructor(val okHttpClient: OkHttpClient
}
Timber
.
d
(
"Returning NEW client for: $url"
)
cache
.
put
(
url
,
client
)
cache
[
url
]
=
client
return
client
}
}
\ No newline at end of file
app/src/main/java/chat/rocket/android/server/infraestructure/SharedPreferencesSettingsRepository.kt
View file @
7dcb3b46
...
...
@@ -2,19 +2,19 @@ package chat.rocket.android.server.infraestructure
import
chat.rocket.android.infrastructure.LocalRepository
import
chat.rocket.android.infrastructure.LocalRepository.Companion.SETTINGS_KEY
import
chat.rocket.android.server.domain.PublicSettings
import
chat.rocket.android.server.domain.SettingsRepository
import
chat.rocket.core.internal.SettingsAdapter
import
chat.rocket.core.model.Value
class
SharedPreferencesSettingsRepository
(
private
val
localRepository
:
LocalRepository
)
:
SettingsRepository
{
private
val
adapter
=
SettingsAdapter
().
lenient
()
override
fun
save
(
url
:
String
,
settings
:
Map
<
String
,
Value
<
Any
>>
)
{
override
fun
save
(
url
:
String
,
settings
:
PublicSettings
)
{
localRepository
.
save
(
"$SETTINGS_KEY$url"
,
adapter
.
toJson
(
settings
))
}
override
fun
get
(
url
:
String
):
Map
<
String
,
Value
<
Any
>>
?
{
override
fun
get
(
url
:
String
):
PublicSettings
?
{
val
settings
=
localRepository
.
get
(
"$SETTINGS_KEY$url"
)
settings
?.
let
{
return
adapter
.
fromJson
(
it
)
...
...
app/src/main/java/chat/rocket/android/util/extensions/Animation.kt
View file @
7dcb3b46
...
...
@@ -2,7 +2,6 @@ package chat.rocket.android.util.extensions
import
android.view.View
import
android.view.ViewAnimationUtils
import
android.view.animation.AccelerateInterpolator
import
android.view.animation.DecelerateInterpolator
fun
View
.
rotateBy
(
value
:
Float
,
duration
:
Long
=
100
)
{
...
...
@@ -12,36 +11,59 @@ fun View.rotateBy(value: Float, duration: Long = 100) {
.
start
()
}
fun
View
.
fadeIn
(
startValue
:
Float
,
finishValue
:
Float
,
duration
:
Long
=
200
)
{
animate
()
.
alpha
(
startValue
)
.
setDuration
(
duration
)
.
setInterpolator
(
DecelerateInterpolator
())
.
withEndAction
({
animate
()
.
alpha
(
finishValue
)
.
setDuration
(
duration
)
.
setInterpolator
(
AccelerateInterpolator
()).
start
()
}).
start
()
fun
View
.
fadeIn
(
start
:
Float
=
0f
,
end
:
Float
=
1f
,
duration
:
Long
=
200
)
{
// already at end alpha, just set visible and return
if
(
alpha
==
end
)
{
setVisible
(
true
)
return
}
val
animation
=
animate
()
.
alpha
(
end
)
.
setDuration
(
duration
)
.
setInterpolator
(
DecelerateInterpolator
())
if
(
start
!=
alpha
)
{
animate
()
.
alpha
(
start
)
.
setDuration
(
duration
/
2
)
// half the time, so the entire animation runs on duration
.
setInterpolator
(
DecelerateInterpolator
())
.
withEndAction
{
animation
.
setDuration
(
duration
/
2
).
start
()
}.
start
()
}
else
{
animation
.
start
()
}
setVisible
(
true
)
}
fun
View
.
fadeOut
(
startValue
:
Float
,
finishValue
:
Float
,
duration
:
Long
=
200
)
{
animate
()
.
alpha
(
startValue
)
.
setDuration
(
duration
)
.
setInterpolator
(
DecelerateInterpolator
())
.
withEndAction
({
animate
()
.
alpha
(
finishValue
)
.
setDuration
(
duration
)
.
setInterpolator
(
AccelerateInterpolator
()).
start
()
}).
start
()
setVisible
(
false
)
fun
View
.
fadeOut
(
start
:
Float
=
1f
,
end
:
Float
=
0f
,
duration
:
Long
=
200
)
{
if
(
alpha
==
end
)
{
setVisible
(
false
)
return
}
val
animation
=
animate
()
.
alpha
(
end
)
.
setDuration
(
duration
)
.
setInterpolator
(
DecelerateInterpolator
())
.
withEndAction
{
setVisible
(
false
)
}
if
(
start
!=
alpha
)
{
animate
()
.
alpha
(
start
)
.
setDuration
(
duration
/
2
)
// half the time, so the entire animation runs on duration
.
setInterpolator
(
DecelerateInterpolator
())
.
withEndAction
{
animation
.
setDuration
(
duration
/
2
).
start
()
}.
start
()
}
else
{
animation
.
start
()
}
}
fun
View
.
circularRevealOrUnreveal
(
centerX
:
Int
,
centerY
:
Int
,
startRadius
:
Float
,
endRadius
:
Float
,
duration
:
Long
=
200
)
{
val
anim
=
ViewAnimationUtils
.
createCircularReveal
(
this
,
centerX
,
centerY
,
startRadius
,
endRadius
)
anim
.
duration
=
duration
...
...
app/src/main/java/chat/rocket/android/util/extensions/Ui.kt
View file @
7dcb3b46
...
...
@@ -6,6 +6,8 @@ import android.support.annotation.LayoutRes
import
android.support.annotation.StringRes
import
android.support.v4.app.Fragment
import
android.support.v7.app.AppCompatActivity
import
android.support.v7.widget.LinearLayoutManager
import
android.support.v7.widget.RecyclerView
import
android.view.LayoutInflater
import
android.view.View
import
android.view.ViewGroup
...
...
@@ -54,4 +56,13 @@ fun Activity.showToast(message: String, duration: Int = Toast.LENGTH_SHORT) = To
fun
Fragment
.
showToast
(
@StringRes
resource
:
Int
,
duration
:
Int
=
Toast
.
LENGTH_SHORT
)
=
showToast
(
getString
(
resource
),
duration
)
fun
Fragment
.
showToast
(
message
:
String
,
duration
:
Int
=
Toast
.
LENGTH_SHORT
)
=
activity
!!
.
showToast
(
message
,
duration
)
\ No newline at end of file
fun
Fragment
.
showToast
(
message
:
String
,
duration
:
Int
=
Toast
.
LENGTH_SHORT
)
=
activity
!!
.
showToast
(
message
,
duration
)
fun
RecyclerView
.
isAtBottom
():
Boolean
{
val
manager
:
RecyclerView
.
LayoutManager
?
=
layoutManager
if
(
manager
is
LinearLayoutManager
)
{
return
manager
.
findFirstVisibleItemPosition
()
==
0
}
return
false
// or true??? we can't determine the first visible item.
}
\ No newline at end of file
app/src/main/res/layout/emoji_popup_layout.xml
View file @
7dcb3b46
...
...
@@ -60,6 +60,7 @@
android:clickable=
"true"
android:focusable=
"true"
android:padding=
"8dp"
android:visibility=
"invisible"
android:src=
"@drawable/ic_search_gray_24px"
/>
<ImageView
...
...
@@ -75,5 +76,4 @@
android:src=
"@drawable/ic_backspace_gray_24dp"
/>
</RelativeLayout>
</android.support.constraint.ConstraintLayout>
\ No newline at end of file
app/src/main/res/layout/fragment_chat_room.xml
View file @
7dcb3b46
...
...
@@ -53,4 +53,19 @@
android:layout_margin=
"5dp"
android:visibility=
"gone"
/>
<TextView
android:id=
"@+id/connection_status_text"
android:layout_width=
"match_parent"
android:layout_height=
"32dp"
android:background=
"@color/colorPrimary"
android:elevation=
"4dp"
android:textColor=
"@color/white"
android:gravity=
"center"
android:textAppearance=
"@style/TextAppearance.AppCompat.Body2"
android:visibility=
"gone"
android:alpha=
"0"
tools:alpha=
"1"
tools:visibility=
"visible"
tools:text=
"connected"
/>
</RelativeLayout>
app/src/main/res/layout/fragment_chat_rooms.xml
View file @
7dcb3b46
...
...
@@ -29,4 +29,19 @@
android:visibility=
"gone"
tools:visibility=
"visible"
/>
<TextView
android:id=
"@+id/connection_status_text"
android:layout_width=
"match_parent"
android:layout_height=
"32dp"
android:background=
"@color/colorPrimary"
android:elevation=
"4dp"
android:textColor=
"@color/white"
android:gravity=
"center"
android:textAppearance=
"@style/TextAppearance.AppCompat.Body2"
android:visibility=
"gone"
android:alpha=
"0"
tools:alpha=
"1"
tools:visibility=
"visible"
tools:text=
"connected"
/>
</RelativeLayout>
\ No newline at end of file
app/src/main/res/layout/message_attachment.xml
View file @
7dcb3b46
...
...
@@ -5,8 +5,8 @@
android:id=
"@+id/attachment_container"
android:layout_width=
"match_parent"
android:layout_height=
"wrap_content"
android:
layout_margin
Start=
"72dp"
android:
layout_margin
End=
"@dimen/screen_edge_left_and_right_margins"
android:
padding
Start=
"72dp"
android:
padding
End=
"@dimen/screen_edge_left_and_right_margins"
android:orientation=
"vertical"
>
<com.facebook.drawee.view.SimpleDraweeView
...
...
app/src/main/res/layout/message_url_preview.xml
View file @
7dcb3b46
...
...
@@ -6,8 +6,8 @@
android:id=
"@+id/url_preview_layout"
android:layout_width=
"match_parent"
android:layout_height=
"wrap_content"
android:
layout_margin
Start=
"72dp"
android:
layout_margin
End=
"24dp"
>
android:
padding
Start=
"72dp"
android:
padding
End=
"24dp"
>
<com.facebook.drawee.view.SimpleDraweeView
android:id=
"@+id/image_preview"
...
...
app/src/main/res/values-pt-rBR/strings.xml
View file @
7dcb3b46
...
...
@@ -88,4 +88,11 @@
<!-- Upload Messages -->
<string
name=
"max_file_size_exceeded"
>
Tamanho de arquivo (%1$d bytes) excedeu tamanho máximo de upload (%2$d bytes)
</string>
<!-- Socket status -->
<string
name=
"status_connected"
>
conectado
</string>
<string
name=
"status_disconnected"
>
desconetado
</string>
<string
name=
"status_connecting"
>
conectando
</string>
<string
name=
"status_authenticating"
>
autenticando
</string>
<string
name=
"status_disconnecting"
>
desconectando
</string>
<string
name=
"status_waiting"
>
conectando em %d segundos
</string>
</resources>
\ No newline at end of file
app/src/main/res/values/strings.xml
View file @
7dcb3b46
...
...
@@ -90,4 +90,12 @@
<!-- Upload Messages -->
<string
name=
"max_file_size_exceeded"
>
File size %1$d bytes exceeded max upload size of %2$d bytes
</string>
<!-- Socket status -->
<string
name=
"status_connected"
>
connected
</string>
<string
name=
"status_disconnected"
>
disconnected
</string>
<string
name=
"status_connecting"
>
connecting
</string>
<string
name=
"status_authenticating"
>
authenticating
</string>
<string
name=
"status_disconnecting"
>
disconnecting
</string>
<string
name=
"status_waiting"
>
connecting in %d seconds
</string>
</resources>
\ No newline at end of file
dependencies.gradle
View file @
7dcb3b46
...
...
@@ -11,6 +11,7 @@ ext {
// Main dependencies
support
:
'27.0.2'
,
constraintLayout
:
'1.0.2'
,
androidKtx
:
'0.1'
,
dagger
:
'2.14.1'
,
exoPlayer
:
'2.6.0'
,
playServices
:
'11.8.0'
,
...
...
@@ -49,6 +50,8 @@ ext {
constraintLayout
:
"com.android.support.constraint:constraint-layout:${versions.constraintLayout}"
,
cardView
:
"com.android.support:cardview-v7:${versions.support}"
,
androidKtx
:
"androidx.core:core-ktx:${versions.androidKtx}"
,
dagger
:
"com.google.dagger:dagger:${versions.dagger}"
,
daggerSupport
:
"com.google.dagger:dagger-android-support:${versions.dagger}"
,
daggerProcessor
:
"com.google.dagger:dagger-compiler:${versions.dagger}"
,
...
...
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