Unverified Commit ac2b6927 authored by Lucio Maciel's avatar Lucio Maciel Committed by GitHub

Merge pull request #681 from filipedelimabrito/feature/show-chat-list-using-sdk

[NEW] Show list of chats using the SDK (pure online)
parents 3242e0c5 d1696701
...@@ -78,6 +78,8 @@ dependencies { ...@@ -78,6 +78,8 @@ dependencies {
implementation libraries.aVLoadingIndicatorView implementation libraries.aVLoadingIndicatorView
implementation libraries.textDrawable
testImplementation libraries.junit testImplementation libraries.junit
androidTestImplementation (libraries.expressoCore , { androidTestImplementation (libraries.expressoCore , {
exclude group: 'com.android.support', module: 'support-annotations' exclude group: 'com.android.support', module: 'support-annotations'
......
...@@ -30,7 +30,7 @@ ...@@ -30,7 +30,7 @@
</activity> </activity>
<activity <activity
android:name=".app.MainActivity" android:name=".chatrooms.ui.MainActivity"
android:theme="@style/ChatListTheme" /> android:theme="@style/ChatListTheme" />
<activity <activity
......
import android.content.Context import android.content.Context
import chat.rocket.android.R import chat.rocket.android.R
import org.threeten.bp.LocalDate import org.threeten.bp.*
import org.threeten.bp.LocalDateTime
import org.threeten.bp.Period
import org.threeten.bp.format.DateTimeFormatter import org.threeten.bp.format.DateTimeFormatter
import org.threeten.bp.format.FormatStyle import org.threeten.bp.format.FormatStyle
import org.threeten.bp.format.TextStyle import org.threeten.bp.format.TextStyle
...@@ -14,20 +12,20 @@ object DateTimeHelper { ...@@ -14,20 +12,20 @@ object DateTimeHelper {
private val lastWeek = today.minusWeeks(1) private val lastWeek = today.minusWeeks(1)
/** /**
* Returns a date from a LocalDateTime or the textual representation if the LocalDateTime has a max period of a week from the current date. * Returns a date from a [LocalDateTime] or the textual representation if the [LocalDateTime] has a max period of a week from the current date.
* *
* @param localDateTime The LocalDateTime. * @param localDateTime The [LocalDateTime].
* @param context The context. * @param context The context.
* @return The date or the textual representation from a LocalDateTime. * @return The date or the textual representation from a [LocalDateTime].
*/ */
fun getDate(localDateTime: LocalDateTime, context: Context): String { fun getDate(localDateTime: LocalDateTime, context: Context): String {
val localDate = localDateTime.toLocalDate() val localDate = localDateTime.toLocalDate()
return when (localDate) { return when (localDate) {
today -> localDateTime.toLocalTime().toString() today -> formatLocalTime(localDateTime.toLocalTime())
yesterday -> context.getString(R.string.msg_yesterday) yesterday -> context.getString(R.string.msg_yesterday)
else -> { else -> {
if (Period.between(lastWeek, localDate).days <= 0) { if (Period.between(lastWeek, localDate).days <= 0) {
formatDate(localDate) formatLocalDate(localDate)
} else { } else {
localDate.dayOfWeek.getDisplayName(TextStyle.FULL, Locale.getDefault()) localDate.dayOfWeek.getDisplayName(TextStyle.FULL, Locale.getDefault())
} }
...@@ -36,18 +34,33 @@ object DateTimeHelper { ...@@ -36,18 +34,33 @@ object DateTimeHelper {
} }
/** /**
* Returns a time from a LocalDateTime. * Returns a time from a [LocalDateTime].
* *
* @param localDateTime The LocalDateTime. * @param localDateTime The [LocalDateTime].
* @return The time from a LocalDateTime. * @return The time from a [LocalDateTime].
*/ */
fun getTime(localDateTime: LocalDateTime): String { fun getTime(localDateTime: LocalDateTime): String {
val formatter = DateTimeFormatter.ofLocalizedTime(FormatStyle.SHORT) val formatter = DateTimeFormatter.ofLocalizedTime(FormatStyle.SHORT)
return localDateTime.toLocalTime().format(formatter).toString() return localDateTime.toLocalTime().format(formatter).toString()
} }
private fun formatDate(localDate: LocalDate): String { /**
* Returns a [LocalDateTime] from a [Long].
*
* @param long The [Long]
* @return The [LocalDateTime] from a [Long].
*/
fun getLocalDateTime(long: Long): LocalDateTime {
return LocalDateTime.ofInstant(Instant.ofEpochMilli(long), ZoneId.systemDefault())
}
private fun formatLocalDate(localDate: LocalDate): String {
val formatter = DateTimeFormatter.ofLocalizedDate(FormatStyle.SHORT) val formatter = DateTimeFormatter.ofLocalizedDate(FormatStyle.SHORT)
return localDate.format(formatter).toString() return localDate.format(formatter).toString()
} }
private fun formatLocalTime(localTime: LocalTime): String {
val formatter = DateTimeFormatter.ofLocalizedTime(FormatStyle.SHORT)
return localTime.format(formatter).toString()
}
} }
\ No newline at end of file
import android.content.Context import android.content.Context
import android.graphics.Typeface
import android.graphics.drawable.Drawable import android.graphics.drawable.Drawable
import android.support.v4.content.ContextCompat import android.support.v4.content.ContextCompat
import android.support.v4.graphics.drawable.DrawableCompat import android.support.v4.graphics.drawable.DrawableCompat
import android.widget.EditText import android.widget.EditText
import android.widget.TextView import android.widget.TextView
import chat.rocket.android.R import chat.rocket.android.R
import chat.rocket.android.helper.TextHelper
import com.amulyakhare.textdrawable.TextDrawable
object DrawableHelper { object DrawableHelper {
private val AVATAR_BACKGROUND_HEXADECIMAL_COLORS = intArrayOf(
0xFFF44336.toInt(), 0xFFE91E63.toInt(), 0xFF9C27B0.toInt(), 0xFF673AB7.toInt(), 0xFF3F51B5.toInt(),
0xFF2196F3.toInt(), 0xFF03A9F4.toInt(), 0xFF00BCD4.toInt(), 0xFF009688.toInt(), 0xFF4CAF50.toInt(),
0xFF8BC34A.toInt(), 0xFFCDDC39.toInt(), 0xFFFFC107.toInt(), 0xFFFF9800.toInt(), 0xFFFF5722.toInt(),
0xFF795548.toInt(), 0xFF9E9E9E.toInt(), 0xFF607D8B.toInt())
/** /**
* Returns a Drawable from its ID. * Returns a Drawable from its ID.
* *
...@@ -37,7 +44,7 @@ object DrawableHelper { ...@@ -37,7 +44,7 @@ object DrawableHelper {
* @see wrapDrawables * @see wrapDrawables
* @see tintDrawable * @see tintDrawable
*/ */
fun wrapDrawable(drawable: Drawable) = DrawableCompat.wrap(drawable) fun wrapDrawable(drawable: Drawable): Drawable = DrawableCompat.wrap(drawable)
/** /**
* Tints an array of Drawable. * Tints an array of Drawable.
...@@ -116,4 +123,29 @@ object DrawableHelper { ...@@ -116,4 +123,29 @@ object DrawableHelper {
} }
return userStatusDrawable return userStatusDrawable
} }
/**
* Returns a drawable with the first character from a string.
*
* @param string The string to get its first character and to get the avatar background color.
* @return A drawable with the string first character.
*/
fun getTextDrawable(string: String): Drawable {
return TextDrawable.builder()
.beginConfig()
.useFont(Typeface.SANS_SERIF)
.endConfig()
.buildRound(TextHelper.getFirstCharacter(string), getAvatarBackgroundColor(string))
}
/**
* Returns a background color to be rendered on the avatar.
*
* @param string Gets the background color based on the provided string.
* @return A hexadecimal color.
* @see (Rocket.Chat/server/startup/avatar.js)
*/
private fun getAvatarBackgroundColor(string: String): Int {
return AVATAR_BACKGROUND_HEXADECIMAL_COLORS[string.length % AVATAR_BACKGROUND_HEXADECIMAL_COLORS.size]
}
} }
\ No newline at end of file
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.chatlist.ChatListFragment
import chat.rocket.android.util.addFragment
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
addFragment("ChatListFragment", R.id.fragment_container) {
ChatListFragment()
}
}
}
\ No newline at end of file
package chat.rocket.android.app.chatlist
import chat.rocket.android.app.User
import org.threeten.bp.LocalDateTime
data class Chat(val user: User,
val name: String,
val type: String,
// Todo replace to Message type instead of String
val lastMessage: String,
val lastMessageDateTime: LocalDateTime,
val totalUnreadMessages: Int)
\ No newline at end of file
package chat.rocket.android.app.chatlist
import android.os.Bundle
import android.support.v4.app.Fragment
import android.support.v7.widget.DividerItemDecoration
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_list.*
import org.threeten.bp.LocalDateTime
class ChatListFragment : Fragment() {
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? = inflater.inflate(R.layout.fragment_chat_list, container, false)
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
showChatList(createDumpData())
}
// This is just a sample showing 8 chat rooms (aka user subscription). We need to get it rid in a real word. REMARK: remove this comment and this method.
private fun createDumpData(): List<Chat> {
val filipe = User("1", "Filipe Brito", "filipe.brito", "online", "https://open.rocket.chat/avatar/filipe.brito")
val lucio = User("2", "Lucio Maciel", "Lucio Maciel", "busy", "https://open.rocket.chat/avatar/lucio.maciel")
val leonardo = User("3", "Leonardo Aramaki", "leonardo.aramaki", "busy", "https://open.rocket.chat/avatar/leonardo.aramaki")
val sing = User("4", "Filipe Brito", "filipe.brito", "online", "https://open.rocket.chat/avatar/filipe.brito")
val hetal = User("5", "Filipe Brito", "filipe.brito", "online", "https://open.rocket.chat/avatar/filipe.brito")
val matheus = User("6", "Filipe Brito", "filipe.brito", "online", "https://open.rocket.chat/avatar/filipe.brito")
val bestrun = User("7", "Filipe Brito", "filipe.brito", "online", "https://open.rocket.chat/avatar/filipe.brito")
val djc = User("8", "3djc", "3djc", "online", "https://open.rocket.chat/avatar/filipe.brito")
val dumpChat1 = Chat(leonardo,
"Leonardo Aramaki",
"d",
"Type something",
LocalDateTime.of(2017, 11, 21,1, 3),
1)
val dumpChat2 = Chat(filipe,
"Filipe Brito",
"d",
"Type something...Type something...Type something",
LocalDateTime.of(2017, 11, 22,1, 3),
150)
val dumpChat3 = Chat(lucio,
"Lucio Maciel",
"d",
"Type something",
LocalDateTime.of(2017, 11, 17,1, 3),
0)
val dumpChat4 = Chat(sing,
"mobile-internal",
"p",
"@aaron.ogle @rafael.kellermann same problem over here. Although all the servers show up on the selection.",
LocalDateTime.of(2017, 11, 15,1, 3),
0)
val dumpChat5 = Chat(hetal,
"general",
"c",
"Has joined the channel.",
LocalDateTime.of(2017, 11, 13,1, 3),
0)
val dumpChat6 = Chat(matheus,
"androidnativeapp",
"c",
"Yes @sttyru, but you'll need to implement from the ground up following the docs at docs.rocket.chat where you can see the REST (HTTP) and Real-Time (WebSockets) calls.",
LocalDateTime.of(2017, 11, 14,1, 3),
0)
val dumpChat7 = Chat(bestrun,
"androidnativeapp-2",
"c",
"Just downloaded .zip and imported into Android Studio then build the project.",
LocalDateTime.of(2017, 11, 4,12, 47),
0)
val dumpChat8 = Chat(djc,
"iosnativeapp",
"c",
"Ok, got confused by the blog announcement that shows a screenshot with github oAuth ! Sorry !",
LocalDateTime.of(2017, 11, 4,12, 43),
0)
// creates a list of chat sorted by lastMessageDateTime attribute.
return listOf(dumpChat1, dumpChat2, dumpChat3, dumpChat4, dumpChat5, dumpChat6, dumpChat7, dumpChat8).sortedByDescending { chat -> chat.lastMessageDateTime }
}
// REMARK: The presenter should call this method. The presenter also need to sort the chat list by latest message (compared by its date).
private fun showChatList(dataSet: List<Chat>) {
activity?.apply {
recycler_view.layoutManager = LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false)
recycler_view.addItemDecoration(DividerItemDecoration(this, DividerItemDecoration.VERTICAL))
recycler_view.adapter = ChatListAdapter(dataSet.toMutableList(), this)
}
}
}
...@@ -8,7 +8,7 @@ import android.view.View ...@@ -8,7 +8,7 @@ import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import chat.rocket.android.R import chat.rocket.android.R
import chat.rocket.android.app.User import chat.rocket.android.app.User
import kotlinx.android.synthetic.main.fragment_chat_list.* import kotlinx.android.synthetic.main.fragment_chat_rooms.*
import org.threeten.bp.LocalDateTime import org.threeten.bp.LocalDateTime
class MessageFragment : Fragment() { class MessageFragment : Fragment() {
......
...@@ -3,7 +3,7 @@ package chat.rocket.android.authentication.presentation ...@@ -3,7 +3,7 @@ package chat.rocket.android.authentication.presentation
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import chat.rocket.android.R import chat.rocket.android.R
import chat.rocket.android.app.MainActivity import chat.rocket.android.chatrooms.ui.MainActivity
import chat.rocket.android.authentication.login.ui.LoginFragment import chat.rocket.android.authentication.login.ui.LoginFragment
import chat.rocket.android.authentication.signup.ui.SignupFragment import chat.rocket.android.authentication.signup.ui.SignupFragment
import chat.rocket.android.authentication.twofactor.ui.TwoFAFragment import chat.rocket.android.authentication.twofactor.ui.TwoFAFragment
...@@ -50,10 +50,10 @@ class AuthenticationNavigator(internal val activity: AuthenticationActivity, int ...@@ -50,10 +50,10 @@ class AuthenticationNavigator(internal val activity: AuthenticationActivity, int
} }
fun toChatList() { fun toChatList() {
val chatRoom = Intent(activity, MainActivity::class.java).apply { val chatList = Intent(activity, MainActivity::class.java).apply {
//TODO any parameter to pass //TODO any parameter to pass
} }
activity.startActivity(chatRoom) activity.startActivity(chatList)
activity.finish() activity.finish()
} }
} }
...@@ -12,6 +12,7 @@ import kotlinx.coroutines.experimental.Job ...@@ -12,6 +12,7 @@ import kotlinx.coroutines.experimental.Job
@Module @Module
@PerFragment @PerFragment
class SignupFragmentModule { class SignupFragmentModule {
@Provides @Provides
fun signupView(frag: SignupFragment): SignupView { fun signupView(frag: SignupFragment): SignupView {
return frag return frag
...@@ -26,4 +27,4 @@ class SignupFragmentModule { ...@@ -26,4 +27,4 @@ class SignupFragmentModule {
fun provideCancelStrategy(owner: LifecycleOwner, jobs: Job): CancelStrategy { fun provideCancelStrategy(owner: LifecycleOwner, jobs: Job): CancelStrategy {
return CancelStrategy(owner, jobs) return CancelStrategy(owner, jobs)
} }
} }
\ No newline at end of file
...@@ -4,8 +4,9 @@ import chat.rocket.android.authentication.signup.ui.SignupFragment ...@@ -4,8 +4,9 @@ import chat.rocket.android.authentication.signup.ui.SignupFragment
import dagger.Module import dagger.Module
import dagger.android.ContributesAndroidInjector import dagger.android.ContributesAndroidInjector
@Module abstract class SignupFragmentProvider { @Module
abstract class SignupFragmentProvider {
@ContributesAndroidInjector(modules = arrayOf(SignupFragmentModule::class)) @ContributesAndroidInjector(modules = [SignupFragmentModule::class])
abstract fun provideSignupFragment(): SignupFragment abstract fun provideSignupFragment(): SignupFragment
} }
\ No newline at end of file
...@@ -153,4 +153,4 @@ class SignupFragment : Fragment(), SignupView { ...@@ -153,4 +153,4 @@ class SignupFragment : Fragment(), SignupView {
text_password.isEnabled = value text_password.isEnabled = value
text_email.isEnabled = value text_email.isEnabled = value
} }
} }
\ No newline at end of file
...@@ -12,6 +12,7 @@ import kotlinx.coroutines.experimental.Job ...@@ -12,6 +12,7 @@ import kotlinx.coroutines.experimental.Job
@Module @Module
@PerFragment @PerFragment
class TwoFAFragmentModule { class TwoFAFragmentModule {
@Provides @Provides
fun loginView(frag: TwoFAFragment): TwoFAView { fun loginView(frag: TwoFAFragment): TwoFAView {
return frag return frag
......
...@@ -6,6 +6,6 @@ import dagger.android.ContributesAndroidInjector ...@@ -6,6 +6,6 @@ import dagger.android.ContributesAndroidInjector
@Module abstract class TwoFAFragmentProvider { @Module abstract class TwoFAFragmentProvider {
@ContributesAndroidInjector(modules = arrayOf(TwoFAFragmentModule::class)) @ContributesAndroidInjector(modules = [TwoFAFragmentModule::class])
abstract fun provideTwoFAFragment(): TwoFAFragment abstract fun provideTwoFAFragment(): TwoFAFragment
} }
package chat.rocket.android.chatrooms.di
import android.arch.lifecycle.LifecycleOwner
import chat.rocket.android.chatrooms.presentation.ChatRoomsView
import chat.rocket.android.chatrooms.ui.ChatRoomsFragment
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 ChatRoomsFragmentModule {
@Provides
fun chatRoomsView(frag: ChatRoomsFragment): ChatRoomsView {
return frag
}
@Provides
fun provideLifecycleOwner(frag: ChatRoomsFragment): LifecycleOwner {
return frag
}
@Provides
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 chat.rocket.android.chatrooms.ui.ChatRoomsFragment
import dagger.Module
import dagger.android.ContributesAndroidInjector
@Module
abstract class ChatRoomsFragmentProvider {
@ContributesAndroidInjector(modules = [ChatRoomsFragmentModule::class])
abstract fun provideChatRoomsFragment(): ChatRoomsFragment
}
\ No newline at end of file
package chat.rocket.android.chatrooms.presentation
import chat.rocket.android.core.lifecycle.CancelStrategy
import chat.rocket.android.util.launchUI
import chat.rocket.core.RocketChatClient
import chat.rocket.core.internal.rest.chatRooms
import chat.rocket.core.model.ChatRoom
import javax.inject.Inject
class ChatRoomsPresenter @Inject constructor(private val view: ChatRoomsView, private val strategy: CancelStrategy) {
@Inject lateinit var client: RocketChatClient
fun chatRooms() {
launchUI(strategy) {
view.showLoading()
val chatRooms = client.chatRooms().update
val openChatRooms = getOpenChatRooms(chatRooms)
val sortedOpenChatRooms = sortChatRooms(openChatRooms)
view.showChatRooms(sortedOpenChatRooms.toMutableList())
view.hideLoading()
}
}
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
}
}
}
\ No newline at end of file
package chat.rocket.android.chatrooms.presentation
import chat.rocket.android.core.behaviours.LoadingView
import chat.rocket.android.core.behaviours.MessageView
import chat.rocket.core.model.ChatRoom
interface ChatRoomsView : LoadingView, MessageView {
/**
* Shows the chat rooms.
*
* @param dataSet The data set to show.
*/
fun showChatRooms(dataSet: MutableList<ChatRoom>)
}
\ No newline at end of file
package chat.rocket.android.app.chatlist package chat.rocket.android.chatrooms.ui
import DateTimeHelper import DateTimeHelper
import DrawableHelper import DrawableHelper
import android.content.Context import android.content.Context
import android.support.v7.widget.RecyclerView import android.support.v7.widget.RecyclerView
import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.widget.ImageView
import android.widget.TextView import android.widget.TextView
import chat.rocket.android.R 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.model.BaseRoom.RoomType
import chat.rocket.core.model.ChatRoom
import com.facebook.drawee.view.SimpleDraweeView import com.facebook.drawee.view.SimpleDraweeView
import kotlinx.android.synthetic.main.item_chat.view.* import kotlinx.android.synthetic.main.item_chat.view.*
class ChatListAdapter(private var dataSet: MutableList<Chat>, private val context: Context) : RecyclerView.Adapter<ChatListAdapter.ViewHolder>() { class ChatRoomsAdapter(private var dataSet: MutableList<ChatRoom>, private val context: Context) : RecyclerView.Adapter<ChatRoomsAdapter.ViewHolder>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder = ViewHolder(parent.inflate(R.layout.item_chat))
val view = LayoutInflater.from(parent.context).inflate(R.layout.item_chat, parent, false)
return ViewHolder(view)
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) { override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val chat = dataSet[position] val chatRoom = dataSet[position]
val chatRoomName = chatRoom.name
holder.userAvatar.setImageURI(chat.user.avatarUri) holder.chatName.textContent = chatRoomName
holder.chatName.text = chat.name
holder.lastMessage.text = chat.lastMessage
holder.lastMessageDateTime.text = DateTimeHelper.getDate(chat.lastMessageDateTime, context)
when (chat.type) { if (chatRoom.type == RoomType.ONE_TO_ONE) {
"p" -> DrawableHelper.compoundDrawable(holder.chatName, DrawableHelper.getDrawableFromId(R.drawable.ic_lock_outline_black, context)) // TODO Check the best way to get the current server url.
"c" -> DrawableHelper.compoundDrawable(holder.chatName, DrawableHelper.getDrawableFromId(R.drawable.ic_hashtag_black, context)) val canonicalUrl = chatRoom.client.restUrl.toString()
"d" -> DrawableHelper.compoundDrawable(holder.chatName, DrawableHelper.getUserStatusDrawable(chat.user.status, context)) holder.userAvatar.setImageURI(UrlHelper.getAvatarUrl(canonicalUrl, chatRoomName))
holder.userAvatar.setVisibility(true)
} else {
holder.roomAvatar.setImageDrawable(DrawableHelper.getTextDrawable(chatRoomName))
holder.roomAvatar.setVisibility(true)
} }
val totalUnreadMessage = chat.totalUnreadMessages val totalUnreadMessage = chatRoom.unread
when { when {
totalUnreadMessage in 1..99 -> { totalUnreadMessage in 1..99 -> {
holder.unreadMessage.text = totalUnreadMessage.toString() holder.unreadMessage.textContent = totalUnreadMessage.toString()
holder.unreadMessage.visibility = View.VISIBLE holder.unreadMessage.setVisibility(true)
} }
totalUnreadMessage > 99 -> { totalUnreadMessage > 99 -> {
holder.unreadMessage.text = context.getString(R.string.msg_more_than_ninety_nine_unread_messages) holder.unreadMessage.textContent = context.getString(R.string.msg_more_than_ninety_nine_unread_messages)
holder.unreadMessage.visibility = View.VISIBLE holder.unreadMessage.setVisibility(true)
} }
} }
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
}
// TODO Change to MySelf
// chatRoom.user?.username -> {
// holder.lastMessage.textContent = context.getString(R.string.msg_you) + ": $message"
// }
else -> {
holder.lastMessage.textContent = "@$senderUsername: $message"
}
}
val localDateTime = DateTimeHelper.getLocalDateTime(lastMessage.timestamp)
holder.lastMessageDateTime.textContent = DateTimeHelper.getDate(localDateTime, context)
}
} }
override fun getItemCount(): Int = dataSet.size override fun getItemCount(): Int = dataSet.size
override fun getItemViewType(position: Int): Int = position
class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) { class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
val userAvatar: SimpleDraweeView = itemView.image_user_avatar val userAvatar: SimpleDraweeView = itemView.image_user_avatar
val roomAvatar: ImageView = itemView.image_room_avatar
val chatName: TextView = itemView.text_chat_name val chatName: TextView = itemView.text_chat_name
val lastMessage: TextView = itemView.text_last_message val lastMessage: TextView = itemView.text_last_message
val lastMessageDateTime: TextView = itemView.text_last_message_date_time val lastMessageDateTime: TextView = itemView.text_last_message_date_time
......
package chat.rocket.android.chatrooms.ui
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 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.widget.DividerItemDecoration
import chat.rocket.core.model.ChatRoom
import dagger.android.support.AndroidSupportInjection
import kotlinx.android.synthetic.main.fragment_chat_rooms.*
import javax.inject.Inject
class ChatRoomsFragment : Fragment(), ChatRoomsView {
@Inject lateinit var presenter: ChatRoomsPresenter
companion object {
fun newInstance() = ChatRoomsFragment()
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
AndroidSupportInjection.inject(this)
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? = inflater.inflate(R.layout.fragment_chat_rooms, container, false)
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
presenter.chatRooms()
}
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)
}
}
override fun showLoading() = view_loading.show()
override fun hideLoading() = view_loading.hide()
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
package chat.rocket.android.chatrooms.ui
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 dagger.android.AndroidInjection
import dagger.android.AndroidInjector
import dagger.android.DispatchingAndroidInjector
import dagger.android.support.HasSupportFragmentInjector
import javax.inject.Inject
class MainActivity : AppCompatActivity(), HasSupportFragmentInjector {
@Inject lateinit var fragmentDispatchingAndroidInjector: DispatchingAndroidInjector<Fragment>
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
AndroidInjection.inject(this)
addFragment("ChatRoomsFragment", R.id.fragment_container) {
ChatRoomsFragment.newInstance()
}
}
override fun supportFragmentInjector(): AndroidInjector<Fragment> {
return fragmentDispatchingAndroidInjector
}
}
\ No newline at end of file
package chat.rocket.android.dagger.module package chat.rocket.android.dagger.module
import chat.rocket.android.app.MainActivity import chat.rocket.android.authentication.di.AuthenticationModule
import chat.rocket.android.authentication.di.*
import chat.rocket.android.authentication.login.di.LoginFragmentProvider import chat.rocket.android.authentication.login.di.LoginFragmentProvider
import chat.rocket.android.authentication.server.di.ServerFragmentProvider import chat.rocket.android.authentication.server.di.ServerFragmentProvider
import chat.rocket.android.authentication.signup.di.SignupFragmentProvider import chat.rocket.android.authentication.signup.di.SignupFragmentProvider
import chat.rocket.android.authentication.twofactor.di.TwoFAFragmentProvider import chat.rocket.android.authentication.twofactor.di.TwoFAFragmentProvider
import chat.rocket.android.authentication.ui.AuthenticationActivity import chat.rocket.android.authentication.ui.AuthenticationActivity
import chat.rocket.android.chatrooms.di.ChatRoomsFragmentProvider
import chat.rocket.android.chatrooms.ui.MainActivity
import chat.rocket.android.dagger.scope.PerActivity import chat.rocket.android.dagger.scope.PerActivity
import dagger.Module import dagger.Module
import dagger.android.ContributesAndroidInjector import dagger.android.ContributesAndroidInjector
@Module abstract class ActivityBuilder { @Module
abstract class ActivityBuilder {
@PerActivity @PerActivity
@ContributesAndroidInjector(modules = [AuthenticationModule::class, @ContributesAndroidInjector(modules = [AuthenticationModule::class,
LoginFragmentProvider::class,
ServerFragmentProvider::class, ServerFragmentProvider::class,
LoginFragmentProvider::class,
SignupFragmentProvider::class, SignupFragmentProvider::class,
TwoFAFragmentProvider::class TwoFAFragmentProvider::class
]) ])
abstract fun bindAuthenticationActivity(): AuthenticationActivity abstract fun bindAuthenticationActivity(): AuthenticationActivity
@ContributesAndroidInjector abstract fun bindMainActivity(): MainActivity @ContributesAndroidInjector(modules = [ChatRoomsFragmentProvider::class])
} abstract fun bindMainActivity(): MainActivity
}
\ No newline at end of file
...@@ -5,6 +5,7 @@ import android.text.Spanned ...@@ -5,6 +5,7 @@ import android.text.Spanned
import android.text.method.LinkMovementMethod import android.text.method.LinkMovementMethod
import android.text.style.ClickableSpan import android.text.style.ClickableSpan
import android.widget.TextView import android.widget.TextView
import chat.rocket.android.util.ifEmpty
object TextHelper { object TextHelper {
...@@ -27,4 +28,15 @@ object TextHelper { ...@@ -27,4 +28,15 @@ object TextHelper {
textView.movementMethod = LinkMovementMethod.getInstance() textView.movementMethod = LinkMovementMethod.getInstance()
textView.setText(spannableString, TextView.BufferType.SPANNABLE) textView.setText(spannableString, TextView.BufferType.SPANNABLE)
} }
/**
* Returns the first character from a string.
*
* @param string The string to get its first character.
* @return The first character from a string.
*/
fun getFirstCharacter(string: String): String {
string.ifEmpty("?")
return string.substring(0, 1).toUpperCase()
}
} }
\ No newline at end of file
package chat.rocket.android.helper
object UrlHelper {
/**
* Returns the avatar URL.
*
* @param serverUrl The serverUrl.
* @param chatRoomName The chat room name.
* @return The avatar URL.
*/
fun getAvatarUrl(serverUrl: String, chatRoomName: String): String = serverUrl + "avatar/" + chatRoomName
}
\ No newline at end of file
...@@ -6,7 +6,9 @@ import chat.rocket.android.R ...@@ -6,7 +6,9 @@ import chat.rocket.android.R
fun AppCompatActivity.addFragment(tag: String, layoutId: Int, newInstance: () -> Fragment) { fun AppCompatActivity.addFragment(tag: String, layoutId: Int, newInstance: () -> Fragment) {
val fragment = supportFragmentManager.findFragmentByTag(tag) ?: newInstance() val fragment = supportFragmentManager.findFragmentByTag(tag) ?: newInstance()
supportFragmentManager.beginTransaction().replace(layoutId, fragment, tag).commit() supportFragmentManager.beginTransaction()
.replace(layoutId, fragment, tag)
.commit()
} }
fun AppCompatActivity.addFragmentBackStack(tag: String, layoutId: Int, newInstance: () -> Fragment) { fun AppCompatActivity.addFragmentBackStack(tag: String, layoutId: Int, newInstance: () -> Fragment) {
......
...@@ -14,10 +14,10 @@ fun String.ifEmpty(value: String): String { ...@@ -14,10 +14,10 @@ fun String.ifEmpty(value: String): String {
} }
fun View.setVisibility(value: Boolean) { fun View.setVisibility(value: Boolean) {
if (value) { visibility = if (value) {
this.visibility = View.VISIBLE View.VISIBLE
} else { } else {
this.visibility = View.GONE View.GONE
} }
} }
...@@ -34,5 +34,5 @@ var TextView.textContent: String ...@@ -34,5 +34,5 @@ var TextView.textContent: String
var TextView.hintContent: String var TextView.hintContent: String
get() = hint.toString() get() = hint.toString()
set(value) { set(value) {
text = value hint = value
} }
package chat.rocket.android.widget
import android.content.Context
import android.graphics.Canvas
import android.graphics.drawable.Drawable
import android.support.annotation.DrawableRes
import android.support.v4.content.ContextCompat
import android.support.v7.widget.RecyclerView
/**
* Adds a default or custom divider to specific item views from the adapter's data set.
* @see RecyclerView.ItemDecoration
*/
class DividerItemDecoration() : RecyclerView.ItemDecoration() {
private lateinit var divider: Drawable
private var boundStart = 0
private var boundRight = 0
// Default divider will be used.
constructor(context: Context) : this() {
val attrs = intArrayOf(android.R.attr.listDivider)
val styledAttributes = context.obtainStyledAttributes(attrs)
divider = styledAttributes.getDrawable(0)
styledAttributes.recycle()
}
// Default divider with custom boundaries (start and right) will be used.
constructor(context: Context, boundStart: Int, boundRight: Int) : this() {
val attrs = intArrayOf(android.R.attr.listDivider)
val styledAttributes = context.obtainStyledAttributes(attrs)
divider = styledAttributes.getDrawable(0)
styledAttributes.recycle()
this.boundStart = boundStart
this.boundRight = boundRight
}
// Custom divider will be used.
constructor(context: Context, @DrawableRes drawableResId: Int) : this() {
val customDrawable = ContextCompat.getDrawable(context, drawableResId)
if (customDrawable != null) {
divider = customDrawable
}
}
override fun onDraw(c: Canvas, parent: RecyclerView, state: RecyclerView.State) {
val left = parent.paddingLeft + boundStart
val right = (parent.width - parent.paddingRight) - boundRight
val childCount = parent.childCount
for (i in 0 until childCount) {
val child = parent.getChildAt(i)
val params = child.layoutParams as RecyclerView.LayoutParams
val top = child.bottom + params.bottomMargin
val bottom = top + divider.intrinsicHeight
divider.setBounds(left, top, right, bottom)
divider.draw(c)
}
}
}
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent"> android:layout_height="match_parent"
tools:context=".chatrooms.ui.ChatRoomsFragment">
<android.support.v7.widget.RecyclerView <android.support.v7.widget.RecyclerView
android:id="@+id/recycler_view" android:id="@+id/recycler_view"
...@@ -24,4 +26,14 @@ ...@@ -24,4 +26,14 @@
app:floatingSearch_searchBarMarginRight="4dp" app:floatingSearch_searchBarMarginRight="4dp"
app:floatingSearch_searchBarMarginTop="28dp" /> app:floatingSearch_searchBarMarginTop="28dp" />
<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> </RelativeLayout>
\ No newline at end of file
...@@ -4,20 +4,34 @@ ...@@ -4,20 +4,34 @@
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginBottom="16dp" android:layout_marginBottom="12dp"
android:layout_marginEnd="@dimen/screen_edge_left_and_right_margins" android:layout_marginEnd="@dimen/screen_edge_left_and_right_margins"
android:layout_marginStart="@dimen/screen_edge_left_and_right_margins" android:layout_marginStart="@dimen/screen_edge_left_and_right_margins"
android:layout_marginTop="16dp"> android:layout_marginTop="12dp">
<com.facebook.drawee.view.SimpleDraweeView <android.support.constraint.ConstraintLayout
android:id="@+id/image_user_avatar" android:id="@+id/avatar_container"
android:layout_width="40dp" android:layout_width="wrap_content"
android:layout_height="40dp" android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toLeftOf="@+id/middle_container" app:layout_constraintRight_toLeftOf="@+id/middle_container"
app:layout_constraintTop_toTopOf="parent" app:layout_constraintTop_toTopOf="parent">
app:roundAsCircle="true" />
<com.facebook.drawee.view.SimpleDraweeView
android:id="@+id/image_user_avatar"
android:layout_width="40dp"
android:layout_height="40dp"
android:visibility="gone"
app:roundAsCircle="true" />
<ImageView
android:id="@+id/image_room_avatar"
android:layout_width="40dp"
android:layout_height="40dp"
android:visibility="gone"
tools:ignore="contentDescription" />
</android.support.constraint.ConstraintLayout>
<android.support.constraint.ConstraintLayout <android.support.constraint.ConstraintLayout
android:id="@+id/middle_container" android:id="@+id/middle_container"
...@@ -25,7 +39,7 @@ ...@@ -25,7 +39,7 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginEnd="16dp" android:layout_marginEnd="16dp"
android:layout_marginStart="16dp" android:layout_marginStart="16dp"
app:layout_constraintLeft_toRightOf="@+id/image_user_avatar" app:layout_constraintLeft_toRightOf="@+id/avatar_container"
app:layout_constraintRight_toLeftOf="@+id/right_container"> app:layout_constraintRight_toLeftOf="@+id/right_container">
<TextView <TextView
...@@ -36,17 +50,16 @@ ...@@ -36,17 +50,16 @@
android:drawablePadding="5dp" android:drawablePadding="5dp"
android:ellipsize="end" android:ellipsize="end"
android:maxLines="1" android:maxLines="1"
tools:text="Ronald Perkins" /> tools:text="General" />
<TextView <TextView
android:id="@+id/text_last_message" android:id="@+id/text_last_message"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:ellipsize="end" android:ellipsize="end"
android:maxLines="1" android:maxLines="2"
app:layout_constraintTop_toBottomOf="@+id/text_chat_name" app:layout_constraintTop_toBottomOf="@+id/text_chat_name"
tools:text="Type something" /> tools:text="You: Type something" />
</android.support.constraint.ConstraintLayout> </android.support.constraint.ConstraintLayout>
<android.support.constraint.ConstraintLayout <android.support.constraint.ConstraintLayout
......
...@@ -34,6 +34,6 @@ ...@@ -34,6 +34,6 @@
<string name="msg_content_description_log_in_using_meteor">Fazer login através do Meteor</string> <string name="msg_content_description_log_in_using_meteor">Fazer login através do Meteor</string>
<string name="msg_content_description_log_in_using_twitter">Fazer login através do Twitter</string> <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_content_description_log_in_using_gitlab">Fazer login através do Gitlab</string>
<string name="msg_content_description_chat_icon">Ícone do chat</string> <string name="msg_you">Você</string>
</resources> </resources>
...@@ -14,4 +14,6 @@ ...@@ -14,4 +14,6 @@
<color name="colorDividerMessageComposer">#D8D8D8</color> <color name="colorDividerMessageComposer">#D8D8D8</color>
<color name="white">#FFFFFFFF</color> <color name="white">#FFFFFFFF</color>
<color name="black">#FF000000</color>
</resources> </resources>
\ No newline at end of file
...@@ -36,6 +36,6 @@ ...@@ -36,6 +36,6 @@
<string name="msg_content_description_log_in_using_meteor">Login using Meteor</string> <string name="msg_content_description_log_in_using_meteor">Login using Meteor</string>
<string name="msg_content_description_log_in_using_twitter">Login using Twitter</string> <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_content_description_log_in_using_gitlab">Login using Gitlab</string>
<string name="msg_content_description_chat_icon">Chat icon</string> <string name="msg_you">You</string>
</resources> </resources>
\ No newline at end of file
...@@ -8,7 +8,7 @@ buildscript { ...@@ -8,7 +8,7 @@ buildscript {
} }
dependencies { dependencies {
classpath 'com.android.tools.build:gradle:3.0.1' classpath "com.android.tools.build:gradle:3.0.1"
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:${versions.kotlin}" classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:${versions.kotlin}"
classpath "org.jetbrains.dokka:dokka-gradle-plugin:${versions.dokka}" classpath "org.jetbrains.dokka:dokka-gradle-plugin:${versions.dokka}"
...@@ -23,6 +23,7 @@ allprojects { ...@@ -23,6 +23,7 @@ allprojects {
jcenter() jcenter()
maven { url "https://oss.sonatype.org/content/repositories/snapshots/" } maven { url "https://oss.sonatype.org/content/repositories/snapshots/" }
maven { url "https://jitpack.io" } maven { url "https://jitpack.io" }
maven { url "http://dl.bintray.com/amulyakhare/maven" } // For TextDrawable.
} }
apply from: rootProject.file('dependencies.gradle') apply from: rootProject.file('dependencies.gradle')
......
...@@ -23,11 +23,12 @@ ext { ...@@ -23,11 +23,12 @@ ext {
okhttp : '3.9.0', okhttp : '3.9.0',
timber : '4.5.1', timber : '4.5.1',
threeTenABP : '1.0.5', threeTenABP : '1.0.5',
fresco : '1.5.0', fresco : '1.7.1',
frescoImageViewer : '0.5.0', frescoImageViewer : '0.5.0',
floatingSearchView : '2.1.1', floatingSearchView : '2.1.1',
androidSvg : '1.2.1', androidSvg : '1.2.1',
aVLoadingIndicatorView : '2.1.3', aVLoadingIndicatorView : '2.1.3',
textDrawable : '1.0.2',
// For testing // For testing
junit : '4.12', junit : '4.12',
...@@ -82,6 +83,8 @@ ext { ...@@ -82,6 +83,8 @@ ext {
aVLoadingIndicatorView : "com.wang.avi:library:${versions.aVLoadingIndicatorView}", aVLoadingIndicatorView : "com.wang.avi:library:${versions.aVLoadingIndicatorView}",
textDrawable : "com.github.rocketchat:textdrawable:${versions.textDrawable}",
// For testing // For testing
toolsJar : files(Jvm.current().getToolsJar()), toolsJar : files(Jvm.current().getToolsJar()),
junit : "junit:junit:$versions.junit", junit : "junit:junit:$versions.junit",
......
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