Commit 1e5148ce authored by Filipe de Lima Brito's avatar Filipe de Lima Brito

Refactor user details.

 - Fix for #3408 bug (Fabric);
 - Changing from Activity to fragment;
 - Logs user details view;
 - Removes lots of needless code;
 - Follows desing for widgets;
 - UI still needs some tweaks.
parent 12db177e
......@@ -87,84 +87,66 @@ android {
dependencies {
implementation fileTree(include: ['*.jar'], dir: 'libs')
implementation project(':player')
implementation project(':emoji')
implementation project(':draw')
implementation project(':util')
implementation project(':core')
implementation project(':suggestions')
implementation libraries.kotlin
implementation libraries.coroutines
implementation libraries.coroutinesAndroid
implementation libraries.appCompat
implementation libraries.recyclerview
implementation libraries.constraintlayout
implementation libraries.cardview
implementation libraries.browser
implementation libraries.androidKtx
implementation libraries.fragmentsKtx
implementation libraries.dagger
implementation libraries.daggerSupport
kapt libraries.daggerProcessor
kapt libraries.daggerAndroidApt
implementation libraries.flexbox
implementation libraries.material
implementation libraries.room
kapt libraries.roomProcessor
implementation libraries.lifecycleExtensions
kapt libraries.lifecycleCompiler
implementation libraries.viewmodelKtx
implementation libraries.workmanager
implementation libraries.rxKotlin
implementation libraries.rxAndroid
implementation libraries.moshi
implementation libraries.okhttp
implementation libraries.okhttpLogger
implementation libraries.timber
implementation libraries.threeTenABP
implementation libraries.fresco
api libraries.frescoOkHttp
implementation libraries.frescoAnimatedGif
implementation libraries.frescoWebP
implementation libraries.frescoAnimatedWebP
implementation libraries.glide
kapt libraries.kotshiCompiler
implementation libraries.kotshiApi
implementation libraries.frescoImageViewer
implementation libraries.markwon
implementation libraries.aVLoadingIndicatorView
implementation libraries.livedataKtx
implementation 'com.google.code.findbugs:jsr305:3.0.2'
// Proprietary libraries
playImplementation libraries.fcm
playImplementation libraries.firebaseAnalytics
playImplementation libraries.playServicesAuth
playImplementation('com.crashlytics.sdk.android:crashlytics:2.9.5@aar') { transitive = true }
playImplementation('com.crashlytics.sdk.android:answers:1.4.3@aar') { transitive = true }
testImplementation libraries.junit
testImplementation libraries.truth
androidTestImplementation libraries.espressoCore
androidTestImplementation libraries.espressoIntents
implementation files('libs/common-54676f5.jar')
implementation files('libs/core-54676f5.jar')
}
kotlin {
......
......@@ -93,16 +93,6 @@
android:name=".settings.password.ui.PasswordActivity"
android:theme="@style/AppTheme" />
<activity
android:name=".userdetails.ui.UserDetailsActivity"
android:theme="@style/AppTheme"
android:windowSoftInputMode="stateAlwaysHidden">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value=".chatroom.ui.ChatRoomActivity" />
</activity>
<receiver
android:name=".push.DirectReplyReceiver"
android:enabled="true"
......
......@@ -16,6 +16,7 @@ sealed class ScreenViewEvent(val screenName: String) {
object ChatRoom : ScreenViewEvent("ChatRoomFragment")
object ChatRooms : ScreenViewEvent("ChatRoomsFragment")
object CreateChannel : ScreenViewEvent("CreateChannelFragment")
object UserDetails : ScreenViewEvent("UserDetailsFragment")
object FavoriteMessages : ScreenViewEvent("FavoriteMessagesFragment")
object Files : ScreenViewEvent("FilesFragment")
object MemberBottomSheet : ScreenViewEvent("MemberBottomSheetFragment")
......
......@@ -5,7 +5,8 @@ import chat.rocket.android.dagger.scope.PerFragment
import dagger.Module
import dagger.android.ContributesAndroidInjector
@Module abstract class TwoFAFragmentProvider {
@Module
abstract class TwoFAFragmentProvider {
@ContributesAndroidInjector(modules = [TwoFAFragmentModule::class])
@PerFragment
......
package chat.rocket.android.chatroom.adapter
import androidx.recyclerview.widget.RecyclerView
import android.view.MenuItem
import android.view.View
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
import chat.rocket.android.R
import chat.rocket.android.chatroom.uimodel.*
import chat.rocket.android.util.extensions.inflate
import chat.rocket.android.chatroom.presentation.ChatRoomNavigator
import chat.rocket.android.chatroom.uimodel.AttachmentUiModel
import chat.rocket.android.chatroom.uimodel.BaseUiModel
import chat.rocket.android.chatroom.uimodel.MessageReplyUiModel
import chat.rocket.android.chatroom.uimodel.MessageUiModel
import chat.rocket.android.chatroom.uimodel.UrlPreviewUiModel
import chat.rocket.android.chatroom.uimodel.toViewType
import chat.rocket.android.emoji.EmojiReactionListener
import chat.rocket.android.util.extensions.inflate
import chat.rocket.android.util.extensions.openTabbedUrl
import chat.rocket.core.model.Message
import chat.rocket.core.model.attachment.actions.Action
import chat.rocket.core.model.attachment.actions.ButtonAction
import chat.rocket.core.model.Message
import chat.rocket.core.model.isSystemMessage
import timber.log.Timber
import java.security.InvalidParameterException
......@@ -22,7 +28,8 @@ class ChatRoomAdapter(
private val roomName: String? = null,
private val actionSelectListener: OnActionSelected? = null,
private val enableActions: Boolean = true,
private val reactionListener: EmojiReactionListener? = null
private val reactionListener: EmojiReactionListener? = null,
private val navigator: ChatRoomNavigator? = null
) : RecyclerView.Adapter<BaseViewHolder<*>>() {
private val dataSet = ArrayList<BaseUiModel<*>>()
......@@ -34,7 +41,11 @@ class ChatRoomAdapter(
return when (viewType.toViewType()) {
BaseUiModel.ViewType.MESSAGE -> {
val view = parent.inflate(R.layout.item_message)
MessageViewHolder(view, actionsListener, reactionListener)
MessageViewHolder(
view,
actionsListener,
reactionListener
) { userId -> navigator?.toUserDetails(userId) }
}
BaseUiModel.ViewType.URL_PREVIEW -> {
val view = parent.inflate(R.layout.message_url_preview)
......@@ -42,11 +53,20 @@ class ChatRoomAdapter(
}
BaseUiModel.ViewType.ATTACHMENT -> {
val view = parent.inflate(R.layout.item_message_attachment)
AttachmentViewHolder(view, actionsListener, reactionListener, actionAttachmentOnClickListener)
AttachmentViewHolder(
view,
actionsListener,
reactionListener,
actionAttachmentOnClickListener
)
}
BaseUiModel.ViewType.MESSAGE_REPLY -> {
val view = parent.inflate(R.layout.item_message_reply)
MessageReplyViewHolder(view, actionsListener, reactionListener) { roomName, permalink ->
MessageReplyViewHolder(
view,
actionsListener,
reactionListener
) { roomName, permalink ->
actionSelectListener?.openDirectMessage(roomName, permalink)
}
}
......@@ -117,7 +137,8 @@ class ChatRoomAdapter(
fun prependData(dataSet: List<BaseUiModel<*>>) {
//---At first we will update all already saved elements with received updated ones
val filteredDataSet = dataSet.filter { newItem ->
val matchedIndex = this.dataSet.indexOfFirst { it.messageId == newItem.messageId && it.viewType == newItem.viewType }
val matchedIndex =
this.dataSet.indexOfFirst { it.messageId == newItem.messageId && it.viewType == newItem.viewType }
if (matchedIndex > -1) {
this.dataSet[matchedIndex] = newItem
notifyItemChanged(matchedIndex)
......@@ -266,7 +287,12 @@ class ChatRoomAdapter(
fun showMessageInfo(id: String)
fun citeMessage(roomName: String, roomType: String, messageId: String, mentionAuthor: Boolean)
fun citeMessage(
roomName: String,
roomType: String,
messageId: String,
mentionAuthor: Boolean
)
fun copyMessage(id: String)
......
package chat.rocket.android.chatroom.adapter
import android.content.Context
import android.graphics.Color
import android.graphics.drawable.Drawable
import android.text.Spannable
......@@ -11,7 +10,6 @@ import androidx.core.view.isVisible
import chat.rocket.android.R
import chat.rocket.android.chatroom.uimodel.MessageUiModel
import chat.rocket.android.emoji.EmojiReactionListener
import chat.rocket.android.userdetails.ui.userDetailsIntent
import chat.rocket.core.model.isSystemMessage
import com.bumptech.glide.load.resource.gif.GifDrawable
import kotlinx.android.synthetic.main.avatar.view.*
......@@ -20,7 +18,8 @@ import kotlinx.android.synthetic.main.item_message.view.*
class MessageViewHolder(
itemView: View,
listener: ActionsListener,
reactionListener: EmojiReactionListener? = null
reactionListener: EmojiReactionListener? = null,
private val avatarListener: (String) -> Unit
) : BaseViewHolder<MessageUiModel>(itemView, listener, reactionListener), Drawable.Callback {
init {
......@@ -73,24 +72,14 @@ class MessageViewHolder(
read_receipt_view.isVisible = true
}
val senderId = data.message.sender?.id
val subscriptionId = data.subscriptionId
text_sender.setOnClickListener {
toUserDetails(context, senderId, subscriptionId)
}
image_avatar.setOnClickListener {
toUserDetails(context, senderId, subscriptionId)
data.message.sender?.id?.let { userId ->
avatarListener(userId)
}
}
}
}
private fun toUserDetails(context: Context, userId: String?, subscriptionId: String) {
userId?.let { context.startActivity(context.userDetailsIntent(it, subscriptionId)) }
}
override fun unscheduleDrawable(who: Drawable?, what: Runnable?) {
with(itemView) {
text_content.removeCallbacks(what)
......
......@@ -5,7 +5,6 @@ import androidx.lifecycle.LifecycleOwner
import chat.rocket.android.chatroom.presentation.ChatRoomView
import chat.rocket.android.chatroom.ui.ChatRoomFragment
import chat.rocket.android.chatrooms.adapter.RoomUiModelMapper
import chat.rocket.android.core.lifecycle.CancelStrategy
import chat.rocket.android.dagger.scope.PerFragment
import chat.rocket.android.db.ChatRoomDao
import chat.rocket.android.db.DatabaseManager
......@@ -16,16 +15,11 @@ import chat.rocket.android.server.domain.SettingsRepository
import chat.rocket.android.server.domain.TokenRepository
import dagger.Module
import dagger.Provides
import kotlinx.coroutines.experimental.Job
import javax.inject.Named
@Module
class ChatRoomFragmentModule {
@Provides
@PerFragment
fun provideJob() = Job()
@Provides
@PerFragment
fun chatRoomView(frag: ChatRoomFragment): ChatRoomView {
......@@ -38,12 +32,6 @@ class ChatRoomFragmentModule {
return frag
}
@Provides
@PerFragment
fun provideCancelStrategy(owner: LifecycleOwner, jobs: Job): CancelStrategy {
return CancelStrategy(owner, jobs)
}
@Provides
@PerFragment
fun provideChatRoomDao(manager: DatabaseManager): ChatRoomDao = manager.chatRoomDao()
......@@ -71,6 +59,12 @@ class ChatRoomFragmentModule {
@Named("currentServer") serverUrl: String,
permissionsInteractor: PermissionsInteractor
): RoomUiModelMapper {
return RoomUiModelMapper(context, repository.get(serverUrl), userInteractor, serverUrl, permissionsInteractor)
return RoomUiModelMapper(
context,
repository.get(serverUrl),
userInteractor,
serverUrl,
permissionsInteractor
)
}
}
package chat.rocket.android.chatroom.di
import androidx.lifecycle.LifecycleOwner
import chat.rocket.android.chatroom.presentation.ChatRoomNavigator
import chat.rocket.android.chatroom.ui.ChatRoomActivity
import chat.rocket.android.core.lifecycle.CancelStrategy
import chat.rocket.android.dagger.scope.PerActivity
import dagger.Module
import dagger.Provides
import kotlinx.coroutines.experimental.Job
@Module
class ChatRoomModule {
@Provides
@PerActivity
fun provideChatRoomNavigator(activity: ChatRoomActivity) = ChatRoomNavigator(activity)
@Provides
@PerActivity
fun provideJob() = Job()
@Provides
@PerActivity
fun provideLifecycleOwner(activity: ChatRoomActivity): LifecycleOwner = activity
@Provides
@PerActivity
fun provideCancelStrategy(owner: LifecycleOwner, jobs: Job): CancelStrategy {
return CancelStrategy(owner, jobs)
}
}
\ No newline at end of file
......@@ -6,6 +6,7 @@ import dagger.Module
import dagger.android.ContributesAndroidInjector
@Module abstract class MessageServiceProvider {
@ContributesAndroidInjector(modules = [AppModule::class])
abstract fun provideMessageService(): MessageService
}
\ No newline at end of file
......@@ -5,15 +5,44 @@ import chat.rocket.android.chatdetails.ui.chatDetailsIntent
import chat.rocket.android.chatinformation.ui.messageInformationIntent
import chat.rocket.android.chatroom.ui.ChatRoomActivity
import chat.rocket.android.chatroom.ui.chatRoomIntent
import chat.rocket.android.favoritemessages.ui.TAG_FAVORITE_MESSAGES_FRAGMENT
import chat.rocket.android.files.ui.TAG_FILES_FRAGMENT
import chat.rocket.android.members.ui.TAG_MEMBERS_FRAGMENT
import chat.rocket.android.mentions.ui.TAG_MENTIONS_FRAGMENT
import chat.rocket.android.pinnedmessages.ui.TAG_PINNED_MESSAGES_FRAGMENT
import chat.rocket.android.server.ui.changeServerIntent
import chat.rocket.android.userdetails.ui.TAG_USER_DETAILS_FRAGMENT
import chat.rocket.android.util.extensions.addFragmentBackStack
import javax.inject.Inject
class ChatRoomNavigator @Inject constructor(internal val activity: ChatRoomActivity) {
fun toUserDetails(userId: String) {
activity.addFragmentBackStack(TAG_USER_DETAILS_FRAGMENT, R.id.fragment_container) {
chat.rocket.android.userdetails.ui.newInstance(userId)
}
}
fun toChatRoom(
chatRoomId: String,
chatRoomName: String,
chatRoomType: String,
isReadOnly: Boolean,
chatRoomLastSeen: Long,
isSubscribed: Boolean,
isCreator: Boolean,
isFavorite: Boolean
) {
activity.startActivity(
activity.chatRoomIntent(
chatRoomId,
chatRoomName,
chatRoomType,
isReadOnly,
chatRoomLastSeen,
isSubscribed,
isCreator,
isFavorite
)
)
activity.overridePendingTransition(R.anim.open_enter, R.anim.open_exit)
}
class ChatRoomNavigator(internal val activity: ChatRoomActivity) {
fun toChatDetails(
chatRoomId: String,
chatRoomType: String,
......
......@@ -40,6 +40,7 @@ import chat.rocket.android.chatroom.adapter.EmojiSuggestionsAdapter
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.ChatRoomNavigator
import chat.rocket.android.chatroom.presentation.ChatRoomPresenter
import chat.rocket.android.chatroom.presentation.ChatRoomView
import chat.rocket.android.chatroom.ui.bottomsheet.MessageActionsBottomSheet
......@@ -68,6 +69,7 @@ import chat.rocket.android.helper.MessageParser
import chat.rocket.android.util.extension.asObservable
import chat.rocket.android.util.extension.createImageFile
import chat.rocket.android.util.extensions.circularRevealOrUnreveal
import chat.rocket.android.util.extensions.clearLightStatusBar
import chat.rocket.android.util.extensions.fadeIn
import chat.rocket.android.util.extensions.fadeOut
import chat.rocket.android.util.extensions.getBitmpap
......@@ -85,6 +87,7 @@ import dagger.android.support.AndroidSupportInjection
import io.reactivex.Observable
import io.reactivex.disposables.CompositeDisposable
import io.reactivex.disposables.Disposable
import kotlinx.android.synthetic.main.app_bar_chat_room.*
import kotlinx.android.synthetic.main.emoji_image_row_item.view.*
import kotlinx.android.synthetic.main.emoji_row_item.view.*
import kotlinx.android.synthetic.main.fragment_chat_room.*
......@@ -145,13 +148,14 @@ internal const val MENU_ACTION_SHOW_DETAILS = 2
class ChatRoomFragment : Fragment(), ChatRoomView, EmojiKeyboardListener, EmojiReactionListener,
ChatRoomAdapter.OnActionSelected, Drawable.Callback {
@Inject
lateinit var presenter: ChatRoomPresenter
@Inject
lateinit var parser: MessageParser
@Inject
lateinit var analyticsManager: AnalyticsManager
@Inject
lateinit var navigator: ChatRoomNavigator
private lateinit var adapter: ChatRoomAdapter
internal lateinit var chatRoomId: String
private lateinit var chatRoomName: String
......@@ -284,7 +288,7 @@ class ChatRoomFragment : Fragment(), ChatRoomView, EmojiKeyboardListener, EmojiR
requireNotNull(bundle) { "no arguments supplied when the fragment was instantiated" }
}
adapter = ChatRoomAdapter(chatRoomId, chatRoomType, chatRoomName, this, reactionListener = this)
adapter = ChatRoomAdapter(chatRoomId, chatRoomType, chatRoomName, this, reactionListener = this, navigator = navigator)
}
override fun onCreateView(
......@@ -1057,7 +1061,11 @@ class ChatRoomFragment : Fragment(), ChatRoomView, EmojiKeyboardListener, EmojiR
}
private fun setupToolbar(toolbarTitle: String) {
(activity as ChatRoomActivity).showToolbarTitle(toolbarTitle)
with(activity as ChatRoomActivity) {
this.clearLightStatusBar()
this.showToolbarTitle(toolbarTitle)
toolbar.isVisible = true
}
}
override fun unscheduleDrawable(who: Drawable?, what: Runnable?) {
......
......@@ -38,14 +38,14 @@ import chat.rocket.android.server.ui.ChangeServerActivity
import chat.rocket.android.settings.di.SettingsFragmentProvider
import chat.rocket.android.settings.password.di.PasswordFragmentProvider
import chat.rocket.android.settings.password.ui.PasswordActivity
import chat.rocket.android.userdetails.di.UserDetailsModule
import chat.rocket.android.userdetails.ui.UserDetailsActivity
import chat.rocket.android.userdetails.di.UserDetailsFragmentProvider
import chat.rocket.android.webview.adminpanel.di.AdminPanelWebViewFragmentProvider
import dagger.Module
import dagger.android.ContributesAndroidInjector
@Module
abstract class ActivityBuilder {
@PerActivity
@ContributesAndroidInjector(
modules = [AuthenticationModule::class,
......@@ -77,9 +77,9 @@ abstract class ActivityBuilder {
@PerActivity
@ContributesAndroidInjector(
modules = [
ChatRoomModule::class,
ChatRoomFragmentProvider::class
modules = [ChatRoomModule::class,
ChatRoomFragmentProvider::class,
UserDetailsFragmentProvider::class
]
)
abstract fun bindChatRoomActivity(): ChatRoomActivity
......@@ -113,8 +113,4 @@ abstract class ActivityBuilder {
@PerActivity
@ContributesAndroidInjector(modules = [DrawModule::class])
abstract fun bindDrawingActivity(): DrawingActivity
@PerActivity
@ContributesAndroidInjector(modules = [UserDetailsModule::class])
abstract fun bindUserDetailsActivity(): UserDetailsActivity
}
......@@ -102,6 +102,18 @@ class DatabaseManager(val context: Application, val serverUrl: String) {
}
}
suspend fun insertOrReplaceRoom(chatRoomEntity: ChatRoomEntity) {
retryDB("insertOrReplace($chatRoomEntity)") {
chatRoomDao().insertOrReplace(chatRoomEntity)
}
}
suspend fun getUser(id: String) = withContext(dbManagerContext) {
retryDB("getUser($id)") {
userDao().getUser(id)
}
}
fun processUsersBatch(users: List<User>) {
launch(dbManagerContext) {
val list = ArrayList<BaseUserEntity>(users.size)
......
......@@ -22,7 +22,7 @@ class MembersFragmentModule {
@Provides
@PerFragment
fun provideChatRoomNavigator(activity: ChatDetailsActivity) = MembersNavigator(activity)
fun provideMembersNavigator(activity: ChatDetailsActivity) = MembersNavigator(activity)
@Provides
@PerFragment
......
package chat.rocket.android.members.presentation
import chat.rocket.android.R
import chat.rocket.android.chatdetails.ui.ChatDetailsActivity
import chat.rocket.android.userdetails.ui.userDetailsIntent
import chat.rocket.android.userdetails.ui.TAG_USER_DETAILS_FRAGMENT
import chat.rocket.android.util.extensions.addFragmentBackStack
class MembersNavigator(internal val activity: ChatDetailsActivity) {
fun toMemberDetails(userId: String) {
activity.apply {
startActivity(this.userDetailsIntent(userId, ""))
}
// TODO
// activity.addFragmentBackStack(TAG_USER_DETAILS_FRAGMENT, R.id.fragment_container) {
// chat.rocket.android.userdetails.ui.newInstance(userId)
// }
}
}
......@@ -37,13 +37,12 @@ internal const val TAG_MENTIONS_FRAGMENT = "MentionsFragment"
private const val BUNDLE_CHAT_ROOM_ID = "chat_room_id"
class MentionsFragment : Fragment(), MentionsView {
private lateinit var chatRoomId: String
private val adapter = ChatRoomAdapter(enableActions = false)
@Inject
lateinit var presenter: MentionsPresenter
@Inject
lateinit var analyticsManager: AnalyticsManager
private lateinit var chatRoomId: String
private val adapter = ChatRoomAdapter(enableActions = false)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
......
package chat.rocket.android.userdetails.di
import androidx.lifecycle.LifecycleOwner
import chat.rocket.android.dagger.scope.PerFragment
import chat.rocket.android.userdetails.presentation.UserDetailsView
import chat.rocket.android.userdetails.ui.UserDetailsFragment
import dagger.Module
import dagger.Provides
@Module
class UserDetailsFragmentModule {
@Provides
@PerFragment
fun provideUserDetailsView(frag: UserDetailsFragment): UserDetailsView = frag
@Provides
@PerFragment
fun provideLifecycleOwner(frag: UserDetailsFragment): LifecycleOwner = frag
}
package chat.rocket.android.userdetails.di
import chat.rocket.android.dagger.scope.PerFragment
import chat.rocket.android.userdetails.ui.UserDetailsFragment
import dagger.Module
import dagger.android.ContributesAndroidInjector
@Module
abstract class UserDetailsFragmentProvider {
@ContributesAndroidInjector(modules = [UserDetailsFragmentModule::class])
@PerFragment
abstract fun provideUserDetailsFragment(): UserDetailsFragment
}
package chat.rocket.android.userdetails.di
import androidx.lifecycle.LifecycleOwner
import chat.rocket.android.core.lifecycle.CancelStrategy
import chat.rocket.android.dagger.scope.PerActivity
import chat.rocket.android.userdetails.presentation.UserDetailsView
import chat.rocket.android.userdetails.ui.UserDetailsActivity
import dagger.Module
import dagger.Provides
import kotlinx.coroutines.experimental.Job
@Module
class UserDetailsModule {
@Provides
@PerActivity
fun provideJob() = Job()
@Provides
@PerActivity
fun provideUserDetailsView(activity: UserDetailsActivity): UserDetailsView = activity
@Provides
@PerActivity
fun provideLifecycleOwner(activity: UserDetailsActivity): LifecycleOwner = activity
@Provides
@PerActivity
fun provideCancelStrategy(owner: LifecycleOwner, jobs: Job): CancelStrategy {
return CancelStrategy(owner, jobs)
}
}
package chat.rocket.android.userdetails.presentation
import chat.rocket.android.chatroom.presentation.ChatRoomNavigator
import chat.rocket.android.chatrooms.domain.FetchChatRoomsInteractor
import chat.rocket.android.core.lifecycle.CancelStrategy
import chat.rocket.android.db.DatabaseManager
import chat.rocket.android.db.model.ChatRoomEntity
import chat.rocket.android.db.model.UserEntity
import chat.rocket.android.server.domain.GetConnectingServerInteractor
import chat.rocket.android.server.infraestructure.ConnectionManagerFactory
import chat.rocket.android.util.extension.launchUI
import chat.rocket.android.util.extensions.avatarUrl
import chat.rocket.android.util.retryIO
import chat.rocket.common.model.RoomType
import chat.rocket.common.model.roomTypeOf
import chat.rocket.common.model.userStatusOf
import chat.rocket.common.util.ifNull
import chat.rocket.core.internal.rest.createDirectMessage
import chat.rocket.core.model.ChatRoom
import kotlinx.coroutines.experimental.CommonPool
import kotlinx.coroutines.experimental.DefaultDispatcher
import kotlinx.coroutines.experimental.withContext
import timber.log.Timber
import javax.inject.Inject
......@@ -23,141 +23,98 @@ class UserDetailsPresenter @Inject constructor(
private val view: UserDetailsView,
private val dbManager: DatabaseManager,
private val strategy: CancelStrategy,
private val navigator: ChatRoomNavigator,
serverInteractor: GetConnectingServerInteractor,
factory: ConnectionManagerFactory
) {
private var currentServer = serverInteractor.get()!!
private val manager = factory.create(currentServer)
private val client = manager.client
private val interactor = FetchChatRoomsInteractor(client,dbManager)
private val interactor = FetchChatRoomsInteractor(client, dbManager)
private lateinit var userEntity: UserEntity
fun loadUserDetails(userId: String) {
launchUI(strategy) {
try {
val user = withContext(CommonPool) {
dbManager.userDao().getUser(id = userId)
}
user?.let { u ->
val openedChatRooms = chatRoomByName(name = u.name)
val avatarUrl = u.username?.let { currentServer.avatarUrl(avatar = it) }
val chatRoom: ChatRoom? = openedChatRooms.firstOrNull()
view.showLoading()
dbManager.getUser(userId)?.let {
userEntity = it
val avatarUrl =
userEntity.username?.let { currentServer.avatarUrl(avatar = it) }
val username = userEntity.username
val name = userEntity.name
val utcOffset =
userEntity.utcOffset // TODO Convert UTC and display like the mockup
view.showUserDetails(
avatarUrl = avatarUrl,
username = u.username,
name = u.name,
utcOffset = u.utcOffset,
status = u.status,
chatRoom = chatRoom
)
if (avatarUrl != null && username != null && name != null && utcOffset != null) {
view.showUserDetails(
avatarUrl = avatarUrl,
name = name,
username = username,
status = userEntity.status,
utcOffset = utcOffset.toString()
)
} else {
throw Exception()
}
}
} catch (ex: Exception) {
Timber.e(ex)
ex.message?.let {
view.showMessage(it)
}.ifNull {
view.showGenericErrorMessage()
}
} finally {
view.hideLoading()
}
}
}
fun createDirectMessage(id: String) = launchUI(strategy) {
try {
val result = retryIO("createDirectMessage($id") {
client.createDirectMessage(username = id)
}
fun createDirectMessage(username: String) {
launchUI(strategy) {
try {
view.showLoading()
interactor.refreshChatRooms()
withContext(DefaultDispatcher) {
val directMessage = retryIO("createDirectMessage($username") {
client.createDirectMessage(username)
}
val userEntity = withContext(CommonPool) {
dbManager.userDao().getUser(id = id)
}
val chatRoomEntity = ChatRoomEntity(
id = directMessage.id,
name = userEntity.username ?: userEntity.name.orEmpty(),
description = null,
type = RoomType.DIRECT_MESSAGE,
fullname = userEntity.name,
subscriptionId = "",
updatedAt = directMessage.updatedAt
)
if (userEntity != null) {
val chatRoom = ChatRoom(
id = result.id,
type = roomTypeOf(RoomType.DIRECT_MESSAGE),
name = userEntity.username ?: userEntity.name.orEmpty(),
fullName = userEntity.name,
favorite = false,
open = false,
alert = false,
status = userStatusOf(userEntity.status),
client = client,
broadcast = false,
archived = false,
default = false,
description = null,
groupMentions = null,
userMentions = null,
lastMessage = null,
lastSeen = null,
topic = null,
announcement = null,
roles = null,
unread = 0,
readonly = false,
muted = null,
subscriptionId = "",
timestamp = null,
updatedAt = result.updatedAt,
user = null
)
dbManager.insertOrReplaceRoom(chatRoomEntity)
withContext(CommonPool + strategy.jobs) {
dbManager.chatRoomDao().insertOrReplace(chatRoom = ChatRoomEntity(
id = chatRoom.id,
name = chatRoom.name,
description = chatRoom.description,
type = chatRoom.type.toString(),
fullname = chatRoom.fullName,
subscriptionId = chatRoom.subscriptionId,
updatedAt = chatRoom.updatedAt
))
}
interactor.refreshChatRooms()
view.toDirectMessage(chatRoom = chatRoom)
}
} catch (ex: Exception) {
Timber.e(ex)
view.onOpenDirectMessageError()
}
}
private suspend fun chatRoomByName(name: String? = null): List<ChatRoom> = withContext(CommonPool) {
return@withContext dbManager.chatRoomDao().getAllSync().filter {
if (name == null) {
return@filter true
}
it.chatRoom.name == name || it.chatRoom.fullname == name
}.map {
with(it.chatRoom) {
ChatRoom(
id = id,
subscriptionId = subscriptionId,
type = roomTypeOf(type),
unread = unread,
broadcast = broadcast ?: false,
alert = alert,
fullName = fullname,
name = name ?: "",
favorite = favorite ?: false,
default = isDefault ?: false,
readonly = readonly,
open = open,
lastMessage = null,
archived = false,
status = null,
user = null,
userMentions = userMentions,
client = client,
announcement = null,
description = null,
groupMentions = groupMentions,
roles = null,
topic = null,
lastSeen = this.lastSeen,
timestamp = timestamp,
updatedAt = updatedAt
)
navigator.toChatRoom(
chatRoomId = chatRoomEntity.id,
chatRoomName = chatRoomEntity.name,
chatRoomType = chatRoomEntity.type,
isReadOnly = false,
chatRoomLastSeen = -1,
isSubscribed = chatRoomEntity.open,
isCreator = true,
isFavorite = false
)
}
} catch (ex: Exception) {
Timber.e(ex)
ex.message?.let {
view.showMessage(it)
}.ifNull {
view.showGenericErrorMessage()
}
} finally {
view.hideLoading()
}
}
}
......
package chat.rocket.android.userdetails.presentation
import chat.rocket.core.model.ChatRoom
import chat.rocket.android.core.behaviours.LoadingView
import chat.rocket.android.core.behaviours.MessageView
interface UserDetailsView {
interface UserDetailsView : LoadingView, MessageView {
fun showUserDetails(avatarUrl: String?, username: String?, name: String?, utcOffset: Float?, status: String, chatRoom: ChatRoom?)
fun toDirectMessage(chatRoom: ChatRoom)
fun onOpenDirectMessageError()
/**
* Shows user detail.
*
* @param avatarUrl The user avatar URL.
* @param name The user's name.
* @param username The user's username.
* @param status The user's status.
* @param utcOffset The user's UTC offset.
*/
fun showUserDetails(
avatarUrl: String,
name: String,
username: String,
status: String,
utcOffset: String
)
}
package chat.rocket.android.userdetails.ui
import android.content.Context
import android.content.Intent
import android.graphics.drawable.BitmapDrawable
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import androidx.core.view.isVisible
import androidx.fragment.app.Fragment
import blurred
import chat.rocket.android.R
import chat.rocket.android.chatroom.ui.chatRoomIntent
import chat.rocket.android.emoji.internal.GlideApp
import chat.rocket.android.userdetails.presentation.UserDetailsPresenter
import chat.rocket.android.userdetails.presentation.UserDetailsView
import chat.rocket.android.util.extension.launchUI
import chat.rocket.android.util.extension.orFalse
import chat.rocket.android.util.extensions.showToast
import chat.rocket.android.util.retryIO
import chat.rocket.common.model.roomTypeOf
import chat.rocket.core.internal.rest.createDirectMessage
import chat.rocket.core.model.ChatRoom
import chat.rocket.core.model.userId
import com.bumptech.glide.Glide
import com.bumptech.glide.Priority
import com.bumptech.glide.load.engine.DiskCacheStrategy
import com.bumptech.glide.load.resource.bitmap.CenterCrop
import com.bumptech.glide.load.resource.bitmap.CenterInside
import com.bumptech.glide.load.resource.bitmap.FitCenter
import com.bumptech.glide.load.resource.bitmap.RoundedCorners
import com.bumptech.glide.request.RequestOptions
import com.google.android.material.snackbar.Snackbar
import dagger.android.AndroidInjection
import dagger.android.AndroidInjector
import dagger.android.DispatchingAndroidInjector
import dagger.android.support.HasSupportFragmentInjector
import kotlinx.android.synthetic.main.activity_user_details.*
import kotlinx.coroutines.experimental.CommonPool
import kotlinx.coroutines.experimental.android.UI
import kotlinx.coroutines.experimental.launch
import kotlinx.coroutines.experimental.withContext
import org.threeten.bp.OffsetDateTime
import org.threeten.bp.ZoneId
import org.threeten.bp.ZoneOffset
import org.threeten.bp.ZonedDateTime
import org.threeten.bp.format.DateTimeFormatter
import timber.log.Timber
import javax.inject.Inject
import kotlin.math.roundToLong
fun Context.userDetailsIntent(userId: String, subscriptionId: String): Intent {
return Intent(this, UserDetailsActivity::class.java).apply {
putExtra(EXTRA_USER_ID, userId)
putExtra(EXTRA_SUBSCRIPTION_ID, subscriptionId)
}
}
const val EXTRA_USER_ID = "EXTRA_USER_ID"
const val EXTRA_SUBSCRIPTION_ID = "EXTRA_USERNAME"
class UserDetailsActivity : AppCompatActivity(), UserDetailsView, HasSupportFragmentInjector {
@Inject
lateinit var fragmentDispatchingAndroidInjector: DispatchingAndroidInjector<Fragment>
@Inject
lateinit var presenter: UserDetailsPresenter
private lateinit var subscriptionId: String
private lateinit var userId: String
override fun onCreate(savedInstanceState: Bundle?) {
AndroidInjection.inject(this)
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_user_details)
setupToolbar()
userId = intent.getStringExtra(EXTRA_USER_ID)
subscriptionId = intent.getStringExtra(EXTRA_SUBSCRIPTION_ID)
showLoadingView(true)
presenter.loadUserDetails(userId = userId)
}
override fun supportFragmentInjector(): AndroidInjector<Fragment> = fragmentDispatchingAndroidInjector
private fun setupToolbar() {
setSupportActionBar(toolbar)
supportActionBar?.setDisplayShowTitleEnabled(false)
toolbar.setNavigationOnClickListener { finish() }
}
override fun showUserDetails(
avatarUrl: String?,
username: String?,
name: String?,
utcOffset: Float?,
status: String,
chatRoom: ChatRoom?
) {
text_view_name.text = name
text_view_username.text = username
text_view_status.text = status.capitalize()
try {
launch(UI) {
val image = withContext(CommonPool) {
try {
val requestOptions = RequestOptions()
.priority(Priority.IMMEDIATE)
.transforms(CenterInside(), FitCenter())
return@withContext GlideApp.with(this@UserDetailsActivity)
.asBitmap()
.diskCacheStrategy(DiskCacheStrategy.ALL)
.load(avatarUrl)
.apply(requestOptions)
.submit()
.get()
} catch (ex: Exception) {
Timber.e(ex)
return@withContext null
} finally {
showLoadingView(false)
}
}
image?.let {
val blurredBitmap = image.blurred(context = this@UserDetailsActivity,
newWidth = toolbar.measuredWidth, newHeight = toolbar.measuredHeight)
toolbar.background = BitmapDrawable(resources, blurredBitmap)
GlideApp.with(this@UserDetailsActivity)
.asBitmap()
.transforms(RoundedCorners(25), CenterCrop())
.load(image)
.into(image_view_avatar)
}
}
utcOffset?.let {
val offsetLong = it.roundToLong()
val offset = if (it > 0) "+$offsetLong" else offsetLong.toString()
val formatter = DateTimeFormatter.ofPattern("'(GMT$offset)' hh:mm a")
val zoneId = ZoneId.systemDefault()
val timeNow = OffsetDateTime.now(ZoneOffset.UTC).plusHours(offsetLong).toLocalDateTime()
text_view_tz.text = formatter.format(ZonedDateTime.of(timeNow, zoneId))
}
text_view_message.setOnClickListener {
if (chatRoom == null) {
presenter.createDirectMessage(id = userId)
} else {
toDirectMessage(chatRoom)
}
}
image_view_message.setOnClickListener {
if (chatRoom == null) {
presenter.createDirectMessage(id = userId)
} else {
toDirectMessage(chatRoom)
}
}
} catch (ex: Exception) {
Timber.e(ex)
}
}
private fun showLoadingView(show: Boolean) {
runOnUiThread {
group_user_details.isVisible = !show
view_loading.isVisible = show
}
}
override fun onOpenDirectMessageError() {
Snackbar.make(root_layout, R.string.error_opening_dm, Snackbar.LENGTH_INDEFINITE)
.setAction(R.string.retry) {
presenter.createDirectMessage(userId)
}.show()
}
override fun toDirectMessage(chatRoom: ChatRoom) {
finish()
if (chatRoom.subscriptionId.isEmpty() || chatRoom.subscriptionId != subscriptionId) {
startActivity(
chatRoomIntent(
chatRoomId = chatRoom.id,
chatRoomName = chatRoom.name,
chatRoomType = chatRoom.type.toString(),
isReadOnly = chatRoom.readonly.orFalse(),
chatRoomLastSeen = chatRoom.lastSeen ?: 0,
isSubscribed = chatRoom.open,
isCreator = false,
isFavorite = chatRoom.favorite
)
)
}
}
}
package chat.rocket.android.userdetails.ui
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.core.view.isVisible
import androidx.fragment.app.Fragment
import chat.rocket.android.R
import chat.rocket.android.analytics.AnalyticsManager
import chat.rocket.android.analytics.event.ScreenViewEvent
import chat.rocket.android.chatroom.ui.ChatRoomActivity
import chat.rocket.android.userdetails.presentation.UserDetailsPresenter
import chat.rocket.android.userdetails.presentation.UserDetailsView
import chat.rocket.android.util.extensions.inflate
import chat.rocket.android.util.extensions.setLightStatusBar
import chat.rocket.android.util.extensions.showToast
import chat.rocket.android.util.extensions.ui
import com.bumptech.glide.Glide
import dagger.android.support.AndroidSupportInjection
import kotlinx.android.synthetic.main.app_bar_chat_room.*
import kotlinx.android.synthetic.main.fragment_user_details.*
import javax.inject.Inject
fun newInstance(userId: String): Fragment {
return UserDetailsFragment().apply {
arguments = Bundle(1).apply {
putString(BUNDLE_USER_ID, userId)
}
}
}
internal const val TAG_USER_DETAILS_FRAGMENT = "UserDetailsFragment"
private const val BUNDLE_USER_ID = "user_id"
class UserDetailsFragment : Fragment(), UserDetailsView {
@Inject
lateinit var presenter: UserDetailsPresenter
@Inject
lateinit var analyticsManager: AnalyticsManager
private lateinit var userId: String
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
AndroidSupportInjection.inject(this)
val bundle = arguments
if (bundle != null) {
userId = bundle.getString(BUNDLE_USER_ID)
} else {
requireNotNull(bundle) { "no arguments supplied when the fragment was instantiated" }
}
}
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? = container?.inflate(R.layout.fragment_user_details)
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
setupToolbar()
presenter.loadUserDetails(userId)
analyticsManager.logScreenView(ScreenViewEvent.UserDetails)
}
override fun showUserDetails(
avatarUrl: String,
name: String,
username: String,
status: String,
utcOffset: String
) {
Glide.with(this)
.asBitmap()
.load(avatarUrl)
.into(image_avatar)
text_name.text = name
text_username.text = username
text_description_status.text = status
text_description_timezone.text = utcOffset
// We should also setup the user details listeners.
text_message.setOnClickListener { presenter.createDirectMessage(username) }
}
override fun showLoading() {
group_user_details.isVisible = false
view_loading.isVisible = true
}
override fun hideLoading() {
group_user_details.isVisible = true
view_loading.isVisible = false
}
override fun showMessage(resId: Int) {
ui { showToast(resId) }
}
override fun showMessage(message: String) {
ui { showToast(message) }
}
override fun showGenericErrorMessage() = showMessage(getString(R.string.msg_generic_error))
private fun setupToolbar() {
with(activity as ChatRoomActivity) {
view?.let { setLightStatusBar(it) }
toolbar.isVisible = false
}
}
}
\ No newline at end of file
......@@ -3,6 +3,7 @@
android:height="24dp"
android:viewportWidth="20"
android:viewportHeight="20">
<path
android:fillColor="#1d74f5"
android:fillType="evenOdd"
......
<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/root_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@android:color/white"
android:fitsSystemWindows="true">
<androidx.core.widget.NestedScrollView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@android:color/white"
app:layout_behavior="com.google.android.material.appbar.AppBarLayout$ScrollingViewBehavior">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@android:color/white">
<com.wang.avi.AVLoadingIndicatorView
android:id="@+id/view_loading"
style="@style/Authentication.AVLoadingIndicatorView"
android:visibility="gone"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:visibility="visible" />
<ImageView
android:id="@+id/image_view_message"
android:layout_width="24dp"
android:layout_height="24dp"
android:layout_marginTop="24dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/text_view_username"
app:srcCompat="@drawable/ic_message_24dp" />
<TextView
android:id="@+id/text_view_message"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/message"
android:textColor="#1d74f5"
android:textSize="18sp"
app:layout_constraintEnd_toEndOf="@+id/image_view_message"
app:layout_constraintStart_toStartOf="@+id/image_view_message"
app:layout_constraintTop_toBottomOf="@+id/image_view_message" />
<TextView
android:id="@+id/text_view_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="8dp"
android:layout_marginEnd="16dp"
android:fontFamily="sans-serif-medium"
android:textColor="@android:color/black"
android:textSize="18sp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:text="Karem Flusser" />
<TextView
android:id="@+id/text_view_username"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginEnd="16dp"
android:textColor="@color/darkGray"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/text_view_name"
tools:text="karem.flusser" />
<TextView
android:id="@+id/textView3"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="24dp"
android:text="@string/status"
android:textColor="@color/darkGray"
android:textSize="14sp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/text_view_message" />
<TextView
android:id="@+id/text_view_status"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:textColor="@android:color/black"
android:textSize="16sp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/textView3"
tools:text="Online" />
<TextView
android:id="@+id/textView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="16dp"
android:text="@string/timezone"
android:textColor="@color/darkGray"
android:textSize="14sp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/text_view_status" />
<TextView
android:id="@+id/text_view_tz"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:textColor="@android:color/black"
android:textSize="16sp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/textView"
tools:text="(UTC-2) 11:08 AM" />
<androidx.constraintlayout.widget.Group
android:id="@+id/group_user_details"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:constraint_referenced_ids="text_view_tz,textView,text_view_status,text_view_message,textView3,text_view_name,text_view_username,image_view_message" />
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.core.widget.NestedScrollView>
<com.google.android.material.appbar.AppBarLayout
android:id="@+id/appBarLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@android:color/white"
app:elevation="0dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<com.google.android.material.appbar.CollapsingToolbarLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
app:contentScrim="@android:color/white"
app:expandedTitleGravity="top"
app:layout_scrollFlags="scroll|exitUntilCollapsed|snap">
<androidx.appcompat.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingBottom="?actionBarSize"
app:elevation="0dp"
app:layout_collapseMode="pin"
app:layout_scrollFlags="scroll|enterAlways"
app:navigationIcon="?android:attr/homeAsUpIndicator"
app:popupTheme="@style/ThemeOverlay.AppCompat.Light" />
<ImageView
android:id="@+id/image_view_avatar"
android:layout_width="98dp"
android:layout_height="98dp"
android:layout_gravity="center_horizontal"
android:layout_marginStart="16dp"
android:layout_marginTop="?actionBarSize"
android:layout_marginEnd="16dp"
android:background="@drawable/bg_round_bounds_white"
tools:srcCompat="@tools:sample/avatars[6]" />
</com.google.android.material.appbar.CollapsingToolbarLayout>
</com.google.android.material.appbar.AppBarLayout>
</androidx.coordinatorlayout.widget.CoordinatorLayout>
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@android:color/white"
android:padding="@dimen/screen_edge_left_and_right_margins">
<ImageView
android:id="@+id/image_avatar"
android:layout_width="98dp"
android:layout_height="98dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:srcCompat="@tools:sample/avatars[6]" />
<TextView
android:id="@+id/text_name"
style="@style/UserDetails.TextView.Name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="12dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/image_avatar"
tools:text="Karem Flusser" />
<TextView
android:id="@+id/text_username"
style="@style/UserDetails.TextView.Username"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="2dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/text_name"
tools:text="karem.flusser" />
<TextView
android:id="@+id/text_message"
style="@style/UserDetails.TextView.Actions"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="24dp"
android:drawableTop="@drawable/ic_message_24dp"
android:text="@string/msg_message"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/text_username" />
<TextView
android:id="@+id/text_title_status"
style="@style/UserDetails.TextView.Title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="32dp"
android:text="@string/status"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/text_message" />
<TextView
android:id="@+id/text_description_status"
style="@style/UserDetails.TextView.Description"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/text_title_status"
tools:text="Online" />
<TextView
android:id="@+id/text_title_timezone"
style="@style/UserDetails.TextView.Title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:text="@string/timezone"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/text_description_status" />
<TextView
android:id="@+id/text_description_timezone"
style="@style/UserDetails.TextView.Description"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/text_title_timezone"
tools:text="(UTC-2) 11:08 AM" />
<com.wang.avi.AVLoadingIndicatorView
android:id="@+id/view_loading"
style="@style/Authentication.AVLoadingIndicatorView"
android:visibility="gone"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:visibility="visible" />
<androidx.constraintlayout.widget.Group
android:id="@+id/group_user_details"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:constraint_referenced_ids="image_avatar, text_name, text_username, text_message, text_title_status, text_description_status, text_title_timezone, text_description_timezone" />
</androidx.constraintlayout.widget.ConstraintLayout>
......@@ -329,11 +329,9 @@
<string name="read_by">Gelesen von</string>
<string name="message_information_title">Nachricht Information</string>
<string name="message_room_changed_privacy">Room type changed to: %1$s by %2$s</string>
<!-- User Details -->
<string name="message">Botschaft</string>
<string name="timezone">Zeitzone</string>
<string name="error_opening_dm">Etwas ist schiefgegangen, als wir dieses Gespräch erstellt haben…</string>
<string name="retry">Wiederholen</string>
<!-- Report -->
<string name="submit">einreichen</string>
......
......@@ -335,10 +335,7 @@
<string name="message_room_changed_privacy">Room type changed to: %1$s by %2$s</string> <!--TODO - Add proper translation-->
<!-- User Details -->
<string name="message">Message</string> <!-- TODO - Add proper translation -->
<string name="timezone">Timezone</string> <!-- TODO - Add proper translation -->
<string name="error_opening_dm">Something went wrong while we were creating this conversation…</string> <!-- TODO - Add proper translation -->
<string name="retry">Retry</string> <!-- TODO - Add proper translation -->
<!-- Report -->
<string name="submit">Submit</string> <!--TODO - Add proper translation-->
......
......@@ -338,10 +338,7 @@
<string name="message_room_changed_privacy">Room type changed to: %1$s by %2$s</string>
<!-- User Details -->
<string name="message">Message</string> <!-- TODO - Add proper translation -->
<string name="timezone">Timezone</string> <!-- TODO - Add proper translation -->
<string name="error_opening_dm">Something went wrong while we were creating this conversation…</string> <!-- TODO - Add proper translation -->
<string name="retry">Retry</string> <!-- TODO - Add proper translation -->
<!-- Report -->
<string name="submit">Submit</string> <!-- TODO - Add proper translation -->
......
......@@ -336,10 +336,7 @@
<string name="message_room_changed_privacy">%2$s ने रूम का प्रकार बदलकर %1$s किया </string>
<!-- User Details -->
<string name="message">संदेश</string>
<string name="timezone">समय क्षेत्र</string>
<string name="error_opening_dm">जब हम इस बातचीत को बना रहे थे तो कुछ गड़बड़ हुई …</string>
<string name="retry">पुन: प्रयास करें</string>
<!-- Report -->
<string name="submit">जमा करें</string>
......
......@@ -331,10 +331,7 @@
<string name="message_room_changed_privacy">Il tipo di stanza è cambiato in: %1$s da %2$s</string>
<!-- User Details -->
<string name="message">Messaggio</string>
<string name="timezone">Fuso Orario</string>
<string name="error_opening_dm">È successo qualcosa di sbagliato durante la creazione di questa conversazione...</string>
<string name="retry">Riprova</string>
<!-- Report -->
<string name="submit">Invia</string>
......
......@@ -339,10 +339,7 @@
<string name="message_room_changed_privacy">ルームタイプを %2$s から %1$s に変更しました</string>
<!-- User Details -->
<string name="message">Message</string> <!--TODO - Add proper translation-->
<string name="timezone">Timezone</string> <!--TODO - Add proper translation-->
<string name="error_opening_dm">Something went wrong while we were creating this conversation…</string> <!-- TODO - Add proper translation -->
<string name="retry">Retry</string> <!-- TODO - Add proper translation -->
<!-- Report -->
<string name="submit">Submit</string> <!--TODO - Add proper translation-->
......
......@@ -336,10 +336,7 @@
<string name="message_room_changed_privacy">O tipo da sala mudou para: %1$s por %2$s</string>
<!-- User Details -->
<string name="message">Mensagem</string>
<string name="timezone">Fuso horário</string>
<string name="error_opening_dm">Algo deu errado quando tentamos abrir esta conversa…</string>
<string name="retry">Retentar</string>
<!-- Report -->
<string name="submit">Enviar</string>
......
......@@ -333,10 +333,7 @@
<string name="message_room_changed_privacy">Тип канала изменен на: %1$s пользователем %2$s</string>
<!-- User Details -->
<string name="message">Сообщение</string>
<string name="timezone">Часовой поясe</string>
<string name="error_opening_dm">При создании этого диалога что-то пошло не так...</string>
<string name="retry">Повторить</string>
<!-- Report -->
<string name="submit">Отправить</string>
......
......@@ -337,10 +337,7 @@
<string name="message_room_changed_privacy">Room type changed to: %1$s by %2$s</string> <!--TODO - Add proper translation-->
<!-- User Details -->
<string name="message">Message</string> <!--TODO - Add proper translation-->
<string name="timezone">Timezone</string> <!--TODO - Add proper translation-->
<string name="error_opening_dm">Something went wrong while we were creating this conversation…</string> <!-- TODO - Add proper translation -->
<string name="retry">Retry</string> <!-- TODO - Add proper translation -->
<!-- Report -->
<string name="submit">Submit</string> <!--TODO - Add proper translation-->
......
......@@ -335,10 +335,7 @@
<string name="message_room_changed_privacy">Room type changed to: %1$s by %2$s</string> <!-- TODO - Add proper translation -->
<!-- User Details -->
<string name="message">Message</string>
<string name="timezone">Timezone</string>
<string name="error_opening_dm">Something went wrong while we were creating this conversation…</string> <!-- TODO - Add proper translation -->
<string name="retry">Retry</string> <!-- TODO - Add proper translation -->
<!-- Report -->
<string name="submit">Submit</string> <!-- TODO - Add proper translation -->
......
......@@ -13,6 +13,8 @@
<color name="colorTimestampText">#FF9DA2A9</color>
<color name="colorTimestampTextUnread">#FF5699FF</color>
<color name="colorLastMessageText">#99000000</color>
<color name="colorUserDetailsNameText">#FF0C0D0F</color>
<color name="colorUserDetailsUsernameText">#8B000000</color>
<!-- User status colors -->
<color name="colorUserStatusOnline">#2FE1A8</color>
......
......@@ -350,11 +350,8 @@ https://github.com/RocketChat/java-code-styles/blob/master/CODING_STYLE.md#strin
<string name="foss" translatable="false">(FOSS)</string>
<!-- User Details -->
<string name="message">Message</string>
<string name="timezone">Timezone</string>
<string name="status" translatable="false">Status</string>
<string name="error_opening_dm">Something went wrong while we were creating this conversation…</string>
<string name="retry">Retry</string>
<!-- Report -->
<string name="submit">Submit</string>
......
......@@ -155,6 +155,43 @@
</style>
<!-- End chat list -->
<!-- User details -->
<style name="UserDetails.TextView.Name" parent="TextAppearance.AppCompat">
<item name="android:textSize">18sp</item>
<item name="android:fontFamily">sans-serif</item>
<item name="android:textStyle">bold</item>
<item name="android:textColor">@color/colorUserDetailsNameText</item>
</style>
<style name="UserDetails.TextView.Username" parent="TextAppearance.AppCompat">
<item name="android:textSize">14sp</item>
<item name="android:fontFamily">sans-serif</item>
<item name="android:textStyle">normal</item>
<item name="android:textColor">@color/colorUserDetailsUsernameText</item>
</style>
<style name="UserDetails.TextView.Actions" parent="TextAppearance.AppCompat">
<item name="android:textSize">16sp</item>
<item name="android:fontFamily">sans-serif</item>
<item name="android:textStyle">normal</item>
<item name="android:textColor">@color/colorAccent</item>
</style>
<style name="UserDetails.TextView.Title" parent="TextAppearance.AppCompat">
<item name="android:textSize">12sp</item>
<item name="android:fontFamily">sans-serif</item>
<item name="android:textStyle">normal</item>
<item name="android:textColor">@color/colorAuthenticationSecondaryText</item>
</style>
<style name="UserDetails.TextView.Description" parent="TextAppearance.AppCompat">
<item name="android:textSize">16sp</item>
<item name="android:fontFamily">sans-serif</item>
<item name="android:textStyle">normal</item>
<item name="android:textColor">@color/colorPrimary</item>
</style>
<!-- End user details-->
<style name="EditText.Password" parent="TextAppearance.AppCompat">
<!-- Hint color and label color in FALSE state -->
<item name="android:textColorHint">@color/colorPrimaryDark</item>
......
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