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
436e7d60
Commit
436e7d60
authored
Mar 07, 2018
by
Leonardo Aramaki
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Fixed conflicts when rebasing
parent
a639df30
Changes
38
Hide whitespace changes
Inline
Side-by-side
Showing
38 changed files
with
1170 additions
and
46 deletions
+1170
-46
DrawableHelper.kt
app/src/main/java/chat/rocket/android/app/DrawableHelper.kt
+8
-7
User.kt
app/src/main/java/chat/rocket/android/app/User.kt
+0
-7
SignupFragment.kt
...rocket/android/authentication/signup/ui/SignupFragment.kt
+5
-5
AutoCompleteType.kt
.../chat/rocket/android/chatroom/adapter/AutoCompleteType.kt
+10
-0
PeopleSuggestionsAdapter.kt
...cket/android/chatroom/adapter/PeopleSuggestionsAdapter.kt
+52
-0
RoomSuggestionsAdapter.kt
...rocket/android/chatroom/adapter/RoomSuggestionsAdapter.kt
+37
-0
ChatRoomPresenter.kt
...rocket/android/chatroom/presentation/ChatRoomPresenter.kt
+116
-2
ChatRoomView.kt
...chat/rocket/android/chatroom/presentation/ChatRoomView.kt
+4
-0
ChatRoomFragment.kt
.../java/chat/rocket/android/chatroom/ui/ChatRoomFragment.kt
+36
-4
ChatRoomViewModel.kt
...at/rocket/android/chatroom/viewmodel/ChatRoomViewModel.kt
+9
-0
PeopleViewModel.kt
...chat/rocket/android/chatroom/viewmodel/PeopleViewModel.kt
+17
-0
AppModule.kt
.../main/java/chat/rocket/android/dagger/module/AppModule.kt
+13
-1
ProfileFragment.kt
...in/java/chat/rocket/android/profile/ui/ProfileFragment.kt
+14
-14
GetChatRoomsInteractor.kt
...at/rocket/android/server/domain/GetChatRoomsInteractor.kt
+8
-0
MessagesRepository.kt
...a/chat/rocket/android/server/domain/MessagesRepository.kt
+12
-0
RoomRepository.kt
.../java/chat/rocket/android/server/domain/RoomRepository.kt
+41
-0
UsersRepository.kt
...java/chat/rocket/android/server/domain/UsersRepository.kt
+42
-0
MemoryMessagesRepository.kt
...ndroid/server/infraestructure/MemoryMessagesRepository.kt
+5
-0
MemoryRoomRepository.kt
...et/android/server/infraestructure/MemoryRoomRepository.kt
+38
-0
MemoryUsersRepository.kt
...t/android/server/infraestructure/MemoryUsersRepository.kt
+39
-0
SuggestionModel.kt
...et/android/widget/autocompletion/model/SuggestionModel.kt
+18
-0
LocalSuggestionProvider.kt
...dget/autocompletion/repository/LocalSuggestionProvider.kt
+5
-0
CompletionStrategy.kt
...roid/widget/autocompletion/strategy/CompletionStrategy.kt
+10
-0
StringMatchingCompletionStrategy.kt
...letion/strategy/regex/StringMatchingCompletionStrategy.kt
+33
-0
TrieCompletionStrategy.kt
...et/autocompletion/strategy/trie/TrieCompletionStrategy.kt
+31
-0
Trie.kt
.../android/widget/autocompletion/strategy/trie/data/Trie.kt
+70
-0
TrieNode.kt
...roid/widget/autocompletion/strategy/trie/data/TrieNode.kt
+47
-0
BaseSuggestionViewHolder.kt
...roid/widget/autocompletion/ui/BaseSuggestionViewHolder.kt
+9
-0
PopupRecyclerView.kt
...ket/android/widget/autocompletion/ui/PopupRecyclerView.kt
+35
-0
SuggestionsAdapter.kt
...et/android/widget/autocompletion/ui/SuggestionsAdapter.kt
+66
-0
SuggestionsView.kt
...ocket/android/widget/autocompletion/ui/SuggestionsView.kt
+194
-0
user_status_white.xml
app/src/main/res/drawable/user_status_white.xml
+12
-0
fragment_authentication_sign_up.xml
app/src/main/res/layout/fragment_authentication_sign_up.xml
+2
-2
fragment_chat_room.xml
app/src/main/res/layout/fragment_chat_room.xml
+12
-4
suggestion_member_item.xml
app/src/main/res/layout/suggestion_member_item.xml
+69
-0
suggestion_room_item.xml
app/src/main/res/layout/suggestion_room_item.xml
+45
-0
colors.xml
app/src/main/res/values/colors.xml
+3
-0
dimens.xml
app/src/main/res/values/dimens.xml
+3
-0
No files found.
app/src/main/java/chat/rocket/android/app/DrawableHelper.kt
View file @
436e7d60
...
...
@@ -5,6 +5,7 @@ import android.support.v4.graphics.drawable.DrawableCompat
import
android.widget.EditText
import
android.widget.TextView
import
chat.rocket.android.R
import
chat.rocket.common.model.UserStatus
object
DrawableHelper
{
...
...
@@ -78,7 +79,7 @@ object DrawableHelper {
* @param drawables The array of Drawable.
* @see compoundDrawable
*/
fun
compoundDrawables
(
textView
:
Array
<
EditText
>,
drawables
:
Array
<
Drawable
>)
{
fun
compoundDrawables
(
textView
:
Array
<
TextView
>,
drawables
:
Array
<
Drawable
>)
{
if
(
textView
.
size
!=
drawables
.
size
)
{
return
}
else
{
...
...
@@ -104,15 +105,15 @@ object DrawableHelper {
* @param context The context.
* @return The user status drawable.
*/
fun
getUserStatusDrawable
(
userStatus
:
String
,
context
:
Context
):
Drawable
{
fun
getUserStatusDrawable
(
userStatus
:
UserStatus
,
context
:
Context
):
Drawable
{
val
userStatusDrawable
=
getDrawableFromId
(
R
.
drawable
.
ic_user_status_black
,
context
).
mutate
()
wrapDrawable
(
userStatusDrawable
)
when
(
userStatus
)
{
// TODO: create a enum or check if it will come from the SDK
"online"
->
tintDrawable
(
userStatusDrawable
,
context
,
R
.
color
.
colorUserStatusOnline
)
"busy"
->
tintDrawable
(
userStatusDrawable
,
context
,
R
.
color
.
colorUserStatusBus
y
)
"away"
->
tintDrawable
(
userStatusDrawable
,
context
,
R
.
color
.
colorUserStatusAway
)
"offline"
->
tintDrawable
(
userStatusDrawable
,
context
,
R
.
color
.
colorUserStatusOffline
)
is
UserStatus
.
Online
->
tintDrawable
(
userStatusDrawable
,
context
,
R
.
color
.
colorUserStatusOnline
)
is
UserStatus
.
Busy
->
tintDrawable
(
userStatusDrawable
,
context
,
R
.
color
.
colorUserStatusBusy
)
is
UserStatus
.
Away
->
tintDrawable
(
userStatusDrawable
,
context
,
R
.
color
.
colorUserStatusAwa
y
)
is
UserStatus
.
Offline
->
tintDrawable
(
userStatusDrawable
,
context
,
R
.
color
.
colorUserStatusOffline
)
else
->
tintDrawable
(
userStatusDrawable
,
context
,
R
.
color
.
colorUserStatusOffline
)
}
return
userStatusDrawable
}
...
...
app/src/main/java/chat/rocket/android/app/User.kt
deleted
100644 → 0
View file @
a639df30
package
chat.rocket.android.app
data class
User
(
val
id
:
String
,
val
name
:
String
,
val
username
:
String
,
val
status
:
String
,
val
avatarUri
:
String
)
\ No newline at end of file
app/src/main/java/chat/rocket/android/authentication/signup/ui/SignupFragment.kt
View file @
436e7d60
...
...
@@ -58,7 +58,7 @@ class SignupFragment : Fragment(), SignupView {
setUpNewUserAgreementListener
()
button_sign_up
.
setOnClickListener
{
presenter
.
signup
(
text_name
.
textContent
,
text_username
.
textContent
,
text_password
.
textContent
,
text_email
.
textContent
)
presenter
.
signup
(
text_
user
name
.
textContent
,
text_username
.
textContent
,
text_password
.
textContent
,
text_email
.
textContent
)
}
}
...
...
@@ -69,8 +69,8 @@ class SignupFragment : Fragment(), SignupView {
override
fun
alertBlankName
()
{
AnimationHelper
.
vibrateSmartPhone
(
appContext
)
AnimationHelper
.
shakeView
(
text_name
)
text_name
.
requestFocus
()
AnimationHelper
.
shakeView
(
text_
user
name
)
text_
user
name
.
requestFocus
()
}
override
fun
alertBlankUsername
()
{
...
...
@@ -122,7 +122,7 @@ class SignupFragment : Fragment(), SignupView {
val
drawables
=
arrayOf
(
personDrawable
,
atDrawable
,
lockDrawable
,
emailDrawable
)
DrawableHelper
.
wrapDrawables
(
drawables
)
DrawableHelper
.
tintDrawables
(
drawables
,
appContext
,
R
.
color
.
colorDrawableTintGrey
)
DrawableHelper
.
compoundDrawables
(
arrayOf
(
text_name
,
text_username
,
text_password
,
text_email
),
drawables
)
DrawableHelper
.
compoundDrawables
(
arrayOf
(
text_
user
name
,
text_username
,
text_password
,
text_email
),
drawables
)
}
private
fun
setUpNewUserAgreementListener
()
{
...
...
@@ -149,7 +149,7 @@ class SignupFragment : Fragment(), SignupView {
private
fun
enableUserInput
(
value
:
Boolean
)
{
button_sign_up
.
isEnabled
=
value
text_name
.
isEnabled
=
value
text_
user
name
.
isEnabled
=
value
text_username
.
isEnabled
=
value
text_password
.
isEnabled
=
value
text_email
.
isEnabled
=
value
...
...
app/src/main/java/chat/rocket/android/chatroom/adapter/AutoCompleteType.kt
0 → 100644
View file @
436e7d60
package
chat.rocket.android.chatroom.adapter
import
android.support.annotation.IntDef
const
val
PEOPLE
=
0L
const
val
ROOMS
=
1L
@Retention
(
AnnotationRetention
.
SOURCE
)
@IntDef
(
value
=
[
PEOPLE
,
ROOMS
])
annotation
class
AutoCompleteType
app/src/main/java/chat/rocket/android/chatroom/adapter/PeopleSuggestionsAdapter.kt
0 → 100644
View file @
436e7d60
package
chat.rocket.android.chatroom.adapter
import
DrawableHelper
import
android.view.LayoutInflater
import
android.view.View
import
android.view.ViewGroup
import
android.widget.ImageView
import
android.widget.TextView
import
chat.rocket.android.R
import
chat.rocket.android.chatroom.adapter.PeopleSuggestionsAdapter.PeopleSuggestionViewHolder
import
chat.rocket.android.chatroom.viewmodel.PeopleViewModel
import
chat.rocket.android.util.extensions.setVisible
import
chat.rocket.android.widget.autocompletion.model.SuggestionModel
import
chat.rocket.android.widget.autocompletion.ui.BaseSuggestionViewHolder
import
chat.rocket.android.widget.autocompletion.ui.SuggestionsAdapter
import
chat.rocket.common.model.UserStatus
import
com.facebook.drawee.view.SimpleDraweeView
class
PeopleSuggestionsAdapter
:
SuggestionsAdapter
<
PeopleSuggestionViewHolder
>(
"@"
)
{
override
fun
onCreateViewHolder
(
parent
:
ViewGroup
,
viewType
:
Int
):
PeopleSuggestionViewHolder
{
val
view
=
LayoutInflater
.
from
(
parent
.
context
).
inflate
(
R
.
layout
.
suggestion_member_item
,
parent
,
false
)
return
PeopleSuggestionViewHolder
(
view
)
}
class
PeopleSuggestionViewHolder
(
view
:
View
)
:
BaseSuggestionViewHolder
(
view
)
{
override
fun
bind
(
item
:
SuggestionModel
,
itemClickListener
:
SuggestionsAdapter
.
ItemClickListener
?)
{
item
as
PeopleViewModel
with
(
itemView
)
{
val
username
=
itemView
.
findViewById
<
TextView
>(
R
.
id
.
text_username
)
val
name
=
itemView
.
findViewById
<
TextView
>(
R
.
id
.
text_name
)
val
avatar
=
itemView
.
findViewById
<
SimpleDraweeView
>(
R
.
id
.
image_avatar
)
val
statusView
=
itemView
.
findViewById
<
ImageView
>(
R
.
id
.
image_status
)
username
.
text
=
item
.
username
name
.
text
=
item
.
name
if
(
item
.
imageUri
.
isEmpty
())
{
avatar
.
setVisible
(
false
)
}
else
{
avatar
.
setVisible
(
true
)
avatar
.
setImageURI
(
item
.
imageUri
)
}
val
status
=
item
.
status
?:
UserStatus
.
Offline
()
val
statusDrawable
=
DrawableHelper
.
getUserStatusDrawable
(
status
,
itemView
.
context
)
statusView
.
setImageDrawable
(
statusDrawable
)
setOnClickListener
{
itemClickListener
?.
onClick
(
item
)
}
}
}
}
}
\ No newline at end of file
app/src/main/java/chat/rocket/android/chatroom/adapter/RoomSuggestionsAdapter.kt
0 → 100644
View file @
436e7d60
package
chat.rocket.android.chatroom.adapter
import
android.view.LayoutInflater
import
android.view.View
import
android.view.ViewGroup
import
android.widget.TextView
import
chat.rocket.android.R
import
chat.rocket.android.chatroom.adapter.RoomSuggestionsAdapter.RoomSuggestionsViewHolder
import
chat.rocket.android.chatroom.viewmodel.ChatRoomViewModel
import
chat.rocket.android.widget.autocompletion.model.SuggestionModel
import
chat.rocket.android.widget.autocompletion.ui.BaseSuggestionViewHolder
import
chat.rocket.android.widget.autocompletion.ui.SuggestionsAdapter
class
RoomSuggestionsAdapter
:
SuggestionsAdapter
<
RoomSuggestionsViewHolder
>(
"#"
)
{
override
fun
onCreateViewHolder
(
parent
:
ViewGroup
,
viewType
:
Int
):
RoomSuggestionsViewHolder
{
val
view
=
LayoutInflater
.
from
(
parent
.
context
).
inflate
(
R
.
layout
.
suggestion_room_item
,
parent
,
false
)
return
RoomSuggestionsViewHolder
(
view
)
}
class
RoomSuggestionsViewHolder
(
view
:
View
)
:
BaseSuggestionViewHolder
(
view
)
{
override
fun
bind
(
item
:
SuggestionModel
,
itemClickListener
:
SuggestionsAdapter
.
ItemClickListener
?)
{
item
as
ChatRoomViewModel
with
(
itemView
)
{
val
fullname
=
itemView
.
findViewById
<
TextView
>(
R
.
id
.
text_fullname
)
val
name
=
itemView
.
findViewById
<
TextView
>(
R
.
id
.
text_name
)
name
.
text
=
item
.
name
fullname
.
text
=
item
.
fullName
setOnClickListener
{
itemClickListener
?.
onClick
(
item
)
}
}
}
}
}
\ No newline at end of file
app/src/main/java/chat/rocket/android/chatroom/presentation/ChatRoomPresenter.kt
View file @
436e7d60
...
...
@@ -2,15 +2,23 @@ package chat.rocket.android.chatroom.presentation
import
android.net.Uri
import
chat.rocket.android.R
import
chat.rocket.android.chatroom.adapter.AutoCompleteType
import
chat.rocket.android.chatroom.adapter.PEOPLE
import
chat.rocket.android.chatroom.adapter.ROOMS
import
chat.rocket.android.chatroom.domain.UriInteractor
import
chat.rocket.android.chatroom.viewmodel.PeopleViewModel
import
chat.rocket.android.chatroom.viewmodel.ChatRoomViewModel
import
chat.rocket.android.chatroom.viewmodel.ViewModelMapper
import
chat.rocket.android.core.lifecycle.CancelStrategy
import
chat.rocket.android.helper.UrlHelper
import
chat.rocket.android.infrastructure.LocalRepository
import
chat.rocket.android.server.domain.*
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.UserStatus
import
chat.rocket.common.model.roomTypeOf
import
chat.rocket.common.util.ifNull
import
chat.rocket.core.internal.realtime.State
...
...
@@ -31,9 +39,13 @@ class ChatRoomPresenter @Inject constructor(private val view: ChatRoomView,
private
val
strategy
:
CancelStrategy
,
getSettingsInteractor
:
GetSettingsInteractor
,
private
val
serverInteractor
:
GetCurrentServerInteractor
,
private
val
getChatRoomsInteractor
:
GetChatRoomsInteractor
,
private
val
permissions
:
GetPermissionsInteractor
,
private
val
uriInteractor
:
UriInteractor
,
private
val
messagesRepository
:
MessagesRepository
,
private
val
usersRepository
:
UsersRepository
,
private
val
roomsRepository
:
RoomRepository
,
private
val
localRepository
:
LocalRepository
,
factory
:
ConnectionManagerFactory
,
private
val
mapper
:
ViewModelMapper
)
{
private
val
currentServer
=
serverInteractor
.
get
()
!!
...
...
@@ -60,7 +72,7 @@ class ChatRoomPresenter @Inject constructor(private val view: ChatRoomView,
// TODO: For now we are marking the room as read if we can get the messages (I mean, no exception occurs)
// but should mark only when the user see the first unread message.
markRoomAsRead
(
chatRoomId
)
val
messagesViewModels
=
mapper
.
map
(
messages
)
view
.
showMessages
(
messagesViewModels
)
...
...
@@ -97,7 +109,7 @@ class ChatRoomPresenter @Inject constructor(private val view: ChatRoomView,
ex
.
message
?.
let
{
view
.
showMessage
(
it
)
}.
ifNull
{
view
.
showGenericErrorMessage
()
view
.
showGenericErrorMessage
()
}
}
finally
{
view
.
enableSendMessageButton
()
...
...
@@ -340,7 +352,109 @@ class ChatRoomPresenter @Inject constructor(private val view: ChatRoomView,
}
}
fun
loadActiveMembers
(
chatRoomId
:
String
,
chatRoomType
:
String
,
offset
:
Long
=
0
,
filterSelfOut
:
Boolean
=
false
)
{
launchUI
(
strategy
)
{
try
{
val
members
=
client
.
getMembers
(
chatRoomId
,
roomTypeOf
(
chatRoomType
),
offset
,
50
).
result
usersRepository
.
saveAll
(
members
)
val
self
=
localRepository
.
get
(
LocalRepository
.
USERNAME_KEY
)
// Take at most the 100 most recent messages distinguished by user. Can return less.
val
recentMessages
=
messagesRepository
.
getRecentMessages
(
chatRoomId
,
100
)
.
filterNot
{
filterSelfOut
&&
it
.
sender
?.
username
==
self
}
val
activeUsers
=
mutableListOf
<
PeopleViewModel
>()
recentMessages
.
forEach
{
val
sender
=
it
.
sender
!!
val
username
=
sender
.
username
?:
""
val
name
=
sender
.
name
?:
""
val
avatarUrl
=
UrlHelper
.
getAvatarUrl
(
currentServer
,
username
)
val
found
=
members
.
firstOrNull
{
member
->
member
.
username
==
username
}
val
status
=
if
(
found
!=
null
)
found
.
status
else
UserStatus
.
Offline
()
val
searchList
=
mutableListOf
(
username
,
name
)
activeUsers
.
add
(
PeopleViewModel
(
avatarUrl
,
username
,
username
,
name
,
status
,
true
,
searchList
))
}
// Filter out from members list the active users.
val
others
=
members
.
filterNot
{
member
->
activeUsers
.
firstOrNull
{
it
.
username
==
member
.
username
}
!=
null
}
// Add found room members who're not active enough and add them in without pinning.
activeUsers
.
addAll
(
others
.
map
{
val
username
=
it
.
username
?:
""
val
name
=
it
.
name
?:
""
val
avatarUrl
=
UrlHelper
.
getAvatarUrl
(
currentServer
,
username
)
val
searchList
=
mutableListOf
(
username
,
name
)
PeopleViewModel
(
avatarUrl
,
username
,
username
,
name
,
it
.
status
,
true
,
searchList
)
})
view
.
populateMembers
(
activeUsers
)
}
catch
(
e
:
RocketChatException
)
{
Timber
.
e
(
e
)
}
}
}
fun
spotlight
(
query
:
String
,
@AutoCompleteType
type
:
Long
,
filterSelfOut
:
Boolean
=
false
)
{
launchUI
(
strategy
)
{
try
{
val
(
users
,
rooms
)
=
client
.
spotlight
(
query
)
when
(
type
)
{
PEOPLE
->
{
if
(
users
.
isNotEmpty
())
{
usersRepository
.
saveAll
(
users
)
}
val
self
=
localRepository
.
get
(
LocalRepository
.
USERNAME_KEY
)
view
.
populateMembers
(
users
.
map
{
val
username
=
it
.
username
?:
""
val
name
=
it
.
name
?:
""
val
searchList
=
mutableListOf
(
username
,
name
)
it
.
emails
?.
forEach
{
email
->
searchList
.
add
(
email
.
address
)
}
PeopleViewModel
(
UrlHelper
.
getAvatarUrl
(
currentServer
,
username
),
username
,
username
,
name
,
it
.
status
,
false
,
searchList
)
}.
filterNot
{
filterSelfOut
&&
self
!=
null
&&
self
==
it
.
text
})
}
ROOMS
->
{
if
(
rooms
.
isNotEmpty
())
{
roomsRepository
.
saveAll
(
rooms
)
}
view
.
populateRooms
(
rooms
.
map
{
val
fullName
=
it
.
fullName
?:
""
val
name
=
it
.
name
?:
""
val
searchList
=
mutableListOf
(
fullName
,
name
)
ChatRoomViewModel
(
name
,
fullName
,
name
,
searchList
)
})
}
}
}
catch
(
e
:
RocketChatException
)
{
Timber
.
e
(
e
)
}
}
}
<<<<<<<
61f4135f2f
be9097b2d53bf96aa9e55d47ec62d8
fun
toMembersList
(
chatRoomId
:
String
,
chatRoomType
:
String
)
=
navigator
.
toMembersList
(
chatRoomId
,
chatRoomType
)
=======
fun
loadChatRooms
()
{
launchUI
(
strategy
)
{
try
{
val
chatRooms
=
getChatRoomsInteractor
.
get
(
currentServer
).
map
{
chatRoom
->
val
name
=
chatRoom
.
name
val
fullName
=
chatRoom
.
fullName
?:
""
ChatRoomViewModel
(
text
=
name
,
name
=
name
,
fullName
=
fullName
,
searchList
=
listOf
(
name
,
fullName
)
)
}
view
.
populateRooms
(
chatRooms
)
}
catch
(
e
:
RocketChatException
)
{
Timber
.
e
(
e
)
}
}
}
>>>>>>>
Load
rooms
from
subscriptions
private
fun
updateMessage
(
streamedMessage
:
Message
)
{
launchUI
(
strategy
)
{
...
...
app/src/main/java/chat/rocket/android/chatroom/presentation/ChatRoomView.kt
View file @
436e7d60
...
...
@@ -2,6 +2,8 @@ package chat.rocket.android.chatroom.presentation
import
android.net.Uri
import
chat.rocket.android.chatroom.viewmodel.BaseViewModel
import
chat.rocket.android.chatroom.viewmodel.PeopleViewModel
import
chat.rocket.android.chatroom.viewmodel.ChatRoomViewModel
import
chat.rocket.android.core.behaviours.LoadingView
import
chat.rocket.android.core.behaviours.MessageView
import
chat.rocket.core.internal.realtime.State
...
...
@@ -100,4 +102,6 @@ interface ChatRoomView : LoadingView, MessageView {
fun
showInvalidFileSize
(
fileSize
:
Int
,
maxFileSize
:
Int
)
fun
showConnectionState
(
state
:
State
)
fun
populateMembers
(
members
:
List
<
PeopleViewModel
>)
fun
populateRooms
(
chatRooms
:
List
<
ChatRoomViewModel
>)
}
\ No newline at end of file
app/src/main/java/chat/rocket/android/chatroom/ui/ChatRoomFragment.kt
View file @
436e7d60
...
...
@@ -16,9 +16,14 @@ import android.support.v7.widget.RecyclerView
import
android.view.*
import
chat.rocket.android.R
import
chat.rocket.android.chatroom.adapter.ChatRoomAdapter
import
chat.rocket.android.chatroom.adapter.PEOPLE
import
chat.rocket.android.chatroom.adapter.PeopleSuggestionsAdapter
import
chat.rocket.android.chatroom.adapter.RoomSuggestionsAdapter
import
chat.rocket.android.chatroom.presentation.ChatRoomPresenter
import
chat.rocket.android.chatroom.presentation.ChatRoomView
import
chat.rocket.android.chatroom.viewmodel.BaseViewModel
import
chat.rocket.android.chatroom.viewmodel.ChatRoomViewModel
import
chat.rocket.android.chatroom.viewmodel.PeopleViewModel
import
chat.rocket.android.helper.EndlessRecyclerViewScrollListener
import
chat.rocket.android.helper.KeyboardHelper
import
chat.rocket.android.helper.MessageParser
...
...
@@ -62,9 +67,10 @@ class ChatRoomFragment : Fragment(), ChatRoomView, EmojiKeyboardPopup.Listener {
private
lateinit
var
chatRoomId
:
String
private
lateinit
var
chatRoomName
:
String
private
lateinit
var
chatRoomType
:
String
private
lateinit
var
emojiKeyboardPopup
:
EmojiKeyboardPopup
private
var
isChatRoomReadOnly
:
Boolean
=
false
private
lateinit
var
emojiKeyboardPopup
:
EmojiKeyboardPopup
private
lateinit
var
actionSnackbar
:
ActionSnackbar
private
var
citation
:
String
?
=
null
private
var
editingMessageId
:
String
?
=
null
...
...
@@ -102,10 +108,11 @@ class ChatRoomFragment : Fragment(), ChatRoomView, EmojiKeyboardPopup.Listener {
setupToolbar
(
chatRoomName
)
presenter
.
loadMessages
(
chatRoomId
,
chatRoomType
)
presenter
.
loadChatRooms
()
setupRecyclerView
()
setupFab
()
setupMessageComposer
()
setupSuggestionsView
()
setupActionSnackbar
()
}
...
...
@@ -174,6 +181,7 @@ class ChatRoomFragment : Fragment(), ChatRoomView, EmojiKeyboardPopup.Listener {
if
(
oldMessagesCount
==
0
&&
dataSet
.
isNotEmpty
())
{
recycler_view
.
scrollToPosition
(
0
)
}
presenter
.
loadActiveMembers
(
chatRoomId
,
chatRoomType
,
filterSelfOut
=
true
)
}
}
...
...
@@ -245,6 +253,14 @@ class ChatRoomFragment : Fragment(), ChatRoomView, EmojiKeyboardPopup.Listener {
override
fun
showGenericErrorMessage
()
=
showMessage
(
getString
(
R
.
string
.
msg_generic_error
))
override
fun
populateMembers
(
members
:
List
<
PeopleViewModel
>)
{
suggestions_view
.
addItems
(
"@"
,
members
)
}
override
fun
populateRooms
(
chatRooms
:
List
<
ChatRoomViewModel
>)
{
suggestions_view
.
addItems
(
"#"
,
chatRooms
)
}
override
fun
copyToClipboard
(
message
:
String
)
{
activity
?.
apply
{
val
clipboard
=
getSystemService
(
Context
.
CLIPBOARD_SERVICE
)
as
ClipboardManager
...
...
@@ -410,14 +426,30 @@ class ChatRoomFragment : Fragment(), ChatRoomView, EmojiKeyboardPopup.Listener {
}
}
private
fun
setupSuggestionsView
()
{
suggestions_view
.
anchor
(
text_message
)
.
bindTokenAdapter
(
PeopleSuggestionsAdapter
())
.
bindTokenAdapter
(
RoomSuggestionsAdapter
())
.
addSuggestionProviderAction
(
"@"
)
{
query
->
if
(
query
.
isNotEmpty
())
{
presenter
.
spotlight
(
query
,
PEOPLE
,
true
)
}
}
.
addSuggestionProviderAction
(
"#"
)
{
query
->
if
(
query
.
isNotEmpty
())
{
presenter
.
loadChatRooms
()
}
}
}
private
fun
openEmojiKeyboardPopup
()
{
if
(!
emojiKeyboardPopup
.
isShowing
()
)
{
if
(!
emojiKeyboardPopup
.
isShowing
)
{
// If keyboard is visible, simply show the popup
if
(
emojiKeyboardPopup
.
isKeyboardOpen
)
{
emojiKeyboardPopup
.
showAtBottom
()
}
else
{
// Open the text keyboard first and immediately after that show the emoji popup
text_message
.
setFocusableInTouchMode
(
true
)
text_message
.
isFocusableInTouchMode
=
true
text_message
.
requestFocus
()
emojiKeyboardPopup
.
showAtBottomPending
()
KeyboardHelper
.
showSoftKeyboard
(
text_message
)
...
...
app/src/main/java/chat/rocket/android/chatroom/viewmodel/ChatRoomViewModel.kt
0 → 100644
View file @
436e7d60
package
chat.rocket.android.chatroom.viewmodel
import
chat.rocket.android.widget.autocompletion.model.SuggestionModel
class
ChatRoomViewModel
(
text
:
String
,
val
fullName
:
String
,
val
name
:
String
,
searchList
:
List
<
String
>)
:
SuggestionModel
(
text
,
searchList
,
false
)
{
}
\ No newline at end of file
app/src/main/java/chat/rocket/android/chatroom/viewmodel/PeopleViewModel.kt
0 → 100644
View file @
436e7d60
package
chat.rocket.android.chatroom.viewmodel
import
chat.rocket.android.widget.autocompletion.model.SuggestionModel
import
chat.rocket.common.model.UserStatus
class
PeopleViewModel
(
val
imageUri
:
String
,
text
:
String
,
val
username
:
String
,
val
name
:
String
,
val
status
:
UserStatus
?,
pinned
:
Boolean
=
false
,
searchList
:
List
<
String
>)
:
SuggestionModel
(
text
,
searchList
,
pinned
)
{
override
fun
toString
():
String
{
return
"PeopleViewModel(imageUri='$imageUri', username='$username', name='$name', status=$status, pinned=$pinned)"
}
}
\ No newline at end of file
app/src/main/java/chat/rocket/android/dagger/module/AppModule.kt
View file @
436e7d60
...
...
@@ -185,7 +185,13 @@ class AppModule {
@Provides
@Singleton
fun
provideChatRoomsRepository
():
ChatRoomsRepository
{
fun
provideRoomRepository
():
RoomRepository
{
return
MemoryRoomRepository
()
}
@Provides
@Singleton
fun
provideChatRoomRepository
():
ChatRoomsRepository
{
return
MemoryChatRoomsRepository
()
}
...
...
@@ -207,6 +213,12 @@ class AppModule {
return
MemoryMessagesRepository
()
}
@Provides
@Singleton
fun
provideUserRepository
():
UsersRepository
{
return
MemoryUsersRepository
()
}
@Provides
@Singleton
fun
provideConfiguration
(
context
:
Application
,
client
:
OkHttpClient
):
SpannableConfiguration
{
...
...
app/src/main/java/chat/rocket/android/profile/ui/ProfileFragment.kt
View file @
436e7d60
...
...
@@ -129,20 +129,20 @@ class ProfileFragment : Fragment(), ProfileView, ActionMode.Callback {
private
fun
listenToChanges
()
{
Observables
.
combineLatest
(
text_name
.
asObservable
(),
text_username
.
asObservable
(),
text_email
.
asObservable
(),
text_avatar_url
.
asObservable
())
{
text_name
,
text_username
,
text_email
,
text_avatar_url
->
return
@combineLatest
(
text_name
.
toString
()
!=
currentName
||
text_username
.
toString
()
!=
currentUsername
||
text_username
.
asObservable
(),
text_email
.
asObservable
(),
text_avatar_url
.
asObservable
())
{
text_name
,
text_username
,
text_email
,
text_avatar_url
->
return
@combineLatest
(
text_name
.
toString
()
!=
currentName
||
text_username
.
toString
()
!=
currentUsername
||
text_email
.
toString
()
!=
currentEmail
||
(
text_avatar_url
.
toString
()
!=
""
&&
text_avatar_url
.
toString
()!=
currentAvatar
))
}.
subscribe
({
isValid
->
if
(
isValid
)
{
startActionMode
()
}
else
{
finishActionMode
()
}
})
(
text_avatar_url
.
toString
()
!=
""
&&
text_avatar_url
.
toString
()
!=
currentAvatar
))
}.
subscribe
({
isValid
->
if
(
isValid
)
{
startActionMode
()
}
else
{
finishActionMode
()
}
})
}
private
fun
startActionMode
()
{
...
...
@@ -154,7 +154,7 @@ class ProfileFragment : Fragment(), ProfileView, ActionMode.Callback {
private
fun
finishActionMode
()
=
actionMode
?.
finish
()
private
fun
enableUserInput
(
value
:
Boolean
)
{
text_name
.
isEnabled
=
value
text_
user
name
.
isEnabled
=
value
text_username
.
isEnabled
=
value
text_email
.
isEnabled
=
value
text_avatar_url
.
isEnabled
=
value
...
...
app/src/main/java/chat/rocket/android/server/domain/GetChatRoomsInteractor.kt
View file @
436e7d60
...
...
@@ -6,6 +6,14 @@ import kotlinx.coroutines.experimental.withContext
import
javax.inject.Inject
class
GetChatRoomsInteractor
@Inject
constructor
(
private
val
repository
:
ChatRoomsRepository
)
{
/**
* Get all ChatRoom objects.
*
* @param url The server url.
*
* @return All the ChatRoom objects.
*/
fun
get
(
url
:
String
)
=
repository
.
get
(
url
)
/**
...
...
app/src/main/java/chat/rocket/android/server/domain/MessagesRepository.kt
View file @
436e7d60
...
...
@@ -8,6 +8,7 @@ interface MessagesRepository {
* Get message by its message id.
*
* @param id The id of the message to get.
*
* @return The Message object given by the id or null if message wasn't found.
*/
fun
getById
(
id
:
String
):
Message
?
...
...
@@ -20,8 +21,19 @@ interface MessagesRepository {
*/
fun
getByRoomId
(
rid
:
String
):
List
<
Message
>
/**
* Get most recent messages up to count different users.
*
* @param rid The id of the room the messages are.
* @param count The count last messages to get.
*
* @return List of last count messages.
*/
fun
getRecentMessages
(
rid
:
String
,
count
:
Long
):
List
<
Message
>
/**
* Get all messages. Use carefully!
*
* @return All messages or an empty list.
*/
fun
getAll
():
List
<
Message
>
...
...
app/src/main/java/chat/rocket/android/server/domain/RoomRepository.kt
0 → 100644
View file @
436e7d60
package
chat.rocket.android.server.domain
import
chat.rocket.common.model.RoomType
import
chat.rocket.core.model.Room
interface
RoomRepository
{
/**
* Get all rooms. Use carefully!
*
* @return All rooms or an empty list.
*/
fun
getAll
():
List
<
Room
>
fun
get
(
query
:
Query
.()
->
Unit
):
List
<
Room
>
/**
* Save a single room object.
*
* @param room The room object to save.
*/
fun
save
(
room
:
Room
)
/**
* Save a list of rooms.
*
* @param roomList The list of rooms to save.
*/
fun
saveAll
(
roomList
:
List
<
Room
>)
/**
* Removes all rooms.
*/
fun
clear
()
data class
Query
(
var
id
:
String
?
=
null
,
var
name
:
String
?
=
null
,
var
fullName
:
String
?
=
null
,
var
type
:
RoomType
?
=
null
,
var
readonly
:
Boolean
?
=
null
)
}
\ No newline at end of file
app/src/main/java/chat/rocket/android/server/domain/UsersRepository.kt
0 → 100644
View file @
436e7d60
package
chat.rocket.android.server.domain
import
chat.rocket.common.model.Email
import
chat.rocket.common.model.User
import
chat.rocket.common.model.UserStatus
interface
UsersRepository
{
/**
* Get all users. Use carefully!
*
* @return All users or an empty list.
*/
fun
getAll
():
List
<
User
>
fun
get
(
query
:
Query
.()
->
Unit
):
List
<
User
>
/**
* Save a single user object.
*
* @param user The user object to save.
*/
fun
save
(
user
:
User
)
/**
* Save a list of users.
*
* @param users The list of users to save.
*/
fun
saveAll
(
userList
:
List
<
User
>)
/**
* Removes all users.
*/
fun
clear
()
data class
Query
(
var
id
:
String
?
=
null
,
var
name
:
String
?
=
null
,
var
username
:
String
?
=
null
,
var
emails
:
List
<
Email
>?
=
null
,
var
utfOffset
:
Float
?
=
null
,
var
status
:
UserStatus
?
=
null
,
var
limit
:
Long
=
0L
)
}
\ No newline at end of file
app/src/main/java/chat/rocket/android/server/infraestructure/MemoryMessagesRepository.kt
View file @
436e7d60
...
...
@@ -15,6 +15,11 @@ class MemoryMessagesRepository : MessagesRepository {
return
messages
.
filter
{
it
.
value
.
roomId
==
rid
}.
values
.
toList
()
}
override
fun
getRecentMessages
(
rid
:
String
,
count
:
Long
):
List
<
Message
>
{
return
getByRoomId
(
rid
).
sortedByDescending
{
it
.
timestamp
}
.
distinctBy
{
it
.
sender
}.
take
(
count
.
toInt
())
}
override
fun
getAll
():
List
<
Message
>
=
messages
.
values
.
toList
()
override
fun
save
(
message
:
Message
)
{
...
...
app/src/main/java/chat/rocket/android/server/infraestructure/MemoryRoomRepository.kt
0 → 100644
View file @
436e7d60
package
chat.rocket.android.server.infraestructure
import
chat.rocket.android.server.domain.RoomRepository
import
chat.rocket.android.server.domain.RoomRepository.Query
import
chat.rocket.core.model.Room
import
java.util.concurrent.CopyOnWriteArrayList
class
MemoryRoomRepository
:
RoomRepository
{
private
val
rooms
=
CopyOnWriteArrayList
<
Room
>()
override
fun
getAll
()
=
rooms
.
toList
()
override
fun
get
(
query
:
Query
.()
->
Unit
):
List
<
Room
>
{
val
q
=
Query
().
apply
(
query
)
return
rooms
.
filter
{
with
(
q
)
{
if
(
name
!=
null
&&
it
.
name
?.
contains
(
name
!!
.
toRegex
())
==
true
)
return
@filter
false
if
(
fullName
!=
null
&&
it
.
fullName
?.
contains
(
fullName
!!
.
toRegex
())
==
true
)
return
@filter
false
if
(
id
!=
null
&&
id
==
it
.
id
)
return
@filter
false
if
(
readonly
!=
null
&&
readonly
==
it
.
readonly
)
return
@filter
false
if
(
type
!=
null
&&
type
==
it
.
type
)
return
@filter
false
return
@filter
true
}
}
}
override
fun
save
(
room
:
Room
)
{
rooms
.
addIfAbsent
(
room
)
}
override
fun
saveAll
(
roomList
:
List
<
Room
>)
{
rooms
.
addAllAbsent
(
roomList
)
}
override
fun
clear
()
{
rooms
.
clear
()
}
}
\ No newline at end of file
app/src/main/java/chat/rocket/android/server/infraestructure/MemoryUsersRepository.kt
0 → 100644
View file @
436e7d60
package
chat.rocket.android.server.infraestructure
import
chat.rocket.android.server.domain.UsersRepository
import
chat.rocket.android.server.domain.UsersRepository.Query
import
chat.rocket.common.model.User
import
java.util.concurrent.CopyOnWriteArrayList
class
MemoryUsersRepository
:
UsersRepository
{
private
val
users
=
CopyOnWriteArrayList
<
User
>()
override
fun
getAll
():
List
<
User
>
{
return
users
.
toList
()
}
override
fun
get
(
query
:
Query
.()
->
Unit
):
List
<
User
>
{
val
q
=
Query
().
apply
(
query
)
return
users
.
filter
{
with
(
q
)
{
if
(
name
!=
null
&&
it
.
name
?.
contains
(
name
!!
.
toRegex
())
==
true
)
return
@filter
false
if
(
username
!=
null
&&
it
.
username
?.
contains
(
username
!!
.
toRegex
())
==
true
)
return
@filter
false
if
(
id
!=
null
&&
id
==
it
.
id
)
return
@filter
false
if
(
status
!=
null
&&
status
==
it
.
status
)
return
@filter
false
return
@filter
true
}
}
}
override
fun
save
(
user
:
User
)
{
users
.
addIfAbsent
(
user
)
}
override
fun
saveAll
(
userList
:
List
<
User
>)
{
users
.
addAllAbsent
(
userList
)
}
override
fun
clear
()
{
this
.
users
.
clear
()
}
}
\ No newline at end of file
app/src/main/java/chat/rocket/android/widget/autocompletion/model/SuggestionModel.kt
0 → 100644
View file @
436e7d60
package
chat.rocket.android.widget.autocompletion.model
abstract
class
SuggestionModel
(
val
text
:
String
,
// This is the text key for searches, must be unique.
val
searchList
:
List
<
String
>
=
emptyList
(),
// Where to search for matches.
val
pinned
:
Boolean
=
false
/* If pinned item will have priority to show */
)
{
override
fun
equals
(
other
:
Any
?):
Boolean
{
if
(
this
===
other
)
return
true
if
(
other
!
is
SuggestionModel
)
return
false
if
(
text
!=
other
.
text
)
return
false
return
true
}
override
fun
hashCode
():
Int
{
return
text
.
hashCode
()
}
}
\ No newline at end of file
app/src/main/java/chat/rocket/android/widget/autocompletion/repository/LocalSuggestionProvider.kt
0 → 100644
View file @
436e7d60
package
chat.rocket.android.widget.autocompletion.repository
interface
LocalSuggestionProvider
{
fun
find
(
prefix
:
String
)
}
\ No newline at end of file
app/src/main/java/chat/rocket/android/widget/autocompletion/strategy/CompletionStrategy.kt
0 → 100644
View file @
436e7d60
package
chat.rocket.android.widget.autocompletion.strategy
import
chat.rocket.android.widget.autocompletion.model.SuggestionModel
interface
CompletionStrategy
{
fun
getItem
(
prefix
:
String
,
position
:
Int
):
SuggestionModel
fun
autocompleteItems
(
prefix
:
String
):
List
<
SuggestionModel
>
fun
addAll
(
list
:
List
<
SuggestionModel
>)
fun
size
():
Int
}
\ No newline at end of file
app/src/main/java/chat/rocket/android/widget/autocompletion/strategy/regex/StringMatchingCompletionStrategy.kt
0 → 100644
View file @
436e7d60
package
chat.rocket.android.widget.autocompletion.strategy.regex
import
chat.rocket.android.widget.autocompletion.model.SuggestionModel
import
chat.rocket.android.widget.autocompletion.strategy.CompletionStrategy
import
java.util.concurrent.CopyOnWriteArrayList
internal
class
StringMatchingCompletionStrategy
:
CompletionStrategy
{
private
val
list
=
CopyOnWriteArrayList
<
SuggestionModel
>()
override
fun
autocompleteItems
(
prefix
:
String
):
List
<
SuggestionModel
>
{
return
list
.
filter
{
it
.
searchList
.
forEach
{
word
->
if
(
word
.
contains
(
prefix
,
ignoreCase
=
true
))
{
return
@filter
true
}
}
false
}.
sortedByDescending
{
it
.
pinned
}.
take
(
5
)
}
override
fun
addAll
(
list
:
List
<
SuggestionModel
>)
{
// this.list.removeAll { !it.pinned }
this
.
list
.
addAllAbsent
(
list
)
}
override
fun
getItem
(
prefix
:
String
,
position
:
Int
):
SuggestionModel
{
return
list
[
position
]
}
override
fun
size
():
Int
{
return
list
.
size
}
}
\ No newline at end of file
app/src/main/java/chat/rocket/android/widget/autocompletion/strategy/trie/TrieCompletionStrategy.kt
0 → 100644
View file @
436e7d60
package
chat.rocket.android.widget.autocompletion.strategy.trie
import
chat.rocket.android.widget.autocompletion.model.SuggestionModel
import
chat.rocket.android.widget.autocompletion.strategy.CompletionStrategy
import
chat.rocket.android.widget.autocompletion.strategy.trie.data.Trie
class
TrieCompletionStrategy
:
CompletionStrategy
{
private
val
items
=
mutableListOf
<
SuggestionModel
>()
private
val
trie
=
Trie
()
override
fun
getItem
(
prefix
:
String
,
position
:
Int
):
SuggestionModel
{
val
item
:
SuggestionModel
if
(
prefix
.
isEmpty
())
{
item
=
items
[
position
]
}
else
{
item
=
autocompleteItems
(
prefix
)[
position
]
}
return
item
}
override
fun
autocompleteItems
(
prefix
:
String
)
=
trie
.
autocompleteItems
(
prefix
)
override
fun
addAll
(
list
:
List
<
SuggestionModel
>)
{
items
.
addAll
(
list
)
list
.
forEach
{
trie
.
insert
(
it
)
}
}
override
fun
size
()
=
items
.
size
}
\ No newline at end of file
app/src/main/java/chat/rocket/android/widget/autocompletion/strategy/trie/data/Trie.kt
0 → 100644
View file @
436e7d60
package
chat.rocket.android.widget.autocompletion.strategy.trie.data
import
chat.rocket.android.widget.autocompletion.model.SuggestionModel
internal
class
Trie
{
private
val
root
=
TrieNode
(
' '
)
private
var
count
=
0
fun
insert
(
item
:
SuggestionModel
)
{
val
sanitizedWord
=
item
.
text
.
trim
().
toLowerCase
()
// Word exists, bail out.
if
(
search
(
sanitizedWord
))
return
var
current
=
root
sanitizedWord
.
forEach
{
ch
->
val
child
=
current
.
getChild
(
ch
)
if
(
child
==
null
)
{
val
node
=
TrieNode
(
ch
,
current
)
current
.
children
[
ch
]
=
node
current
=
node
count
++
}
else
{
current
=
child
}
}
// Set last node as leaf.
if
(
current
!=
root
)
{
current
.
isLeaf
=
true
current
.
item
=
item
}
}
fun
search
(
word
:
String
):
Boolean
{
val
sanitizedWord
=
word
.
trim
().
toLowerCase
()
var
current
=
root
sanitizedWord
.
forEach
{
ch
->
val
child
=
current
.
getChild
(
ch
)
if
(
child
==
null
)
{
return
false
}
current
=
child
}
if
(
current
.
isLeaf
)
{
return
true
}
return
false
}
fun
autocomplete
(
prefix
:
String
):
List
<
String
>
{
val
sanitizedPrefix
=
prefix
.
trim
().
toLowerCase
()
var
lastNode
:
TrieNode
?
=
root
sanitizedPrefix
.
forEach
{
ch
->
lastNode
=
lastNode
?.
getChild
(
ch
)
if
(
lastNode
==
null
)
return
emptyList
()
}
return
lastNode
!!
.
getWords
()
}
fun
autocompleteItems
(
prefix
:
String
):
List
<
SuggestionModel
>
{
val
sanitizedPrefix
=
prefix
.
trim
().
toLowerCase
()
var
lastNode
:
TrieNode
?
=
root
sanitizedPrefix
.
forEach
{
ch
->
lastNode
=
lastNode
?.
getChild
(
ch
)
if
(
lastNode
==
null
)
return
emptyList
()
}
return
lastNode
!!
.
getItems
()
}
fun
getCount
()
=
count
}
\ No newline at end of file
app/src/main/java/chat/rocket/android/widget/autocompletion/strategy/trie/data/TrieNode.kt
0 → 100644
View file @
436e7d60
package
chat.rocket.android.widget.autocompletion.strategy.trie.data
import
chat.rocket.android.widget.autocompletion.model.SuggestionModel
internal
class
TrieNode
(
internal
var
data
:
Char
,
internal
var
parent
:
TrieNode
?
=
null
,
internal
var
isLeaf
:
Boolean
=
false
,
internal
var
item
:
SuggestionModel
?
=
null
)
{
val
children
=
hashMapOf
<
Char
,
TrieNode
>()
fun
getChild
(
c
:
Char
):
TrieNode
?
{
children
.
forEach
{
if
(
it
.
key
==
c
)
return
it
.
value
}
return
null
}
fun
getWords
():
List
<
String
>
{
val
list
=
arrayListOf
<
String
>()
if
(
isLeaf
)
{
list
.
add
(
toString
())
}
children
.
forEach
{
node
->
node
.
value
.
let
{
list
.
addAll
(
it
.
getWords
())
}
}
return
list
}
class
X
:
SuggestionModel
(
""
)
fun
getItems
():
List
<
SuggestionModel
>
{
val
list
=
arrayListOf
<
SuggestionModel
>()
if
(
isLeaf
)
{
list
.
add
(
item
!!
)
}
children
.
forEach
{
node
->
node
.
value
.
let
{
list
.
addAll
(
it
.
getItems
())
}
}
return
list
}
override
fun
toString
():
String
=
if
(
parent
==
null
)
""
else
"${parent.toString()}$data"
}
\ No newline at end of file
app/src/main/java/chat/rocket/android/widget/autocompletion/ui/BaseSuggestionViewHolder.kt
0 → 100644
View file @
436e7d60
package
chat.rocket.android.widget.autocompletion.ui
import
android.support.v7.widget.RecyclerView
import
android.view.View
import
chat.rocket.android.widget.autocompletion.model.SuggestionModel
abstract
class
BaseSuggestionViewHolder
(
view
:
View
)
:
RecyclerView
.
ViewHolder
(
view
)
{
abstract
fun
bind
(
item
:
SuggestionModel
,
itemClickListener
:
SuggestionsAdapter
.
ItemClickListener
?)
}
\ No newline at end of file
app/src/main/java/chat/rocket/android/widget/autocompletion/ui/PopupRecyclerView.kt
0 → 100644
View file @
436e7d60
package
chat.rocket.android.widget.autocompletion.ui
import
android.content.Context
import
android.support.v7.widget.RecyclerView
import
android.util.AttributeSet
import
android.util.DisplayMetrics
import
android.view.WindowManager
import
chat.rocket.android.R
internal
class
PopupRecyclerView
:
RecyclerView
{
private
var
displayWidth
:
Int
=
0
constructor
(
context
:
Context
?)
:
this
(
context
,
null
)
constructor
(
context
:
Context
?,
attrs
:
AttributeSet
?)
:
this
(
context
,
attrs
,
0
)
constructor
(
context
:
Context
?,
attrs
:
AttributeSet
?,
defStyle
:
Int
)
:
super
(
context
,
attrs
,
defStyle
)
{
val
wm
=
context
!!
.
getSystemService
(
Context
.
WINDOW_SERVICE
)
as
WindowManager
val
display
=
wm
.
defaultDisplay
val
size
=
DisplayMetrics
()
display
.
getMetrics
(
size
)
val
screenWidth
=
size
.
widthPixels
displayWidth
=
screenWidth
}
override
fun
onMeasure
(
widthSpec
:
Int
,
heightSpec
:
Int
)
{
val
hSpec
=
MeasureSpec
.
makeMeasureSpec
(
resources
.
getDimensionPixelSize
(
R
.
dimen
.
popup_max_height
),
MeasureSpec
.
AT_MOST
)
val
wSpec
=
MeasureSpec
.
makeMeasureSpec
(
displayWidth
,
MeasureSpec
.
EXACTLY
)
super
.
onMeasure
(
wSpec
,
hSpec
)
}
override
fun
onLayout
(
changed
:
Boolean
,
l
:
Int
,
t
:
Int
,
r
:
Int
,
b
:
Int
)
{
super
.
onLayout
(
changed
,
l
+
40
,
t
,
r
-
40
,
b
)
}
}
\ No newline at end of file
app/src/main/java/chat/rocket/android/widget/autocompletion/ui/SuggestionsAdapter.kt
0 → 100644
View file @
436e7d60
package
chat.rocket.android.widget.autocompletion.ui
import
android.support.v7.widget.RecyclerView
import
chat.rocket.android.widget.autocompletion.model.SuggestionModel
import
chat.rocket.android.widget.autocompletion.strategy.CompletionStrategy
import
chat.rocket.android.widget.autocompletion.strategy.regex.StringMatchingCompletionStrategy
import
java.lang.reflect.Type
import
kotlin.properties.Delegates
abstract
class
SuggestionsAdapter
<
VH
:
BaseSuggestionViewHolder
>(
val
token
:
String
)
:
RecyclerView
.
Adapter
<
VH
>()
{
private
val
strategy
:
CompletionStrategy
=
StringMatchingCompletionStrategy
()
private
var
itemType
:
Type
?
=
null
private
var
itemClickListener
:
ItemClickListener
?
=
null
private
var
providerExternal
:
((
query
:
String
)
->
Unit
)?
=
null
private
var
prefix
:
String
by
Delegates
.
observable
(
""
,
{
_
,
_
,
_
->
strategy
.
autocompleteItems
(
prefix
)
notifyItemRangeChanged
(
0
,
5
)
})
init
{
setHasStableIds
(
true
)
}
override
fun
getItemId
(
position
:
Int
):
Long
{
return
getItem
(
position
).
text
.
hashCode
().
toLong
()
}
override
fun
onBindViewHolder
(
holder
:
VH
,
position
:
Int
)
{
holder
.
bind
(
getItem
(
position
),
itemClickListener
)
}
override
fun
getItemCount
()
=
strategy
.
autocompleteItems
(
prefix
).
size
private
fun
getItem
(
position
:
Int
):
SuggestionModel
{
return
strategy
.
autocompleteItems
(
prefix
)[
position
]
}
fun
autocomplete
(
prefix
:
String
)
{
this
.
prefix
=
prefix
.
toLowerCase
().
trim
()
}
fun
addItems
(
list
:
List
<
SuggestionModel
>)
{
strategy
.
addAll
(
list
)
// Since we've just added new items we should check for possible new completion suggestions.
strategy
.
autocompleteItems
(
prefix
)
notifyItemRangeChanged
(
0
,
5
)
}
fun
setOnClickListener
(
clickListener
:
ItemClickListener
)
{
this
.
itemClickListener
=
clickListener
}
fun
hasItemClickListener
()
=
itemClickListener
!=
null
fun
prefix
()
=
prefix
fun
cancel
()
{
strategy
.
addAll
(
emptyList
())
strategy
.
autocompleteItems
(
prefix
)
notifyDataSetChanged
()
}
interface
ItemClickListener
{
fun
onClick
(
item
:
SuggestionModel
)
}
}
\ No newline at end of file
app/src/main/java/chat/rocket/android/widget/autocompletion/ui/SuggestionsView.kt
0 → 100644
View file @
436e7d60
package
chat.rocket.android.widget.autocompletion.ui
import
android.content.Context
import
android.support.transition.Slide
import
android.support.transition.TransitionManager
import
android.support.v7.widget.DefaultItemAnimator
import
android.support.v7.widget.LinearLayoutManager
import
android.support.v7.widget.RecyclerView
import
android.text.Editable
import
android.text.InputType
import
android.text.TextWatcher
import
android.util.AttributeSet
import
android.view.Gravity
import
android.view.View
import
android.widget.EditText
import
android.widget.FrameLayout
import
chat.rocket.android.widget.autocompletion.model.SuggestionModel
import
java.lang.ref.WeakReference
import
java.util.concurrent.atomic.AtomicInteger
/**
* This is a special index that means we're not at an autocompleting state.
*/
private
const
val
NO_STATE_INDEX
=
0
class
SuggestionsView
:
FrameLayout
,
TextWatcher
{
private
val
recyclerView
:
RecyclerView
// Maps tokens to their respecive adapters.
private
val
adaptersByToken
=
hashMapOf
<
String
,
SuggestionsAdapter
<
out
BaseSuggestionViewHolder
>>()
private
val
externalProvidersByToken
=
hashMapOf
<
String
,
((
query
:
String
)
->
Unit
)>()
private
val
localProvidersByToken
=
hashMapOf
<
String
,
HashMap
<
String
,
List
<
SuggestionModel
>>>()
private
var
editor
:
WeakReference
<
EditText
>?
=
null
private
var
completionStartIndex
=
AtomicInteger
(
NO_STATE_INDEX
)
companion
object
{
private
val
SLIDE_TRANSITION
=
Slide
(
Gravity
.
BOTTOM
).
setDuration
(
200
)
}
constructor
(
context
:
Context
)
:
this
(
context
,
null
)
constructor
(
context
:
Context
,
attrs
:
AttributeSet
?)
:
this
(
context
,
attrs
,
0
)
constructor
(
context
:
Context
,
attrs
:
AttributeSet
?,
defStyleAttr
:
Int
)
:
super
(
context
,
attrs
,
defStyleAttr
,
0
)
{
recyclerView
=
RecyclerView
(
context
)
val
layoutManager
=
LinearLayoutManager
(
context
,
LinearLayoutManager
.
VERTICAL
,
false
)
recyclerView
.
itemAnimator
=
DefaultItemAnimator
()
recyclerView
.
layoutManager
=
layoutManager
recyclerView
.
visibility
=
View
.
GONE
addView
(
recyclerView
)
}
override
fun
afterTextChanged
(
s
:
Editable
)
{
}
override
fun
beforeTextChanged
(
s
:
CharSequence
,
start
:
Int
,
count
:
Int
,
after
:
Int
)
{
// If we have a deletion.
if
(
after
==
0
)
{
val
deleted
=
s
.
subSequence
(
start
,
start
+
count
).
toString
()
if
(
adaptersByToken
.
containsKey
(
deleted
)
&&
completionStartIndex
.
get
()
>
NO_STATE_INDEX
)
{
// We have removed the '@', '#' or any other action token so halt completion.
cancelSuggestions
(
true
)
}
}
}
override
fun
onTextChanged
(
s
:
CharSequence
,
start
:
Int
,
before
:
Int
,
count
:
Int
)
{
// If we don't have any adapter bound to any token bail out.
if
(
adaptersByToken
.
isEmpty
())
return
val
new
=
s
.
subSequence
(
start
,
start
+
count
).
toString
()
if
(
adaptersByToken
.
containsKey
(
new
))
{
swapAdapter
(
getAdapterForToken
(
new
)
!!
)
completionStartIndex
.
compareAndSet
(
NO_STATE_INDEX
,
start
+
1
)
editor
?.
let
{
// Disable keyboard suggestions when autocompleting.
val
editText
=
it
.
get
()
if
(
editText
!=
null
)
{
editText
.
inputType
=
editText
.
inputType
or
InputType
.
TYPE_TEXT_VARIATION_FILTER
expand
()
}
}
}
if
(
new
.
startsWith
(
" "
))
{
// just halts the completion execution
cancelSuggestions
(
false
)
return
}
val
prefixEndIndex
=
editor
?.
get
()
?.
selectionStart
?:
NO_STATE_INDEX
if
(
prefixEndIndex
==
NO_STATE_INDEX
||
prefixEndIndex
<
completionStartIndex
.
get
())
return
val
prefix
=
s
.
subSequence
(
completionStartIndex
.
get
(),
editor
?.
get
()
?.
selectionStart
?:
completionStartIndex
.
get
()).
toString
()
recyclerView
.
adapter
?.
let
{
it
as
SuggestionsAdapter
// we need to look up only after the '@'
it
.
autocomplete
(
prefix
)
val
cacheMap
=
localProvidersByToken
[
it
.
token
]
if
(
cacheMap
!=
null
&&
cacheMap
[
prefix
]
!=
null
)
{
it
.
addItems
(
cacheMap
[
prefix
]
!!
)
}
else
{
// fetch more suggestions from an external source if any
externalProvidersByToken
[
it
.
token
]
?.
invoke
(
prefix
)
}
}
}
private
fun
swapAdapter
(
adapter
:
SuggestionsAdapter
<
*
>):
SuggestionsView
{
recyclerView
.
adapter
=
adapter
// Don't override if user set an item click listener already/
if
(!
adapter
.
hasItemClickListener
())
{
setOnItemClickListener
(
adapter
)
{
// set default item click behavior
}
}
return
this
}
fun
getAdapterForToken
(
token
:
String
):
SuggestionsAdapter
<
*
>?
=
adaptersByToken
.
get
(
token
)
fun
anchor
(
editText
:
EditText
):
SuggestionsView
{
editText
.
removeTextChangedListener
(
this
)
editText
.
addTextChangedListener
(
this
)
editor
=
WeakReference
(
editText
)
return
this
}
fun
bindTokenAdapter
(
adapter
:
SuggestionsAdapter
<
*
>):
SuggestionsView
{
adaptersByToken
.
getOrPut
(
adapter
.
token
,
{
adapter
})
return
this
}
fun
addItems
(
token
:
String
,
list
:
List
<
SuggestionModel
>):
SuggestionsView
{
if
(
list
.
isNotEmpty
())
{
val
adapter
=
adapter
(
token
)
localProvidersByToken
.
getOrPut
(
token
,
{
hashMapOf
()
})
.
put
(
adapter
.
prefix
(),
list
)
if
(
completionStartIndex
.
get
()
>
NO_STATE_INDEX
&&
adapter
.
itemCount
==
0
)
expand
()
adapter
.
addItems
(
list
)
}
return
this
}
fun
setOnItemClickListener
(
tokenAdapter
:
SuggestionsAdapter
<
*
>,
clickListener
:
(
item
:
SuggestionModel
)
->
Unit
):
SuggestionsView
{
tokenAdapter
.
setOnClickListener
(
object
:
SuggestionsAdapter
.
ItemClickListener
{
override
fun
onClick
(
item
:
SuggestionModel
)
{
insertSuggestionOnEditor
(
item
)
clickListener
.
invoke
(
item
)
cancelSuggestions
(
true
)
collapse
()
}
})
return
this
}
fun
addSuggestionProviderAction
(
token
:
String
,
provider
:
(
query
:
String
)
->
Unit
):
SuggestionsView
{
externalProvidersByToken
.
getOrPut
(
token
,
{
provider
})
return
this
}
private
fun
adapter
(
token
:
String
):
SuggestionsAdapter
<
*
>
{
return
adaptersByToken
[
token
]
?:
throw
IllegalStateException
(
"no adapter binds to token \"$token\""
)
}
private
fun
cancelSuggestions
(
haltCompletion
:
Boolean
)
{
// Reset completion start index only if we've deleted the token that triggered completion or
// we finished the completion process.
if
(
haltCompletion
)
{
completionStartIndex
.
set
(
NO_STATE_INDEX
)
}
collapse
()
// Re-enable keyboard suggestions.
val
editText
=
editor
?.
get
()
if
(
editText
!=
null
)
{
editText
.
inputType
=
editText
.
inputType
and
InputType
.
TYPE_TEXT_VARIATION_FILTER
.
inv
()
}
}
private
fun
insertSuggestionOnEditor
(
item
:
SuggestionModel
)
{
editor
?.
get
()
?.
let
{
val
suggestionText
=
item
.
text
it
.
text
.
replace
(
completionStartIndex
.
get
(),
it
.
selectionStart
,
"$suggestionText "
)
}
}
private
fun
collapse
()
{
TransitionManager
.
beginDelayedTransition
(
this
,
SLIDE_TRANSITION
)
recyclerView
.
visibility
=
View
.
GONE
}
private
fun
expand
()
{
TransitionManager
.
beginDelayedTransition
(
this
,
SLIDE_TRANSITION
)
recyclerView
.
visibility
=
View
.
VISIBLE
}
}
\ No newline at end of file
app/src/main/res/drawable/user_status_white.xml
0 → 100644
View file @
436e7d60
<?xml version="1.0" encoding="utf-8"?>
<vector
xmlns:android=
"http://schemas.android.com/apk/res/android"
android:width=
"10dp"
android:height=
"10dp"
android:viewportWidth=
"10.0"
android:viewportHeight=
"10.0"
>
<path
android:pathData=
"M5,5m-5,0a5,5 0,1 1,10 0a5,5 0,1 1,-10 0"
android:fillType=
"evenOdd"
android:fillColor=
"#FFFFFF"
android:strokeWidth=
"1"
/>
</vector>
\ No newline at end of file
app/src/main/res/layout/fragment_authentication_sign_up.xml
View file @
436e7d60
...
...
@@ -16,7 +16,7 @@
app:layout_constraintTop_toTopOf=
"parent"
/>
<EditText
android:id=
"@+id/text_name"
android:id=
"@+id/text_
user
name"
style=
"@style/Authentication.EditText"
android:layout_marginTop=
"32dp"
android:drawableStart=
"@drawable/ic_person_black_24dp"
...
...
@@ -37,7 +37,7 @@
android:inputType=
"text"
app:layout_constraintLeft_toLeftOf=
"parent"
app:layout_constraintRight_toRightOf=
"parent"
app:layout_constraintTop_toBottomOf=
"@+id/text_name"
/>
app:layout_constraintTop_toBottomOf=
"@+id/text_
user
name"
/>
<EditText
android:id=
"@+id/text_password"
...
...
app/src/main/res/layout/fragment_chat_room.xml
View file @
436e7d60
...
...
@@ -28,8 +28,16 @@
layout=
"@layout/message_list"
android:layout_width=
"match_parent"
android:layout_height=
"match_parent"
/>
</FrameLayout>
<chat.rocket.android.widget.autocompletion.ui.SuggestionsView
android:id=
"@+id/suggestions_view"
android:layout_width=
"match_parent"
android:layout_height=
"wrap_content"
android:layout_above=
"@+id/layout_message_composer"
android:background=
"@color/whitesmoke"
/>
<include
android:id=
"@+id/layout_message_composer"
layout=
"@layout/message_composer"
...
...
@@ -58,15 +66,15 @@
android:id=
"@+id/connection_status_text"
android:layout_width=
"match_parent"
android:layout_height=
"32dp"
android:alpha=
"0"
android:background=
"@color/colorPrimary"
android:elevation=
"4dp"
android:textColor=
"@color/white"
android:gravity=
"center"
android:textAppearance=
"@style/TextAppearance.AppCompat.Body2"
android:textColor=
"@color/white"
android:visibility=
"gone"
android:alpha=
"0"
tools:alpha=
"1"
tools:
visibility=
"visible
"
tools:
text=
"connected"
/>
tools:
text=
"connected
"
tools:
visibility=
"visible"
/>
</RelativeLayout>
app/src/main/res/layout/suggestion_member_item.xml
0 → 100644
View file @
436e7d60
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
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_margin=
"2dp"
android:background=
"@color/whitesmoke"
>
<FrameLayout
android:id=
"@+id/image_avatar_container"
android:layout_width=
"wrap_content"
android:layout_height=
"wrap_content"
android:layout_centerVertical=
"true"
>
<com.facebook.drawee.view.SimpleDraweeView
android:id=
"@+id/image_avatar"
android:layout_width=
"24dp"
android:layout_height=
"24dp"
android:layout_margin=
"4dp"
app:roundedCornerRadius=
"3dp"
tools:src=
"@tools:sample/avatars"
/>
<ImageView
android:id=
"@+id/image_status"
android:layout_width=
"12dp"
android:layout_height=
"12dp"
android:layout_gravity=
"bottom|end"
android:background=
"@drawable/user_status_white"
android:padding=
"2dp"
/>
</FrameLayout>
<RelativeLayout
android:layout_width=
"match_parent"
android:layout_height=
"wrap_content"
android:layout_centerVertical=
"true"
android:layout_toEndOf=
"@id/image_avatar_container"
android:layout_toRightOf=
"@id/image_avatar_container"
android:background=
"@color/whitesmoke"
>
<TextView
android:id=
"@+id/text_username"
android:layout_width=
"wrap_content"
android:layout_height=
"wrap_content"
android:layout_centerVertical=
"true"
android:maxLines=
"1"
android:textColor=
"@color/black"
android:textSize=
"16sp"
tools:text=
"@tools:sample/full_names"
/>
<TextView
android:id=
"@+id/text_name"
android:layout_width=
"wrap_content"
android:layout_height=
"wrap_content"
android:layout_alignParentEnd=
"true"
android:layout_alignParentRight=
"true"
android:layout_centerVertical=
"true"
android:layout_toEndOf=
"@+id/text_username"
android:layout_toRightOf=
"@+id/text_username"
android:maxLines=
"1"
android:paddingLeft=
"8dp"
android:paddingStart=
"8dp"
android:textColor=
"@color/gray_material"
android:textSize=
"16sp"
tools:text=
"@tools:sample/full_names"
/>
</RelativeLayout>
</RelativeLayout>
\ No newline at end of file
app/src/main/res/layout/suggestion_room_item.xml
0 → 100644
View file @
436e7d60
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
xmlns:android=
"http://schemas.android.com/apk/res/android"
xmlns:tools=
"http://schemas.android.com/tools"
android:layout_width=
"match_parent"
android:layout_height=
"wrap_content"
android:layout_margin=
"2dp"
android:background=
"@color/whitesmoke"
>
<RelativeLayout
android:layout_width=
"match_parent"
android:layout_height=
"wrap_content"
android:layout_centerVertical=
"true"
android:layout_toEndOf=
"@id/image_avatar_container"
android:layout_toRightOf=
"@id/image_avatar_container"
android:background=
"@color/whitesmoke"
>
<TextView
android:id=
"@+id/text_name"
android:layout_width=
"wrap_content"
android:layout_height=
"wrap_content"
android:layout_centerVertical=
"true"
android:maxLines=
"1"
android:textColor=
"@color/black"
android:textSize=
"16sp"
tools:text=
"@tools:sample/full_names"
/>
<TextView
android:id=
"@+id/text_fullname"
android:layout_width=
"wrap_content"
android:layout_height=
"wrap_content"
android:layout_alignParentEnd=
"true"
android:layout_alignParentRight=
"true"
android:layout_centerVertical=
"true"
android:layout_toEndOf=
"@+id/text_name"
android:layout_toRightOf=
"@+id/text_name"
android:maxLines=
"1"
android:paddingLeft=
"8dp"
android:paddingStart=
"8dp"
android:textColor=
"@color/gray_material"
android:textSize=
"16sp"
tools:text=
"@tools:sample/full_names"
/>
</RelativeLayout>
</RelativeLayout>
\ No newline at end of file
app/src/main/res/values/colors.xml
View file @
436e7d60
...
...
@@ -36,4 +36,7 @@
<color
name=
"colorEmojiIcon"
>
#FF767676
</color>
<color
name=
"default_popup_background_color"
>
#ffffffff
</color>
<color
name=
"default_popup_item_color"
>
#ff000000
</color>
</resources>
app/src/main/res/values/dimens.xml
View file @
436e7d60
...
...
@@ -25,4 +25,7 @@
<dimen
name=
"padding_mention"
>
4dp
</dimen>
<dimen
name=
"radius_mention"
>
6dp
</dimen>
<!-- Autocomplete Popup -->
<dimen
name=
"popup_max_height"
>
150dp
</dimen>
</resources>
\ No newline at end of file
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment