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
62af887c
Unverified
Commit
62af887c
authored
Mar 18, 2018
by
divyanshu bhargava
Committed by
GitHub
Mar 18, 2018
Browse files
Options
Browse Files
Download
Plain Diff
Merge pull request #2 from RocketChat/develop-2.x
Rocket.chat 2.0 alpha-1
parents
676b84c0
d5fde6a3
Changes
63
Show whitespace changes
Inline
Side-by-side
Showing
63 changed files
with
1524 additions
and
492 deletions
+1524
-492
build.gradle
app/build.gradle
+3
-9
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
ColorImage.kt
...src/main/java/chat/rocket/android/app/utils/ColorImage.kt
+0
-134
CustomImageFormatConfigurator.kt
...rocket/android/app/utils/CustomImageFormatConfigurator.kt
+0
-24
SvgDecoder.kt
...src/main/java/chat/rocket/android/app/utils/SvgDecoder.kt
+0
-111
SignupFragment.kt
...rocket/android/authentication/signup/ui/SignupFragment.kt
+11
-10
AutoCompleteType.kt
.../chat/rocket/android/chatroom/adapter/AutoCompleteType.kt
+10
-0
ChatRoomAdapter.kt
...a/chat/rocket/android/chatroom/adapter/ChatRoomAdapter.kt
+0
-1
ImageAttachmentViewHolder.kt
...ket/android/chatroom/adapter/ImageAttachmentViewHolder.kt
+12
-1
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
+149
-20
ChatRoomView.kt
...chat/rocket/android/chatroom/presentation/ChatRoomView.kt
+9
-0
ChatRoomActivity.kt
.../java/chat/rocket/android/chatroom/ui/ChatRoomActivity.kt
+13
-2
ChatRoomFragment.kt
.../java/chat/rocket/android/chatroom/ui/ChatRoomFragment.kt
+58
-9
ChatRoomViewModel.kt
...at/rocket/android/chatroom/viewmodel/ChatRoomViewModel.kt
+9
-0
PeopleViewModel.kt
...chat/rocket/android/chatroom/viewmodel/PeopleViewModel.kt
+17
-0
ViewModelMapper.kt
...chat/rocket/android/chatroom/viewmodel/ViewModelMapper.kt
+37
-15
ChatRoomsPresenter.kt
...cket/android/chatrooms/presentation/ChatRoomsPresenter.kt
+41
-5
ChatRoomsFragment.kt
...ava/chat/rocket/android/chatrooms/ui/ChatRoomsFragment.kt
+3
-1
AppModule.kt
.../main/java/chat/rocket/android/dagger/module/AppModule.kt
+14
-8
UrlHelper.kt
app/src/main/java/chat/rocket/android/helper/UrlHelper.kt
+2
-1
MainNavigator.kt
...va/chat/rocket/android/main/presentation/MainNavigator.kt
+8
-2
MembersFragment.kt
...in/java/chat/rocket/android/members/ui/MembersFragment.kt
+8
-0
MemberViewModel.kt
.../chat/rocket/android/members/viewmodel/MemberViewModel.kt
+1
-1
ProfilePresenter.kt
...t/rocket/android/profile/presentation/ProfilePresenter.kt
+8
-8
ProfileFragment.kt
...in/java/chat/rocket/android/profile/ui/ProfileFragment.kt
+16
-16
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
+226
-0
hold.xml
app/src/main/res/anim/hold.xml
+1
-1
slide_up.xml
app/src/main/res/anim/slide_up.xml
+1
-1
suggestions_menu_decorator.xml
app/src/main/res/drawable/suggestions_menu_decorator.xml
+7
-0
user_status_white.xml
app/src/main/res/drawable/user_status_white.xml
+12
-0
fragment_authentication_log_in.xml
app/src/main/res/layout/fragment_authentication_log_in.xml
+0
-1
fragment_authentication_server.xml
app/src/main/res/layout/fragment_authentication_server.xml
+10
-10
fragment_authentication_sign_up.xml
app/src/main/res/layout/fragment_authentication_sign_up.xml
+75
-74
fragment_chat_room.xml
app/src/main/res/layout/fragment_chat_room.xml
+12
-4
message_composer.xml
app/src/main/res/layout/message_composer.xml
+12
-0
suggestion_member_item.xml
app/src/main/res/layout/suggestion_member_item.xml
+74
-0
suggestion_room_item.xml
app/src/main/res/layout/suggestion_room_item.xml
+45
-0
strings.xml
app/src/main/res/values-pt-rBR/strings.xml
+2
-0
colors.xml
app/src/main/res/values/colors.xml
+4
-1
dimens.xml
app/src/main/res/values/dimens.xml
+3
-0
strings.xml
app/src/main/res/values/strings.xml
+1
-0
build.gradle
build.gradle
+1
-1
dependencies.gradle
dependencies.gradle
+3
-7
No files found.
app/build.gradle
View file @
62af887c
...
...
@@ -12,8 +12,8 @@ android {
applicationId
"chat.rocket.android"
minSdkVersion
21
targetSdkVersion
versions
.
targetSdk
versionCode
1011
versionName
"2.0.0-
dev9
"
versionCode
2000
versionName
"2.0.0-
alpha1
"
testInstrumentationRunner
"android.support.test.runner.AndroidJUnitRunner"
multiDexEnabled
true
}
...
...
@@ -32,7 +32,6 @@ android {
signingConfig
signingConfigs
.
release
minifyEnabled
false
proguardFiles
getDefaultProguardFile
(
'proguard-android.txt'
),
'proguard-rules.pro'
applicationIdSuffix
".dev"
}
debug
{
...
...
@@ -95,14 +94,9 @@ dependencies {
implementation
libraries
.
kotshiApi
implementation
libraries
.
frescoImageViewer
implementation
(
libraries
.
androidSvg
)
{
exclude
group:
'org.jetbrains'
,
module:
'annotations-java5'
}
implementation
libraries
.
markwon
implementation
(
libraries
.
markwonImageLoader
)
{
exclude
group:
'com.caverock'
,
module:
'androidsvg'
}
implementation
libraries
.
markwonImageLoader
implementation
libraries
.
sheetMenu
...
...
app/src/main/java/chat/rocket/android/app/DrawableHelper.kt
View file @
62af887c
...
...
@@ -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 @
676b84c0
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/app/utils/ColorImage.kt
deleted
100644 → 0
View file @
676b84c0
package
chat.rocket.android.app.utils
import
android.graphics.drawable.ColorDrawable
import
android.graphics.drawable.Drawable
import
android.support.annotation.ColorInt
import
android.support.annotation.Nullable
import
android.support.v4.graphics.ColorUtils
import
com.facebook.common.internal.ByteStreams
import
com.facebook.imageformat.ImageFormat
import
com.facebook.imageformat.ImageFormatCheckerUtils
import
com.facebook.imagepipeline.common.ImageDecodeOptions
import
com.facebook.imagepipeline.decoder.ImageDecoder
import
com.facebook.imagepipeline.drawable.DrawableFactory
import
com.facebook.imagepipeline.image.CloseableImage
import
com.facebook.imagepipeline.image.EncodedImage
import
com.facebook.imagepipeline.image.QualityInfo
import
java.io.IOException
/**
* Simple decoder that can decode color images that have the following format: <color>#FF5722</color>.
*
* @see {https://github.com/facebook/fresco/blob/master/samples/showcase/src/main/java/com/facebook/fresco/samples/showcase/imageformat/color/ColorImageExample.java}
*/
object
ColorImage
{
// Custom ImageFormat for color images.
private
val
imageFormatColor
=
ImageFormat
(
"IMAGE_FORMAT_COLOR"
,
"color"
)
// XML color tag that our colors must start with.
val
colorTag
=
"<color>"
/**
* Creates a new image format checker for [ColorImage.imageFormatColor].
*
* @return the image format checker.
*/
fun
createFormatChecker
():
ImageFormat
.
FormatChecker
=
ColorFormatChecker
()
/**
* Creates a new decoder that can decode [ColorImage.imageFormatColor] images.
*
* @return the decoder.
*/
fun
createDecoder
():
ImageDecoder
=
ColorDecoder
()
fun
createDrawableFactory
():
ColorDrawableFactory
=
ColorDrawableFactory
()
/**
* Custom color format checker that verifies that the header of the file corresponds to our [ColorImage.colorTag].
*/
class
ColorFormatChecker
:
ImageFormat
.
FormatChecker
{
private
val
header
=
ImageFormatCheckerUtils
.
asciiBytes
(
colorTag
)
override
fun
getHeaderSize
():
Int
{
return
header
.
size
}
@Nullable
override
fun
determineFormat
(
headerBytes
:
ByteArray
,
headerSize
:
Int
):
ImageFormat
?
{
if
(
headerSize
>
getHeaderSize
())
{
if
(
ImageFormatCheckerUtils
.
startsWithPattern
(
headerBytes
,
header
))
{
return
imageFormatColor
}
}
return
null
}
}
/**
* Custom closeable color image that holds a single color int value.
*/
class
CloseableColorImage
(
@field
:
ColorInt
@get
:
ColorInt
val
color
:
Int
)
:
CloseableImage
()
{
private
var
isClosed
=
false
override
fun
close
()
{
isClosed
=
true
}
override
fun
getSizeInBytes
():
Int
=
0
override
fun
isClosed
():
Boolean
=
isClosed
override
fun
getWidth
():
Int
=
0
override
fun
getHeight
():
Int
=
0
}
/**
* Decodes a color XML tag: <color>#rrggbb</color>.
*/
class
ColorDecoder
:
ImageDecoder
{
@Nullable
override
fun
decode
(
encodedImage
:
EncodedImage
,
length
:
Int
,
qualityInfo
:
QualityInfo
,
options
:
ImageDecodeOptions
):
CloseableImage
?
{
try
{
// Read the file as a string
val
text
=
String
(
ByteStreams
.
toByteArray
(
encodedImage
.
inputStream
))
// Check if the string matches "<color>#"
if
(!
text
.
startsWith
(
colorTag
+
"#"
))
{
return
null
}
// Parse the int value between # and <
val
startIndex
=
colorTag
.
length
+
1
val
endIndex
=
text
.
lastIndexOf
(
'<'
)
var
color
=
Integer
.
parseInt
(
text
.
substring
(
startIndex
,
endIndex
),
16
)
// Add the alpha component so that we actually see the color
color
=
ColorUtils
.
setAlphaComponent
(
color
,
255
)
// Return the CloseableImage
return
CloseableColorImage
(
color
)
}
catch
(
e
:
IOException
)
{
// TODO: are we using the android.util.Log for logging that type of errors? or should we use the SDK logger?
e
.
printStackTrace
()
}
// Return nothing if an error occurred
return
null
}
}
/**
* Color drawable factory that is able to render a [CloseableColorImage] by creating a new [ColorDrawable] for the given color.
*/
class
ColorDrawableFactory
:
DrawableFactory
{
override
fun
supportsImageType
(
image
:
CloseableImage
):
Boolean
{
// We can only handle CloseableColorImages.
return
image
is
CloseableColorImage
}
@Nullable
override
fun
createDrawable
(
image
:
CloseableImage
):
Drawable
?
{
// Just return a simple ColorDrawable with the given color value.
return
ColorDrawable
((
image
as
CloseableColorImage
).
color
)
}
}
}
\ No newline at end of file
app/src/main/java/chat/rocket/android/app/utils/CustomImageFormatConfigurator.kt
deleted
100644 → 0
View file @
676b84c0
package
chat.rocket.android.app.utils
import
com.facebook.drawee.backends.pipeline.DraweeConfig
import
com.facebook.imagepipeline.decoder.ImageDecoderConfig
/**
* Utility class to add custom decoders and drawable factories.
*
* @see {https://github.com/facebook/fresco/blob/master/samples/showcase/src/main/java/com/facebook/fresco/samples/showcase/CustomImageFormatConfigurator.java}
*/
object
CustomImageFormatConfigurator
{
fun
createImageDecoderConfig
()
:
ImageDecoderConfig
{
return
ImageDecoderConfig
.
newBuilder
()
.
addDecodingCapability
(
SvgDecoder
.
svgFormat
,
SvgDecoder
.
SvgFormatChecker
(),
SvgDecoder
.
Decoder
())
.
build
()
}
fun
addCustomDrawableFactories
(
draweeConfigBuilder
:
DraweeConfig
.
Builder
)
{
// We always add the color drawable factory so that it can be used for image decoder overrides.
draweeConfigBuilder
.
addCustomDrawableFactory
(
ColorImage
.
createDrawableFactory
())
draweeConfigBuilder
.
addCustomDrawableFactory
(
SvgDecoder
.
SvgDrawableFactory
())
}
}
\ No newline at end of file
app/src/main/java/chat/rocket/android/app/utils/SvgDecoder.kt
deleted
100644 → 0
View file @
676b84c0
package
chat.rocket.android.app.utils
import
android.graphics.Rect
import
android.graphics.drawable.Drawable
import
android.graphics.drawable.PictureDrawable
import
android.support.annotation.Nullable
import
com.caverock.androidsvg.SVG
import
com.caverock.androidsvg.SVGParseException
import
com.facebook.imageformat.ImageFormat
import
com.facebook.imageformat.ImageFormatCheckerUtils
import
com.facebook.imagepipeline.common.ImageDecodeOptions
import
com.facebook.imagepipeline.decoder.ImageDecoder
import
com.facebook.imagepipeline.drawable.DrawableFactory
import
com.facebook.imagepipeline.image.CloseableImage
import
com.facebook.imagepipeline.image.EncodedImage
import
com.facebook.imagepipeline.image.QualityInfo
/**
* SVG example that defines all classes required to decode and render SVG images.
*
* @see {https://github.com/facebook/fresco/blob/master/samples/showcase/src/main/java/com/facebook/fresco/samples/showcase/imageformat/svg/SvgDecoderExample.java}
*/
object
SvgDecoder
{
val
svgFormat
=
ImageFormat
(
"SVG_FORMAT"
,
"svg"
)
// We do not include the closing ">" since there can be additional information.
private
val
headerTag
=
"<svg"
private
val
possibleHeaderTags
=
arrayOf
(
ImageFormatCheckerUtils
.
asciiBytes
(
"<?xml"
))
/**
* Custom SVG format checker that verifies that the header of the file corresponds to our [SvgDecoder.headerTag] or [SvgDecoder.possibleHeaderTags].
*/
class
SvgFormatChecker
:
ImageFormat
.
FormatChecker
{
private
val
header
=
ImageFormatCheckerUtils
.
asciiBytes
(
headerTag
)
override
fun
getHeaderSize
():
Int
{
return
header
.
size
}
@Nullable
override
fun
determineFormat
(
headerBytes
:
ByteArray
,
headerSize
:
Int
):
ImageFormat
?
{
if
(
headerSize
>
getHeaderSize
())
{
if
(
ImageFormatCheckerUtils
.
startsWithPattern
(
headerBytes
,
header
))
{
return
svgFormat
}
if
(
possibleHeaderTags
.
any
{
ImageFormatCheckerUtils
.
startsWithPattern
(
headerBytes
,
it
)
&&
ImageFormatCheckerUtils
.
indexOfPattern
(
headerBytes
,
headerBytes
.
size
,
header
,
header
.
size
)
>
-
1
})
{
return
svgFormat
}
}
return
null
}
}
/**
* Custom closeable SVG image that holds a single SVG.
*/
class
CloseableSvgImage
(
val
svg
:
SVG
)
:
CloseableImage
()
{
private
var
isClose
=
false
override
fun
close
()
{
isClose
=
true
}
override
fun
getSizeInBytes
():
Int
=
0
override
fun
isClosed
():
Boolean
=
isClose
override
fun
getWidth
():
Int
=
0
override
fun
getHeight
():
Int
=
0
}
/**
* Decodes a [SvgDecoder.svgFormat] image.
*/
class
Decoder
:
ImageDecoder
{
@Nullable
override
fun
decode
(
encodedImage
:
EncodedImage
,
length
:
Int
,
qualityInfo
:
QualityInfo
,
options
:
ImageDecodeOptions
):
CloseableImage
?
{
try
{
val
svg
=
SVG
.
getFromInputStream
(
encodedImage
.
inputStream
)
return
CloseableSvgImage
(
svg
)
}
catch
(
e
:
SVGParseException
)
{
// TODO: are we using the android.util.Log for logging that type of errors? or should we use the SDK logger?
e
.
printStackTrace
()
}
// Return nothing if an error occurred
return
null
}
}
/**
* SVG drawable factory that creates [PictureDrawable]s for SVG images.
*/
class
SvgDrawableFactory
:
DrawableFactory
{
override
fun
supportsImageType
(
image
:
CloseableImage
):
Boolean
{
return
image
is
CloseableSvgImage
}
@Nullable
override
fun
createDrawable
(
image
:
CloseableImage
):
Drawable
?
{
return
SvgPictureDrawable
((
image
as
CloseableSvgImage
).
svg
)
}
}
class
SvgPictureDrawable
(
private
val
svg
:
SVG
)
:
PictureDrawable
(
null
)
{
override
fun
onBoundsChange
(
bounds
:
Rect
)
{
super
.
onBoundsChange
(
bounds
)
picture
=
svg
.
renderToPicture
(
bounds
.
width
(),
bounds
.
height
())
}
}
}
\ No newline at end of file
app/src/main/java/chat/rocket/android/authentication/signup/ui/SignupFragment.kt
View file @
62af887c
...
...
@@ -19,12 +19,15 @@ import javax.inject.Inject
class
SignupFragment
:
Fragment
(),
SignupView
{
@Inject
lateinit
var
presenter
:
SignupPresenter
private
val
layoutListener
=
ViewTreeObserver
.
OnGlobalLayoutListener
{
if
(
KeyboardHelper
.
isSoftKeyboardShown
(
constraint
_layout
.
rootView
))
{
text_new_user_agreement
.
setVisible
(
false
)
if
(
KeyboardHelper
.
isSoftKeyboardShown
(
relative
_layout
.
rootView
))
{
bottom_container
.
setVisible
(
false
)
}
else
{
text_new_user_agreement
.
setVisible
(
true
)
bottom_container
.
apply
{
postDelayed
({
setVisible
(
true
)
},
3
)
}
}
}
...
...
@@ -42,24 +45,22 @@ class SignupFragment : Fragment(), SignupView {
override
fun
onViewCreated
(
view
:
View
,
savedInstanceState
:
Bundle
?)
{
super
.
onViewCreated
(
view
,
savedInstanceState
)
activity
?.
window
?.
setSoftInputMode
(
WindowManager
.
LayoutParams
.
SOFT_INPUT_STATE_VISIBLE
)
if
(
Build
.
VERSION
.
SDK_INT
<=
Build
.
VERSION_CODES
.
M
)
{
tintEditTextDrawableStart
()
}
constraint
_layout
.
viewTreeObserver
.
addOnGlobalLayoutListener
(
layoutListener
)
relative
_layout
.
viewTreeObserver
.
addOnGlobalLayoutListener
(
layoutListener
)
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
)
}
}
override
fun
onDestroyView
()
{
relative_layout
.
viewTreeObserver
.
removeOnGlobalLayoutListener
(
layoutListener
)
super
.
onDestroyView
()
constraint_layout
.
viewTreeObserver
.
removeOnGlobalLayoutListener
(
layoutListener
)
}
override
fun
alertBlankName
()
{
...
...
@@ -146,7 +147,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 @
62af887c
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/ChatRoomAdapter.kt
View file @
62af887c
...
...
@@ -120,7 +120,6 @@ class ChatRoomAdapter(
val
indexOfFirst
=
dataSet
.
indexOfFirst
{
it
.
messageId
==
message
.
messageId
}
Timber
.
d
(
"index: $index"
)
if
(
index
>
-
1
)
{
message
.
nextDownStreamMessage
=
dataSet
[
index
].
nextDownStreamMessage
dataSet
[
index
]
=
message
notifyItemChanged
(
index
)
while
(
dataSet
[
index
].
nextDownStreamMessage
!=
null
)
{
...
...
app/src/main/java/chat/rocket/android/chatroom/adapter/ImageAttachmentViewHolder.kt
View file @
62af887c
...
...
@@ -2,6 +2,8 @@ package chat.rocket.android.chatroom.adapter
import
android.view.View
import
chat.rocket.android.chatroom.viewmodel.ImageAttachmentViewModel
import
com.facebook.drawee.backends.pipeline.Fresco
import
com.facebook.drawee.interfaces.DraweeController
import
chat.rocket.android.widget.emoji.EmojiReactionListener
import
com.stfalcon.frescoimageviewer.ImageViewer
import
kotlinx.android.synthetic.main.message_attachment.view.*
...
...
@@ -20,12 +22,21 @@ class ImageAttachmentViewHolder(itemView: View,
override
fun
bindViews
(
data
:
ImageAttachmentViewModel
)
{
with
(
itemView
)
{
image_attachment
.
setImageURI
(
data
.
attachmentUrl
)
val
controller
=
Fresco
.
newDraweeControllerBuilder
().
apply
{
setUri
(
data
.
attachmentUrl
)
autoPlayAnimations
=
true
oldController
=
image_attachment
.
controller
}.
build
()
image_attachment
.
controller
=
controller
file_name
.
text
=
data
.
attachmentTitle
image_attachment
.
setOnClickListener
{
view
->
// TODO - implement a proper image viewer with a proper Transition
val
builder
=
ImageViewer
.
createPipelineDraweeControllerBuilder
()
.
setAutoPlayAnimations
(
true
)
ImageViewer
.
Builder
(
view
.
context
,
listOf
(
data
.
attachmentUrl
))
.
setStartPosition
(
0
)
.
hideStatusBar
(
false
)
.
setCustomDraweeControllerBuilder
(
builder
)
.
show
()
}
}
...
...
app/src/main/java/chat/rocket/android/chatroom/adapter/PeopleSuggestionsAdapter.kt
0 → 100644
View file @
62af887c
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 @
62af887c
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 @
62af887c
...
...
@@ -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.ChatRoomViewModel
import
chat.rocket.android.chatroom.viewmodel.PeopleViewModel
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
()
!!
...
...
@@ -188,7 +200,8 @@ class ChatRoomPresenter @Inject constructor(private val view: ChatRoomView,
launch
(
parent
=
strategy
.
jobs
)
{
if
(
chatRoomId
!=
null
&&
chatRoomType
!=
null
)
{
val
roomType
=
roomTypeOf
(
chatRoomType
!!
)
val
lastMessage
=
messagesRepository
.
getByRoomId
(
chatRoomId
!!
).
sortedByDescending
{
it
.
timestamp
}.
first
()
messagesRepository
.
getByRoomId
(
chatRoomId
!!
)
.
sortedByDescending
{
it
.
timestamp
}.
firstOrNull
()
?.
let
{
lastMessage
->
val
instant
=
Instant
.
ofEpochMilli
(
lastMessage
.
timestamp
)
val
messages
=
client
.
history
(
chatRoomId
!!
,
roomType
,
count
=
50
,
oldest
=
instant
.
toString
())
...
...
@@ -210,6 +223,7 @@ class ChatRoomPresenter @Inject constructor(private val view: ChatRoomView,
}
}
}
}
fun
unsubscribeMessages
(
chatRoomId
:
String
)
{
manager
.
removeStatusChannel
(
stateChannel
)
...
...
@@ -340,8 +354,123 @@ 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
)
}
}
}
fun
toMembersList
(
chatRoomId
:
String
,
chatRoomType
:
String
)
=
navigator
.
toMembersList
(
chatRoomId
,
chatRoomType
)
fun
loadChatRooms
()
{
launchUI
(
strategy
)
{
try
{
val
chatRooms
=
getChatRoomsInteractor
.
get
(
currentServer
)
.
filterNot
{
it
.
type
is
RoomType
.
DirectMessage
||
it
.
type
is
RoomType
.
Livechat
}
.
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
)
}
}
}
fun
joinChat
(
chatRoomId
:
String
)
{
launchUI
(
strategy
)
{
try
{
client
.
joinChat
(
chatRoomId
)
view
.
onJoined
()
}
catch
(
ex
:
RocketChatException
)
{
Timber
.
e
(
ex
)
}
}
}
/**
* Send an emoji reaction to a message.
*/
...
...
app/src/main/java/chat/rocket/android/chatroom/presentation/ChatRoomView.kt
View file @
62af887c
...
...
@@ -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.ChatRoomViewModel
import
chat.rocket.android.chatroom.viewmodel.PeopleViewModel
import
chat.rocket.android.core.behaviours.LoadingView
import
chat.rocket.android.core.behaviours.MessageView
import
chat.rocket.core.internal.realtime.State
...
...
@@ -100,5 +102,12 @@ interface ChatRoomView : LoadingView, MessageView {
fun
showInvalidFileSize
(
fileSize
:
Int
,
maxFileSize
:
Int
)
fun
showConnectionState
(
state
:
State
)
fun
populateMembers
(
members
:
List
<
PeopleViewModel
>)
fun
populateRooms
(
chatRooms
:
List
<
ChatRoomViewModel
>)
/**
* This user has joined the chat callback.
*/
fun
onJoined
()
fun
showReactionsPopup
(
messageId
:
String
)
}
\ No newline at end of file
app/src/main/java/chat/rocket/android/chatroom/ui/ChatRoomActivity.kt
View file @
62af887c
...
...
@@ -23,13 +23,19 @@ import javax.inject.Inject
import
timber.log.Timber
fun
Context
.
chatRoomIntent
(
chatRoomId
:
String
,
chatRoomName
:
String
,
chatRoomType
:
String
,
isChatRoomReadOnly
:
Boolean
,
chatRoomLastSeen
:
Long
):
Intent
{
fun
Context
.
chatRoomIntent
(
chatRoomId
:
String
,
chatRoomName
:
String
,
chatRoomType
:
String
,
isChatRoomReadOnly
:
Boolean
,
chatRoomLastSeen
:
Long
,
isChatRoomSubscribed
:
Boolean
=
true
):
Intent
{
return
Intent
(
this
,
ChatRoomActivity
::
class
.
java
).
apply
{
putExtra
(
INTENT_CHAT_ROOM_ID
,
chatRoomId
)
putExtra
(
INTENT_CHAT_ROOM_NAME
,
chatRoomName
)
putExtra
(
INTENT_CHAT_ROOM_TYPE
,
chatRoomType
)
putExtra
(
INTENT_IS_CHAT_ROOM_READ_ONLY
,
isChatRoomReadOnly
)
putExtra
(
INTENT_CHAT_ROOM_LAST_SEEN
,
chatRoomLastSeen
)
putExtra
(
INTENT_CHAT_IS_SUBSCRIBED
,
isChatRoomSubscribed
)
}
}
...
...
@@ -38,6 +44,7 @@ private const val INTENT_CHAT_ROOM_NAME = "chat_room_name"
private
const
val
INTENT_CHAT_ROOM_TYPE
=
"chat_room_type"
private
const
val
INTENT_IS_CHAT_ROOM_READ_ONLY
=
"is_chat_room_read_only"
private
const
val
INTENT_CHAT_ROOM_LAST_SEEN
=
"chat_room_last_seen"
private
const
val
INTENT_CHAT_IS_SUBSCRIBED
=
"is_chat_room_subscribed"
class
ChatRoomActivity
:
AppCompatActivity
(),
HasSupportFragmentInjector
{
@Inject
lateinit
var
fragmentDispatchingAndroidInjector
:
DispatchingAndroidInjector
<
Fragment
>
...
...
@@ -50,6 +57,7 @@ class ChatRoomActivity : AppCompatActivity(), HasSupportFragmentInjector {
private
lateinit
var
chatRoomName
:
String
private
lateinit
var
chatRoomType
:
String
private
var
isChatRoomReadOnly
:
Boolean
=
false
private
var
isChatRoomSubscribed
:
Boolean
=
true
private
var
chatRoomLastSeen
:
Long
=
-
1L
override
fun
onCreate
(
savedInstanceState
:
Bundle
?)
{
...
...
@@ -76,8 +84,11 @@ class ChatRoomActivity : AppCompatActivity(), HasSupportFragmentInjector {
chatRoomLastSeen
=
intent
.
getLongExtra
(
INTENT_CHAT_ROOM_LAST_SEEN
,
-
1
)
isChatRoomSubscribed
=
intent
.
getBooleanExtra
(
INTENT_CHAT_IS_SUBSCRIBED
,
true
)
addFragment
(
"ChatRoomFragment"
,
R
.
id
.
fragment_container
)
{
newInstance
(
chatRoomId
,
chatRoomName
,
chatRoomType
,
isChatRoomReadOnly
,
chatRoomLastSeen
)
newInstance
(
chatRoomId
,
chatRoomName
,
chatRoomType
,
isChatRoomReadOnly
,
chatRoomLastSeen
,
isChatRoomSubscribed
)
}
}
...
...
app/src/main/java/chat/rocket/android/chatroom/ui/ChatRoomFragment.kt
View file @
62af887c
...
...
@@ -16,28 +16,36 @@ 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.MessageViewModel
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
import
chat.rocket.android.util.extensions.*
import
chat.rocket.android.widget.emoji.*
import
chat.rocket.core.internal.realtime.State
import
chat.rocket.core.model.Message
import
dagger.android.support.AndroidSupportInjection
import
io.reactivex.disposables.CompositeDisposable
import
kotlinx.android.synthetic.main.fragment_chat_room.*
import
kotlinx.android.synthetic.main.message_attachment_options.*
import
kotlinx.android.synthetic.main.item_chat.*
import
kotlinx.android.synthetic.main.message_composer.*
import
kotlinx.android.synthetic.main.message_list.*
import
timber.log.Timber
import
javax.inject.Inject
fun
newInstance
(
chatRoomId
:
String
,
chatRoomName
:
String
,
chatRoomType
:
String
,
isChatRoomReadOnly
:
Boolean
,
chatRoomLastSeen
:
Long
):
Fragment
{
fun
newInstance
(
chatRoomId
:
String
,
chatRoomName
:
String
,
chatRoomType
:
String
,
isChatRoomReadOnly
:
Boolean
,
chatRoomLastSeen
:
Long
,
isSubscribed
:
Boolean
=
true
):
Fragment
{
return
ChatRoomFragment
().
apply
{
arguments
=
Bundle
(
1
).
apply
{
putString
(
BUNDLE_CHAT_ROOM_ID
,
chatRoomId
)
...
...
@@ -45,6 +53,7 @@ fun newInstance(chatRoomId: String, chatRoomName: String, chatRoomType: String,
putString
(
BUNDLE_CHAT_ROOM_TYPE
,
chatRoomType
)
putBoolean
(
BUNDLE_IS_CHAT_ROOM_READ_ONLY
,
isChatRoomReadOnly
)
putLong
(
BUNDLE_CHAT_ROOM_LAST_SEEN
,
chatRoomLastSeen
)
putBoolean
(
BUNDLE_CHAT_ROOM_IS_SUBSCRIBED
,
isSubscribed
)
}
}
}
...
...
@@ -55,6 +64,7 @@ private const val BUNDLE_CHAT_ROOM_TYPE = "chat_room_type"
private
const
val
BUNDLE_IS_CHAT_ROOM_READ_ONLY
=
"is_chat_room_read_only"
private
const
val
REQUEST_CODE_FOR_PERFORM_SAF
=
42
private
const
val
BUNDLE_CHAT_ROOM_LAST_SEEN
=
"chat_room_last_seen"
private
const
val
BUNDLE_CHAT_ROOM_IS_SUBSCRIBED
=
"chat_room_is_subscribed"
class
ChatRoomFragment
:
Fragment
(),
ChatRoomView
,
EmojiKeyboardListener
,
EmojiReactionListener
{
@Inject
lateinit
var
presenter
:
ChatRoomPresenter
...
...
@@ -63,8 +73,9 @@ class ChatRoomFragment : Fragment(), ChatRoomView, EmojiKeyboardListener, EmojiR
private
lateinit
var
chatRoomId
:
String
private
lateinit
var
chatRoomName
:
String
private
lateinit
var
chatRoomType
:
String
private
lateinit
var
emojiKeyboardPopup
:
EmojiKeyboardPopup
private
var
isSubscribed
:
Boolean
=
true
private
var
isChatRoomReadOnly
:
Boolean
=
false
private
lateinit
var
emojiKeyboardPopup
:
EmojiKeyboardPopup
private
var
chatRoomLastSeen
:
Long
=
-
1
private
lateinit
var
actionSnackbar
:
ActionSnackbar
private
var
citation
:
String
?
=
null
...
...
@@ -90,6 +101,7 @@ class ChatRoomFragment : Fragment(), ChatRoomView, EmojiKeyboardListener, EmojiR
chatRoomName
=
bundle
.
getString
(
BUNDLE_CHAT_ROOM_NAME
)
chatRoomType
=
bundle
.
getString
(
BUNDLE_CHAT_ROOM_TYPE
)
isChatRoomReadOnly
=
bundle
.
getBoolean
(
BUNDLE_IS_CHAT_ROOM_READ_ONLY
)
isSubscribed
=
bundle
.
getBoolean
(
BUNDLE_CHAT_ROOM_IS_SUBSCRIBED
)
chatRoomLastSeen
=
bundle
.
getLong
(
BUNDLE_CHAT_ROOM_LAST_SEEN
)
}
else
{
requireNotNull
(
bundle
)
{
"no arguments supplied when the fragment was instantiated"
}
...
...
@@ -104,10 +116,11 @@ class ChatRoomFragment : Fragment(), ChatRoomView, EmojiKeyboardListener, EmojiR
setupToolbar
(
chatRoomName
)
presenter
.
loadMessages
(
chatRoomId
,
chatRoomType
)
presenter
.
loadChatRooms
()
setupRecyclerView
()
setupFab
()
setupMessageComposer
()
setupSuggestionsView
()
setupActionSnackbar
()
}
...
...
@@ -155,13 +168,13 @@ class ChatRoomFragment : Fragment(), ChatRoomView, EmojiKeyboardListener, EmojiR
override
fun
showMessages
(
dataSet
:
List
<
BaseViewModel
<*
>>)
{
// track the message sent immediately after the current message
var
prevMessageViewModel
:
MessageViewModel
?
=
null
var
prevMessageViewModel
:
MessageViewModel
?
=
null
// Loop over received messages to determine first unread
for
(
i
in
dataSet
.
indices
)
{
val
msgModel
=
dataSet
[
i
]
if
(
msgModel
is
MessageViewModel
){
if
(
msgModel
is
MessageViewModel
)
{
val
msg
=
msgModel
.
rawData
if
(
msg
.
timestamp
<
chatRoomLastSeen
)
{
// This message was sent before the last seen of the room. Hence, it was seen.
...
...
@@ -199,6 +212,7 @@ class ChatRoomFragment : Fragment(), ChatRoomView, EmojiKeyboardListener, EmojiR
if
(
oldMessagesCount
==
0
&&
dataSet
.
isNotEmpty
())
{
recycler_view
.
scrollToPosition
(
0
)
}
presenter
.
loadActiveMembers
(
chatRoomId
,
chatRoomType
,
filterSelfOut
=
true
)
}
}
...
...
@@ -273,6 +287,14 @@ class ChatRoomFragment : Fragment(), ChatRoomView, EmojiKeyboardListener, EmojiR
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
...
...
@@ -362,6 +384,13 @@ class ChatRoomFragment : Fragment(), ChatRoomView, EmojiKeyboardListener, EmojiR
}
}
override
fun
onJoined
()
{
input_container
.
setVisible
(
true
)
button_join_chat
.
setVisible
(
false
)
isSubscribed
=
true
setupMessageComposer
()
}
private
val
dismissStatus
=
{
connection_status_text
.
fadeOut
()
}
...
...
@@ -392,6 +421,10 @@ class ChatRoomFragment : Fragment(), ChatRoomView, EmojiKeyboardListener, EmojiR
if
(
isChatRoomReadOnly
)
{
text_room_is_read_only
.
setVisible
(
true
)
input_container
.
setVisible
(
false
)
}
else
if
(!
isSubscribed
)
{
input_container
.
setVisible
(
false
)
button_join_chat
.
setVisible
(
true
)
button_join_chat
.
setOnClickListener
{
presenter
.
joinChat
(
chatRoomId
)
}
}
else
{
button_send
.
alpha
=
0f
button_send
.
setVisible
(
false
)
...
...
@@ -458,14 +491,30 @@ class ChatRoomFragment : Fragment(), ChatRoomView, EmojiKeyboardListener, EmojiR
}
}
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 @
62af887c
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 @
62af887c
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/chatroom/viewmodel/ViewModelMapper.kt
View file @
62af887c
...
...
@@ -96,31 +96,53 @@ class ViewModelMapper @Inject constructor(private val context: Context,
}
private
fun
mapFileAttachment
(
message
:
Message
,
attachment
:
FileAttachment
):
BaseViewModel
<
*
>?
{
val
attachmentUrl
=
attachmentUrl
(
"$baseUrl${attachment.url}"
)
val
attachmentTitle
=
attachment
.
title
val
id
=
"${message.id}_${attachment.titleLink}"
.
hashCode
().
toLong
(
)
val
attachmentUrl
=
attachmentUrl
(
attachment
)
val
attachmentTitle
=
attachment
Title
(
attachment
)
val
id
=
attachmentId
(
message
,
attachment
)
return
when
(
attachment
)
{
is
ImageAttachment
->
ImageAttachmentViewModel
(
message
,
attachment
,
message
.
id
,
attachmentUrl
,
attachmentTitle
?:
""
,
id
,
getReactions
(
message
))
attachmentUrl
,
attachmentTitle
,
id
,
getReactions
(
message
))
is
VideoAttachment
->
VideoAttachmentViewModel
(
message
,
attachment
,
message
.
id
,
attachmentUrl
,
attachmentTitle
?:
""
,
id
,
getReactions
(
message
))
attachmentUrl
,
attachmentTitle
,
id
,
getReactions
(
message
))
is
AudioAttachment
->
AudioAttachmentViewModel
(
message
,
attachment
,
message
.
id
,
attachmentUrl
,
attachmentTitle
?:
""
,
id
,
getReactions
(
message
))
attachmentUrl
,
attachmentTitle
,
id
,
getReactions
(
message
))
else
->
null
}
}
private
fun
attachmentUrl
(
url
:
String
):
String
{
var
response
=
url
val
httpUrl
=
HttpUrl
.
parse
(
url
)
private
fun
attachmentId
(
message
:
Message
,
attachment
:
FileAttachment
):
Long
{
return
"${message.id}_${attachment.url}"
.
hashCode
().
toLong
()
}
private
fun
attachmentTitle
(
attachment
:
FileAttachment
):
CharSequence
{
return
with
(
attachment
)
{
title
?.
let
{
return
@with
it
}
val
fileUrl
=
HttpUrl
.
parse
(
url
)
fileUrl
?.
let
{
return
@with
it
.
pathSegments
().
last
()
}
return
@with
""
}
}
private
fun
attachmentUrl
(
attachment
:
FileAttachment
):
String
{
return
with
(
attachment
)
{
if
(
url
.
startsWith
(
"http"
))
return
@with
url
val
fullUrl
=
"$baseUrl$url"
val
httpUrl
=
HttpUrl
.
parse
(
fullUrl
)
httpUrl
?.
let
{
response
=
it
.
newBuilder
().
apply
{
return
@with
it
.
newBuilder
().
apply
{
addQueryParameter
(
"rc_uid"
,
token
?.
userId
)
addQueryParameter
(
"rc_token"
,
token
?.
authToken
)
}.
build
().
toString
()
}
return
response
// Fallback to baseUrl + url
return
@with
fullUrl
}
}
private
suspend
fun
mapMessage
(
message
:
Message
):
MessageViewModel
=
withContext
(
CommonPool
)
{
...
...
app/src/main/java/chat/rocket/android/chatrooms/presentation/ChatRoomsPresenter.kt
View file @
62af887c
...
...
@@ -9,12 +9,12 @@ import chat.rocket.android.server.infraestructure.chatRooms
import
chat.rocket.android.server.infraestructure.state
import
chat.rocket.android.util.extensions.launchUI
import
chat.rocket.common.RocketChatException
import
chat.rocket.common.model.BaseRoom
import
chat.rocket.common.model.RoomType
import
chat.rocket.common.model.*
import
chat.rocket.core.internal.model.Subscription
import
chat.rocket.core.internal.realtime.State
import
chat.rocket.core.internal.realtime.StreamMessage
import
chat.rocket.core.internal.realtime.Type
import
chat.rocket.core.internal.rest.spotlight
import
chat.rocket.core.model.ChatRoom
import
chat.rocket.core.model.Room
import
kotlinx.coroutines.experimental.*
...
...
@@ -34,6 +34,7 @@ class ChatRoomsPresenter @Inject constructor(private val view: ChatRoomsView,
factory
:
ConnectionManagerFactory
)
{
private
val
manager
:
ConnectionManager
=
factory
.
create
(
serverInteractor
.
get
()
!!
)
private
val
currentServer
=
serverInteractor
.
get
()
!!
private
val
client
=
manager
.
client
private
var
reloadJob
:
Deferred
<
List
<
ChatRoom
>>?
=
null
private
val
settings
=
settingsRepository
.
get
(
currentServer
)
!!
...
...
@@ -69,7 +70,9 @@ class ChatRoomsPresenter @Inject constructor(private val view: ChatRoomsView,
}
navigator
.
toChatRoom
(
chatRoom
.
id
,
roomName
,
chatRoom
.
type
.
toString
(),
chatRoom
.
readonly
?:
false
,
chatRoom
.
lastSeen
?:
-
1
)
chatRoom
.
type
.
toString
(),
chatRoom
.
readonly
?:
false
,
chatRoom
.
lastSeen
?:
-
1
,
chatRoom
.
open
)
}
/**
...
...
@@ -79,9 +82,42 @@ class ChatRoomsPresenter @Inject constructor(private val view: ChatRoomsView,
fun
chatRoomsByName
(
name
:
String
)
{
val
currentServer
=
serverInteractor
.
get
()
!!
launchUI
(
strategy
)
{
try
{
val
roomList
=
getChatRoomsInteractor
.
getByName
(
currentServer
,
name
)
if
(
roomList
.
isEmpty
())
{
val
(
users
,
rooms
)
=
client
.
spotlight
(
name
)
val
chatRoomsCombined
=
mutableListOf
<
ChatRoom
>()
chatRoomsCombined
.
addAll
(
usersToChatRooms
(
users
))
chatRoomsCombined
.
addAll
(
roomsToChatRooms
(
rooms
))
view
.
updateChatRooms
(
chatRoomsCombined
)
}
else
{
view
.
updateChatRooms
(
roomList
)
}
}
catch
(
ex
:
RocketChatException
)
{
Timber
.
e
(
ex
)
}
}
}
private
suspend
fun
usersToChatRooms
(
users
:
List
<
User
>):
List
<
ChatRoom
>
{
return
users
.
map
{
ChatRoom
(
it
.
id
,
RoomType
.
DIRECT_MESSAGE
,
SimpleUser
(
username
=
it
.
username
,
name
=
it
.
name
,
id
=
null
),
it
.
name
?:
""
,
it
.
name
,
false
,
null
,
null
,
null
,
null
,
null
,
false
,
false
,
false
,
0L
,
null
,
0L
,
null
,
client
)
}
}
private
suspend
fun
roomsToChatRooms
(
rooms
:
List
<
Room
>):
List
<
ChatRoom
>
{
return
rooms
.
map
{
ChatRoom
(
it
.
id
,
it
.
type
,
it
.
user
,
it
.
name
?:
""
,
it
.
fullName
,
it
.
readonly
,
it
.
updatedAt
,
null
,
null
,
it
.
topic
,
it
.
announcement
,
false
,
false
,
false
,
0L
,
null
,
0L
,
it
.
lastMessage
,
client
)
}
}
private
suspend
fun
loadRooms
():
List
<
ChatRoom
>
{
...
...
app/src/main/java/chat/rocket/android/chatrooms/ui/ChatRoomsFragment.kt
View file @
62af887c
...
...
@@ -134,8 +134,10 @@ class ChatRoomsFragment : Fragment(), ChatRoomsView {
}
private
val
dismissStatus
=
{
if
(
connection_status_text
!=
null
)
{
connection_status_text
.
fadeOut
()
}
}
private
fun
setupToolbar
()
{
(
activity
as
AppCompatActivity
).
supportActionBar
?.
title
=
getString
(
R
.
string
.
title_chats
)
...
...
app/src/main/java/chat/rocket/android/dagger/module/AppModule.kt
View file @
62af887c
...
...
@@ -7,7 +7,6 @@ import android.content.SharedPreferences
import
chat.rocket.android.BuildConfig
import
chat.rocket.android.R
import
chat.rocket.android.app.RocketChatDatabase
import
chat.rocket.android.app.utils.CustomImageFormatConfigurator
import
chat.rocket.android.authentication.infraestructure.MemoryTokenRepository
import
chat.rocket.android.authentication.infraestructure.SharedPreferencesMultiServerTokenRepository
import
chat.rocket.android.dagger.qualifier.ForFresco
...
...
@@ -131,7 +130,6 @@ class AppModule {
listeners
.
add
(
RequestLoggingListener
())
return
OkHttpImagePipelineConfigFactory
.
newBuilder
(
context
,
okHttpClient
)
.
setImageDecoderConfig
(
CustomImageFormatConfigurator
.
createImageDecoderConfig
())
.
setRequestListeners
(
listeners
)
.
setDownsampleEnabled
(
true
)
//.experiment().setBitmapPrepareToDraw(true).experiment()
...
...
@@ -141,11 +139,7 @@ class AppModule {
@Provides
@Singleton
fun
provideDraweeConfig
():
DraweeConfig
{
val
draweeConfigBuilder
=
DraweeConfig
.
newBuilder
()
CustomImageFormatConfigurator
.
addCustomDrawableFactories
(
draweeConfigBuilder
)
return
draweeConfigBuilder
.
build
()
return
DraweeConfig
.
newBuilder
().
build
()
}
@Provides
...
...
@@ -185,7 +179,13 @@ class AppModule {
@Provides
@Singleton
fun
provideChatRoomsRepository
():
ChatRoomsRepository
{
fun
provideRoomRepository
():
RoomRepository
{
return
MemoryRoomRepository
()
}
@Provides
@Singleton
fun
provideChatRoomRepository
():
ChatRoomsRepository
{
return
MemoryChatRoomsRepository
()
}
...
...
@@ -207,6 +207,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/helper/UrlHelper.kt
View file @
62af887c
...
...
@@ -11,7 +11,8 @@ object UrlHelper {
* @param avatarName The avatar name.
* @return The avatar URL.
*/
fun
getAvatarUrl
(
serverUrl
:
String
,
avatarName
:
String
):
String
=
removeTrailingSlash
(
serverUrl
)
+
"/avatar/"
+
avatarName
fun
getAvatarUrl
(
serverUrl
:
String
,
avatarName
:
String
,
format
:
String
=
"jpeg"
):
String
=
removeTrailingSlash
(
serverUrl
)
+
"/avatar/"
+
removeTrailingSlash
(
avatarName
)
+
"?format=$format"
/**
* Returns the CAS URL.
...
...
app/src/main/java/chat/rocket/android/main/presentation/MainNavigator.kt
View file @
62af887c
...
...
@@ -29,8 +29,14 @@ class MainNavigator(internal val activity: MainActivity, internal val context: C
}
}
fun
toChatRoom
(
chatRoomId
:
String
,
chatRoomName
:
String
,
chatRoomType
:
String
,
isChatRoomReadOnly
:
Boolean
,
chatRoomLastSeen
:
Long
)
{
activity
.
startActivity
(
context
.
chatRoomIntent
(
chatRoomId
,
chatRoomName
,
chatRoomType
,
isChatRoomReadOnly
,
chatRoomLastSeen
))
fun
toChatRoom
(
chatRoomId
:
String
,
chatRoomName
:
String
,
chatRoomType
:
String
,
isChatRoomReadOnly
:
Boolean
,
chatRoomLastSeen
:
Long
,
isChatRoomSubscribed
:
Boolean
)
{
activity
.
startActivity
(
context
.
chatRoomIntent
(
chatRoomId
,
chatRoomName
,
chatRoomType
,
isChatRoomReadOnly
,
chatRoomLastSeen
,
isChatRoomSubscribed
))
activity
.
overridePendingTransition
(
R
.
anim
.
open_enter
,
R
.
anim
.
open_exit
)
}
}
\ No newline at end of file
app/src/main/java/chat/rocket/android/members/ui/MembersFragment.kt
View file @
62af887c
...
...
@@ -15,6 +15,7 @@ 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.hideKeyboard
import
chat.rocket.android.util.extensions.inflate
import
chat.rocket.android.util.extensions.setVisible
import
chat.rocket.android.util.extensions.showToast
...
...
@@ -22,6 +23,10 @@ import chat.rocket.android.widget.DividerItemDecoration
import
dagger.android.support.AndroidSupportInjection
import
kotlinx.android.synthetic.main.fragment_members.*
import
javax.inject.Inject
import
android.view.inputmethod.InputMethodManager.HIDE_IMPLICIT_ONLY
import
android.app.Activity
import
android.view.inputmethod.InputMethodManager
fun
newInstance
(
chatRoomId
:
String
,
chatRoomType
:
String
):
Fragment
{
return
MembersFragment
().
apply
{
...
...
@@ -60,6 +65,9 @@ class MembersFragment : Fragment(), MembersView {
override
fun
onViewCreated
(
view
:
View
,
savedInstanceState
:
Bundle
?)
{
super
.
onViewCreated
(
view
,
savedInstanceState
)
val
imm
=
activity
?.
getSystemService
(
Activity
.
INPUT_METHOD_SERVICE
)
as
InputMethodManager
imm
.
toggleSoftInput
(
InputMethodManager
.
HIDE_IMPLICIT_ONLY
,
0
)
(
activity
as
AppCompatActivity
).
supportActionBar
?.
title
=
""
setupRecyclerView
()
...
...
app/src/main/java/chat/rocket/android/members/viewmodel/MemberViewModel.kt
View file @
62af887c
...
...
@@ -25,7 +25,7 @@ class MemberViewModel(private val member: User, private val settings: Map<String
private
fun
getUserAvatar
():
String
?
{
val
username
=
member
.
username
?:
"?"
return
baseUrl
?.
let
{
UrlHelper
.
getAvatarUrl
(
baseUrl
,
username
)
UrlHelper
.
getAvatarUrl
(
baseUrl
,
username
,
"png"
)
}
}
...
...
app/src/main/java/chat/rocket/android/profile/presentation/ProfilePresenter.kt
View file @
62af887c
...
...
@@ -13,7 +13,7 @@ import chat.rocket.core.internal.rest.setAvatar
import
chat.rocket.core.internal.rest.updateProfile
import
javax.inject.Inject
class
ProfilePresenter
@Inject
constructor
(
private
val
view
:
ProfileView
,
class
ProfilePresenter
@Inject
constructor
(
private
val
view
:
ProfileView
,
private
val
strategy
:
CancelStrategy
,
serverInteractor
:
GetCurrentServerInteractor
,
factory
:
RocketChatClientFactory
)
{
...
...
@@ -30,8 +30,8 @@ class ProfilePresenter @Inject constructor (private val view: ProfileView,
val
avatarUrl
=
UrlHelper
.
getAvatarUrl
(
serverUrl
,
myself
.
username
!!
)
view
.
showProfile
(
avatarUrl
,
myself
.
name
!!
,
myself
.
username
!!
,
myself
.
name
?:
""
,
myself
.
username
?:
""
,
myself
.
emails
?.
get
(
0
)
?.
address
!!
)
}
catch
(
exception
:
RocketChatException
)
{
...
...
app/src/main/java/chat/rocket/android/profile/ui/ProfileFragment.kt
View file @
62af887c
...
...
@@ -56,8 +56,8 @@ class ProfileFragment : Fragment(), ProfileView, ActionMode.Callback {
text_email
.
textContent
=
email
text_avatar_url
.
textContent
=
""
currentName
=
name
currentUsername
=
user
name
currentName
=
user
name
currentUsername
=
name
currentEmail
=
email
currentAvatar
=
avatarUrl
...
...
@@ -133,10 +133,10 @@ class ProfileFragment : Fragment(), ProfileView, ActionMode.Callback {
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
.
toString
()
!=
currentUsername
||
text_email
.
toString
()
!=
currentEmail
||
(
text_avatar_url
.
toString
()
!=
""
&&
text_avatar_url
.
toString
()!=
currentAvatar
))
}.
subscribe
({
isValid
->
(
text_avatar_url
.
toString
()
!=
""
&&
text_avatar_url
.
toString
()
!=
currentAvatar
))
}.
subscribe
({
isValid
->
if
(
isValid
)
{
startActionMode
()
}
else
{
...
...
@@ -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 @
62af887c
...
...
@@ -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 @
62af887c
...
...
@@ -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 @
62af887c
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 @
62af887c
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 @
62af887c
...
...
@@ -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 @
62af887c
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 @
62af887c
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 @
62af887c
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 @
62af887c
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 @
62af887c
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 @
62af887c
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 @
62af887c
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 @
62af887c
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 @
62af887c
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 @
62af887c
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 @
62af887c
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 @
62af887c
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 @
62af887c
package
chat.rocket.android.widget.autocompletion.ui
import
android.content.Context
import
android.graphics.Canvas
import
android.graphics.Rect
import
android.graphics.drawable.Drawable
import
android.support.annotation.DrawableRes
import
android.support.transition.Slide
import
android.support.transition.TransitionManager
import
android.support.v4.content.ContextCompat
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.R
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 respective 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
.
addItemDecoration
(
TopItemDecoration
(
context
,
R
.
drawable
.
suggestions_menu_decorator
))
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
}
private
class
TopItemDecoration
()
:
RecyclerView
.
ItemDecoration
()
{
private
lateinit
var
divider
:
Drawable
private
val
padding
=
Rect
()
// Custom divider will be used.
constructor
(
context
:
Context
,
@DrawableRes
drawableResId
:
Int
)
:
this
()
{
val
customDrawable
=
ContextCompat
.
getDrawable
(
context
,
drawableResId
)
if
(
customDrawable
!=
null
)
{
divider
=
customDrawable
}
}
override
fun
onDrawOver
(
c
:
Canvas
,
parent
:
RecyclerView
,
state
:
RecyclerView
.
State
)
{
val
left
=
parent
.
paddingLeft
val
right
=
(
parent
.
width
-
parent
.
paddingRight
)
val
parentParams
=
parent
.
layoutParams
as
FrameLayout
.
LayoutParams
val
top
=
parent
.
top
-
parentParams
.
topMargin
-
parent
.
paddingTop
val
bottom
=
top
+
divider
.
intrinsicHeight
divider
.
setBounds
(
left
,
top
,
right
,
bottom
)
divider
.
draw
(
c
)
}
}
}
\ No newline at end of file
app/src/main/res/anim/hold.xml
View file @
62af887c
<?xml version="1.0" encoding="utf-8"?>
<set
xmlns:android=
"http://schemas.android.com/apk/res/android"
>
<translate
android:duration=
"
8
00"
android:duration=
"
5
00"
android:fromYDelta=
"0.0%p"
android:toYDelta=
"0.0%p"
/>
</set>
\ No newline at end of file
app/src/main/res/anim/slide_up.xml
View file @
62af887c
<?xml version="1.0" encoding="utf-8"?>
<set
xmlns:android=
"http://schemas.android.com/apk/res/android"
>
<translate
xmlns:android=
"http://schemas.android.com/apk/res/android"
android:duration=
"
3
50"
android:duration=
"
2
50"
android:fromYDelta=
"100.0%"
android:toYDelta=
"0.0%"
/>
</set>
\ No newline at end of file
app/src/main/res/drawable/suggestions_menu_decorator.xml
0 → 100644
View file @
62af887c
<?xml version="1.0" encoding="utf-8"?>
<shape
xmlns:android=
"http://schemas.android.com/apk/res/android"
android:shape=
"rectangle"
>
<solid
android:color=
"#10000000"
/>
<corners
android:radius=
"5dp"
/>
<size
android:height=
"2dp"
/>
</shape>
\ No newline at end of file
app/src/main/res/drawable/user_status_white.xml
0 → 100644
View file @
62af887c
<?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_log_in.xml
View file @
62af887c
...
...
@@ -9,7 +9,6 @@
tools:context=
".authentication.login.ui.LoginFragment"
>
<android.support.constraint.ConstraintLayout
android:id=
"@+id/middle_container"
android:layout_width=
"match_parent"
android:layout_height=
"wrap_content"
>
...
...
app/src/main/res/layout/fragment_authentication_server.xml
View file @
62af887c
...
...
@@ -14,19 +14,11 @@
android:layout_centerHorizontal=
"true"
android:text=
"@string/title_sign_in_your_server"
/>
<TextView
android:id=
"@+id/text_server_protocol"
style=
"@style/Authentication.TextView"
android:layout_below=
"@id/text_headline"
android:layout_marginTop=
"32dp"
android:gravity=
"center_vertical"
android:text=
"@string/default_protocol"
/>
<EditText
android:id=
"@+id/text_server_url"
style=
"@style/Authentication.EditText"
android:layout_below=
"@id/text_headline"
android:layout_marginStart=
"
0
dp"
android:layout_marginStart=
"
-4
dp"
android:layout_marginTop=
"32dp"
android:layout_toEndOf=
"@id/text_server_protocol"
android:cursorVisible=
"false"
...
...
@@ -35,7 +27,15 @@
android:digits=
"0123456789abcdefghijklmnopqrstuvwxyz.-/:"
android:inputType=
"textUri"
android:paddingEnd=
"0dp"
android:paddingStart=
"0dp"
/>
android:paddingStart=
"2dp"
/>
<TextView
android:id=
"@+id/text_server_protocol"
style=
"@style/Authentication.TextView"
android:layout_below=
"@id/text_headline"
android:layout_marginTop=
"32dp"
android:gravity=
"center_vertical"
android:text=
"@string/default_protocol"
/>
<com.wang.avi.AVLoadingIndicatorView
android:id=
"@+id/view_loading"
...
...
app/src/main/res/layout/fragment_authentication_sign_up.xml
View file @
62af887c
<?xml version="1.0" encoding="utf-8"?>
<
android.support.constraint.Constraint
Layout
xmlns:android=
"http://schemas.android.com/apk/res/android"
<
Relative
Layout
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/
constraint
_layout"
android:id=
"@+id/
relative
_layout"
android:layout_width=
"match_parent"
android:layout_height=
"match_parent"
android:focusableInTouchMode=
"true"
tools:context=
".authentication.signup.ui.SignupFragment"
>
<ScrollView
android:id=
"@+id/scroll_view"
android:layout_width=
"match_parent"
android:layout_height=
"wrap_content"
>
<LinearLayout
android:layout_width=
"match_parent"
android:layout_height=
"wrap_content"
android:orientation=
"vertical"
>
<TextView
android:id=
"@+id/text_headline"
style=
"@style/Authentication.Headline.TextView"
android:text=
"@string/title_sign_up"
app:layout_constraintLeft_toLeftOf=
"parent"
app:layout_constraintRight_toRightOf=
"parent"
app:layout_constraintTop_toTopOf=
"parent"
/>
android:layout_gravity=
"center"
android:text=
"@string/title_sign_up"
/>
<EditText
android:id=
"@+id/text_name"
...
...
@@ -22,10 +31,7 @@
android:drawableStart=
"@drawable/ic_person_black_24dp"
android:hint=
"@string/msg_name"
android:imeOptions=
"actionNext"
android:inputType=
"textCapWords"
app:layout_constraintLeft_toLeftOf=
"parent"
app:layout_constraintRight_toRightOf=
"parent"
app:layout_constraintTop_toBottomOf=
"@+id/text_headline"
/>
android:inputType=
"textCapWords"
/>
<EditText
android:id=
"@+id/text_username"
...
...
@@ -34,10 +40,7 @@
android:drawableStart=
"@drawable/ic_at_black_24dp"
android:hint=
"@string/msg_username"
android:imeOptions=
"actionNext"
android:inputType=
"text"
app:layout_constraintLeft_toLeftOf=
"parent"
app:layout_constraintRight_toRightOf=
"parent"
app:layout_constraintTop_toBottomOf=
"@+id/text_name"
/>
android:inputType=
"text"
/>
<EditText
android:id=
"@+id/text_password"
...
...
@@ -46,53 +49,51 @@
android:drawableStart=
"@drawable/ic_lock_black_24dp"
android:hint=
"@string/msg_password"
android:imeOptions=
"actionNext"
android:inputType=
"textPassword"
app:layout_constraintLeft_toLeftOf=
"parent"
app:layout_constraintRight_toRightOf=
"parent"
app:layout_constraintTop_toBottomOf=
"@+id/text_username"
/>
android:inputType=
"textPassword"
/>
<EditText
android:id=
"@+id/text_email"
style=
"@style/Authentication.EditText"
android:layout_marginBottom=
"16dp"
android:layout_marginTop=
"16dp"
android:drawableStart=
"@drawable/ic_email_black_24dp"
android:hint=
"@string/msg_email"
android:imeOptions=
"actionDone"
android:inputType=
"textEmailAddress"
app:layout_constraintLeft_toLeftOf=
"parent"
app:layout_constraintRight_toRightOf=
"parent"
app:layout_constraintTop_toBottomOf=
"@+id/text_password"
/>
android:inputType=
"textEmailAddress"
/>
</LinearLayout>
</ScrollView>
<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_below=
"@id/scroll_view"
android:layout_centerHorizontal=
"true"
android:layout_marginTop=
"16dp"
android:visibility=
"gone"
app:indicatorName=
"BallPulseIndicator"
app:layout_constraintLeft_toLeftOf=
"parent"
app:layout_constraintRight_toRightOf=
"parent"
app:layout_constraintTop_toBottomOf=
"@+id/text_email"
tools:visibility=
"visible"
/>
<LinearLayout
android:id=
"@+id/bottom_container"
android:layout_width=
"match_parent"
android:layout_height=
"wrap_content"
android:layout_alignParentBottom=
"true"
android:orientation=
"vertical"
>
<TextView
android:id=
"@+id/text_new_user_agreement"
android:layout_width=
"wrap_content"
android:layout_height=
"wrap_content"
android:layout_marginBottom=
"16dp"
android:layout_marginEnd=
"@dimen/screen_edge_left_and_right_margins"
android:layout_marginStart=
"@dimen/screen_edge_left_and_right_margins"
android:layout_gravity=
"center"
android:layout_margin=
"@dimen/screen_edge_left_and_right_margins"
android:gravity=
"center"
android:textColorLink=
"@color/colorAccent"
app:layout_constraintBottom_toTopOf=
"@+id/button_sign_up"
app:layout_constraintLeft_toLeftOf=
"parent"
app:layout_constraintRight_toRightOf=
"parent"
/>
android:textColorLink=
"@color/colorAccent"
/>
<Button
android:id=
"@+id/button_sign_up"
style=
"@style/Authentication.Button"
android:text=
"@string/title_sign_up"
app:layout_constraintBottom_toBottomOf=
"parent"
/
>
android:text=
"@string/title_sign_up"
/>
</LinearLayout
>
</android.support.constraint.ConstraintLayout>
\ No newline at end of file
</RelativeLayout>
\ No newline at end of file
app/src/main/res/layout/fragment_chat_room.xml
View file @
62af887c
...
...
@@ -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/suggestion_background_color"
/>
<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/message_composer.xml
View file @
62af887c
...
...
@@ -27,6 +27,18 @@
android:visibility=
"gone"
app:layout_constraintTop_toBottomOf=
"@+id/divider"
/>
<Button
android:id=
"@+id/button_join_chat"
android:layout_width=
"match_parent"
android:layout_height=
"45dp"
android:background=
"@color/white"
android:text=
"@string/action_join_chat"
android:textColor=
"@color/colorAccent"
android:visibility=
"gone"
app:layout_constraintEnd_toEndOf=
"parent"
app:layout_constraintStart_toStartOf=
"parent"
app:layout_constraintTop_toBottomOf=
"@+id/divider"
/>
<LinearLayout
android:id=
"@+id/input_container"
android:layout_width=
"match_parent"
...
...
app/src/main/res/layout/suggestion_member_item.xml
0 → 100644
View file @
62af887c
<?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_marginBottom=
"2dp"
android:layout_marginEnd=
"2dp"
android:layout_marginLeft=
"4dp"
android:layout_marginRight=
"2dp"
android:layout_marginStart=
"4dp"
android:layout_marginTop=
"2dp"
android:background=
"@color/suggestion_background_color"
>
<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/suggestion_background_color"
>
<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 @
62af887c
<?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/suggestion_background_color"
>
<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/suggestion_background_color"
>
<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-pt-rBR/strings.xml
View file @
62af887c
...
...
@@ -24,6 +24,8 @@
<string
name=
"action_logout"
>
Sair
</string>
<string
name=
"action_files"
>
Arquivos
</string>
<string
name=
"action_confirm_password"
>
Confirme a nova senha
</string>
<string
name=
"action_join_chat"
>
Entrar no Chat
</string>
<!-- Settings List -->
<string-array
name=
"settings_actions"
>
...
...
app/src/main/res/values/colors.xml
View file @
62af887c
...
...
@@ -15,7 +15,7 @@
<color
name=
"colorUserStatusOnline"
>
#2FE1A8
</color>
<color
name=
"colorUserStatusBusy"
>
#F33E5B
</color>
<color
name=
"colorUserStatusAway"
>
#FDD236
</color>
<color
name=
"colorUserStatusOffline"
>
#
1F2228
</color>
<color
name=
"colorUserStatusOffline"
>
#
d9d9d9
</color>
<color
name=
"colorDrawableTintGrey"
>
#9FA2A8
</color>
...
...
@@ -36,4 +36,7 @@
<color
name=
"colorEmojiIcon"
>
#FF767676
</color>
<!-- Suggestions -->
<color
name=
"suggestion_background_color"
>
@android:color/white
</color>
</resources>
app/src/main/res/values/dimens.xml
View file @
62af887c
...
...
@@ -32,4 +32,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
app/src/main/res/values/strings.xml
View file @
62af887c
...
...
@@ -25,6 +25,7 @@
<string
name=
"action_logout"
>
Logout
</string>
<string
name=
"action_files"
>
Files
</string>
<string
name=
"action_confirm_password"
>
Confirm Password Change
</string>
<string
name=
"action_join_chat"
>
Join Chat
</string>
<!-- Settings List -->
<string-array
name=
"settings_actions"
>
...
...
build.gradle
View file @
62af887c
...
...
@@ -14,7 +14,7 @@ buildscript {
classpath
"org.jetbrains.kotlin:kotlin-gradle-plugin:${versions.kotlin}"
classpath
"org.jetbrains.dokka:dokka-gradle-plugin:${versions.dokka}"
classpath
'com.google.gms:google-services:3.2.0'
classpath
'io.fabric.tools:gradle:1.
+
'
classpath
'io.fabric.tools:gradle:1.
25.1
'
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
...
...
dependencies.gradle
View file @
62af887c
...
...
@@ -4,7 +4,7 @@ ext {
compileSdk
:
27
,
targetSdk
:
27
,
buildTools
:
'27.0.3'
,
kotlin
:
'1.2.
21
'
,
kotlin
:
'1.2.
30
'
,
coroutine
:
'0.22'
,
dokka
:
'0.9.15'
,
...
...
@@ -25,8 +25,7 @@ ext {
rxBinding
:
'2.0.0'
,
fresco
:
'1.8.1'
,
kotshi
:
'0.3.0'
,
frescoImageViewer
:
'0.5.0'
,
androidSvg
:
'master-SNAPSHOT'
,
frescoImageViewer
:
'0.5.1'
,
markwon
:
'1.0.3'
,
sheetMenu
:
'1.3.3'
,
aVLoadingIndicatorView
:
'2.1.3'
,
...
...
@@ -86,8 +85,7 @@ ext {
kotshiApi
:
"se.ansman.kotshi:api:${versions.kotshi}"
,
kotshiCompiler
:
"se.ansman.kotshi:compiler:${versions.kotshi}"
,
frescoImageViewer
:
"com.github.stfalcon:frescoimageviewer:${versions.frescoImageViewer}"
,
androidSvg
:
"com.github.BigBadaboom:androidsvg:${versions.androidSvg}"
,
frescoImageViewer
:
"com.github.luciofm:FrescoImageViewer:${versions.frescoImageViewer}"
,
markwon
:
"ru.noties:markwon:${versions.markwon}"
,
markwonImageLoader
:
"ru.noties:markwon-image-loader:${versions.markwon}"
,
...
...
@@ -96,8 +94,6 @@ ext {
aVLoadingIndicatorView
:
"com.wang.avi:library:${versions.aVLoadingIndicatorView}"
,
swipeBackLayout
:
"me.imid.swipebacklayout.lib:library:${versions.swipeBackLayout}"
,
// For testing
junit
:
"junit:junit:$versions.junit"
,
expressoCore
:
"com.android.support.test.espresso:espresso-core:${versions.expresso}"
,
...
...
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