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
c1871a62
Unverified
Commit
c1871a62
authored
Mar 06, 2018
by
Lucio Maciel
Committed by
GitHub
Mar 06, 2018
Browse files
Options
Browse Files
Download
Plain Diff
Merge pull request #796 from RocketChat/release/2.0.0-beta7
[RELEASE] 2.0.0 beta 7
parents
fd036b3c
a639df30
Changes
65
Expand all
Show whitespace changes
Inline
Side-by-side
Showing
65 changed files
with
1537 additions
and
356 deletions
+1537
-356
build.gradle
app/build.gradle
+4
-2
LoginPresenter.kt
...droid/authentication/login/presentation/LoginPresenter.kt
+28
-12
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
ChatRoomFragmentModule.kt
...chat/rocket/android/chatroom/di/ChatRoomFragmentModule.kt
+5
-0
ChatRoomNavigator.kt
...rocket/android/chatroom/presentation/ChatRoomNavigator.kt
+15
-0
ChatRoomPresenter.kt
...rocket/android/chatroom/presentation/ChatRoomPresenter.kt
+72
-58
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
+26
-10
ChatRoomFragment.kt
.../java/chat/rocket/android/chatroom/ui/ChatRoomFragment.kt
+52
-4
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
+13
-10
ChatRoomsFragment.kt
...ava/chat/rocket/android/chatrooms/ui/ChatRoomsFragment.kt
+51
-8
ActivityBuilder.kt
...java/chat/rocket/android/dagger/module/ActivityBuilder.kt
+2
-1
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
MemberBottomSheetFragment.kt
...hat/rocket/android/member/ui/MemberBottomSheetFragment.kt
+80
-0
MembersAdapter.kt
...ava/chat/rocket/android/members/adapter/MembersAdapter.kt
+42
-0
MembersFragmentModule.kt
...a/chat/rocket/android/members/di/MembersFragmentModule.kt
+35
-0
MembersFragmentProvider.kt
...chat/rocket/android/members/di/MembersFragmentProvider.kt
+12
-0
MembersNavigator.kt
...t/rocket/android/members/presentation/MembersNavigator.kt
+14
-0
MembersPresenter.kt
...t/rocket/android/members/presentation/MembersPresenter.kt
+53
-0
MembersView.kt
...a/chat/rocket/android/members/presentation/MembersView.kt
+16
-0
MembersFragment.kt
...in/java/chat/rocket/android/members/ui/MembersFragment.kt
+108
-0
MemberViewModel.kt
.../chat/rocket/android/members/viewmodel/MemberViewModel.kt
+46
-0
MemberViewModelMapper.kt
...rocket/android/members/viewmodel/MemberViewModelMapper.kt
+17
-0
SettingsRepository.kt
...a/chat/rocket/android/server/domain/SettingsRepository.kt
+19
-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
+12
-2
Text.kt
...src/main/java/chat/rocket/android/util/extensions/Text.kt
+3
-0
Ui.kt
app/src/main/java/chat/rocket/android/util/extensions/Ui.kt
+12
-1
ic_gitlab.xml
app/src/main/res/drawable/ic_gitlab.xml
+72
-32
emoji_popup_layout.xml
app/src/main/res/layout/emoji_popup_layout.xml
+1
-1
fragment_authentication_server.xml
app/src/main/res/layout/fragment_authentication_server.xml
+2
-2
fragment_chat_room.xml
app/src/main/res/layout/fragment_chat_room.xml
+17
-1
fragment_chat_rooms.xml
app/src/main/res/layout/fragment_chat_rooms.xml
+15
-0
fragment_member_bottom_sheet.xml
app/src/main/res/layout/fragment_member_bottom_sheet.xml
+88
-0
fragment_members.xml
app/src/main/res/layout/fragment_members.xml
+24
-0
fragment_profile.xml
app/src/main/res/layout/fragment_profile.xml
+1
-1
item_member.xml
app/src/main/res/layout/item_member.xml
+31
-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
chatroom_actions.xml
app/src/main/res/menu/chatroom_actions.xml
+5
-0
strings.xml
app/src/main/res/values-pt-rBR/strings.xml
+13
-0
colors.xml
app/src/main/res/values/colors.xml
+2
-0
strings.xml
app/src/main/res/values/strings.xml
+14
-0
dependencies.gradle
dependencies.gradle
+3
-0
No files found.
app/build.gradle
View file @
c1871a62
...
...
@@ -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
11
versionName
"2.0.0-dev
9
"
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/authentication/login/presentation/LoginPresenter.kt
View file @
c1871a62
...
...
@@ -7,14 +7,14 @@ import chat.rocket.android.helper.NetworkHelper
import
chat.rocket.android.infrastructure.LocalRepository
import
chat.rocket.android.server.domain.*
import
chat.rocket.android.server.infraestructure.RocketChatClientFactory
import
chat.rocket.android.util.extensions.isEmailValid
import
chat.rocket.android.util.extensions.launchUI
import
chat.rocket.common.RocketChatException
import
chat.rocket.common.RocketChatTwoFactorException
import
chat.rocket.common.model.Token
import
chat.rocket.common.util.ifNull
import
chat.rocket.core.RocketChatClient
import
chat.rocket.core.internal.rest.login
import
chat.rocket.core.internal.rest.me
import
chat.rocket.core.internal.rest.registerPushToken
import
chat.rocket.core.internal.rest.*
import
javax.inject.Inject
class
LoginPresenter
@Inject
constructor
(
private
val
view
:
LoginView
,
...
...
@@ -93,15 +93,31 @@ class LoginPresenter @Inject constructor(private val view: LoginView,
view
.
showLoading
()
try
{
val
token
=
client
.
login
(
usernameOrEmail
,
password
)
var
token
:
Token
?
=
null
if
(
usernameOrEmail
.
isEmailValid
())
{
token
=
client
.
loginWithEmail
(
usernameOrEmail
,
password
)
}
else
{
val
settings
=
settingsInteractor
.
get
(
server
)
if
(
settings
!=
null
)
{
token
=
if
(
settings
.
ldapEnabled
())
{
client
.
loginWithLdap
(
usernameOrEmail
,
password
)
}
else
{
client
.
login
(
usernameOrEmail
,
password
)
}
}
else
{
navigator
.
toServerScreen
()
}
}
if
(
token
!=
null
)
{
val
me
=
client
.
me
()
multiServerRepository
.
save
(
server
,
TokenModel
(
token
.
userId
,
token
.
authToken
)
)
multiServerRepository
.
save
(
server
,
TokenModel
(
token
.
userId
,
token
.
authToken
))
localRepository
.
save
(
LocalRepository
.
USERNAME_KEY
,
me
.
username
)
registerPushToken
()
navigator
.
toChatList
()
}
else
{
view
.
showGenericErrorMessage
()
}
}
catch
(
exception
:
RocketChatException
)
{
when
(
exception
)
{
is
RocketChatTwoFactorException
->
{
...
...
app/src/main/java/chat/rocket/android/chatroom/adapter/AudioAttachmentViewHolder.kt
View file @
c1871a62
...
...
@@ -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 @
c1871a62
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 @
c1871a62
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 @
c1871a62
...
...
@@ -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 @
c1871a62
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 @
c1871a62
...
...
@@ -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 @
c1871a62
...
...
@@ -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/di/ChatRoomFragmentModule.kt
View file @
c1871a62
package
chat.rocket.android.chatroom.di
import
android.arch.lifecycle.LifecycleOwner
import
chat.rocket.android.chatroom.presentation.ChatRoomNavigator
import
chat.rocket.android.chatroom.presentation.ChatRoomView
import
chat.rocket.android.chatroom.ui.ChatRoomActivity
import
chat.rocket.android.chatroom.ui.ChatRoomFragment
import
chat.rocket.android.core.lifecycle.CancelStrategy
import
chat.rocket.android.dagger.scope.PerFragment
...
...
@@ -13,6 +15,9 @@ import kotlinx.coroutines.experimental.Job
@PerFragment
class
ChatRoomFragmentModule
{
@Provides
fun
provideChatRoomNavigator
(
activity
:
ChatRoomActivity
)
=
ChatRoomNavigator
(
activity
)
@Provides
fun
chatRoomView
(
frag
:
ChatRoomFragment
):
ChatRoomView
{
return
frag
...
...
app/src/main/java/chat/rocket/android/chatroom/presentation/ChatRoomNavigator.kt
0 → 100644
View file @
c1871a62
package
chat.rocket.android.chatroom.presentation
import
chat.rocket.android.R
import
chat.rocket.android.chatroom.ui.ChatRoomActivity
import
chat.rocket.android.members.ui.newInstance
import
chat.rocket.android.util.extensions.addFragmentBackStack
class
ChatRoomNavigator
(
internal
val
activity
:
ChatRoomActivity
)
{
fun
toMembersList
(
chatRoomId
:
String
,
chatRoomType
:
String
)
{
activity
.
addFragmentBackStack
(
"MembersFragment"
,
R
.
id
.
fragment_container
)
{
newInstance
(
chatRoomId
,
chatRoomType
)
}
}
}
\ No newline at end of file
app/src/main/java/chat/rocket/android/chatroom/presentation/ChatRoomPresenter.kt
View file @
c1871a62
...
...
@@ -6,41 +6,50 @@ 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
class
ChatRoomPresenter
@Inject
constructor
(
private
val
view
:
ChatRoomView
,
private
val
navigator
:
ChatRoomNavigator
,
private
val
strategy
:
CancelStrategy
,
getSettingsInteractor
:
GetSettingsInteractor
,
private
val
serverInteractor
:
GetCurrentServerInteractor
,
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
{
...
...
@@ -55,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
)
}
}
catch
(
ex
:
Exception
)
{
ex
.
printStackTrace
()
ex
.
message
?.
let
{
...
...
@@ -69,6 +75,10 @@ class ChatRoomPresenter @Inject constructor(private val view: ChatRoomView,
}
finally
{
view
.
hideLoading
()
}
if
(
offset
==
0L
)
{
subscribeState
()
}
}
}
...
...
@@ -131,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
)
...
...
@@ -142,55 +152,68 @@ 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
)
{
is
State
.
Authenticating
->
Timber
.
d
(
"Authenticating"
)
is
State
.
Connected
->
{
Timber
.
d
(
"Connected"
)
subId
=
client
.
subscribeRoomMessages
(
roomId
)
{
success
,
_
->
Timber
.
d
(
"subscribe messages for $roomId: $success"
)
for
(
state
in
stateChannel
)
{
Timber
.
d
(
"Got new state: $state - last: $lastState"
)
if
(
state
!=
lastState
)
{
launch
(
UI
)
{
view
.
showConnectionState
(
state
)
}
if
(
state
is
State
.
Connected
)
{
loadMissingMessages
()
}
}
lastState
=
state
}
}
Timber
.
d
(
"Done on statusChannel"
)
}
when
(
client
.
state
)
{
is
State
.
Connected
->
{
Timber
.
d
(
"Already connected"
)
subId
=
client
.
subscribeRoomMessages
(
roomId
)
{
success
,
_
->
Timber
.
d
(
"subscribe messages for $roomId: $success"
)
private
fun
subscribeMessages
(
roomId
:
String
)
{
manager
.
subscribeRoomMessages
(
roomId
,
messagesChannel
)
launch
(
CommonPool
+
strategy
.
jobs
)
{
for
(
message
in
messagesChannel
)
{
Timber
.
d
(
"New message for room ${message.roomId}"
)
updateMessage
(
message
)
}
}
else
->
client
.
connect
()
}
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
)
{
listenMessages
(
roomId
)
view
.
showNewMessage
(
models
)
}
// 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")
if
(
messages
.
result
.
size
==
50
)
{
// we loade at least count messages, try one more to fetch more messages
loadMissingMessages
()
}
}
listenMessages(roomId)
}*/
}
fun
unsubscribeMessages
()
{
launch
(
CommonPool
)
{
client
.
removeStateChannel
(
stateChannel
)
subId
?.
let
{
subscriptionId
->
client
.
unsubscribe
(
subscriptionId
)
}
}
fun
unsubscribeMessages
(
chatRoomId
:
String
)
{
manager
.
removeStatusChannel
(
stateChannel
)
manager
.
unsubscribeRoomMessages
(
chatRoomId
)
}
/**
...
...
@@ -317,16 +340,7 @@ class ChatRoomPresenter @Inject constructor(private val view: ChatRoomView,
}
}
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
)
}
}
}
fun
toMembersList
(
chatRoomId
:
String
,
chatRoomType
:
String
)
=
navigator
.
toMembersList
(
chatRoomId
,
chatRoomType
)
private
fun
updateMessage
(
streamedMessage
:
Message
)
{
launchUI
(
strategy
)
{
...
...
app/src/main/java/chat/rocket/android/chatroom/presentation/ChatRoomView.kt
View file @
c1871a62
...
...
@@ -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 @
c1871a62
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 @
c1871a62
...
...
@@ -8,9 +8,12 @@ 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
chat.rocket.common.model.RoomType
import
chat.rocket.common.model.roomTypeOf
import
dagger.android.AndroidInjection
import
dagger.android.AndroidInjector
import
dagger.android.DispatchingAndroidInjector
...
...
@@ -35,6 +38,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
...
...
@@ -45,6 +53,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"
}
...
...
@@ -57,7 +68,7 @@ class ChatRoomActivity : AppCompatActivity(), HasSupportFragmentInjector {
isChatRoomReadOnly
=
intent
.
getBooleanExtra
(
INTENT_IS_CHAT_ROOM_READ_ONLY
,
true
)
requireNotNull
(
chatRoomType
)
{
"no is_chat_room_read_only provided in Intent extras"
}
setupToolbar
(
chatRoomName
)
setupToolbar
()
addFragment
(
"ChatRoomFragment"
,
R
.
id
.
fragment_container
)
{
newInstance
(
chatRoomId
,
chatRoomName
,
chatRoomType
,
isChatRoomReadOnly
)
...
...
@@ -72,22 +83,23 @@ class ChatRoomActivity : AppCompatActivity(), HasSupportFragmentInjector {
return
fragmentDispatchingAndroidInjector
}
private
fun
setupToolbar
(
chatRoomName
:
String
)
{
private
fun
setupToolbar
()
{
setSupportActionBar
(
toolbar
)
supportActionBar
?.
setDisplayShowTitleEnabled
(
false
)
text_room_name
.
textContent
=
chatRoomName
va
r
drawable
:
Drawable
?
=
null
when
(
chatR
oomType
)
{
RoomType
.
CHANNEL
.
toString
()
->
{
drawable
=
DrawableHelper
.
getDrawableFromId
(
R
.
drawable
.
ic_room_channel
,
this
)
va
l
roomType
=
roomTypeOf
(
chatRoomType
)
val
drawable
=
when
(
r
oomType
)
{
is
RoomType
.
Channel
->
{
DrawableHelper
.
getDrawableFromId
(
R
.
drawable
.
ic_room_channel
,
this
)
}
RoomType
.
PRIVATE_GROUP
.
toString
()
->
{
drawable
=
DrawableHelper
.
getDrawableFromId
(
R
.
drawable
.
ic_room_lock
,
this
)
is
RoomType
.
PrivateGroup
->
{
DrawableHelper
.
getDrawableFromId
(
R
.
drawable
.
ic_room_lock
,
this
)
}
RoomType
.
DIRECT_MESSAGE
.
toString
()
->
{
drawable
=
DrawableHelper
.
getDrawableFromId
(
R
.
drawable
.
ic_room_dm
,
this
)
is
RoomType
.
DirectMessage
->
{
DrawableHelper
.
getDrawableFromId
(
R
.
drawable
.
ic_room_dm
,
this
)
}
else
->
null
}
drawable
?.
let
{
...
...
@@ -102,6 +114,10 @@ class ChatRoomActivity : AppCompatActivity(), HasSupportFragmentInjector {
}
}
fun
setupToolbarTitle
(
toolbarTitle
:
String
)
{
text_room_name
.
textContent
=
toolbarTitle
}
private
fun
finishActivity
()
{
super
.
onBackPressed
()
overridePendingTransition
(
R
.
anim
.
close_enter
,
R
.
anim
.
close_exit
)
...
...
app/src/main/java/chat/rocket/android/chatroom/ui/ChatRoomFragment.kt
View file @
c1871a62
...
...
@@ -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.*
...
...
@@ -98,6 +99,8 @@ class ChatRoomFragment : Fragment(), ChatRoomView, EmojiKeyboardPopup.Listener {
override
fun
onViewCreated
(
view
:
View
,
savedInstanceState
:
Bundle
?)
{
super
.
onViewCreated
(
view
,
savedInstanceState
)
setupToolbar
(
chatRoomName
)
presenter
.
loadMessages
(
chatRoomId
,
chatRoomType
)
setupRecyclerView
()
...
...
@@ -112,7 +115,7 @@ class ChatRoomFragment : Fragment(), ChatRoomView, EmojiKeyboardPopup.Listener {
}
override
fun
onDestroyView
()
{
presenter
.
unsubscribeMessages
()
presenter
.
unsubscribeMessages
(
chatRoomId
)
handler
.
removeCallbacksAndMessages
(
null
)
unsubscribeTextMessage
()
super
.
onDestroyView
()
...
...
@@ -133,6 +136,9 @@ class ChatRoomFragment : Fragment(), ChatRoomView, EmojiKeyboardPopup.Listener {
override
fun
onOptionsItemSelected
(
item
:
MenuItem
):
Boolean
{
when
(
item
.
itemId
)
{
R
.
id
.
action_members_list
->
{
presenter
.
toMembersList
(
chatRoomId
,
chatRoomType
)
}
R
.
id
.
action_pinned_messages
->
{
val
intent
=
Intent
(
activity
,
PinnedMessagesActivity
::
class
.
java
).
apply
{
putExtra
(
BUNDLE_CHAT_ROOM_ID
,
chatRoomId
)
...
...
@@ -220,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
)
}
}
}
}
...
...
@@ -247,6 +259,7 @@ class ChatRoomFragment : Fragment(), ChatRoomView, EmojiKeyboardPopup.Listener {
actionSnackbar
.
show
()
text_message
.
textContent
=
text
editingMessageId
=
messageId
KeyboardHelper
.
showSoftKeyboard
(
text_message
)
}
}
...
...
@@ -283,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
)
{
...
...
@@ -310,11 +345,21 @@ class ChatRoomFragment : Fragment(), ChatRoomView, EmojiKeyboardPopup.Listener {
text_room_is_read_only
.
setVisible
(
true
)
input_container
.
setVisible
(
false
)
}
else
{
button_send
.
alpha
=
0f
button_send
.
setVisible
(
false
)
button_show_attachment_options
.
alpha
=
1f
button_show_attachment_options
.
setVisible
(
true
)
subscribeTextMessage
()
emojiKeyboardPopup
=
EmojiKeyboardPopup
(
activity
!!
,
activity
!!
.
findViewById
(
R
.
id
.
fragment_container
))
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
()
{
...
...
@@ -390,6 +435,7 @@ class ChatRoomFragment : Fragment(), ChatRoomView, EmojiKeyboardPopup.Listener {
actionSnackbar
=
ActionSnackbar
.
make
(
message_list_container
,
parser
=
parser
)
actionSnackbar
.
cancelView
.
setOnClickListener
({
clearMessageComposition
()
KeyboardHelper
.
showSoftKeyboard
(
text_message
)
})
}
...
...
@@ -401,9 +447,7 @@ class ChatRoomFragment : Fragment(), ChatRoomView, EmojiKeyboardPopup.Listener {
}
private
fun
unsubscribeTextMessage
()
{
if
(!
compositeDisposable
.
isDisposed
)
{
compositeDisposable
.
dispose
()
}
compositeDisposable
.
clear
()
}
private
fun
setupComposeMessageButtons
(
charSequence
:
CharSequence
)
{
...
...
@@ -435,4 +479,8 @@ class ChatRoomFragment : Fragment(), ChatRoomView, EmojiKeyboardPopup.Listener {
view_dim
.
setVisible
(
false
)
}
private
fun
setupToolbar
(
toolbarTitle
:
String
)
{
(
activity
as
ChatRoomActivity
).
setupToolbarTitle
(
toolbarTitle
)
}
}
\ No newline at end of file
app/src/main/java/chat/rocket/android/chatroom/viewmodel/AudioAttachmentViewModel.kt
View file @
c1871a62
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 @
c1871a62
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 @
c1871a62
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 @
c1871a62
...
...
@@ -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 @
c1871a62
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 @
c1871a62
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 @
c1871a62
...
...
@@ -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
,
...
...
@@ -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 @
c1871a62
This diff is collapsed.
Click to expand it.
app/src/main/java/chat/rocket/android/chatrooms/presentation/ChatRoomsView.kt
View file @
c1871a62
...
...
@@ -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 @
c1871a62
...
...
@@ -11,6 +11,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
...
...
@@ -23,6 +25,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
()
...
...
@@ -73,27 +76,27 @@ class ChatRoomsAdapter(private val context: Context,
private
fun
bindName
(
chatRoom
:
ChatRoom
,
textView
:
TextView
)
{
textView
.
textContent
=
chatRoom
.
name
var
drawable
:
Drawable
?
=
null
when
(
chatRoom
.
type
)
{
var
drawable
=
when
(
chatRoom
.
type
)
{
is
RoomType
.
Channel
->
{
drawable
=
DrawableHelper
.
getDrawableFromId
(
R
.
drawable
.
ic_room_channel
,
context
)
DrawableHelper
.
getDrawableFromId
(
R
.
drawable
.
ic_room_channel
,
context
)
}
is
RoomType
.
PrivateGroup
->
{
drawable
=
DrawableHelper
.
getDrawableFromId
(
R
.
drawable
.
ic_room_lock
,
context
)
DrawableHelper
.
getDrawableFromId
(
R
.
drawable
.
ic_room_lock
,
context
)
}
is
RoomType
.
DirectMessage
->
{
drawable
=
DrawableHelper
.
getDrawableFromId
(
R
.
drawable
.
ic_room_dm
,
context
)
DrawableHelper
.
getDrawableFromId
(
R
.
drawable
.
ic_room_dm
,
context
)
}
else
->
null
}
drawable
?.
let
{
val
wrappedDrawable
=
DrawableHelper
.
wrapDrawable
(
it
)
val
mutableDrawable
=
wrappedDrawable
.
mutate
()
DrawableHelper
.
tintDrawable
(
mutableDrawable
,
context
,
when
(
chatRoom
.
alert
||
chatRoom
.
unread
>
0
)
{
val
color
=
when
(
chatRoom
.
alert
||
chatRoom
.
unread
>
0
)
{
true
->
R
.
color
.
colorPrimaryText
false
->
R
.
color
.
colorSecondaryText
})
}
DrawableHelper
.
tintDrawable
(
mutableDrawable
,
context
,
color
)
DrawableHelper
.
compoundDrawable
(
textView
,
mutableDrawable
)
}
}
...
...
app/src/main/java/chat/rocket/android/chatrooms/ui/ChatRoomsFragment.kt
View file @
c1871a62
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
()
}
...
...
@@ -53,6 +62,11 @@ class ChatRoomsFragment : Fragment(), ChatRoomsView {
presenter
.
loadChatRooms
()
}
override
fun
onDestroyView
()
{
listJob
?.
cancel
()
super
.
onDestroyView
()
}
override
fun
onCreateOptionsMenu
(
menu
:
Menu
,
inflater
:
MenuInflater
)
{
super
.
onCreateOptionsMenu
(
menu
,
inflater
)
inflater
.
inflate
(
R
.
menu
.
chatrooms
,
menu
)
...
...
@@ -71,18 +85,23 @@ class ChatRoomsFragment : Fragment(), ChatRoomsView {
}
override
suspend
fun
updateChatRooms
(
newDataSet
:
List
<
ChatRoom
>)
{
activity
.
apply
{
launch
(
UI
)
{
activity
?.
apply
{
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
()
if
(
isActive
)
{
adapter
.
updateRooms
(
newDataSet
)
diff
.
dispatchUpdatesTo
(
adapter
)
}
}
}
}
override
fun
showNoChatRoomsToDisplay
()
=
text_no_data_to_display
.
setVisible
(
true
)
...
...
@@ -96,6 +115,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 +148,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/dagger/module/ActivityBuilder.kt
View file @
c1871a62
...
...
@@ -16,6 +16,7 @@ import chat.rocket.android.dagger.scope.PerActivity
import
chat.rocket.android.main.di.MainActivityProvider
import
chat.rocket.android.main.di.MainModule
import
chat.rocket.android.main.ui.MainActivity
import
chat.rocket.android.members.di.MembersFragmentProvider
import
chat.rocket.android.profile.di.ProfileFragmentProvider
import
chat.rocket.android.settings.password.di.PasswordFragmentProvider
import
chat.rocket.android.settings.password.ui.PasswordActivity
...
...
@@ -44,7 +45,7 @@ abstract class ActivityBuilder {
abstract
fun
bindMainActivity
():
MainActivity
@PerActivity
@ContributesAndroidInjector
(
modules
=
[
ChatRoomFragmentProvider
::
class
])
@ContributesAndroidInjector
(
modules
=
[
ChatRoomFragmentProvider
::
class
,
MembersFragmentProvider
::
class
])
abstract
fun
bindChatRoomActivity
():
ChatRoomActivity
@PerActivity
...
...
app/src/main/java/chat/rocket/android/helper/MessageParser.kt
View file @
c1871a62
...
...
@@ -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 @
c1871a62
...
...
@@ -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
()
...
...
@@ -51,4 +54,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 @
c1871a62
...
...
@@ -35,6 +35,7 @@ class MainActivity : AppCompatActivity(), MainView, HasSupportFragmentInjector {
super
.
onCreate
(
savedInstanceState
)
setContentView
(
R
.
layout
.
activity_main
)
presenter
.
connect
()
setupToolbar
()
setupNavigationView
()
}
...
...
@@ -47,6 +48,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/member/ui/MemberBottomSheetFragment.kt
0 → 100644
View file @
c1871a62
package
chat.rocket.android.member.ui
import
android.os.Bundle
import
android.support.design.widget.BottomSheetDialogFragment
import
android.view.LayoutInflater
import
android.view.View
import
android.view.ViewGroup
import
chat.rocket.android.R
import
chat.rocket.android.util.extensions.content
import
chat.rocket.android.util.extensions.setVisible
import
chat.rocket.android.util.extensions.textContent
import
kotlinx.android.synthetic.main.fragment_member_bottom_sheet.*
fun
newInstance
(
avatarUri
:
String
,
realName
:
String
,
username
:
String
,
email
:
String
,
utcOffset
:
String
):
BottomSheetDialogFragment
{
return
MemberBottomSheetFragment
().
apply
{
arguments
=
Bundle
(
1
).
apply
{
putString
(
BUNDLE_AVATAR_URI
,
avatarUri
)
putString
(
BUNDLE_REAL_NAME
,
realName
)
putString
(
BUNDLE_USERNAME
,
username
)
putString
(
BUNDLE_EMAIL
,
email
)
putString
(
BUNDLE_UTC_OFFSET
,
utcOffset
)
}
}
}
private
const
val
BUNDLE_AVATAR_URI
=
"avatar_uri"
private
const
val
BUNDLE_REAL_NAME
=
"real_name"
private
const
val
BUNDLE_USERNAME
=
"username"
private
const
val
BUNDLE_EMAIL
=
"email"
private
const
val
BUNDLE_UTC_OFFSET
=
"utc_offset"
class
MemberBottomSheetFragment
:
BottomSheetDialogFragment
()
{
private
lateinit
var
avatarUri
:
String
private
lateinit
var
realName
:
String
private
lateinit
var
username
:
String
private
lateinit
var
email
:
String
private
lateinit
var
utcOffset
:
String
override
fun
onCreate
(
savedInstanceState
:
Bundle
?)
{
super
.
onCreate
(
savedInstanceState
)
val
bundle
=
arguments
if
(
bundle
!=
null
)
{
avatarUri
=
bundle
.
getString
(
BUNDLE_AVATAR_URI
)
realName
=
bundle
.
getString
(
BUNDLE_REAL_NAME
)
username
=
bundle
.
getString
(
BUNDLE_USERNAME
)
email
=
bundle
.
getString
(
BUNDLE_EMAIL
)
utcOffset
=
bundle
.
getString
(
BUNDLE_UTC_OFFSET
)
}
else
{
requireNotNull
(
bundle
)
{
"no arguments supplied when the fragment was instantiated"
}
}
}
override
fun
onCreateView
(
inflater
:
LayoutInflater
,
container
:
ViewGroup
?,
savedInstanceState
:
Bundle
?):
View
?
=
inflater
.
inflate
(
R
.
layout
.
fragment_member_bottom_sheet
,
container
,
false
)
override
fun
onViewCreated
(
view
:
View
,
savedInstanceState
:
Bundle
?)
{
super
.
onViewCreated
(
view
,
savedInstanceState
)
showMemberDetails
()
}
private
fun
showMemberDetails
()
{
image_bottom_sheet_avatar
.
setImageURI
(
avatarUri
)
text_bottom_sheet_member_name
.
content
=
realName
text_bottom_sheet_member_username
.
content
=
username
if
(
email
.
isNotEmpty
())
{
text_member_email_address
.
textContent
=
email
}
else
{
text_email_address
.
setVisible
(
false
)
text_member_email_address
.
setVisible
(
false
)
}
if
(
utcOffset
.
isNotEmpty
()){
text_member_utc
.
content
=
utcOffset
}
else
{
text_utc
.
setVisible
(
false
)
text_member_utc
.
setVisible
(
false
)
}
}
}
\ No newline at end of file
app/src/main/java/chat/rocket/android/members/adapter/MembersAdapter.kt
0 → 100644
View file @
c1871a62
package
chat.rocket.android.members.adapter
import
android.support.v7.widget.RecyclerView
import
android.view.View
import
android.view.ViewGroup
import
chat.rocket.android.R
import
chat.rocket.android.members.viewmodel.MemberViewModel
import
chat.rocket.android.util.extensions.content
import
chat.rocket.android.util.extensions.inflate
import
kotlinx.android.synthetic.main.avatar.view.*
import
kotlinx.android.synthetic.main.item_member.view.*
class
MembersAdapter
(
private
val
listener
:
(
MemberViewModel
)
->
Unit
)
:
RecyclerView
.
Adapter
<
MembersAdapter
.
ViewHolder
>()
{
private
var
dataSet
:
List
<
MemberViewModel
>
=
ArrayList
()
override
fun
onCreateViewHolder
(
parent
:
ViewGroup
,
viewType
:
Int
):
MembersAdapter
.
ViewHolder
=
ViewHolder
(
parent
.
inflate
(
R
.
layout
.
item_member
))
override
fun
onBindViewHolder
(
holder
:
MembersAdapter
.
ViewHolder
,
position
:
Int
)
=
holder
.
bind
(
dataSet
[
position
],
listener
)
override
fun
getItemCount
():
Int
=
dataSet
.
size
fun
prependData
(
dataSet
:
List
<
MemberViewModel
>)
{
this
.
dataSet
=
dataSet
notifyItemRangeInserted
(
0
,
dataSet
.
size
)
}
fun
appendData
(
dataSet
:
List
<
MemberViewModel
>)
{
val
previousDataSetSize
=
this
.
dataSet
.
size
this
.
dataSet
+=
dataSet
notifyItemRangeInserted
(
previousDataSetSize
,
dataSet
.
size
)
}
class
ViewHolder
(
itemView
:
View
)
:
RecyclerView
.
ViewHolder
(
itemView
)
{
fun
bind
(
memberViewModel
:
MemberViewModel
,
listener
:
(
MemberViewModel
)
->
Unit
)
=
with
(
itemView
)
{
image_avatar
.
setImageURI
(
memberViewModel
.
avatarUri
)
text_member
.
content
=
memberViewModel
.
displayName
setOnClickListener
{
listener
(
memberViewModel
)
}
}
}
}
\ No newline at end of file
app/src/main/java/chat/rocket/android/members/di/MembersFragmentModule.kt
0 → 100644
View file @
c1871a62
package
chat.rocket.android.members.di
import
android.arch.lifecycle.LifecycleOwner
import
chat.rocket.android.chatroom.ui.ChatRoomActivity
import
chat.rocket.android.core.lifecycle.CancelStrategy
import
chat.rocket.android.dagger.scope.PerFragment
import
chat.rocket.android.members.presentation.MembersNavigator
import
chat.rocket.android.members.presentation.MembersView
import
chat.rocket.android.members.ui.MembersFragment
import
dagger.Module
import
dagger.Provides
import
kotlinx.coroutines.experimental.Job
@Module
@PerFragment
class
MembersFragmentModule
{
@Provides
fun
provideChatRoomNavigator
(
activity
:
ChatRoomActivity
)
=
MembersNavigator
(
activity
)
@Provides
fun
membersView
(
frag
:
MembersFragment
):
MembersView
{
return
frag
}
@Provides
fun
provideLifecycleOwner
(
frag
:
MembersFragment
):
LifecycleOwner
{
return
frag
}
@Provides
fun
provideCancelStrategy
(
owner
:
LifecycleOwner
,
jobs
:
Job
):
CancelStrategy
{
return
CancelStrategy
(
owner
,
jobs
)
}
}
\ No newline at end of file
app/src/main/java/chat/rocket/android/members/di/MembersFragmentProvider.kt
0 → 100644
View file @
c1871a62
package
chat.rocket.android.members.di
import
chat.rocket.android.members.ui.MembersFragment
import
dagger.Module
import
dagger.android.ContributesAndroidInjector
@Module
abstract
class
MembersFragmentProvider
{
@ContributesAndroidInjector
(
modules
=
[
MembersFragmentModule
::
class
])
abstract
fun
provideMembersFragment
():
MembersFragment
}
\ No newline at end of file
app/src/main/java/chat/rocket/android/members/presentation/MembersNavigator.kt
0 → 100644
View file @
c1871a62
package
chat.rocket.android.members.presentation
import
chat.rocket.android.chatroom.ui.ChatRoomActivity
import
chat.rocket.android.member.ui.newInstance
class
MembersNavigator
(
internal
val
activity
:
ChatRoomActivity
)
{
fun
toMemberDetails
(
avatarUri
:
String
,
realName
:
String
,
username
:
String
,
email
:
String
,
utcOffset
:
String
)
{
activity
.
apply
{
newInstance
(
avatarUri
,
realName
,
username
,
email
,
utcOffset
)
.
show
(
supportFragmentManager
,
"MemberBottomSheetFragment"
)
}
}
}
app/src/main/java/chat/rocket/android/members/presentation/MembersPresenter.kt
0 → 100644
View file @
c1871a62
package
chat.rocket.android.members.presentation
import
chat.rocket.android.core.lifecycle.CancelStrategy
import
chat.rocket.android.members.viewmodel.MemberViewModel
import
chat.rocket.android.members.viewmodel.MemberViewModelMapper
import
chat.rocket.android.server.domain.GetCurrentServerInteractor
import
chat.rocket.android.server.infraestructure.RocketChatClientFactory
import
chat.rocket.android.util.extensions.launchUI
import
chat.rocket.common.RocketChatException
import
chat.rocket.common.model.roomTypeOf
import
chat.rocket.common.util.ifNull
import
chat.rocket.core.RocketChatClient
import
chat.rocket.core.internal.rest.getMembers
import
javax.inject.Inject
class
MembersPresenter
@Inject
constructor
(
private
val
view
:
MembersView
,
private
val
navigator
:
MembersNavigator
,
private
val
strategy
:
CancelStrategy
,
private
val
serverInteractor
:
GetCurrentServerInteractor
,
factory
:
RocketChatClientFactory
,
private
val
mapper
:
MemberViewModelMapper
)
{
private
val
client
:
RocketChatClient
=
factory
.
create
(
serverInteractor
.
get
()
!!
)
fun
loadChatRoomsMembers
(
chatRoomId
:
String
,
chatRoomType
:
String
,
offset
:
Long
=
0
)
{
launchUI
(
strategy
)
{
try
{
view
.
showLoading
()
val
members
=
client
.
getMembers
(
chatRoomId
,
roomTypeOf
(
chatRoomType
),
offset
,
60
)
val
memberViewModels
=
mapper
.
mapToViewModelList
(
members
.
result
)
view
.
showMembers
(
memberViewModels
,
members
.
total
)
}
catch
(
ex
:
RocketChatException
)
{
ex
.
message
?.
let
{
view
.
showMessage
(
it
)
}.
ifNull
{
view
.
showGenericErrorMessage
()
}
}
finally
{
view
.
hideLoading
()
}
}
}
fun
toMemberDetails
(
memberViewModel
:
MemberViewModel
)
{
val
avatarUri
=
memberViewModel
.
avatarUri
.
toString
()
val
realName
=
memberViewModel
.
realName
.
toString
()
val
username
=
"@${memberViewModel.username}"
val
email
=
memberViewModel
.
email
?:
""
val
utcOffset
=
memberViewModel
.
utcOffset
.
toString
()
navigator
.
toMemberDetails
(
avatarUri
,
realName
,
username
,
email
,
utcOffset
)
}
}
\ No newline at end of file
app/src/main/java/chat/rocket/android/members/presentation/MembersView.kt
0 → 100644
View file @
c1871a62
package
chat.rocket.android.members.presentation
import
chat.rocket.android.core.behaviours.LoadingView
import
chat.rocket.android.core.behaviours.MessageView
import
chat.rocket.android.members.viewmodel.MemberViewModel
interface
MembersView
:
LoadingView
,
MessageView
{
/**
* Shows a list of members of a room.
*
* @param dataSet The data set to show.
* @param total The total number of members.
*/
fun
showMembers
(
dataSet
:
List
<
MemberViewModel
>,
total
:
Long
)
}
\ No newline at end of file
app/src/main/java/chat/rocket/android/members/ui/MembersFragment.kt
0 → 100644
View file @
c1871a62
package
chat.rocket.android.members.ui
import
android.os.Bundle
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
import
chat.rocket.android.R
import
chat.rocket.android.chatroom.ui.ChatRoomActivity
import
chat.rocket.android.helper.EndlessRecyclerViewScrollListener
import
chat.rocket.android.members.adapter.MembersAdapter
import
chat.rocket.android.members.presentation.MembersPresenter
import
chat.rocket.android.members.presentation.MembersView
import
chat.rocket.android.members.viewmodel.MemberViewModel
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.widget.DividerItemDecoration
import
dagger.android.support.AndroidSupportInjection
import
kotlinx.android.synthetic.main.fragment_members.*
import
javax.inject.Inject
fun
newInstance
(
chatRoomId
:
String
,
chatRoomType
:
String
):
Fragment
{
return
MembersFragment
().
apply
{
arguments
=
Bundle
(
1
).
apply
{
putString
(
BUNDLE_CHAT_ROOM_ID
,
chatRoomId
)
putString
(
BUNDLE_CHAT_ROOM_TYPE
,
chatRoomType
)
}
}
}
private
const
val
BUNDLE_CHAT_ROOM_ID
=
"chat_room_id"
private
const
val
BUNDLE_CHAT_ROOM_TYPE
=
"chat_room_type"
class
MembersFragment
:
Fragment
(),
MembersView
{
@Inject
lateinit
var
presenter
:
MembersPresenter
private
val
adapter
:
MembersAdapter
=
MembersAdapter
{
memberViewModel
->
presenter
.
toMemberDetails
(
memberViewModel
)
}
private
val
linearLayoutManager
=
LinearLayoutManager
(
context
,
LinearLayoutManager
.
VERTICAL
,
false
)
private
lateinit
var
chatRoomId
:
String
private
lateinit
var
chatRoomType
:
String
override
fun
onCreate
(
savedInstanceState
:
Bundle
?)
{
super
.
onCreate
(
savedInstanceState
)
AndroidSupportInjection
.
inject
(
this
)
val
bundle
=
arguments
if
(
bundle
!=
null
)
{
chatRoomId
=
bundle
.
getString
(
BUNDLE_CHAT_ROOM_ID
)
chatRoomType
=
bundle
.
getString
(
BUNDLE_CHAT_ROOM_TYPE
)
}
else
{
requireNotNull
(
bundle
)
{
"no arguments supplied when the fragment was instantiated"
}
}
}
override
fun
onCreateView
(
inflater
:
LayoutInflater
,
container
:
ViewGroup
?,
savedInstanceState
:
Bundle
?):
View
?
=
container
?.
inflate
(
R
.
layout
.
fragment_members
)
override
fun
onViewCreated
(
view
:
View
,
savedInstanceState
:
Bundle
?)
{
super
.
onViewCreated
(
view
,
savedInstanceState
)
(
activity
as
AppCompatActivity
).
supportActionBar
?.
title
=
""
setupRecyclerView
()
presenter
.
loadChatRoomsMembers
(
chatRoomId
,
chatRoomType
)
}
override
fun
showMembers
(
dataSet
:
List
<
MemberViewModel
>,
total
:
Long
)
{
activity
?.
apply
{
setupToolbar
(
total
)
if
(
adapter
.
itemCount
==
0
)
{
adapter
.
prependData
(
dataSet
)
if
(
dataSet
.
size
>=
59
)
{
// TODO Check why the API retorns the specified count -1
recycler_view
.
addOnScrollListener
(
object
:
EndlessRecyclerViewScrollListener
(
linearLayoutManager
)
{
override
fun
onLoadMore
(
page
:
Int
,
totalItemsCount
:
Int
,
recyclerView
:
RecyclerView
?)
{
presenter
.
loadChatRoomsMembers
(
chatRoomId
,
chatRoomType
,
page
*
60L
)
}
})
}
}
else
{
adapter
.
appendData
(
dataSet
)
}
}
}
override
fun
showLoading
()
=
view_loading
.
setVisible
(
true
)
override
fun
hideLoading
()
=
view_loading
.
setVisible
(
false
)
override
fun
showMessage
(
resId
:
Int
)
=
showToast
(
resId
)
override
fun
showMessage
(
message
:
String
)
=
showToast
(
message
)
override
fun
showGenericErrorMessage
()
=
showMessage
(
getString
(
R
.
string
.
msg_generic_error
))
private
fun
setupRecyclerView
()
{
activity
?.
apply
{
recycler_view
.
layoutManager
=
linearLayoutManager
recycler_view
.
addItemDecoration
(
DividerItemDecoration
(
this
))
recycler_view
.
adapter
=
adapter
}
}
private
fun
setupToolbar
(
totalMembers
:
Long
)
{
(
activity
as
ChatRoomActivity
).
setupToolbarTitle
(
getString
(
R
.
string
.
title_members
,
totalMembers
))
}
}
\ No newline at end of file
app/src/main/java/chat/rocket/android/members/viewmodel/MemberViewModel.kt
0 → 100644
View file @
c1871a62
package
chat.rocket.android.members.viewmodel
import
chat.rocket.android.helper.UrlHelper
import
chat.rocket.android.server.domain.useRealName
import
chat.rocket.common.model.User
import
chat.rocket.core.model.Value
class
MemberViewModel
(
private
val
member
:
User
,
private
val
settings
:
Map
<
String
,
Value
<
Any
>>,
private
val
baseUrl
:
String
?)
{
val
avatarUri
:
String
?
val
displayName
:
String
val
realName
:
String
?
val
username
:
String
?
val
email
:
String
?
val
utcOffset
:
Float
?
init
{
avatarUri
=
getUserAvatar
()
displayName
=
getUserDisplayName
()
realName
=
getUserRealName
()
username
=
getUserUsername
()
email
=
getUserEmail
()
utcOffset
=
getUserUtcOffset
()
}
private
fun
getUserAvatar
():
String
?
{
val
username
=
member
.
username
?:
"?"
return
baseUrl
?.
let
{
UrlHelper
.
getAvatarUrl
(
baseUrl
,
username
)
}
}
private
fun
getUserDisplayName
():
String
{
val
username
=
member
.
username
val
realName
=
member
.
name
val
senderName
=
if
(
settings
.
useRealName
())
realName
else
username
return
senderName
?:
username
.
toString
()
}
private
fun
getUserRealName
():
String
?
=
member
.
name
private
fun
getUserUsername
():
String
?
=
member
.
username
private
fun
getUserEmail
():
String
?
=
member
.
emails
?.
get
(
0
)
?.
address
private
fun
getUserUtcOffset
():
Float
?
=
member
.
utcOffset
}
\ No newline at end of file
app/src/main/java/chat/rocket/android/members/viewmodel/MemberViewModelMapper.kt
0 → 100644
View file @
c1871a62
package
chat.rocket.android.members.viewmodel
import
chat.rocket.android.server.domain.GetCurrentServerInteractor
import
chat.rocket.android.server.domain.GetSettingsInteractor
import
chat.rocket.android.server.domain.baseUrl
import
chat.rocket.common.model.User
import
chat.rocket.core.model.Value
import
javax.inject.Inject
class
MemberViewModelMapper
@Inject
constructor
(
serverInteractor
:
GetCurrentServerInteractor
,
getSettingsInteractor
:
GetSettingsInteractor
)
{
private
var
settings
:
Map
<
String
,
Value
<
Any
>>
=
getSettingsInteractor
.
get
(
serverInteractor
.
get
()
!!
)
!!
private
val
baseUrl
=
settings
.
baseUrl
()
fun
mapToViewModelList
(
memberList
:
List
<
User
>):
List
<
MemberViewModel
>
{
return
memberList
.
map
{
MemberViewModel
(
it
,
settings
,
baseUrl
)
}
}
}
\ No newline at end of file
app/src/main/java/chat/rocket/android/server/domain/SettingsRepository.kt
View file @
c1871a62
...
...
@@ -48,30 +48,31 @@ 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
fun
PublicSettings
.
ldapEnabled
():
Boolean
=
this
[
LDAP_ENABLE
]
?.
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 +81,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 @
c1871a62
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 @
c1871a62
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 @
c1871a62
...
...
@@ -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 @
c1871a62
...
...
@@ -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 @
c1871a62
...
...
@@ -12,7 +12,12 @@ fun View.rotateBy(value: Float, duration: Long = 100) {
.
start
()
}
fun
View
.
fadeIn
(
startValue
:
Float
,
finishValue
:
Float
,
duration
:
Long
=
200
)
{
fun
View
.
fadeIn
(
startValue
:
Float
=
0f
,
finishValue
:
Float
=
1f
,
duration
:
Long
=
200
)
{
if
(
alpha
==
finishValue
)
{
setVisible
(
true
)
return
}
animate
()
.
alpha
(
startValue
)
.
setDuration
(
duration
)
...
...
@@ -27,7 +32,12 @@ fun View.fadeIn(startValue: Float, finishValue: Float, duration: Long = 200) {
setVisible
(
true
)
}
fun
View
.
fadeOut
(
startValue
:
Float
,
finishValue
:
Float
,
duration
:
Long
=
200
)
{
fun
View
.
fadeOut
(
startValue
:
Float
=
1f
,
finishValue
:
Float
=
0f
,
duration
:
Long
=
200
)
{
if
(
alpha
==
finishValue
)
{
setVisible
(
false
)
return
}
animate
()
.
alpha
(
startValue
)
.
setDuration
(
duration
)
...
...
app/src/main/java/chat/rocket/android/util/extensions/Text.kt
View file @
c1871a62
...
...
@@ -3,6 +3,7 @@ package chat.rocket.android.util.extensions
import
android.text.Spannable
import
android.text.Spanned
import
android.text.TextUtils
import
android.util.Patterns
import
android.widget.EditText
import
android.widget.TextView
import
chat.rocket.android.widget.emoji.EmojiParser
...
...
@@ -31,6 +32,8 @@ fun EditText.erase() {
}
}
fun
String
.
isEmailValid
():
Boolean
=
Patterns
.
EMAIL_ADDRESS
.
matcher
(
this
).
matches
()
var
TextView
.
textContent
:
String
get
()
=
text
.
toString
()
set
(
value
)
{
...
...
app/src/main/java/chat/rocket/android/util/extensions/Ui.kt
View file @
c1871a62
...
...
@@ -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
...
...
@@ -55,3 +57,12 @@ 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
)
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/drawable/ic_gitlab.xml
View file @
c1871a62
...
...
@@ -19,58 +19,98 @@
android:strokeWidth=
"1"
/>
<path
android:fillColor=
"#FFFFFF"
android:fillType=
"nonZero"
android:pathData=
"M121.13,18.01L119.97,21.57L117.68,28.64C117.56,29 117.04,29 116.92,28.64L114.63,21.57L107,21.57L104.71,28.64C104.59,29 104.08,29 103.96,28.64L101.66,21.57L100.5,18.01C100.4,17.68 100.51,17.33 100.79,17.12L110.82,9.84L120.84,17.12C121.12,17.33 121.23,17.68 121.13,18.01"
android:strokeColor=
"#00000000"
android:fillAlpha=
"1"
android:fillColor=
"#ffffff"
android:pathData=
"M122.13,22.99L120.97,19.43C119.6,15.19 118.83,12.83 118.68,12.36C118.56,12 118.04,12 117.92,12.36C117.77,12.83 117,15.19 115.63,19.43L108,19.43C106.63,15.19 105.86,12.83 105.71,12.36C105.59,12 105.08,12 104.96,12.36C104.81,12.83 104.04,15.19 102.66,19.43C101.96,21.57 101.58,22.75 101.5,22.99C101.4,23.32 101.51,23.67 101.79,23.88C102.46,24.37 105.8,26.79 111.82,31.16C117.83,26.79 121.17,24.37 121.84,23.88C122.12,23.67 122.23,23.32 122.13,22.99"
/>
<path
android:fillAlpha=
"0"
android:fillColor=
"#FF000000"
android:pathData=
"M122.13,22.99L120.97,19.43C119.6,15.19 118.83,12.83 118.68,12.36C118.56,12 118.04,12 117.92,12.36C117.77,12.83 117,15.19 115.63,19.43L108,19.43C106.63,15.19 105.86,12.83 105.71,12.36C105.59,12 105.08,12 104.96,12.36C104.81,12.83 104.04,15.19 102.66,19.43C101.96,21.57 101.58,22.75 101.5,22.99C101.4,23.32 101.51,23.67 101.79,23.88C102.46,24.37 105.8,26.79 111.82,31.16C117.83,26.79 121.17,24.37 121.84,23.88C122.12,23.67 122.23,23.32 122.13,22.99"
android:strokeAlpha=
"0"
android:strokeColor=
"#000000"
android:strokeWidth=
"1"
/>
<path
android:fillColor=
"#FFFFFF"
android:fillType=
"nonZero"
android:pathData=
"M110.82,9.84l0,0l3.81,11.73l-7.62,0z"
android:strokeColor=
"#00000000"
android:fillAlpha=
"1"
android:fillColor=
"#ffffff"
android:pathData=
"M111.82,31.16L111.82,31.16L115.63,19.43L108.01,19.43L111.82,31.16Z"
/>
<path
android:fillAlpha=
"0"
android:fillColor=
"#FF000000"
android:pathData=
"M111.82,31.16L111.82,31.16L115.63,19.43L108.01,19.43L111.82,31.16Z"
android:strokeAlpha=
"0"
android:strokeColor=
"#000000"
android:strokeWidth=
"1"
/>
<path
android:fillColor=
"#FFFFFF"
android:fillType=
"nonZero"
android:pathData=
"M110.82,9.84l-3.81,11.73l-5.34,0z"
android:strokeColor=
"#00000000"
android:fillAlpha=
"1"
android:fillColor=
"#ffffff"
android:pathData=
"M111.82,31.16L108.01,19.43L102.67,19.43L111.82,31.16Z"
/>
<path
android:fillAlpha=
"0"
android:fillColor=
"#FF000000"
android:pathData=
"M111.82,31.16L108.01,19.43L102.67,19.43L111.82,31.16Z"
android:strokeAlpha=
"0"
android:strokeColor=
"#000000"
android:strokeWidth=
"1"
/>
<path
android:fillColor=
"#FFFFFF"
android:fillType=
"nonZero"
android:pathData=
"M101.66,21.57L101.66,21.57L100.5,18.01C100.4,17.68 100.51,17.33 100.79,17.12L110.82,9.84L101.66,21.57L101.66,21.57Z"
android:strokeColor=
"#00000000"
android:fillAlpha=
"1"
android:fillColor=
"#ffffff"
android:pathData=
"M102.66,19.43C101.96,21.57 101.58,22.75 101.5,22.99C101.4,23.32 101.51,23.67 101.79,23.88C102.46,24.37 105.8,26.79 111.82,31.16L102.66,19.43L102.66,19.43L102.66,19.43Z"
/>
<path
android:fillAlpha=
"0"
android:fillColor=
"#FF000000"
android:pathData=
"M102.66,19.43C101.96,21.57 101.58,22.75 101.5,22.99C101.4,23.32 101.51,23.67 101.79,23.88C102.46,24.37 105.8,26.79 111.82,31.16L102.66,19.43L102.66,19.43L102.66,19.43Z"
android:strokeAlpha=
"0"
android:strokeColor=
"#000000"
android:strokeWidth=
"1"
/>
<path
android:fillColor=
"#FFFFFF"
android:fillType=
"nonZero"
android:pathData=
"M101.66,21.57L107,21.57L104.71,28.64C104.59,29 104.08,29 103.96,28.64L101.66,21.57L101.66,21.57Z"
android:strokeColor=
"#00000000"
android:fillAlpha=
"1"
android:fillColor=
"#ffffff"
android:pathData=
"M108,19.43C106.63,15.19 105.86,12.83 105.71,12.36C105.59,12 105.08,12 104.96,12.36C104.81,12.83 104.04,15.19 102.66,19.43L102.66,19.43L108,19.43Z"
/>
<path
android:fillAlpha=
"0"
android:fillColor=
"#FF000000"
android:pathData=
"M108,19.43C106.63,15.19 105.86,12.83 105.71,12.36C105.59,12 105.08,12 104.96,12.36C104.81,12.83 104.04,15.19 102.66,19.43L102.66,19.43L108,19.43Z"
android:strokeAlpha=
"0"
android:strokeColor=
"#000000"
android:strokeWidth=
"1"
/>
<path
android:fillColor=
"#FFFFFF"
android:fillType=
"nonZero"
android:pathData=
"M110.82,9.84l3.81,11.73l5.34,0z"
android:strokeColor=
"#00000000"
android:fillAlpha=
"1"
android:fillColor=
"#ffffff"
android:pathData=
"M111.82,31.16L115.63,19.43L120.97,19.43L111.82,31.16Z"
/>
<path
android:fillAlpha=
"0"
android:fillColor=
"#FF000000"
android:pathData=
"M111.82,31.16L115.63,19.43L120.97,19.43L111.82,31.16Z"
android:strokeAlpha=
"0"
android:strokeColor=
"#000000"
android:strokeWidth=
"1"
/>
<path
android:fillColor=
"#FFFFFF"
android:fillType=
"nonZero"
android:pathData=
"M119.97,21.57L119.97,21.57L121.13,18.01C121.24,17.68 121.12,17.33 120.84,17.12L110.82,9.84L119.97,21.57L119.97,21.57Z"
android:strokeColor=
"#00000000"
android:fillAlpha=
"1"
android:fillColor=
"#ffffff"
android:pathData=
"M120.97,19.43C121.67,21.57 122.05,22.75 122.13,22.99C122.24,23.32 122.12,23.67 121.84,23.88C121.17,24.37 117.83,26.79 111.82,31.16L120.97,19.43L120.97,19.43L120.97,19.43Z"
/>
<path
android:fillAlpha=
"0"
android:fillColor=
"#FF000000"
android:pathData=
"M120.97,19.43C121.67,21.57 122.05,22.75 122.13,22.99C122.24,23.32 122.12,23.67 121.84,23.88C121.17,24.37 117.83,26.79 111.82,31.16L120.97,19.43L120.97,19.43L120.97,19.43Z"
android:strokeAlpha=
"0"
android:strokeColor=
"#000000"
android:strokeWidth=
"1"
/>
<path
android:fillColor=
"#FFFFFF"
android:fillType=
"nonZero"
android:pathData=
"M119.97,21.57L114.63,21.57L116.92,28.64C117.04,29 117.56,29 117.68,28.64L119.97,21.57L119.97,21.57Z"
android:strokeColor=
"#00000000"
android:fillAlpha=
"1"
android:fillColor=
"#ffffff"
android:pathData=
"M115.63,19.43C117,15.19 117.77,12.83 117.92,12.36C118.04,12 118.56,12 118.68,12.36C118.83,12.83 119.6,15.19 120.97,19.43L120.97,19.43L115.63,19.43Z"
/>
<path
android:fillAlpha=
"0"
android:fillColor=
"#FF000000"
android:pathData=
"M115.63,19.43C117,15.19 117.77,12.83 117.92,12.36C118.04,12 118.56,12 118.68,12.36C118.83,12.83 119.6,15.19 120.97,19.43L120.97,19.43L115.63,19.43Z"
android:strokeAlpha=
"0"
android:strokeColor=
"#000000"
android:strokeWidth=
"1"
/>
</vector>
\ No newline at end of file
app/src/main/res/layout/emoji_popup_layout.xml
View file @
c1871a62
...
...
@@ -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_authentication_server.xml
View file @
c1871a62
...
...
@@ -32,7 +32,7 @@
android:cursorVisible=
"false"
android:hint=
"@string/default_server"
android:imeOptions=
"actionDone"
android:digits=
"0123456789abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ
.-/:"
android:digits=
"0123456789abcdefghijklmnopqrstuvwxyz.-/:"
android:inputType=
"textUri"
android:paddingEnd=
"0dp"
android:paddingStart=
"0dp"
/>
...
...
app/src/main/res/layout/fragment_chat_room.xml
View file @
c1871a62
...
...
@@ -4,7 +4,8 @@
xmlns:tools=
"http://schemas.android.com/tools"
android:id=
"@+id/root_layout"
android:layout_width=
"match_parent"
android:layout_height=
"match_parent"
>
android:layout_height=
"match_parent"
tools:context=
".chatroom.ui.ChatRoomFragment"
>
<com.wang.avi.AVLoadingIndicatorView
android:id=
"@+id/view_loading"
...
...
@@ -53,4 +54,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 @
c1871a62
...
...
@@ -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/fragment_member_bottom_sheet.xml
0 → 100644
View file @
c1871a62
<?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:id=
"@+id/member_bottom_sheet"
android:layout_width=
"match_parent"
android:layout_height=
"match_parent"
android:paddingBottom=
"16dp"
app:layout_behavior=
"android.support.design.widget.BottomSheetBehavior"
>
<com.facebook.drawee.view.SimpleDraweeView
android:id=
"@+id/image_bottom_sheet_avatar"
android:layout_width=
"match_parent"
android:layout_height=
"200dp"
/>
<LinearLayout
android:id=
"@+id/name_and_username_container"
android:layout_width=
"match_parent"
android:layout_height=
"wrap_content"
android:background=
"@color/colorBackgroundMemberContainer"
android:orientation=
"vertical"
android:paddingBottom=
"10dp"
android:paddingStart=
"16dp"
android:paddingTop=
"10dp"
app:layout_constraintBottom_toBottomOf=
"@+id/image_bottom_sheet_avatar"
app:layout_constraintLeft_toLeftOf=
"parent"
>
<TextView
android:id=
"@+id/text_bottom_sheet_member_name"
style=
"@style/TextAppearance.AppCompat.Title"
android:layout_width=
"wrap_content"
android:layout_height=
"wrap_content"
android:textColor=
"@color/white"
tools:text=
"Ronald Perkins"
/>
<TextView
android:id=
"@+id/text_bottom_sheet_member_username"
style=
"@style/Sender.Name.TextView"
android:layout_width=
"wrap_content"
android:layout_height=
"wrap_content"
android:layout_marginTop=
"5dp"
android:textColor=
"@color/white"
tools:text=
"\@ronaldPerkins"
/>
</LinearLayout>
<TextView
android:id=
"@+id/text_email_address"
android:layout_width=
"wrap_content"
android:layout_height=
"wrap_content"
android:layout_marginStart=
"16dp"
android:layout_marginTop=
"16dp"
android:text=
"@string/msg_email_address"
app:layout_constraintLeft_toLeftOf=
"parent"
app:layout_constraintTop_toBottomOf=
"@+id/image_bottom_sheet_avatar"
/>
<TextView
android:id=
"@+id/text_member_email_address"
android:layout_width=
"wrap_content"
android:layout_height=
"wrap_content"
android:layout_marginStart=
"16dp"
android:layout_marginTop=
"10dp"
android:textColor=
"@color/colorPrimaryText"
app:layout_constraintLeft_toLeftOf=
"parent"
app:layout_constraintTop_toBottomOf=
"@+id/text_email_address"
tools:text=
"ronald@perkins.com"
/>
<TextView
android:id=
"@+id/text_utc"
android:layout_width=
"wrap_content"
android:layout_height=
"wrap_content"
android:layout_marginStart=
"16dp"
android:layout_marginTop=
"16dp"
android:text=
"@string/msg_utc_offset"
app:layout_constraintLeft_toLeftOf=
"parent"
app:layout_constraintTop_toBottomOf=
"@+id/text_member_email_address"
/>
<TextView
android:id=
"@+id/text_member_utc"
android:layout_width=
"wrap_content"
android:layout_height=
"wrap_content"
android:layout_marginStart=
"16dp"
android:layout_marginTop=
"10dp"
android:textColor=
"@color/colorPrimaryText"
app:layout_constraintLeft_toLeftOf=
"parent"
app:layout_constraintTop_toBottomOf=
"@+id/text_utc"
tools:text=
"+01:00"
/>
</android.support.constraint.ConstraintLayout>
\ No newline at end of file
app/src/main/res/layout/fragment_members.xml
0 → 100644
View file @
c1871a62
<?xml version="1.0" encoding="utf-8"?>
<android.support.design.widget.CoordinatorLayout
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=
"match_parent"
tools:context=
".members.ui.MembersFragment"
>
<android.support.v7.widget.RecyclerView
android:id=
"@+id/recycler_view"
android:layout_width=
"match_parent"
android:layout_height=
"match_parent"
android:scrollbars=
"vertical"
/>
<com.wang.avi.AVLoadingIndicatorView
android:id=
"@+id/view_loading"
android:layout_width=
"wrap_content"
android:layout_height=
"wrap_content"
android:layout_centerInParent=
"true"
android:layout_gravity=
"center"
app:indicatorColor=
"@color/black"
app:indicatorName=
"BallPulseIndicator"
/>
</android.support.design.widget.CoordinatorLayout>
\ No newline at end of file
app/src/main/res/layout/fragment_profile.xml
View file @
c1871a62
...
...
@@ -49,7 +49,7 @@
<EditText
android:id=
"@+id/text_avatar_url"
style=
"@style/
EditText.Profile
"
style=
"@style/
Profile.EditText
"
android:layout_marginTop=
"16dp"
android:drawableStart=
"@drawable/ic_link_black_24dp"
android:hint=
"@string/msg_avatar_url"
...
...
app/src/main/res/layout/item_member.xml
0 → 100644
View file @
c1871a62
<?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:layout_marginBottom=
"6dp"
android:layout_marginEnd=
"@dimen/screen_edge_left_and_right_margins"
android:layout_marginStart=
"@dimen/screen_edge_left_and_right_margins"
android:layout_marginTop=
"6dp"
>
<include
android:id=
"@+id/layout_avatar"
layout=
"@layout/avatar"
android:layout_width=
"40dp"
android:layout_height=
"40dp"
app:layout_constraintLeft_toLeftOf=
"parent"
app:layout_constraintTop_toTopOf=
"parent"
/>
<TextView
android:id=
"@+id/text_member"
style=
"@style/Sender.Name.TextView"
android:layout_width=
"wrap_content"
android:layout_height=
"wrap_content"
android:layout_marginStart=
"16dp"
app:layout_constraintBottom_toBottomOf=
"@+id/layout_avatar"
app:layout_constraintLeft_toRightOf=
"@+id/layout_avatar"
app:layout_constraintTop_toTopOf=
"@+id/layout_avatar"
tools:text=
"Ronald Perkins"
/>
</android.support.constraint.ConstraintLayout>
\ No newline at end of file
app/src/main/res/layout/message_attachment.xml
View file @
c1871a62
...
...
@@ -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 @
c1871a62
...
...
@@ -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/menu/chatroom_actions.xml
View file @
c1871a62
...
...
@@ -2,6 +2,11 @@
<menu
xmlns:android=
"http://schemas.android.com/apk/res/android"
xmlns:app=
"http://schemas.android.com/apk/res-auto"
>
<item
android:id=
"@+id/action_members_list"
android:title=
"@string/title_members_list"
app:showAsAction=
"never"
/>
<item
android:id=
"@+id/action_pinned_messages"
android:title=
"@string/title_pinned_messages"
...
...
app/src/main/res/values-pt-rBR/strings.xml
View file @
c1871a62
...
...
@@ -7,6 +7,7 @@
<string
name=
"title_legal_terms"
>
Termos Legais
</string>
<string
name=
"title_chats"
>
Chats
</string>
<string
name=
"title_profile"
>
Perfil
</string>
<string
name=
"title_members"
>
Membros (%d)
</string>
<string
name=
"title_settings"
>
Configurações
</string>
<string
name=
"title_password"
>
Alterar senha
</string>
<string
name=
"title_update_profile"
>
Editar perfil
</string>
...
...
@@ -59,6 +60,8 @@
<string
name=
"msg_content_description_show_attachment_options"
>
Mostrar opções de anexo
</string>
<string
name=
"msg_you"
>
Você
</string>
<string
name=
"msg_unknown"
>
Desconhecido
</string>
<string
name=
"msg_email_address"
>
Endereço de e-mail
</string>
<string
name=
"msg_utc_offset"
>
Deslocamento de UTC
</string>
<string
name=
"msg_new_password"
>
Informe a nova senha
</string>
<string
name=
"msg_confirm_password"
>
Confirme a nova senha
</string>
...
...
@@ -89,10 +92,20 @@
<string
name=
"permission_deleting_not_allowed"
>
Remoção não permitida
</string>
<string
name=
"permission_pinning_not_allowed"
>
Fixar não permitido
</string>
<!-- Members List -->
<string
name=
"title_members_list"
>
Lista de Membros
</string>
<!-- Pinned Messages -->
<string
name=
"title_pinned_messages"
>
Mensagens Pinadas
</string>
<!-- 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/colors.xml
View file @
c1871a62
...
...
@@ -25,6 +25,8 @@
<color
name=
"colorDim"
>
#99000000
</color>
<color
name=
"colorBackgroundMemberContainer"
>
#4D000000
</color>
<color
name=
"white"
>
#FFFFFFFF
</color>
<color
name=
"black"
>
#FF000000
</color>
<color
name=
"red"
>
#FFFF0000
</color>
...
...
app/src/main/res/values/strings.xml
View file @
c1871a62
...
...
@@ -8,6 +8,7 @@
<string
name=
"title_legal_terms"
>
Legal Terms
</string>
<string
name=
"title_chats"
>
Chats
</string>
<string
name=
"title_profile"
>
Profile
</string>
<string
name=
"title_members"
>
Members (%d)
</string>
<string
name=
"title_settings"
>
Settings
</string>
<string
name=
"title_password"
>
Change Password
</string>
<string
name=
"title_update_profile"
>
Update profile
</string>
...
...
@@ -61,6 +62,8 @@
<string
name=
"msg_content_description_show_attachment_options"
>
Show attachment options
</string>
<string
name=
"msg_you"
>
You
</string>
<string
name=
"msg_unknown"
>
Unknown
</string>
<string
name=
"msg_email_address"
>
E-mail address
</string>
<string
name=
"msg_utc_offset"
>
UTC offset
</string>
<string
name=
"msg_new_password"
>
Enter New Password
</string>
<string
name=
"msg_confirm_password"
>
Confirm New Password
</string>
...
...
@@ -91,10 +94,21 @@
<string
name=
"permission_deleting_not_allowed"
>
Deleting is not allowed
</string>
<string
name=
"permission_pinning_not_allowed"
>
Pinning is not allowed
</string>
<!-- Members List -->
<string
name=
"title_members_list"
>
Members List
</string>
<!-- Pinned Messages -->
<string
name=
"title_pinned_messages"
>
Pinned Messages
</string>
<!-- 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 @
c1871a62
...
...
@@ -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