Commit 81a4bae4 authored by Filipe de Lima Brito's avatar Filipe de Lima Brito

Get active user streams.

parent d0e17275
......@@ -472,7 +472,7 @@ class ChatRoomPresenter @Inject constructor(private val view: ChatRoomView,
fun loadChatRooms() {
launchUI(strategy) {
try {
val chatRooms = getChatRoomsInteractor.get(currentServer)
val chatRooms = getChatRoomsInteractor.getAll(currentServer)
.filterNot {
it.type is RoomType.DirectMessage || it.type is RoomType.Livechat
}
......
......@@ -18,6 +18,7 @@ import chat.rocket.common.model.BaseRoom
import chat.rocket.common.model.RoomType
import chat.rocket.common.model.SimpleUser
import chat.rocket.common.model.User
import chat.rocket.common.util.ifNull
import chat.rocket.core.internal.model.Subscription
import chat.rocket.core.internal.realtime.socket.model.State
import chat.rocket.core.internal.realtime.socket.model.StreamMessage
......@@ -32,7 +33,8 @@ import timber.log.Timber
import javax.inject.Inject
import kotlin.reflect.KProperty1
class ChatRoomsPresenter @Inject constructor(private val view: ChatRoomsView,
class ChatRoomsPresenter @Inject constructor(
private val view: ChatRoomsView,
private val strategy: CancelStrategy,
private val navigator: MainNavigator,
private val serverInteractor: GetCurrentServerInteractor,
......@@ -41,16 +43,17 @@ class ChatRoomsPresenter @Inject constructor(private val view: ChatRoomsView,
private val refreshSettingsInteractor: RefreshSettingsInteractor,
private val viewModelMapper: ViewModelMapper,
settingsRepository: SettingsRepository,
factory: ConnectionManagerFactory) {
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)
private val subscriptionsChannel = Channel<StreamMessage<BaseRoom>>()
private val stateChannel = Channel<State>()
private val subscriptionsChannel = Channel<StreamMessage<BaseRoom>>()
private val activeUserChannel = Channel<User>()
private val activeUserList = mutableListOf<User>()
private var lastState = manager.state
fun loadChatRooms() {
......@@ -59,13 +62,18 @@ class ChatRoomsPresenter @Inject constructor(private val view: ChatRoomsView,
view.showLoading()
subscribeStatusChange()
try {
view.updateChatRooms(loadRooms())
} catch (e: RocketChatException) {
Timber.e(e)
view.showMessage(e.message!!)
view.updateChatRooms(getUserChatRooms())
} catch (ex: RocketChatException) {
ex.message?.let {
view.showMessage(it)
}.ifNull {
view.showGenericErrorMessage()
}
Timber.e(ex)
} finally {
view.hideLoading()
}
subscribeActiveUsers()
subscribeRoomUpdates()
}
}
......@@ -93,7 +101,7 @@ class ChatRoomsPresenter @Inject constructor(private val view: ChatRoomsView,
val currentServer = serverInteractor.get()!!
launchUI(strategy) {
try {
val roomList = getChatRoomsInteractor.getByName(currentServer, name)
val roomList = getChatRoomsInteractor.getAllByName(currentServer, name)
if (roomList.isEmpty()) {
val (users, rooms) = retryIO("spotlight($name)") {
client.spotlight(name)
......@@ -111,11 +119,12 @@ class ChatRoomsPresenter @Inject constructor(private val view: ChatRoomsView,
}
}
private suspend fun usersToChatRooms(users: List<User>): List<ChatRoom> {
private fun usersToChatRooms(users: List<User>): List<ChatRoom> {
return users.map {
ChatRoom(id = it.id,
type = RoomType.DIRECT_MESSAGE,
user = SimpleUser(username = it.username, name = it.name, id = null),
status = getActiveUserByUsername(it.name!!)?.status,
name = it.name ?: "",
fullName = it.name,
readonly = false,
......@@ -137,11 +146,12 @@ class ChatRoomsPresenter @Inject constructor(private val view: ChatRoomsView,
}
}
private suspend fun roomsToChatRooms(rooms: List<Room>): List<ChatRoom> {
private fun roomsToChatRooms(rooms: List<Room>): List<ChatRoom> {
return rooms.map {
ChatRoom(id = it.id,
type = it.type,
user = it.user,
status = getActiveUserByUsername(it.name!!)?.status,
name = it.name ?: "",
fullName = it.fullName,
readonly = it.readonly,
......@@ -163,7 +173,7 @@ class ChatRoomsPresenter @Inject constructor(private val view: ChatRoomsView,
}
}
private suspend fun loadRooms(): List<ChatRoom> {
private suspend fun getUserChatRooms(): List<ChatRoom> {
val chatRooms = retryIO("chatRooms") { manager.chatRooms().update }
val sortedRooms = sortRooms(chatRooms)
Timber.d("Loaded rooms: ${sortedRooms.size}")
......@@ -174,7 +184,7 @@ class ChatRoomsPresenter @Inject constructor(private val view: ChatRoomsView,
fun updateSortedChatRooms() {
val currentServer = serverInteractor.get()!!
launchUI(strategy) {
val roomList = getChatRoomsInteractor.get(currentServer)
val roomList = getChatRoomsInteractor.getAll(currentServer)
view.updateChatRooms(sortRooms(roomList))
}
}
......@@ -220,13 +230,6 @@ class ChatRoomsPresenter @Inject constructor(private val view: ChatRoomsView,
}
}
private fun updateRooms() {
Timber.d("Updating Rooms")
launch(strategy.jobs) {
view.updateChatRooms(getChatRoomsWithPreviews(getChatRoomsInteractor.get(currentServer)))
}
}
private suspend fun getChatRoomsWithPreviews(chatRooms: List<ChatRoom>): List<ChatRoom> {
return chatRooms.map {
if (it.lastMessage != null) {
......@@ -241,12 +244,6 @@ class ChatRoomsPresenter @Inject constructor(private val view: ChatRoomsView,
return chatRooms.filter(ChatRoom::open)
}
private fun sortChatRooms(chatRooms: List<ChatRoom>): List<ChatRoom> {
return chatRooms.sortedByDescending { chatRoom ->
chatRoom.lastMessage?.timestamp
}
}
private suspend fun subscribeStatusChange() {
lastState = manager.state
launch(CommonPool + strategy.jobs) {
......@@ -256,10 +253,9 @@ class ChatRoomsPresenter @Inject constructor(private val view: ChatRoomsView,
launch(UI) {
view.showConnectionState(state)
}
if (state is State.Connected) {
reloadRooms()
updateRooms()
updateChatRooms()
}
}
lastState = state
......@@ -299,7 +295,7 @@ class ChatRoomsPresenter @Inject constructor(private val view: ChatRoomsView,
}
}
updateRooms()
updateChatRooms()
}
private suspend fun updateSubscription(message: StreamMessage<Subscription>) {
......@@ -318,7 +314,7 @@ class ChatRoomsPresenter @Inject constructor(private val view: ChatRoomsView,
}
}
updateRooms()
updateChatRooms()
}
private suspend fun reloadRooms() {
......@@ -329,7 +325,7 @@ class ChatRoomsPresenter @Inject constructor(private val view: ChatRoomsView,
reloadJob = async(CommonPool + strategy.jobs) {
delay(1000)
Timber.d("reloading rooms after wait")
loadRooms()
getUserChatRooms()
}
reloadJob?.await()
} catch (ex: Exception) {
......@@ -340,12 +336,13 @@ class ChatRoomsPresenter @Inject constructor(private val view: ChatRoomsView,
// Update a ChatRoom with a Room information
private fun updateRoom(room: Room) {
Timber.d("Updating Room: ${room.id} - ${room.name}")
val chatRooms = getChatRoomsInteractor.get(currentServer).toMutableList()
val chatRooms = getChatRoomsInteractor.getAll(currentServer).toMutableList()
val chatRoom = chatRooms.find { chatRoom -> chatRoom.id == room.id }
chatRoom?.apply {
val newRoom = ChatRoom(id = room.id,
type = room.type,
user = room.user ?: user,
status = getActiveUserByUsername(room.name!!)?.status,
name = room.name ?: name,
fullName = room.fullName ?: fullName,
readonly = room.readonly,
......@@ -373,12 +370,13 @@ class ChatRoomsPresenter @Inject constructor(private val view: ChatRoomsView,
// Update a ChatRoom with a Subscription information
private fun updateSubscription(subscription: Subscription) {
Timber.d("Updating subscription: ${subscription.id} - ${subscription.name}")
val chatRooms = getChatRoomsInteractor.get(currentServer).toMutableList()
val chatRooms = getChatRoomsInteractor.getAll(currentServer).toMutableList()
val chatRoom = chatRooms.find { chatRoom -> chatRoom.id == subscription.roomId }
chatRoom?.apply {
val newRoom = ChatRoom(id = subscription.roomId,
type = subscription.type,
user = subscription.user ?: user,
status = getActiveUserByUsername(subscription.name)?.status,
name = subscription.name,
fullName = subscription.fullName ?: fullName,
readonly = subscription.readonly ?: readonly,
......@@ -403,9 +401,10 @@ class ChatRoomsPresenter @Inject constructor(private val view: ChatRoomsView,
}
}
private fun removeRoom(id: String,
chatRooms: MutableList<ChatRoom> = getChatRoomsInteractor.get(currentServer).toMutableList()) {
private fun removeRoom(
id: String,
chatRooms: MutableList<ChatRoom> = getChatRoomsInteractor.getAll(currentServer).toMutableList()
) {
Timber.d("Removing ROOM: $id")
synchronized(this) {
chatRooms.removeAll { chatRoom -> chatRoom.id == id }
......@@ -413,8 +412,106 @@ class ChatRoomsPresenter @Inject constructor(private val view: ChatRoomsView,
saveChatRoomsInteractor.save(currentServer, sortRooms(chatRooms))
}
private suspend fun subscribeActiveUsers() {
manager.addActiveUserChannel(activeUserChannel)
for (user in activeUserChannel) {
processActiveUser(user)
}
}
private fun processActiveUser(user: User) {
// The first activeUsers stream contains all details of the users (username, UTC Offset,
// etc.), so we add each user to our [activeUserList] because the following streams don't
// contain those details.
if (!activeUserList.any { user_ -> user_.id == user.id }) {
Timber.i("Got first active user stream for the user: $user")
activeUserList.add(user)
} else {
// After the first stream the next is about the active users updates.
Timber.i("Got update of active user stream for the user: $user")
updateActiveUser(user)
}
getActiveUserById(user.id)?.let {
updateChatRoomWithUserStatus(it)
}
}
private fun updateActiveUser(user: User) {
activeUserList.find { user_ -> user_.id == user.id }?.let {
val newUser = User(
id = user.id,
name = user.name ?: it.name,
username = user.username ?: it.username,
status = user.status ?: it.status,
emails = user.emails ?: it.emails,
utcOffset = user.utcOffset ?: it.utcOffset
)
activeUserList.remove(it)
activeUserList.add(newUser)
}
}
private fun getActiveUserById(id: String): User? {
return activeUserList.find { user -> user.id == id }
}
private fun getActiveUserByUsername(username: String): User? {
return activeUserList.find { user -> user.username == username }
}
private fun updateChatRoomWithUserStatus(user_: User) {
val username = user_.username
val status = user_.status
if (username != null && status != null) {
getChatRoomsInteractor.getByName(currentServer, username)?.let {
val newRoom = ChatRoom(
id = it.id,
type = it.type,
user = it.user,
status = status,
name = it.name,
fullName = it.fullName,
readonly = it.readonly,
updatedAt = it.updatedAt,
timestamp = it.timestamp,
lastSeen = it.lastSeen,
topic = it.topic,
description = it.description,
announcement = it.announcement,
default = it.default,
favorite = it.favorite,
open = it.open,
alert = it.alert,
unread = it.unread,
userMenstions = it.userMenstions,
groupMentions = it.groupMentions,
lastMessage = it.lastMessage,
client = client
)
getChatRoomsInteractor.remove(currentServer, it)
getChatRoomsInteractor.add(currentServer, newRoom)
launchUI(strategy) {
view.updateChatRooms(sortRooms(getChatRoomsInteractor.getAll(currentServer)))
}
}
}
}
private fun updateChatRooms() {
Timber.i("Updating ChatRooms")
launch(strategy.jobs) {
val chatRooms = getChatRoomsWithPreviews(
getChatRoomsInteractor.getAll(currentServer)
)
view.updateChatRooms(chatRooms)
}
}
fun disconnect() {
manager.removeStatusChannel(stateChannel)
manager.removeRoomsAndSubscriptionsChannel(subscriptionsChannel)
manager.removeActiveUserChannel(activeUserChannel)
}
}
\ No newline at end of file
......@@ -22,6 +22,7 @@ import chat.rocket.android.util.extensions.inflate
import chat.rocket.android.util.extensions.setVisible
import chat.rocket.android.util.extensions.textContent
import chat.rocket.common.model.RoomType
import chat.rocket.common.model.UserStatus
import chat.rocket.core.model.ChatRoom
import com.facebook.drawee.view.SimpleDraweeView
import kotlinx.android.synthetic.main.item_chat.view.*
......@@ -91,7 +92,12 @@ class ChatRoomsAdapter(private val context: Context,
DrawableHelper.getDrawableFromId(R.drawable.ic_room_lock, context)
}
is RoomType.DirectMessage -> {
DrawableHelper.getDrawableFromId(R.drawable.ic_room_dm, context)
val status = chatRoom.status
if (status == null) {
DrawableHelper.getUserStatusDrawable(UserStatus.Offline(), context)
} else {
DrawableHelper.getUserStatusDrawable(status, context)
}
}
else -> null
}
......@@ -103,7 +109,9 @@ class ChatRoomsAdapter(private val context: Context,
true -> R.color.colorPrimaryText
false -> R.color.colorSecondaryText
}
if (chatRoom.type !is RoomType.DirectMessage) {
DrawableHelper.tintDrawable(mutableDrawable, context, color)
}
DrawableHelper.compoundDrawable(textView, mutableDrawable)
}
}
......
......@@ -19,14 +19,7 @@ import chat.rocket.android.infrastructure.SharedPrefsLocalRepository
import chat.rocket.android.push.GroupedPush
import chat.rocket.android.push.PushManager
import chat.rocket.android.server.domain.*
import chat.rocket.android.server.infraestructure.MemoryChatRoomsRepository
import chat.rocket.android.server.infraestructure.MemoryMessagesRepository
import chat.rocket.android.server.infraestructure.MemoryRoomRepository
import chat.rocket.android.server.infraestructure.MemoryUsersRepository
import chat.rocket.android.server.infraestructure.ServerDao
import chat.rocket.android.server.infraestructure.SharedPreferencesAccountsRepository
import chat.rocket.android.server.infraestructure.SharedPreferencesSettingsRepository
import chat.rocket.android.server.infraestructure.SharedPrefsCurrentServerRepository
import chat.rocket.android.server.infraestructure.*
import chat.rocket.android.util.AppJsonAdapterFactory
import chat.rocket.android.util.TimberLogger
import chat.rocket.common.internal.FallbackSealedClassJsonAdapter
......@@ -200,6 +193,12 @@ class AppModule {
return MemoryChatRoomsRepository()
}
@Provides
@Singleton
fun provideActiveUsersRepository(): ActiveUsersRepository {
return MemoryActiveUsersRepository()
}
@Provides
@Singleton
fun provideMoshi(): Moshi {
......
package chat.rocket.android.server.domain
import chat.rocket.common.model.User
interface ActiveUsersRepository {
fun save(url: String, activeUsers: List<User>)
fun get(url: String): List<User>
}
\ No newline at end of file
package chat.rocket.android.server.domain
import chat.rocket.common.model.User
import javax.inject.Inject
class GetActiveUsersInteractor @Inject constructor(private val repository: ActiveUsersRepository) {
fun getActiveUserById(url: String, id: String): User? {
return repository.get(url).find { user -> user.id == id }
}
fun getActiveUserByUsername(url: String, username: String): User? {
return repository.get(url).find { user -> user.username == username }
}
}
\ No newline at end of file
......@@ -8,13 +8,13 @@ import javax.inject.Inject
class GetChatRoomsInteractor @Inject constructor(private val repository: ChatRoomsRepository) {
/**
* Get all ChatRoom objects.
* Get all [ChatRoom].
*
* @param url The server url.
*
* @return All the ChatRoom objects.
* @return All the [ChatRoom] objects.
*/
fun get(url: String) = repository.get(url)
fun getAll(url: String) = repository.get(url)
/**
* Get a list of chat rooms that contains the name parameter.
......@@ -23,7 +23,7 @@ class GetChatRoomsInteractor @Inject constructor(private val repository: ChatRoo
* @param name The name of chat room to look for or a chat room that contains this name.
* @return A list of ChatRoom objects with the given name.
*/
suspend fun getByName(url: String, name: String): List<ChatRoom> = withContext(CommonPool) {
suspend fun getAllByName(url: String, name: String): List<ChatRoom> = withContext(CommonPool) {
val allChatRooms = repository.get(url)
if (name.isEmpty()) {
return@withContext allChatRooms
......@@ -34,11 +34,11 @@ class GetChatRoomsInteractor @Inject constructor(private val repository: ChatRoo
}
/**
* Get a specific room by its id.
* Get a specific [ChatRoom] by its id.
*
* @param serverUrl The server url where the room is.
* @param roomId The id of the room to get.
* @return The ChatRoom object or null if we couldn't find any.
* @return The [ChatRoom] object or null if we couldn't find any.
*/
suspend fun getById(serverUrl: String, roomId: String): ChatRoom? = withContext(CommonPool) {
val allChatRooms = repository.get(serverUrl)
......@@ -46,4 +46,43 @@ class GetChatRoomsInteractor @Inject constructor(private val repository: ChatRoo
it.id == roomId
}
}
/**
* Get a specific [ChatRoom] by its name.
*
* @param serverUrl The server url where the room is.
* @param name The name of the room to get.
* @return The [ChatRoom] object or null if we couldn't find any.
*/
fun getByName(serverUrl: String, name: String): ChatRoom? {
return getAll(serverUrl).toMutableList().find { chatRoom -> chatRoom.name == name }
}
/**
* Add a [ChatRoom].
*
* @param url The server url.
* @param chatRoom The [ChatRoom] to be added to the list.
*/
fun add(url: String, chatRoom: ChatRoom) {
val chatRooms: MutableList<ChatRoom> = getAll(url).toMutableList()
synchronized(this) {
chatRooms.add(chatRoom)
}
repository.save(url, chatRooms)
}
/**
* Removes a [ChatRoom].
*
* @param url The server url.
* @param chatRoom The [ChatRoom] to be removed from the list.
*/
fun remove(url: String, chatRoom: ChatRoom) {
val chatRooms: MutableList<ChatRoom> = getAll(url).toMutableList()
synchronized(this) {
chatRooms.removeAll { chatRoom_ -> chatRoom_.id == chatRoom.id }
}
repository.save(url, chatRooms)
}
}
\ No newline at end of file
package chat.rocket.android.server.domain
import chat.rocket.common.model.User
import javax.inject.Inject
class SaveActiveUsersInteractor @Inject constructor(private val repository: ActiveUsersRepository) {
fun save(url: String, activeUsers: List<User>) = repository.save(url, activeUsers)
}
\ No newline at end of file
package chat.rocket.android.server.infraestructure
import chat.rocket.common.model.BaseRoom
import chat.rocket.common.model.User
import chat.rocket.core.RocketChatClient
import chat.rocket.core.internal.realtime.unsubscribe
import chat.rocket.core.internal.realtime.*
import chat.rocket.core.internal.realtime.socket.connect
import chat.rocket.core.internal.realtime.socket.disconnect
import chat.rocket.core.internal.realtime.socket.model.State
import chat.rocket.core.internal.realtime.socket.model.StreamMessage
import chat.rocket.core.internal.realtime.subscribeRoomMessages
import chat.rocket.core.internal.realtime.subscribeRooms
import chat.rocket.core.internal.realtime.subscribeSubscriptions
import chat.rocket.core.internal.realtime.subscribeUserDataChanges
import chat.rocket.core.internal.rest.chatRooms
import chat.rocket.core.model.Message
import chat.rocket.core.model.Myself
......@@ -28,11 +25,13 @@ class ConnectionManager(internal val client: RocketChatClient) {
private val roomAndSubscriptionChannels = ArrayList<Channel<StreamMessage<BaseRoom>>>()
private val roomMessagesChannels = LinkedHashMap<String, Channel<Message>>()
private val userDataChannels = ArrayList<Channel<Myself>>()
private val activeUsersChannels = ArrayList<Channel<User>>()
private val subscriptionIdMap = HashMap<String, String>()
private var subscriptionId: String? = null
private var roomsId: String? = null
private var userId: String? = null
private var userDataId: String? = null
private var activeUserId: String? = null
fun connect() {
if (connectJob?.isActive == true && (state !is State.Disconnected)) {
......@@ -60,9 +59,13 @@ class ConnectionManager(internal val client: RocketChatClient) {
Timber.d("Subscribed to rooms: $id")
roomsId = id
}
client.subscribeUserDataChanges { _, id ->
Timber.d("Subscribed to the user: $id")
userId = id
client.subscribeUserData { _, id ->
Timber.d("Subscribed to the userData id: $id")
userDataId = id
}
client.subscribeActiveUsers { _, id ->
Timber.d("Subscribed to the activeUser id: $id")
activeUserId = id
}
resubscribeRooms()
......@@ -115,6 +118,14 @@ class ConnectionManager(internal val client: RocketChatClient) {
}
}
launch(parent = connectJob) {
for (user in client.activeUsersChannel) {
Timber.d("Got activeUsers")
for (channel in activeUsersChannels) {
channel.send(user)
}
}
}
client.connect()
// Broadcast initial state...
......@@ -154,6 +165,10 @@ class ConnectionManager(internal val client: RocketChatClient) {
fun removeUserDataChannel(channel: Channel<Myself>) = userDataChannels.remove(channel)
fun addActiveUserChannel(channel: Channel<User>) = activeUsersChannels.add(channel)
fun removeActiveUserChannel(channel: Channel<User>) = activeUsersChannels.remove(channel)
fun subscribeRoomMessages(roomId: String, channel: Channel<Message>) {
val oldSub = roomMessagesChannels.put(roomId, channel)
if (oldSub != null) {
......
package chat.rocket.android.server.infraestructure
import chat.rocket.android.server.domain.ActiveUsersRepository
import chat.rocket.common.model.User
class MemoryActiveUsersRepository : ActiveUsersRepository {
val cache = HashMap<String, List<User>>()
override fun save(url: String, activeUsers: List<User>) {
cache[url] = activeUsers
}
override fun get(url: String): List<User> = cache[url] ?: emptyList()
}
\ No newline at end of file
......@@ -16,6 +16,7 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:visibility="gone"
app:indicatorColor="@color/black"
app:indicatorName="BallPulseIndicator" />
......@@ -33,15 +34,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>
\ No newline at end of file
......@@ -5,19 +5,19 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?android:attr/selectableItemBackground"
android:paddingStart="@dimen/screen_edge_left_and_right_padding"
android:paddingBottom="@dimen/chat_item_top_and_bottom_padding"
android:paddingEnd="@dimen/screen_edge_left_and_right_padding"
android:paddingTop="@dimen/chat_item_top_and_bottom_padding"
android:paddingBottom="@dimen/chat_item_top_and_bottom_padding">
android:paddingStart="@dimen/screen_edge_left_and_right_padding"
android:paddingTop="@dimen/chat_item_top_and_bottom_padding">
<com.facebook.drawee.view.SimpleDraweeView
android:id="@+id/image_avatar"
android:layout_width="40dp"
android:layout_height="40dp"
app:roundedCornerRadius="3dp"
android:layout_marginTop="6dp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"/>
app:layout_constraintTop_toTopOf="parent"
app:roundedCornerRadius="3dp" />
<TextView
android:id="@+id/text_chat_name"
......@@ -25,17 +25,18 @@
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
app:layout_constraintStart_toEndOf="@id/image_avatar"
android:drawablePadding="6dp"
android:textDirection="locale"
tools:text="General"/>
app:layout_constraintStart_toEndOf="@id/image_avatar"
tools:text="General" />
<TextView
android:id="@+id/text_last_message_date_time"
style="@style/Timestamp.TextView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="5dp"
android:layout_marginEnd="5dp"
android:layout_marginStart="5dp"
app:layout_constraintBaseline_toBaselineOf="@+id/text_chat_name"
app:layout_constraintEnd_toEndOf="parent"
tools:text="11:45 AM" />
......@@ -44,23 +45,23 @@
android:id="@+id/text_last_message"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="2dp"
android:ellipsize="end"
android:maxLines="2"
android:layout_marginTop="2dp"
android:textDirection="locale"
app:layout_constraintEnd_toStartOf="@id/layout_unread_messages_badge"
app:layout_constraintStart_toStartOf="@id/text_chat_name"
app:layout_constraintTop_toBottomOf="@id/text_chat_name"
app:layout_constraintEnd_toStartOf="@id/layout_unread_messages_badge"
android:textDirection="locale"
tools:text="You: Type something that is very big and need at least to lines, or maybe even more"/>
tools:text="You: Type something that is very big and need at least to lines, or maybe even more" />
<include
android:id="@+id/layout_unread_messages_badge"
layout="@layout/unread_messages_badge"
android:layout_width="18dp"
android:layout_height="18dp"
android:layout_marginStart="5dp"
android:layout_marginEnd="5dp"
app:layout_constraintTop_toTopOf="@id/text_last_message"
app:layout_constraintEnd_toEndOf="parent"/>
android:layout_marginStart="5dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="@id/text_last_message" />
</android.support.constraint.ConstraintLayout>
\ No newline at end of file
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment