diff --git a/Habitica/res/layout/tavern_chat_intro_item.xml b/Habitica/res/layout/tavern_chat_intro_item.xml
new file mode 100644
index 000000000..9dd30971e
--- /dev/null
+++ b/Habitica/res/layout/tavern_chat_intro_item.xml
@@ -0,0 +1,80 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/adapter/social/ChatRecyclerViewAdapter.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/adapter/social/ChatRecyclerViewAdapter.kt
index a0b641d0d..7e4260e65 100644
--- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/adapter/social/ChatRecyclerViewAdapter.kt
+++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/adapter/social/ChatRecyclerViewAdapter.kt
@@ -8,7 +8,7 @@ import com.habitrpg.android.habitica.R
import com.habitrpg.android.habitica.extensions.inflate
import com.habitrpg.android.habitica.models.social.ChatMessage
import com.habitrpg.android.habitica.models.user.User
-import com.habitrpg.android.habitica.ui.viewHolders.ChatRecyclerViewHolder
+import com.habitrpg.android.habitica.ui.viewHolders.ChatRecyclerMessageViewHolder
import io.reactivex.BackpressureStrategy
import io.reactivex.Flowable
import io.reactivex.subjects.PublishSubject
@@ -39,7 +39,7 @@ class ChatRecyclerViewAdapter(data: OrderedRealmCollection?, autoUp
return if (viewType == 0) {
SystemChatMessageViewHolder(parent.inflate(R.layout.system_chat_message))
} else {
- ChatRecyclerViewHolder(parent.inflate(R.layout.chat_item), uuid, isTavern)
+ ChatRecyclerMessageViewHolder(parent.inflate(R.layout.chat_item), uuid, isTavern)
}
}
@@ -48,7 +48,7 @@ class ChatRecyclerViewAdapter(data: OrderedRealmCollection?, autoUp
if (data[position].isSystemMessage) {
(holder as? SystemChatMessageViewHolder)?.bind(data[position])
} else {
- val chatHolder = holder as? ChatRecyclerViewHolder ?: return
+ val chatHolder = holder as? ChatRecyclerMessageViewHolder ?: return
val message = data[position]
chatHolder.bind(message,
uuid,
diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/adapter/social/InboxAdapter.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/adapter/social/InboxAdapter.kt
index fe5f23111..efad2cf84 100644
--- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/adapter/social/InboxAdapter.kt
+++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/adapter/social/InboxAdapter.kt
@@ -1,6 +1,8 @@
package com.habitrpg.android.habitica.ui.adapter.social
import android.view.ViewGroup
+import androidx.paging.DataSource
+import androidx.paging.PagedList
import androidx.paging.PagedListAdapter
import androidx.recyclerview.widget.DiffUtil
import com.habitrpg.android.habitica.R
@@ -8,13 +10,18 @@ import com.habitrpg.android.habitica.extensions.inflate
import com.habitrpg.android.habitica.models.social.ChatMessage
import com.habitrpg.android.habitica.models.user.User
import com.habitrpg.android.habitica.ui.viewHolders.ChatRecyclerViewHolder
+import com.habitrpg.android.habitica.ui.viewHolders.ChatRecyclerIntroViewHolder
+import com.habitrpg.android.habitica.ui.viewHolders.ChatRecyclerMessageViewHolder
import io.reactivex.BackpressureStrategy
import io.reactivex.Flowable
import io.reactivex.subjects.PublishSubject
+import com.habitrpg.android.habitica.models.members.Member
+
+class InboxAdapter(private var user: User?, private var replyToUser : Member) : PagedListAdapter(DIFF_CALLBACK) {
+ private val FIRST_MESSAGE = 0
+ private val NORMAL_MESSAGE = 1
-class InboxAdapter(private var user: User?) : PagedListAdapter(DIFF_CALLBACK) {
private var expandedMessageId: String? = null
-
private val likeMessageEvents = PublishSubject.create()
private val userLabelClickEvents = PublishSubject.create()
private val deleteMessageEvents = PublishSubject.create()
@@ -22,24 +29,49 @@ class InboxAdapter(private var user: User?) : PagedListAdapter()
private val copyMessageEvents = PublishSubject.create()
+ private fun isPositionIntroMessage(position: Int) : Boolean {
+ return (position == super.getItemCount() - 1)
+ }
+
+ override fun getItemViewType(position: Int): Int {
+ return if (isPositionIntroMessage(position)) FIRST_MESSAGE else NORMAL_MESSAGE
+ }
+
+ override fun getItemId(position: Int): Long {
+ return if (isPositionIntroMessage(position)) -1 else super.getItemId(position)
+ }
+
+ override fun getItem(position: Int) : ChatMessage? {
+ return if (isPositionIntroMessage(position)) ChatMessage() else super.getItem(position)
+ }
+
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ChatRecyclerViewHolder {
- return ChatRecyclerViewHolder(parent.inflate(R.layout.chat_item), user?.id ?: "", false)
+ return if (viewType == FIRST_MESSAGE) ChatRecyclerIntroViewHolder(parent.inflate(R.layout.tavern_chat_intro_item), replyToUser.id!!)
+ else ChatRecyclerMessageViewHolder(parent.inflate(R.layout.chat_item), user?.id ?: "", false)
}
override fun onBindViewHolder(holder: ChatRecyclerViewHolder, position: Int) {
- val message = getItem(position) ?: return
-
- holder.bind(message,
+ val firstMessage : Boolean = getItemViewType(position) == FIRST_MESSAGE
+ if (firstMessage) {
+ val introHolder = holder as ChatRecyclerIntroViewHolder
+ introHolder.bind(replyToUser)
+ introHolder.onOpenProfile = { userLabelClickEvents.onNext(it) }
+ }
+ else {
+ val message : ChatMessage = getItem(position) ?: return
+ val messageHolder = holder as ChatRecyclerMessageViewHolder
+ messageHolder.bind(message,
user?.id ?: "",
user,
expandedMessageId == message.id)
- holder.onShouldExpand = { expandMessage(message.id, position) }
- holder.onLikeMessage = { likeMessageEvents.onNext(it) }
- holder.onOpenProfile = { userLabelClickEvents.onNext(it) }
- holder.onReply = { replyMessageEvents.onNext(it) }
- holder.onCopyMessage = { copyMessageEvents.onNext(it) }
- holder.onFlagMessage = { flagMessageEvents.onNext(it) }
- holder.onDeleteMessage = { deleteMessageEvents.onNext(it) }
+ messageHolder.onShouldExpand = { expandMessage(message.id, position) }
+ messageHolder.onLikeMessage = { likeMessageEvents.onNext(it) }
+ messageHolder.onOpenProfile = { userLabelClickEvents.onNext(it) }
+ messageHolder.onReply = { replyMessageEvents.onNext(it) }
+ messageHolder.onCopyMessage = { copyMessageEvents.onNext(it) }
+ messageHolder.onFlagMessage = { flagMessageEvents.onNext(it) }
+ messageHolder.onDeleteMessage = { deleteMessageEvents.onNext(it) }
+ }
}
fun getUserLabelClickFlowable(): Flowable {
@@ -59,6 +91,8 @@ class InboxAdapter(private var user: User?) : PagedListAdapter
+ chatAdapter = InboxAdapter(user, member)
+ viewModel?.messages?.observe(this.viewLifecycleOwner, Observer { chatAdapter?.submitList(it) })
- chatAdapter = InboxAdapter(user)
- viewModel?.messages?.observe(this.viewLifecycleOwner, { chatAdapter?.submitList(it) })
- viewModel?.getMemberData()?.observe(this.viewLifecycleOwner, {
- activity?.binding?.toolbarTitle?.text = it?.profile?.name
- })
binding?.recyclerView?.adapter = chatAdapter
binding?.recyclerView?.itemAnimator = SafeDefaultItemAnimator()
- chatAdapter?.let { adapter ->
- compositeSubscription.add(adapter.getUserLabelClickFlowable().subscribe({
- FullProfileActivity.open(it)
- }, RxErrorHandler.handleEmptyError()))
- compositeSubscription.add(adapter.getDeleteMessageFlowable().subscribe({ this.showDeleteConfirmationDialog(it) }, RxErrorHandler.handleEmptyError()))
- compositeSubscription.add(adapter.getFlagMessageClickFlowable().subscribe({ this.showFlagConfirmationDialog(it) }, RxErrorHandler.handleEmptyError()))
- compositeSubscription.add(adapter.getCopyMessageFlowable().subscribe({ this.copyMessageToClipboard(it) }, RxErrorHandler.handleEmptyError()))
- }
+ chatAdapter?.let { adapter ->
+ compositeSubscription.add(adapter.getUserLabelClickFlowable().subscribe(Consumer {
+ FullProfileActivity.open(it)
+ }, RxErrorHandler.handleEmptyError()))
+ compositeSubscription.add(adapter.getDeleteMessageFlowable().subscribe(Consumer { this.showDeleteConfirmationDialog(it) }, RxErrorHandler.handleEmptyError()))
+ compositeSubscription.add(adapter.getFlagMessageClickFlowable().subscribe(Consumer { this.showFlagConfirmationDialog(it) }, RxErrorHandler.handleEmptyError()))
+ compositeSubscription.add(adapter.getCopyMessageFlowable().subscribe(Consumer { this.copyMessageToClipboard(it) }, RxErrorHandler.handleEmptyError()))
+ }
+ }))
binding?.chatBarView?.sendAction = { sendMessage(it) }
binding?.chatBarView?.maxChatLength = configManager.maxChatLength()
diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/viewHolders/ChatRecyclerViewHolder.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/viewHolders/ChatRecyclerViewHolder.kt
index 545c1cda0..d23ae1352 100644
--- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/viewHolders/ChatRecyclerViewHolder.kt
+++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/viewHolders/ChatRecyclerViewHolder.kt
@@ -21,8 +21,31 @@ import com.habitrpg.android.habitica.ui.views.HabiticaIconsHelper
import io.reactivex.Maybe
import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.schedulers.Schedulers
+import com.habitrpg.android.habitica.models.members.Member
-class ChatRecyclerViewHolder(itemView: View, private var userId: String, private val isTavern: Boolean) : RecyclerView.ViewHolder(itemView) {
+open class ChatRecyclerViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {}
+
+class ChatRecyclerIntroViewHolder(itemView: View, replyToUUID: String) : ChatRecyclerViewHolder(itemView) {
+ private val binding = TavernChatIntroItemBinding.bind(itemView)
+
+ var onOpenProfile: ((String) -> Unit)? = null
+
+ init {
+ binding.avatarView.setOnClickListener { onOpenProfile?.invoke(replyToUUID) }
+ binding.displayNameTextview.setOnClickListener { onOpenProfile?.invoke(replyToUUID) }
+ binding.sublineTextview.setOnClickListener { onOpenProfile?.invoke(replyToUUID) }
+ }
+
+ fun bind(member: Member) {
+ binding.avatarView.setAvatar(member)
+ binding.displayNameTextview.username = member.displayName
+ binding.displayNameTextview.tier = member.contributor?.level ?: 0
+ binding.sublineTextview.text = "@" + member.username
+ }
+}
+
+
+class ChatRecyclerMessageViewHolder(itemView: View, private var userId: String, private val isTavern: Boolean) : ChatRecyclerViewHolder(itemView) {
val binding = ChatItemBinding.bind(itemView)
val context: Context = itemView.context
diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/viewmodels/InboxViewModel.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/viewmodels/InboxViewModel.kt
index c3c48c6ab..310537ceb 100644
--- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/viewmodels/InboxViewModel.kt
+++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/viewmodels/InboxViewModel.kt
@@ -36,7 +36,7 @@ class InboxViewModel(recipientID: String?, recipientUsername: String?) : BaseVie
.setEnablePlaceholders(false)
.build()
- private val dataSourceFactory = MessagesDataSourceFactory(socialRepository, recipientID)
+ private val dataSourceFactory = MessagesDataSourceFactory(socialRepository, recipientID, ChatMessage())
val messages: LiveData> = dataSourceFactory.toLiveData(config)
private val member: MutableLiveData by lazy {
MutableLiveData()
@@ -85,7 +85,7 @@ class InboxViewModel(recipientID: String?, recipientUsername: String?) : BaseVie
}
}
-private class MessagesDataSource(val socialRepository: SocialRepository, var recipientID: String?):
+private class MessagesDataSource(val socialRepository: SocialRepository, var recipientID: String?, var footer : ChatMessage?):
PositionalDataSource() {
private var lastFetchWasEnd = false
override fun loadRange(params: LoadRangeParams, callback: LoadRangeCallback) {
@@ -97,9 +97,16 @@ private class MessagesDataSource(val socialRepository: SocialRepository, var rec
if (recipientID?.isNotBlank() != true) { return@launch }
val page = ceil(params.startPosition.toFloat() / params.loadSize.toFloat()).toInt()
socialRepository.retrieveInboxMessages(recipientID ?: "", page)
- .subscribe({
- if (it.size != 10) lastFetchWasEnd = true
- callback.onResult(it)
+ .subscribe(Consumer {
+ if (it.size < 10) {
+ lastFetchWasEnd = true
+ if (footer != null)
+ callback.onResult(it.plusElement(footer!!))
+ else
+ callback.onResult(it)
+ }
+ else
+ callback.onResult(it)
}, RxErrorHandler.handleEmptyError())
}
}
@@ -115,23 +122,28 @@ private class MessagesDataSource(val socialRepository: SocialRepository, var rec
if (recipientID?.isNotBlank() != true) { return@flatMapPublisher Flowable.just(it) }
socialRepository.retrieveInboxMessages(recipientID ?: "", 0)
.doOnNext {
- messages -> if (messages.size != 10) lastFetchWasEnd = true
+ messages -> if (messages.size < 10) {
+ lastFetchWasEnd = true
+ }
}
} else {
Flowable.just(it)
}
}
- .subscribe({
- callback.onResult(it, 0)
+ .subscribe(Consumer {
+ if (it.size < 10 && footer != null)
+ callback.onResult(it.plusElement(footer!!), 0)
+ else
+ callback.onResult(it, 0)
}, RxErrorHandler.handleEmptyError())
}
}
}
-private class MessagesDataSourceFactory(val socialRepository: SocialRepository, var recipientID: String?) :
+private class MessagesDataSourceFactory(val socialRepository: SocialRepository, var recipientID: String?, val footer : ChatMessage?) :
DataSource.Factory() {
val sourceLiveData = MutableLiveData()
- var latestSource: MessagesDataSource = MessagesDataSource(socialRepository, recipientID)
+ var latestSource: MessagesDataSource = MessagesDataSource(socialRepository, recipientID, footer)
fun updateRecipientID(newID: String?) {
recipientID = newID
@@ -139,7 +151,7 @@ private class MessagesDataSourceFactory(val socialRepository: SocialRepository,
}
override fun create(): DataSource {
- latestSource = MessagesDataSource(socialRepository, recipientID)
+ latestSource = MessagesDataSource(socialRepository, recipientID, footer)
sourceLiveData.postValue(latestSource)
return latestSource
}