Commit bee51600 authored by Filipe de Lima Brito's avatar Filipe de Lima Brito

Show message list of a chat room.

parent f40817af
......@@ -34,7 +34,7 @@
android:theme="@style/ChatListTheme" />
<activity
android:name=".app.ChatRoomActivity"
android:name=".chatroom.ui.ChatRoomActivity"
android:theme="@style/AppTheme" />
<activity
......
package chat.rocket.android.app
import android.os.Bundle
import android.support.v7.app.AppCompatActivity
import chat.rocket.android.R
import chat.rocket.android.app.chatroom.MessageFragment
import chat.rocket.android.util.addFragment
class ChatRoomActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_chat_room)
addFragment("MessageFragment", R.id.fragment_container) {
MessageFragment()
}
}
}
\ No newline at end of file
package chat.rocket.android.app.chatroom
import chat.rocket.android.app.User
import org.threeten.bp.LocalDateTime
data class Message(val user: User,
val textContent: String?,
val imageAttachmentUri: String?,
val localDatetime: LocalDateTime)
\ No newline at end of file
package chat.rocket.android.app.chatroom
import android.os.Bundle
import android.support.v4.app.Fragment
import android.support.v7.widget.LinearLayoutManager
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import chat.rocket.android.R
import chat.rocket.android.app.User
import kotlinx.android.synthetic.main.fragment_chat_rooms.*
import org.threeten.bp.LocalDateTime
class MessageFragment : Fragment() {
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? = inflater.inflate(R.layout.fragment_message, container, false)
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
showMessageList(createDumpData())
}
// This is just a sample showing 2 messages in the chat room. We need to get it rid in a real word. REMARK: remove this comment and this method.
private fun createDumpData(): List<Message> {
val user1 = User("1",
"Filipe Brito",
"filipe.brito",
"online",
"https://open.rocket.chat/avatar/filipe.brito")
val user2 = User("2",
"Lucio Maciel",
"Lucio Maciel",
"busy",
"https://open.rocket.chat/avatar/lucio.maciel")
val message1 = Message(user1,
"This is a multiline chat message from Bertie that will take more than just one line of text. I have sure that everything is amazing!",
"https://rocket.chat/images/index/livechat.png",
LocalDateTime.now())
val message2 = Message(user2, "Great!",
"https://rocket.chat/images/index/screenshot.png",
LocalDateTime.now().plusHours(1))
return listOf(message1, message2)
}
// REMARK: The presenter should call this method.
private fun showMessageList(dataSet: List<Message>) {
activity?.apply {
recycler_view.layoutManager = LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false)
recycler_view.adapter = MessageListAdapter(this, dataSet.toMutableList()) {}
}
}
}
\ No newline at end of file
package chat.rocket.android.app.chatroom
import DateTimeHelper
import android.content.Context
import android.support.v7.widget.RecyclerView
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.TextView
import chat.rocket.android.R
import com.facebook.drawee.view.SimpleDraweeView
import com.stfalcon.frescoimageviewer.ImageViewer
import kotlinx.android.synthetic.main.item_message.view.*
class MessageListAdapter(private val context: Context, private var dataSet: MutableList<Message>, private val listener: (Message) -> Unit) : RecyclerView.Adapter<MessageListAdapter.ViewHolder>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val view = LayoutInflater.from(parent.context).inflate(R.layout.item_message, parent, false)
return ViewHolder(view)
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val message = dataSet[position]
holder.userAvatar.setImageURI(message.user.avatarUri)
holder.userName.text = message.user.name
holder.time.text = DateTimeHelper.getTime(message.localDatetime)
val textContent = message.textContent
if (textContent.isNullOrBlank()) {
holder.textContent.visibility = View.GONE
} else {
holder.textContent.text = textContent
}
val imageAttachmentUri = message.imageAttachmentUri
if (imageAttachmentUri.isNullOrBlank()) {
holder.imageAttachment.visibility = View.GONE
} else {
holder.imageAttachment.setImageURI(imageAttachmentUri)
holder.imageAttachment.setOnClickListener({
ImageViewer.Builder(context, listOf(imageAttachmentUri))
.hideStatusBar(false)
.show()
})
}
}
override fun getItemCount(): Int = dataSet.size
class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
val userAvatar: SimpleDraweeView = itemView.image_user_avatar
val userName: TextView = itemView.text_user_name
val time: TextView = itemView.text_message_time
val textContent: TextView = itemView.text_content
val imageAttachment: SimpleDraweeView = itemView.image_attachment
}
}
\ No newline at end of file
......@@ -6,7 +6,6 @@ import chat.rocket.android.authentication.ui.AuthenticationActivity
import chat.rocket.android.dagger.scope.PerActivity
import dagger.Module
import dagger.Provides
import kotlinx.coroutines.experimental.Job
@Module
class AuthenticationModule {
......@@ -14,9 +13,4 @@ class AuthenticationModule {
@Provides
@PerActivity
fun provideAuthenticationNavigator(activity: AuthenticationActivity, context: Context) = AuthenticationNavigator(activity, context)
@Provides
fun provideJob(): Job {
return Job()
}
}
}
\ No newline at end of file
......@@ -6,6 +6,6 @@ import dagger.android.ContributesAndroidInjector
@Module abstract class LoginFragmentProvider {
@ContributesAndroidInjector(modules = arrayOf(LoginFragmentModule::class))
@ContributesAndroidInjector(modules = [(LoginFragmentModule::class)])
abstract fun provideLoginFragment(): LoginFragment
}
}
\ No newline at end of file
......@@ -19,7 +19,6 @@ class LoginPresenter @Inject constructor(private val view: LoginView,
private val settingsInteractor: GetSettingsInteractor,
private val serverInteractor: GetCurrentServerInteractor,
factory: RocketChatClientFactory) {
// TODO - we should validate the current server when opening the app, and have a nonnull get()
private val client: RocketChatClient = factory.create(serverInteractor.get()!!)
......@@ -36,6 +35,8 @@ class LoginPresenter @Inject constructor(private val view: LoginView,
return
}
view.showSignUpView(settings.registrationEnabled())
var hasSocial = false
if (settings.facebookEnabled()) {
view.enableLoginByFacebook()
......@@ -65,8 +66,6 @@ class LoginPresenter @Inject constructor(private val view: LoginView,
view.enableLoginByGitlab()
hasSocial = true
}
view.showSignUpView(settings.registrationEnabled())
view.showOauthView(hasSocial)
}
......@@ -103,9 +102,9 @@ class LoginPresenter @Inject constructor(private val view: LoginView,
}
}
}
} finally {
view.hideLoading()
}
view.hideLoading()
} else {
view.showNoInternetConnection()
}
......@@ -114,7 +113,5 @@ class LoginPresenter @Inject constructor(private val view: LoginView,
}
}
fun signup() {
navigator.toSignUp()
}
fun signup() = navigator.toSignUp()
}
......@@ -8,7 +8,6 @@ import android.support.v4.app.Fragment
import android.text.style.ClickableSpan
import android.view.*
import android.widget.ImageButton
import android.view.inputmethod.InputMethodManager
import android.widget.ScrollView
import android.widget.Toast
import chat.rocket.android.R
......@@ -57,12 +56,6 @@ class LoginFragment : Fragment(), LoginView {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
activity?.apply {
text_username_or_email.requestFocus()
val imm = getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
imm.showSoftInput(text_username_or_email, InputMethodManager.SHOW_IMPLICIT)
}
if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.M) {
tintEditTextDrawableStart()
}
......@@ -216,7 +209,7 @@ class LoginFragment : Fragment(), LoginView {
button_log_in.isEnabled = value
text_username_or_email.isEnabled = value
text_password.isEnabled = value
if (isEditTextEmpty()) {
if (!isEditTextEmpty()) {
showSignUpView(value)
showOauthView(value)
}
......
......@@ -32,6 +32,7 @@ class AuthenticationNavigator(internal val activity: AuthenticationActivity, int
fun toWebPage(url: String) {
activity.startActivity(context.webViewIntent(url))
activity.overridePendingTransition(R.anim.slide_up, R.anim.hold)
}
fun toChatList() {
......
......@@ -6,6 +6,6 @@ import dagger.android.ContributesAndroidInjector
@Module abstract class ServerFragmentProvider {
@ContributesAndroidInjector(modules = arrayOf(ServerFragmentModule::class))
@ContributesAndroidInjector(modules = [(ServerFragmentModule::class)])
abstract fun provideServerFragment(): ServerFragment
}
\ No newline at end of file
......@@ -6,6 +6,7 @@ import chat.rocket.android.helper.NetworkHelper
import chat.rocket.android.server.domain.*
import chat.rocket.android.server.infraestructure.RocketChatClientFactory
import chat.rocket.android.util.launchUI
import chat.rocket.common.util.ifNull
import chat.rocket.core.RocketChatClient
import chat.rocket.core.internal.rest.settings
import java.security.InvalidParameterException
......@@ -17,36 +18,41 @@ class ServerPresenter @Inject constructor(private val view: ServerView,
private val serverInteractor: SaveCurrentServerInteractor,
private val settingsInteractor: SaveSettingsInteractor,
private val factory: RocketChatClientFactory) {
private var settingsFilter = arrayOf(SITE_URL, SITE_NAME,
FAVICON_512, USE_REALNAME, ALLOW_ROOM_NAME_SPECIAL_CHARS, FAVORITE_ROOMS,
ACCOUNT_LOGIN_FORM, ACCOUNT_GOOGLE, ACCOUNT_FACEBOOK, ACCOUNT_GITHUB, ACCOUNT_GITLAB,
ACCOUNT_LINKEDIN, ACCOUNT_METEOR, ACCOUNT_TWITTER, ACCOUNT_WORDPRESS, LDAP_ENABLE,
ACCOUNT_REGISTRATION, STORAGE_TYPE, HIDE_USER_JOIN, HIDE_USER_LEAVE, HIDE_TYPE_AU,
private lateinit var client: RocketChatClient
private var settingsFilter = arrayOf(SITE_URL, SITE_NAME, FAVICON_512, USE_REALNAME, ALLOW_ROOM_NAME_SPECIAL_CHARS, FAVORITE_ROOMS,
ACCOUNT_LOGIN_FORM, ACCOUNT_GOOGLE, ACCOUNT_FACEBOOK, ACCOUNT_GITHUB, ACCOUNT_GITLAB, ACCOUNT_LINKEDIN, ACCOUNT_METEOR,
ACCOUNT_TWITTER, ACCOUNT_WORDPRESS, LDAP_ENABLE, ACCOUNT_REGISTRATION, STORAGE_TYPE, HIDE_USER_JOIN, HIDE_USER_LEAVE, HIDE_TYPE_AU,
HIDE_MUTE_UNMUTE, HIDE_TYPE_RU, ACCOUNT_CUSTOM_FIELDS)
fun connect(server: String) {
var cli: RocketChatClient? = null
try {
cli = factory.create(server)
} catch (ex: InvalidParameterException) {
view.showMessage(ex.message!!)
client = factory.create(server)
} catch (exception: InvalidParameterException) {
exception.message?.let {
view.showMessage(it)
}.ifNull {
view.showGenericErrorMessage()
}
return
}
cli?.let { client ->
client.let { rocketChatClient ->
launchUI(strategy) {
if (NetworkHelper.hasInternetAccess()) {
view.showLoading()
try {
val settings = client.settings(*settingsFilter)
val settings = rocketChatClient.settings(*settingsFilter)
settingsInteractor.save(server, settings)
serverInteractor.save(server)
navigator.toLogin()
} catch (ex: Exception) {
ex.printStackTrace()
view.showMessage(ex.message!!)
} catch (exception: Exception) {
exception.message?.let {
view.showMessage(it)
}.ifNull {
view.showGenericErrorMessage()
}
} finally {
view.hideLoading()
}
......
......@@ -3,10 +3,12 @@ package chat.rocket.android.authentication.signup.presentation
import chat.rocket.android.authentication.presentation.AuthenticationNavigator
import chat.rocket.android.core.lifecycle.CancelStrategy
import chat.rocket.android.helper.NetworkHelper
import chat.rocket.android.helper.UrlHelper
import chat.rocket.android.server.domain.GetCurrentServerInteractor
import chat.rocket.android.server.infraestructure.RocketChatClientFactory
import chat.rocket.android.util.launchUI
import chat.rocket.common.RocketChatException
import chat.rocket.common.util.ifNull
import chat.rocket.core.internal.rest.login
import chat.rocket.core.internal.rest.signup
import javax.inject.Inject
......@@ -46,15 +48,15 @@ class SignupPresenter @Inject constructor(private val view: SignupView,
client.login(username, password) // TODO This function returns a user token so should we save it?
navigator.toChatList()
} catch (exception: RocketChatException) {
val errorMessage = exception.message
if (errorMessage != null) {
view.showMessage(errorMessage)
} else {
exception.message?.let {
view.showMessage(it)
}.ifNull {
view.showGenericErrorMessage()
}
}
} finally {
view.hideLoading()
view.hideLoading()
}
} else {
view.showNoInternetConnection()
}
......@@ -65,13 +67,13 @@ class SignupPresenter @Inject constructor(private val view: SignupView,
fun termsOfService() {
serverInteractor.get()?.let {
navigator.toWebPage("/terms-of-service")
navigator.toWebPage(UrlHelper.getTermsOfServiceUrl(it))
}
}
fun privacyPolicy() {
serverInteractor.get()?.let {
navigator.toWebPage("/privacy-policy")
navigator.toWebPage(UrlHelper.getPrivacyPolicyUrl(it))
}
}
}
\ No newline at end of file
......@@ -8,6 +8,7 @@ import chat.rocket.android.helper.NetworkHelper
import chat.rocket.android.util.launchUI
import chat.rocket.common.RocketChatAuthException
import chat.rocket.common.RocketChatException
import chat.rocket.common.util.ifNull
import chat.rocket.core.internal.rest.login
import javax.inject.Inject
......@@ -20,42 +21,44 @@ class TwoFAPresenter @Inject constructor(private val view: TwoFAView,
// TODO: If the usernameOrEmail and password was informed by the user on the previous screen, then we should pass only the pin, like this: fun authenticate(pin: EditText)
fun authenticate(usernameOrEmail: String, password: String, twoFactorAuthenticationCode: String) {
val server = serverInteractor.get()
if (twoFactorAuthenticationCode.isBlank()) {
view.alertBlankTwoFactorAuthenticationCode()
} else if (server == null) {
navigator.toServerScreen()
} else {
launchUI(strategy) {
when {
server == null -> {
navigator.toServerScreen()
}
twoFactorAuthenticationCode.isBlank() -> {
view.alertBlankTwoFactorAuthenticationCode()
}
else -> {
launchUI(strategy) {
val client = factory.create(server)
if (NetworkHelper.hasInternetAccess()) {
view.showLoading()
val client = factory.create(server)
if (NetworkHelper.hasInternetAccess()) {
view.showLoading()
try {
// The token is saved via the client TokenProvider
client.login(usernameOrEmail, password, twoFactorAuthenticationCode)
navigator.toChatList()
} catch (exception: RocketChatException) {
if (exception is RocketChatAuthException) {
view.alertInvalidTwoFactorAuthenticationCode()
} else {
val message = exception.message
if (message != null) {
view.showMessage(message)
try {
// The token is saved via the client TokenProvider
client.login(usernameOrEmail, password, twoFactorAuthenticationCode)
navigator.toChatList()
} catch (exception: RocketChatException) {
if (exception is RocketChatAuthException) {
view.alertInvalidTwoFactorAuthenticationCode()
} else {
view.showGenericErrorMessage()
exception.message?.let {
view.showMessage(it)
}.ifNull {
view.showGenericErrorMessage()
}
}
} finally {
view.hideLoading()
}
} else {
view.showNoInternetConnection()
}
view.hideLoading()
} else {
view.showNoInternetConnection()
}
}
}
}
fun signup() {
navigator.toSignUp()
}
fun signup() = navigator.toSignUp()
}
\ No newline at end of file
......@@ -21,7 +21,7 @@ class AuthenticationActivity : AppCompatActivity(), HasSupportFragmentInjector {
AndroidInjection.inject(this)
addFragment("authenticationServerFragment", R.id.fragment_container) {
addFragment("ServerFragment", R.id.fragment_container) {
ServerFragment.newInstance()
}
}
......
package chat.rocket.android.chatroom.di
import android.arch.lifecycle.LifecycleOwner
import chat.rocket.android.chatroom.presentation.ChatRoomView
import chat.rocket.android.chatroom.ui.ChatRoomFragment
import chat.rocket.android.core.lifecycle.CancelStrategy
import chat.rocket.android.dagger.scope.PerFragment
import dagger.Module
import dagger.Provides
import kotlinx.coroutines.experimental.Job
@Module
@PerFragment
class ChatRoomFragmentModule {
@Provides
fun chatRoomView(frag: ChatRoomFragment): ChatRoomView {
return frag
}
@Provides
fun provideLifecycleOwner(frag: ChatRoomFragment): LifecycleOwner {
return frag
}
@Provides
fun provideCancelStrategy(owner: LifecycleOwner, jobs: Job): CancelStrategy {
return CancelStrategy(owner, jobs)
}
}
\ No newline at end of file
package chat.rocket.android.chatroom.di
import chat.rocket.android.chatroom.ui.ChatRoomFragment
import dagger.Module
import dagger.android.ContributesAndroidInjector
@Module
abstract class ChatRoomFragmentProvider {
@ContributesAndroidInjector(modules = [ChatRoomFragmentModule::class])
abstract fun provideChatRoomFragment(): ChatRoomFragment
}
\ No newline at end of file
package chat.rocket.android.chatroom.presentation
import chat.rocket.android.core.lifecycle.CancelStrategy
import chat.rocket.android.server.domain.GetCurrentServerInteractor
import chat.rocket.android.server.infraestructure.RocketChatClientFactory
import chat.rocket.android.util.launchUI
import chat.rocket.common.model.BaseRoom
import chat.rocket.common.util.ifNull
import chat.rocket.core.RocketChatClient
import chat.rocket.core.internal.rest.messages
import javax.inject.Inject
class ChatRoomPresenter @Inject constructor(private val view: ChatRoomView,
private val strategy: CancelStrategy,
private val serverInteractor: GetCurrentServerInteractor,
factory: RocketChatClientFactory) {
private val client = factory.create(serverInteractor.get()!!)
fun messages(chatRoomId: String, chatRoomType: String, offset: Int = 0) {
launchUI(strategy) {
view.showLoading()
try {
val messages = client.messages(chatRoomId, BaseRoom.RoomType.valueOf(chatRoomType), offset.toLong(), 30).result
view.showMessages(messages.toMutableList(), serverInteractor.get()!!)
} catch (ex: Exception) {
ex.message?.let {
view.showMessage(it)
}.ifNull {
view.showGenericErrorMessage()
}
} finally {
view.hideLoading()
}
}
}
}
\ No newline at end of file
package chat.rocket.android.chatroom.presentation
import chat.rocket.android.core.behaviours.LoadingView
import chat.rocket.android.core.behaviours.MessageView
import chat.rocket.core.model.Message
interface ChatRoomView : LoadingView, MessageView {
/**
* Shows the chat room messages.
*
* @param dataSet The data set to show.
* @param serverUrl The server URL.
*/
fun showMessages(dataSet: MutableList<Message>, serverUrl: String)
}
\ No newline at end of file
package chat.rocket.android.chatroom.ui
import android.content.Context
import android.content.Intent
import android.os.Bundle
import android.support.v4.app.Fragment
import android.support.v7.app.AppCompatActivity
import chat.rocket.android.R
import chat.rocket.android.util.addFragment
import chat.rocket.android.util.textContent
import dagger.android.AndroidInjection
import dagger.android.AndroidInjector
import dagger.android.DispatchingAndroidInjector
import dagger.android.support.HasSupportFragmentInjector
import kotlinx.android.synthetic.main.app_bar_chat_room.*
import javax.inject.Inject
fun Context.chatRoomIntent(chatRoomId: String, chatRoomName: String, chatRoomType: String, isChatRoomOpen: Boolean): 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_OPEN, isChatRoomOpen)
}
}
private const val INTENT_CHAT_ROOM_ID = "chat_room_id"
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_OPEN = "is_chat_room_open"
class ChatRoomActivity : AppCompatActivity(), HasSupportFragmentInjector {
@Inject lateinit var fragmentDispatchingAndroidInjector: DispatchingAndroidInjector<Fragment>
private lateinit var chatRoomId: String
private lateinit var chatRoomName: String
private lateinit var chatRoomType: String
private var isChatRoomOpen: Boolean = true
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_chat_room)
AndroidInjection.inject(this)
chatRoomId = intent.getStringExtra(INTENT_CHAT_ROOM_ID)
requireNotNull(chatRoomId) { "no chat_room_id provided in Intent extras" }
chatRoomName = intent.getStringExtra(INTENT_CHAT_ROOM_NAME)
requireNotNull(chatRoomName) { "no chat_room_name provided in Intent extras" }
chatRoomType = intent.getStringExtra(INTENT_CHAT_ROOM_TYPE)
requireNotNull(chatRoomType) { "no chat_room_type provided in Intent extras" }
isChatRoomOpen = intent.getBooleanExtra(INTENT_IS_CHAT_ROOM_OPEN, true)
requireNotNull(chatRoomType) { "no is_chat_room_open provided in Intent extras" }
setupToolbar(chatRoomName)
addFragment("ChatRoomFragment", R.id.fragment_container) {
newInstance(chatRoomId, chatRoomName, chatRoomType, isChatRoomOpen)
}
}
override fun onBackPressed() = finishActivity()
override fun supportFragmentInjector(): AndroidInjector<Fragment> {
return fragmentDispatchingAndroidInjector
}
private fun setupToolbar(chatRoomName: String) {
text_room_name.textContent = chatRoomName
toolbar.setNavigationOnClickListener {
finishActivity()
}
}
private fun finishActivity() {
super.onBackPressed()
overridePendingTransition(R.anim.close_enter, R.anim.close_exit)
}
}
\ No newline at end of file
package chat.rocket.android.chatroom.ui
import DateTimeHelper
import android.content.Context
import android.support.v7.widget.RecyclerView
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.helper.UrlHelper
import chat.rocket.android.util.inflate
import chat.rocket.android.util.setVisibility
import chat.rocket.android.util.textContent
import chat.rocket.common.util.ifNull
import chat.rocket.core.model.Message
import com.facebook.drawee.view.SimpleDraweeView
import kotlinx.android.synthetic.main.item_message.view.*
class ChatRoomAdapter(private val context: Context,
private var dataSet: MutableList<Message>,
private val serverUrl: String) : RecyclerView.Adapter<ChatRoomAdapter.ViewHolder>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder = ViewHolder(parent.inflate(R.layout.item_message))
override fun onBindViewHolder(holder: ViewHolder, position: Int) = holder.bind(dataSet[position])
override fun getItemCount(): Int = dataSet.size
override fun getItemViewType(position: Int): Int = position
fun addDataSet(dataSet: List<Message>) {
val previousDataSetSize = this.dataSet.size
this.dataSet.addAll(previousDataSetSize, dataSet)
notifyItemRangeInserted(previousDataSetSize, dataSet.size)
}
inner class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
fun bind(message: Message) = with(itemView) {
bindUserAvatar(message, image_user_avatar, image_unknown_user)
bindUserName(message, text_user_name)
bindTime(message, text_message_time)
bindContent(message, text_content)
}
private fun bindUserAvatar(message: Message, drawee: SimpleDraweeView, imageUnknownUser: ImageView) = message.sender?.username.let {
drawee.setImageURI(UrlHelper.getAvatarUrl(serverUrl, it.toString()))
}.ifNull {
imageUnknownUser.setVisibility(true)
}
private fun bindUserName(message: Message, textView: TextView) = message.sender?.username.let {
textView.textContent = it.toString()
}.ifNull {
textView.textContent = context.getString(R.string.msg_unknown)
}
private fun bindTime(message: Message, textView: TextView) {
textView.textContent = DateTimeHelper.getTime(DateTimeHelper.getLocalDateTime(message.timestamp))
}
private fun bindContent(message: Message, textView: TextView) {
textView.textContent = message.message
}
}
}
\ No newline at end of file
package chat.rocket.android.chatroom.ui
import android.os.Bundle
import android.support.v4.app.Fragment
import android.support.v7.widget.LinearLayoutManager
import android.support.v7.widget.RecyclerView
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.Toast
import chat.rocket.android.R
import chat.rocket.android.chatroom.presentation.ChatRoomPresenter
import chat.rocket.android.chatroom.presentation.ChatRoomView
import chat.rocket.android.helper.EndlessRecyclerViewScrollListener
import chat.rocket.android.util.inflate
import chat.rocket.android.util.setVisibility
import chat.rocket.core.model.Message
import dagger.android.support.AndroidSupportInjection
import kotlinx.android.synthetic.main.fragment_chat_rooms.*
import javax.inject.Inject
fun newInstance(chatRoomId: String, chatRoomName: String, chatRoomType: String, isChatRoomOpen: Boolean): Fragment {
return ChatRoomFragment().apply {
arguments = Bundle(1).apply {
putString(BUNDLE_CHAT_ROOM_ID, chatRoomId)
putString(BUNDLE_CHAT_ROOM_NAME, chatRoomName)
putString(BUNDLE_CHAT_ROOM_TYPE, chatRoomType)
putBoolean(BUNDLE_IS_CHAT_ROOM_OPEN, isChatRoomOpen)
}
}
}
private const val BUNDLE_CHAT_ROOM_ID = "chat_room_id"
private const val BUNDLE_CHAT_ROOM_NAME = "chat_room_name"
private const val BUNDLE_CHAT_ROOM_TYPE = "chat_room_type"
private const val BUNDLE_IS_CHAT_ROOM_OPEN = "is_chat_room_open"
class ChatRoomFragment : Fragment(), ChatRoomView {
@Inject lateinit var presenter: ChatRoomPresenter
private lateinit var chatRoomId: String
private lateinit var chatRoomName: String
private lateinit var chatRoomType: String
private var isChatRoomOpen: Boolean = true
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
AndroidSupportInjection.inject(this)
val bundle = arguments
if (bundle != null) {
chatRoomId = bundle.getString(BUNDLE_CHAT_ROOM_ID)
chatRoomName = bundle.getString(BUNDLE_CHAT_ROOM_NAME)
chatRoomType = bundle.getString(BUNDLE_CHAT_ROOM_TYPE)
isChatRoomOpen = bundle.getBoolean(BUNDLE_IS_CHAT_ROOM_OPEN)
} 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_chat_room)
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
presenter.messages(chatRoomId, chatRoomType)
}
override fun showMessages(dataSet: MutableList<Message>, serverUrl: String) {
activity?.apply {
if (recycler_view.adapter == null) {
recycler_view.adapter = ChatRoomAdapter(this, dataSet, serverUrl)
val linearLayoutManager = LinearLayoutManager(context, LinearLayoutManager.VERTICAL, true)
recycler_view.layoutManager = linearLayoutManager
if (dataSet.size >= 30) {
recycler_view.addOnScrollListener(object : EndlessRecyclerViewScrollListener(linearLayoutManager) {
override fun onLoadMore(page: Int, totalItemsCount: Int, recyclerView: RecyclerView?) {
presenter.messages(chatRoomId, chatRoomType, page * 30)
}
})
}
} else {
(recycler_view.adapter as ChatRoomAdapter).addDataSet(dataSet)
}
}
}
override fun showLoading() = view_loading.setVisibility(true)
override fun hideLoading() = view_loading.setVisibility(false)
override fun showMessage(message: String) = Toast.makeText(activity, message, Toast.LENGTH_SHORT).show()
override fun showGenericErrorMessage() = showMessage(getString(R.string.msg_generic_error))
}
\ No newline at end of file
......@@ -27,9 +27,4 @@ class ChatRoomsFragmentModule {
fun provideCancelStrategy(owner: LifecycleOwner, jobs: Job): CancelStrategy {
return CancelStrategy(owner, jobs)
}
@Provides
fun provideJob(): Job {
return Job()
}
}
\ No newline at end of file
package chat.rocket.android.chatrooms.di
import android.content.Context
import chat.rocket.android.chatrooms.presentation.ChatRoomsNavigator
import chat.rocket.android.chatrooms.ui.MainActivity
import chat.rocket.android.dagger.scope.PerActivity
import dagger.Module
import dagger.Provides
@Module
class ChatRoomsModule {
@Provides
@PerActivity
fun provideAuthenticationNavigator(activity: MainActivity, context: Context) = ChatRoomsNavigator(activity, context)
}
\ No newline at end of file
package chat.rocket.android.chatrooms.presentation
import android.content.Context
import chat.rocket.android.R
import chat.rocket.android.chatroom.ui.chatRoomIntent
import chat.rocket.android.chatrooms.ui.MainActivity
class ChatRoomsNavigator(private val activity: MainActivity, private val context: Context) {
fun toChatRoom(chatRoomId: String, chatRoomName: String, chatRoomType: String, isChatRoomOpen: Boolean) {
activity.startActivity(context.chatRoomIntent(chatRoomId, chatRoomName, chatRoomType, isChatRoomOpen))
activity.overridePendingTransition(R.anim.open_enter, R.anim.open_exit)
}
}
\ No newline at end of file
......@@ -11,30 +11,38 @@ import javax.inject.Inject
class ChatRoomsPresenter @Inject constructor(private val view: ChatRoomsView,
private val strategy: CancelStrategy,
private val serverInteractor: GetCurrentServerInteractor,
private val factory: RocketChatClientFactory) {
lateinit var client: RocketChatClient
private val navigator: ChatRoomsNavigator,
serverInteractor: GetCurrentServerInteractor,
factory: RocketChatClientFactory) {
private val client = factory.create(serverInteractor.get()!!)
fun chatRooms() {
// TODO - check for current server
client = factory.create(serverInteractor.get()!!)
fun loadChatRooms() {
launchUI(strategy) {
view.showLoading()
val chatRooms = client.chatRooms().update
val openChatRooms = getOpenChatRooms(chatRooms)
val sortedOpenChatRooms = sortChatRooms(openChatRooms)
view.showChatRooms(sortedOpenChatRooms.toMutableList())
view.hideLoading()
try {
val chatRooms = client.chatRooms().update
val openChatRooms = getOpenChatRooms(chatRooms)
val sortedOpenChatRooms = sortChatRooms(openChatRooms)
view.showChatRooms(sortedOpenChatRooms.toMutableList())
} catch (ex: Exception) {
view.showMessage(ex.message!!)
} finally {
view.hideLoading()
}
}
}
fun loadChatRoom(chatRoom: ChatRoom) {
navigator.toChatRoom(chatRoom.id, chatRoom.name, chatRoom.type.name, chatRoom.open)
}
private fun getOpenChatRooms(chatRooms: List<ChatRoom>): List<ChatRoom> {
return chatRooms.filter(ChatRoom::open)
}
private fun sortChatRooms(chatRooms: List<ChatRoom>): List<ChatRoom> {
return chatRooms.sortedByDescending {
chatRoom -> chatRoom.lastMessage?.timestamp
return chatRooms.sortedByDescending { chatRoom ->
chatRoom.lastMessage?.timestamp
}
}
}
\ No newline at end of file
......@@ -18,70 +18,87 @@ import chat.rocket.core.model.ChatRoom
import com.facebook.drawee.view.SimpleDraweeView
import kotlinx.android.synthetic.main.item_chat.view.*
class ChatRoomsAdapter(private var dataSet: MutableList<ChatRoom>, private val context: Context) : RecyclerView.Adapter<ChatRoomsAdapter.ViewHolder>() {
class ChatRoomsAdapter(private var dataSet: MutableList<ChatRoom>,
private val context: Context,
private val listener: (ChatRoom) -> Unit) : RecyclerView.Adapter<ChatRoomsAdapter.ViewHolder>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder = ViewHolder(parent.inflate(R.layout.item_chat))
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val chatRoom = dataSet[position]
val chatRoomName = chatRoom.name
override fun onBindViewHolder(holder: ViewHolder, position: Int) = holder.bind(dataSet[position])
holder.chatName.textContent = chatRoomName
override fun getItemCount(): Int = dataSet.size
override fun getItemViewType(position: Int): Int = position
inner class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
fun bind(chatRoom: ChatRoom) = with(itemView) {
bindAvatar(chatRoom, image_user_avatar, image_room_avatar)
bindName(chatRoom, text_chat_name)
bindLastMessageDateTime(chatRoom, text_last_message_date_time)
bindLastMessage(chatRoom, text_last_message)
bindUnreadMessages(chatRoom, text_total_unread_messages)
if (chatRoom.type == RoomType.ONE_TO_ONE) {
// TODO Check the best way to get the current server url.
val canonicalUrl = chatRoom.client.url
holder.userAvatar.setImageURI(UrlHelper.getAvatarUrl(canonicalUrl, chatRoomName))
holder.userAvatar.setVisibility(true)
} else {
holder.roomAvatar.setImageDrawable(DrawableHelper.getTextDrawable(chatRoomName))
holder.roomAvatar.setVisibility(true)
setOnClickListener { listener(chatRoom) }
}
val totalUnreadMessage = chatRoom.unread
when {
totalUnreadMessage in 1..99 -> {
holder.unreadMessage.textContent = totalUnreadMessage.toString()
holder.unreadMessage.setVisibility(true)
private fun bindAvatar(chatRoom: ChatRoom, drawee: SimpleDraweeView, imageView: ImageView) {
val chatRoomName = chatRoom.name
if (chatRoom.type == RoomType.ONE_TO_ONE) {
val serverUrl = chatRoom.client.url
drawee.setImageURI(UrlHelper.getAvatarUrl(serverUrl, chatRoomName))
drawee.setVisibility(true)
} else {
imageView.setImageDrawable(DrawableHelper.getTextDrawable(chatRoomName))
imageView.setVisibility(true)
}
totalUnreadMessage > 99 -> {
holder.unreadMessage.textContent = context.getString(R.string.msg_more_than_ninety_nine_unread_messages)
holder.unreadMessage.setVisibility(true)
}
private fun bindName(chatRoom: ChatRoom, textView: TextView) {
textView.textContent = chatRoom.name
}
private fun bindLastMessageDateTime(chatRoom: ChatRoom, textView: TextView) {
val lastMessage = chatRoom.lastMessage
if (lastMessage != null) {
val localDateTime = DateTimeHelper.getLocalDateTime(lastMessage.timestamp)
textView.textContent = DateTimeHelper.getDate(localDateTime, context)
}
}
val lastMessage = chatRoom.lastMessage
val lastMessageSender = lastMessage?.sender
if (lastMessage != null && lastMessageSender != null) {
val message = lastMessage.message
val senderUsername = lastMessageSender.username
when (senderUsername) {
chatRoomName -> {
holder.lastMessage.textContent = message
}
private fun bindLastMessage(chatRoom: ChatRoom, textView: TextView) {
val lastMessage = chatRoom.lastMessage
val lastMessageSender = lastMessage?.sender
if (lastMessage != null && lastMessageSender != null) {
val message = lastMessage.message
val senderUsername = lastMessageSender.username
when (senderUsername) {
chatRoom.name -> {
textView.textContent = message
}
// TODO Change to MySelf
// chatRoom.user?.username -> {
// holder.lastMessage.textContent = context.getString(R.string.msg_you) + ": $message"
// }
else -> {
holder.lastMessage.textContent = "@$senderUsername: $message"
// chatRoom.user?.username -> {
// holder.lastMessage.textContent = context.getString(R.string.msg_you) + ": $message"
// }
else -> {
textView.textContent = "@$senderUsername: $message"
}
}
}
val localDateTime = DateTimeHelper.getLocalDateTime(lastMessage.timestamp)
holder.lastMessageDateTime.textContent = DateTimeHelper.getDate(localDateTime, context)
}
}
override fun getItemCount(): Int = dataSet.size
override fun getItemViewType(position: Int): Int = position
class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
val userAvatar: SimpleDraweeView = itemView.image_user_avatar
val roomAvatar: ImageView = itemView.image_room_avatar
val chatName: TextView = itemView.text_chat_name
val lastMessage: TextView = itemView.text_last_message
val lastMessageDateTime: TextView = itemView.text_last_message_date_time
val unreadMessage: TextView = itemView.text_total_unread_messages
private fun bindUnreadMessages(chatRoom: ChatRoom, textView: TextView) {
val totalUnreadMessage = chatRoom.unread
when {
totalUnreadMessage in 1..99 -> {
textView.textContent = totalUnreadMessage.toString()
textView.setVisibility(true)
}
totalUnreadMessage > 99 -> {
textView.textContent = context.getString(R.string.msg_more_than_ninety_nine_unread_messages)
textView.setVisibility(true)
}
}
}
}
}
\ No newline at end of file
......@@ -10,6 +10,7 @@ import android.widget.Toast
import chat.rocket.android.R
import chat.rocket.android.chatrooms.presentation.ChatRoomsPresenter
import chat.rocket.android.chatrooms.presentation.ChatRoomsView
import chat.rocket.android.util.setVisibility
import chat.rocket.android.widget.DividerItemDecoration
import chat.rocket.core.model.ChatRoom
import dagger.android.support.AndroidSupportInjection
......@@ -32,20 +33,22 @@ class ChatRoomsFragment : Fragment(), ChatRoomsView {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
presenter.chatRooms()
presenter.loadChatRooms()
}
override fun showChatRooms(dataSet: MutableList<ChatRoom>) {
activity?.apply {
recycler_view.layoutManager = LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false)
recycler_view.addItemDecoration(DividerItemDecoration(this, 144, 32))
recycler_view.adapter = ChatRoomsAdapter(dataSet, this)
recycler_view.adapter = ChatRoomsAdapter(dataSet, this) { chatRoom ->
presenter.loadChatRoom(chatRoom)
}
}
}
override fun showLoading() = view_loading.show()
override fun showLoading() = view_loading.setVisibility(true)
override fun hideLoading() = view_loading.hide()
override fun hideLoading() = view_loading.setVisibility(false)
override fun showMessage(message: String) = Toast.makeText(activity, message, Toast.LENGTH_SHORT).show()
......
......@@ -6,7 +6,10 @@ import chat.rocket.android.authentication.server.di.ServerFragmentProvider
import chat.rocket.android.authentication.signup.di.SignupFragmentProvider
import chat.rocket.android.authentication.twofactor.di.TwoFAFragmentProvider
import chat.rocket.android.authentication.ui.AuthenticationActivity
import chat.rocket.android.chatroom.di.ChatRoomFragmentProvider
import chat.rocket.android.chatroom.ui.ChatRoomActivity
import chat.rocket.android.chatrooms.di.ChatRoomsFragmentProvider
import chat.rocket.android.chatrooms.di.ChatRoomsModule
import chat.rocket.android.chatrooms.ui.MainActivity
import chat.rocket.android.dagger.scope.PerActivity
import dagger.Module
......@@ -24,6 +27,11 @@ abstract class ActivityBuilder {
])
abstract fun bindAuthenticationActivity(): AuthenticationActivity
@ContributesAndroidInjector(modules = [ChatRoomsFragmentProvider::class])
@PerActivity
@ContributesAndroidInjector(modules = [ChatRoomsModule::class, ChatRoomsFragmentProvider::class])
abstract fun bindMainActivity(): MainActivity
@PerActivity
@ContributesAndroidInjector(modules = [ChatRoomFragmentProvider::class])
abstract fun bindChatRoomActivity(): ChatRoomActivity
}
\ No newline at end of file
......@@ -17,6 +17,7 @@ import chat.rocket.common.util.PlatformLogger
import chat.rocket.core.RocketChatClient
import dagger.Module
import dagger.Provides
import kotlinx.coroutines.experimental.Job
import okhttp3.OkHttpClient
import okhttp3.logging.HttpLoggingInterceptor
import javax.inject.Singleton
......@@ -43,6 +44,11 @@ class AppModule {
return Room.databaseBuilder(context, RocketChatDatabase::class.java, "rocketchat-db").build()
}
@Provides
fun provideJob(): Job {
return Job()
}
@Provides
@Singleton
fun provideContext(application: Application): Context {
......@@ -104,4 +110,4 @@ class AppModule {
fun provideSettingsRepository(): SettingsRepository {
return MemorySettingsRepository()
}
}
}
\ No newline at end of file
package chat.rocket.android.helper
import android.support.v7.widget.GridLayoutManager
import android.support.v7.widget.LinearLayoutManager
import android.support.v7.widget.RecyclerView
import android.support.v7.widget.StaggeredGridLayoutManager
/**
* Info: https://github.com/codepath/android_guides/wiki/Endless-Scrolling-with-AdapterViews-and-RecyclerView
*/
abstract class EndlessRecyclerViewScrollListener : RecyclerView.OnScrollListener {
private var visibleThreshold = 5 // The minimum amount of items to have below of the current scroll position before loading more.
private var currentPage = 0 // The current offset index of data loaded
private var previousTotalItemCount = 0 // The total number of items in the dataset after the last load
private var loading = true // True if we are still waiting for the last set of data to load.
private val startingPageIndex = 0 // Sets the starting page index
private var layoutManager: RecyclerView.LayoutManager
constructor(layoutManager: LinearLayoutManager) {
this.layoutManager = layoutManager
}
constructor(layoutManager: GridLayoutManager) {
this.layoutManager = layoutManager
visibleThreshold *= layoutManager.spanCount
}
constructor(layoutManager: StaggeredGridLayoutManager) {
this.layoutManager = layoutManager
visibleThreshold *= layoutManager.spanCount
}
private fun getLastVisibleItem(lastVisibleItemPositions: IntArray): Int {
var maxSize = 0
for (i in lastVisibleItemPositions.indices) {
if (i == 0) {
maxSize = lastVisibleItemPositions[i]
} else if (lastVisibleItemPositions[i] > maxSize) {
maxSize = lastVisibleItemPositions[i]
}
}
return maxSize
}
// This happens many times a second during a scroll, so be wary of the code you place here.
// We are given a few useful parameters to help us work out if we need to load some more data,
// but first we check if we are waiting for the previous load to finish.
override fun onScrolled(view: RecyclerView?, dx: Int, dy: Int) {
var lastVisibleItemPosition = 0
val totalItemCount = layoutManager.itemCount
when (layoutManager) {
is StaggeredGridLayoutManager -> {
val lastVisibleItemPositions = (layoutManager as StaggeredGridLayoutManager).findLastVisibleItemPositions(null)
// get maximum element within the list
lastVisibleItemPosition = getLastVisibleItem(lastVisibleItemPositions)
}
is GridLayoutManager -> lastVisibleItemPosition = (layoutManager as GridLayoutManager).findLastVisibleItemPosition()
is LinearLayoutManager -> lastVisibleItemPosition = (layoutManager as LinearLayoutManager).findLastVisibleItemPosition()
}
// If the total item count is zero and the previous isn't, assume the
// list is invalidated and should be reset back to initial state
// If it’s still loading, we check to see if the dataset count has
// changed, if so we conclude it has finished loading and update the current page
// number and total item count.
// If it isn’t currently loading, we check to see if we have breached
// the visibleThreshold and need to reload more data.
// If we do need to reload some more data, we execute onLoadMore to fetch the data.
// threshold should reflect how many total columns there are too
// If the total item count is zero and the previous isn't, assume the
// list is invalidated and should be reset back to initial state
if (totalItemCount < previousTotalItemCount) {
this.currentPage = this.startingPageIndex
this.previousTotalItemCount = totalItemCount
if (totalItemCount == 0) {
this.loading = true
}
}
// If it’s still loading, we check to see if the dataset count has
// changed, if so we conclude it has finished loading and update the current page
// number and total item count.
if (loading && totalItemCount > previousTotalItemCount) {
loading = false
previousTotalItemCount = totalItemCount
}
// If it isn’t currently loading, we check to see if we have breached
// the visibleThreshold and need to reload more data.
// If we do need to reload some more data, we execute onLoadMore to fetch the data.
// threshold should reflect how many total columns there are too
if (!loading && lastVisibleItemPosition + visibleThreshold > totalItemCount) {
currentPage++
onLoadMore(currentPage, totalItemCount, view)
loading = true
}
}
// Call this method whenever performing new searches
fun resetState() {
this.currentPage = this.startingPageIndex
this.previousTotalItemCount = 0
this.loading = true
}
// Defines the process for actually loading more data based on page
abstract fun onLoadMore(page: Int, totalItemsCount: Int, recyclerView: RecyclerView?)
}
\ No newline at end of file
......@@ -5,9 +5,39 @@ object UrlHelper {
/**
* Returns the avatar URL.
*
* @param serverUrl The serverUrl.
* @param chatRoomName The chat room name.
* @param serverUrl The server URL.
* @param avatarName The avatar name.
* @return The avatar URL.
*/
fun getAvatarUrl(serverUrl: String, chatRoomName: String): String = serverUrl + "avatar/" + chatRoomName
fun getAvatarUrl(serverUrl: String, avatarName: String): String = removeTrailingSlash(serverUrl)+ "/avatar/" + avatarName
/**
* Returns the server's Terms of Service URL.
*
* @param serverUrl The server URL.
* @return The server's Terms of Service URL.
*/
fun getTermsOfServiceUrl(serverUrl: String) = removeTrailingSlash(serverUrl) + "/terms-of-service"
/**
* Returns the server's Privacy Policy URL.
*
* @param serverUrl The server URL.
* @return The server's Privacy Policy URL.
*/
fun getPrivacyPolicyUrl(serverUrl: String) = removeTrailingSlash(serverUrl) + "/privacy-policy"
/**
* Returns an URL without trailing slash.
*
* @param serverUrl The URL to remove the trailing slash (if exists).
* @return An URL without trailing slash.
*/
fun removeTrailingSlash(serverUrl: String): String {
return if (serverUrl[serverUrl.length - 1] == '/') {
serverUrl.replace("/+$", "")
} else {
serverUrl
}
}
}
\ No newline at end of file
......@@ -4,5 +4,6 @@ import chat.rocket.core.model.Value
import javax.inject.Inject
class SaveSettingsInteractor @Inject constructor(private val repository: SettingsRepository) {
fun save(url: String, settings: Map<String, Value<Any>>) = repository.save(url, settings)
}
\ No newline at end of file
......@@ -6,9 +6,9 @@ import chat.rocket.core.RocketChatClient
import okhttp3.OkHttpClient
import javax.inject.Inject
class RocketChatClientFactory @Inject constructor(val okHttpClient: OkHttpClient,
val repository: AuthTokenRepository,
val logger: PlatformLogger) {
class RocketChatClientFactory @Inject constructor(private val okHttpClient: OkHttpClient,
private val repository: AuthTokenRepository,
private val logger: PlatformLogger) {
private val cache = HashMap<String, RocketChatClient>()
fun create(url: String): RocketChatClient {
......
<?xml version="1.0" encoding="utf-8"?>
<accelerateInterpolator xmlns:android="http://schemas.android.com/apk/res/android"
android:factor="1.5" />
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
android:background="@android:color/black"
android:fillAfter="true"
android:fillBefore="true"
android:fillEnabled="true"
android:shareInterpolator="false"
android:zAdjustment="top">
<alpha
android:duration="250"
android:fromAlpha="0.2"
android:interpolator="@anim/accelerate_cubic"
android:toAlpha="1.0" />
<scale
android:duration="250"
android:fromXScale="0.9"
android:fromYScale="0.9"
android:interpolator="@anim/accelerate_cubic"
android:pivotX="50.0%p"
android:pivotY="50.0%p"
android:toXScale="1.0"
android:toYScale="1.0" />
</set>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
android:zAdjustment="top">
<translate
android:duration="250"
android:fromXDelta="0.0%p"
android:interpolator="@anim/accelerate_cubic"
android:toXDelta="100.0%p" />
</set>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<decelerateInterpolator xmlns:android="http://schemas.android.com/apk/res/android"
android:factor="1.5" />
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
android:zAdjustment="top">
<translate
android:duration="250"
android:fromXDelta="100.0%p"
android:interpolator="@anim/decelerate_cubic"
android:toXDelta="0.0%p" />
</set>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
android:background="@android:color/black"
android:fillAfter="true"
android:fillBefore="false"
android:fillEnabled="true"
android:shareInterpolator="false"
android:zAdjustment="normal">
<alpha
android:duration="250"
android:fromAlpha="1.0"
android:interpolator="@anim/decelerate_cubic"
android:toAlpha="0.2" />
<scale
android:duration="250"
android:fromXScale="1.0"
android:fromYScale="1.0"
android:interpolator="@anim/decelerate_cubic"
android:pivotX="50.0%p"
android:pivotY="50.0%p"
android:toXScale="0.9"
android:toYScale="0.9" />
</set>
\ No newline at end of file
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:fillColor="#FF000000"
android:pathData="M12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM13,19h-2v-2h2v2zM15.07,11.25l-0.9,0.92C13.45,12.9 13,13.5 13,15h-2v-0.5c0,-1.1 0.45,-2.1 1.17,-2.83l1.24,-1.26c0.37,-0.36 0.59,-0.86 0.59,-1.41 0,-1.1 -0.9,-2 -2,-2s-2,0.9 -2,2L8,9c0,-2.21 1.79,-4 4,-4s4,1.79 4,4c0,0.88 -0.36,1.68 -0.93,2.25z"/>
</vector>
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:theme="@style/AuthenticationTheme">
android:theme="@style/AuthenticationTheme"
tools:context=".authentication.ui.AuthenticationActivity">
<FrameLayout
android:id="@+id/fragment_container"
......
<?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="match_parent"
android:theme="@style/AppTheme">
android:theme="@style/AppTheme"
tools:context=".chatroom.ui.ChatRoomActivity">
<include
android:id="@+id/layout_app_bar"
layout="@layout/app_bar" />
layout="@layout/app_bar_chat_room" />
<FrameLayout
android:id="@+id/fragment_container"
......
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:theme="@style/ChatListTheme">
android:theme="@style/ChatListTheme"
tools:context=".chatrooms.ui.MainActivity">
<FrameLayout
android:id="@+id/fragment_container"
......
<?xml version="1.0" encoding="utf-8"?>
<android.support.design.widget.AppBarLayout 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:background="@color/colorPrimary"
android:theme="@style/Theme.AppCompat.Light.NoActionBar">
<android.support.v7.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
app:layout_scrollFlags="scroll|enterAlways"
app:navigationIcon="?android:attr/homeAsUpIndicator"
app:popupTheme="@style/ThemeOverlay.AppCompat.Light"
app:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar">
<android.support.constraint.ConstraintLayout
android:id="@+id/toolbar_content_container"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal">
<!-- TODO implement -->
<com.facebook.drawee.view.SimpleDraweeView
android:id="@+id/image_room_avatar"
android:layout_width="30dp"
android:layout_height="30dp"
android:visibility="gone"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:roundAsCircle="true" />
<TextView
android:id="@+id/text_room_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="10dp"
android:ellipsize="end"
android:maxLines="1"
android:textColor="@color/white"
android:textSize="18sp"
android:textStyle="bold"
tools:text="Developers" />
</android.support.constraint.ConstraintLayout>
</android.support.v7.widget.Toolbar>
</android.support.design.widget.AppBarLayout>
\ No newline at end of file
<?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="match_parent"
android:orientation="vertical">
......@@ -18,4 +20,14 @@
android:layout_height="wrap_content"
android:layout_alignParentBottom="true" />
<com.wang.avi.AVLoadingIndicatorView
android:id="@+id/view_loading"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:visibility="gone"
app:indicatorColor="@color/black"
app:indicatorName="BallPulseIndicator"
tools:visibility="visible" />
</RelativeLayout>
\ No newline at end of file
......@@ -4,10 +4,10 @@
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="16dp"
android:layout_marginBottom="10dp"
android:layout_marginEnd="@dimen/screen_edge_left_and_right_margins"
android:layout_marginStart="@dimen/screen_edge_left_and_right_margins"
android:layout_marginTop="16dp">
android:layout_marginTop="10dp">
<com.facebook.drawee.view.SimpleDraweeView
android:id="@+id/image_user_avatar"
......@@ -17,6 +17,18 @@
app:layout_constraintTop_toTopOf="parent"
app:roundAsCircle="true" />
<!-- TODO define the correct bg color for this-->
<ImageView
android:id="@+id/image_unknown_user"
android:layout_width="40dp"
android:layout_height="40dp"
android:src="@drawable/ic_help_black_24dp"
android:tint="@color/colorAccent"
android:visibility="gone"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:ignore="contentDescription" />
<LinearLayout
android:id="@+id/top_container"
android:layout_width="0dp"
......@@ -54,11 +66,13 @@
app:layout_constraintTop_toBottomOf="@+id/top_container"
tools:text="This is a multiline chat message from Bertie that will take more than just one line of text. I have sure that everything is amazing!" />
<!-- TODO implement -->
<com.facebook.drawee.view.SimpleDraweeView
android:id="@+id/image_attachment"
android:layout_width="0dp"
android:layout_height="150dp"
android:layout_marginTop="5dp"
android:visibility="gone"
app:layout_constraintLeft_toLeftOf="@id/top_container"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toBottomOf="@+id/text_content" />
......
......@@ -35,5 +35,6 @@
<string name="msg_content_description_log_in_using_twitter">Fazer login através do Twitter</string>
<string name="msg_content_description_log_in_using_gitlab">Fazer login através do Gitlab</string>
<string name="msg_you">Você</string>
<string name="msg_unknown">Desconhecido</string>
</resources>
</resources>
\ No newline at end of file
......@@ -37,5 +37,6 @@
<string name="msg_content_description_log_in_using_twitter">Login using Twitter</string>
<string name="msg_content_description_log_in_using_gitlab">Login using Gitlab</string>
<string name="msg_you">You</string>
<string name="msg_unknown">Unknown</string>
</resources>
\ 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