Skip to content

Commit 9f0e392

Browse files
Okuro3499dogi
andcommitted
chat: smoother paginating (fixes #13557) (#13558)
Co-authored-by: dogi <dogi@users.noreply.github.com>
1 parent 43a2e5a commit 9f0e392

12 files changed

Lines changed: 122 additions & 16 deletions

File tree

app/build.gradle

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,8 @@ android {
1212
applicationId "org.ole.planet.myplanet"
1313
minSdk = 26
1414
targetSdk = 36
15-
versionCode = 5587
16-
versionName = "0.55.87"
15+
versionCode = 5588
16+
versionName = "0.55.88"
1717
ndkVersion = '26.3.11579264'
1818
vectorDrawables.useSupportLibrary = true
1919
}

app/src/main/java/org/ole/planet/myplanet/model/ChatMessage.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ data class ChatMessage(
88
companion object {
99
const val QUERY = 1
1010
const val RESPONSE = 2
11+
const val LOAD_MORE = 3
1112
const val RESPONSE_SOURCE_UNKNOWN = 0
1213
const val RESPONSE_SOURCE_SHARED_VIEW_MODEL = 1
1314
const val RESPONSE_SOURCE_NETWORK = 2

app/src/main/java/org/ole/planet/myplanet/repository/ResourcesRepositoryImpl.kt

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,6 @@ import org.ole.planet.myplanet.model.RealmResourceActivity
2828
import org.ole.planet.myplanet.model.RealmSearchActivity
2929
import org.ole.planet.myplanet.model.RealmTag
3030
import org.ole.planet.myplanet.model.RealmUser
31-
import org.ole.planet.myplanet.repository.LibraryWithMetadata
32-
import org.ole.planet.myplanet.repository.TeamsRepository
3331
import org.ole.planet.myplanet.utils.DownloadUtils
3432
import org.ole.planet.myplanet.utils.FileUtils
3533
import org.ole.planet.myplanet.utils.UrlUtils

app/src/main/java/org/ole/planet/myplanet/ui/chat/ChatAdapter.kt

Lines changed: 34 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import androidx.recyclerview.widget.RecyclerView
1111
import org.ole.planet.myplanet.R
1212
import org.ole.planet.myplanet.callback.OnChatItemClickListener
1313
import org.ole.planet.myplanet.databinding.ItemAiResponseMessageBinding
14+
import org.ole.planet.myplanet.databinding.ItemChatLoadMoreBinding
1415
import org.ole.planet.myplanet.databinding.ItemUserMessageBinding
1516
import org.ole.planet.myplanet.model.ChatMessage
1617
import org.ole.planet.myplanet.utils.DiffUtils
@@ -28,13 +29,20 @@ class ChatAdapter(
2829
) {
2930
val animatedMessages = HashMap<Int, Boolean>()
3031
var lastAnimatedPosition: Int = -1
32+
var onLoadMoreClick: (() -> Unit)? = null
3133

3234
private var chatItemClickListener: OnChatItemClickListener? = null
3335

3436
fun setOnChatItemClickListener(listener: OnChatItemClickListener) {
3537
this.chatItemClickListener = listener
3638
}
3739

40+
class LoadMoreViewHolder(private val binding: ItemChatLoadMoreBinding) : RecyclerView.ViewHolder(binding.root) {
41+
fun bind(onClick: (() -> Unit)?) {
42+
binding.btnLoadMore.setOnClickListener { onClick?.invoke() }
43+
}
44+
}
45+
3846
class QueryViewHolder(private val textUserMessageBinding: ItemUserMessageBinding, private val copyToClipboard: (String) -> Unit) : RecyclerView.ViewHolder(textUserMessageBinding.root) {
3947
fun bind(query: String) {
4048
textUserMessageBinding.textGchatMessageMe.text = query
@@ -118,6 +126,22 @@ class ChatAdapter(
118126
submitList(emptyList())
119127
}
120128

129+
fun prependMessages(messages: List<ChatMessage>, hasLoadMoreAbove: Boolean) {
130+
val current = currentList.toMutableList()
131+
val shift = messages.size + if (hasLoadMoreAbove) 1 else 0
132+
if (lastAnimatedPosition >= 0) lastAnimatedPosition += shift
133+
val remapped = HashMap<Int, Boolean>(animatedMessages.size)
134+
animatedMessages.forEach { (pos, v) -> remapped[pos + shift] = v }
135+
animatedMessages.clear()
136+
animatedMessages.putAll(remapped)
137+
if (current.firstOrNull()?.viewType == ChatMessage.LOAD_MORE) current.removeAt(0)
138+
val newList = mutableListOf<ChatMessage>()
139+
if (hasLoadMoreAbove) newList.add(ChatMessage("", ChatMessage.LOAD_MORE))
140+
newList.addAll(messages)
141+
newList.addAll(current)
142+
submitList(newList)
143+
}
144+
121145
private fun scrollToLastItem() {
122146
val lastPosition = itemCount - 1
123147
if (lastPosition >= 0) {
@@ -131,6 +155,10 @@ class ChatAdapter(
131155

132156
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
133157
return when (viewType) {
158+
ChatMessage.LOAD_MORE -> {
159+
val loadMoreBinding = ItemChatLoadMoreBinding.inflate(LayoutInflater.from(context), parent, false)
160+
LoadMoreViewHolder(loadMoreBinding)
161+
}
134162
ChatMessage.QUERY -> {
135163
val userMessageBinding = ItemUserMessageBinding.inflate(LayoutInflater.from(context), parent, false)
136164
QueryViewHolder(userMessageBinding, this::copyToClipboard)
@@ -146,10 +174,8 @@ class ChatAdapter(
146174
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
147175
val chatItem = getItem(position)
148176
when (holder.itemViewType) {
149-
ChatMessage.QUERY -> {
150-
val queryViewHolder = holder as QueryViewHolder
151-
queryViewHolder.bind(chatItem.message)
152-
}
177+
ChatMessage.LOAD_MORE -> (holder as LoadMoreViewHolder).bind(onLoadMoreClick)
178+
ChatMessage.QUERY -> (holder as QueryViewHolder).bind(chatItem.message)
153179
ChatMessage.RESPONSE -> {
154180
val responseViewHolder = holder as ResponseViewHolder
155181
val shouldAnimate = (position == lastAnimatedPosition && !animatedMessages.containsKey(position))
@@ -159,8 +185,10 @@ class ChatAdapter(
159185
}
160186
else -> throw IllegalArgumentException("Invalid view type")
161187
}
162-
holder.itemView.setOnClickListener {
163-
chatItemClickListener?.onChatItemClick(position, chatItem)
188+
if (holder.itemViewType != ChatMessage.LOAD_MORE) {
189+
holder.itemView.setOnClickListener {
190+
chatItemClickListener?.onChatItemClick(position, chatItem)
191+
}
164192
}
165193
}
166194
override fun onViewRecycled(holder: RecyclerView.ViewHolder) {

app/src/main/java/org/ole/planet/myplanet/ui/chat/ChatDetailFragment.kt

Lines changed: 58 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,8 @@ class ChatDetailFragment : Fragment() {
8484
private var speechRecognizer: SpeechRecognizer? = null
8585
private var isListening = false
8686
private var textBeforeVoice: String = ""
87+
private var allConversations: List<RealmConversation> = emptyList()
88+
private var loadedCount = 0
8789

8890
private val requestPermissionLauncher = registerForActivityResult(ActivityResultContracts.RequestPermission()) { isGranted ->
8991
if (isGranted) {
@@ -266,6 +268,7 @@ class ChatDetailFragment : Fragment() {
266268
}
267269
return@ChatAdapter { job.cancel() }
268270
}
271+
mAdapter.onLoadMoreClick = ::loadMoreConversations
269272
binding.recyclerGchat.apply {
270273
adapter = mAdapter
271274
layoutManager = LinearLayoutManager(requireContext())
@@ -341,7 +344,12 @@ class ChatDetailFragment : Fragment() {
341344
customProgressDialog.setText(getString(R.string.please_wait))
342345
customProgressDialog.show()
343346
try {
344-
val messages = sharedViewModel.parseNewsConversations(newsConversations)
347+
val messages = withContext(Dispatchers.IO) {
348+
val conversations = JsonUtils.gson.fromJson(newsConversations, Array<RealmConversation>::class.java).toList()
349+
allConversations = conversations
350+
loadedCount = minOf(PAGE_SIZE, conversations.size)
351+
buildInitialPage()
352+
}
345353
mAdapter.submitList(messages) {
346354
binding.recyclerGchat.post {
347355
binding.recyclerGchat.scrollToPosition(mAdapter.itemCount - 1)
@@ -401,14 +409,14 @@ class ChatDetailFragment : Fragment() {
401409
launch {
402410
sharedViewModel.selectedChatHistory.collect { conversations ->
403411
mAdapter.clearData()
412+
allConversations = emptyList()
413+
loadedCount = 0
404414
binding.editGchatMessage.text.clear()
405415
binding.textGchatIndicator.visibility = View.GONE
406416
if (!conversations.isNullOrEmpty()) {
407-
val messages = mutableListOf<ChatMessage>()
408-
for (conversation in conversations) {
409-
conversation.query?.let { messages.add(ChatMessage(it, ChatMessage.QUERY)) }
410-
conversation.response?.let { messages.add(ChatMessage(it, ChatMessage.RESPONSE, ChatMessage.RESPONSE_SOURCE_SHARED_VIEW_MODEL)) }
411-
}
417+
allConversations = conversations
418+
loadedCount = minOf(PAGE_SIZE, conversations.size)
419+
val messages = buildInitialPage()
412420
mAdapter.submitList(messages) {
413421
binding.recyclerGchat.post {
414422
binding.recyclerGchat.scrollToPosition(mAdapter.itemCount - 1)
@@ -561,6 +569,8 @@ class ChatDetailFragment : Fragment() {
561569

562570
private fun clearConversation() {
563571
mAdapter.clearData()
572+
allConversations = emptyList()
573+
loadedCount = 0
564574
_id = ""
565575
_rev = ""
566576
currentID = ""
@@ -767,6 +777,47 @@ class ChatDetailFragment : Fragment() {
767777
add("conversations", conversationsArray)
768778
}
769779

780+
private fun continueConversationRealm(id: String, query: String, chatResponse: String) {
781+
val realmChatId = when {
782+
id.isNotBlank() -> id
783+
_id.isNotBlank() -> _id
784+
currentID.isNotBlank() -> currentID
785+
else -> return
786+
}
787+
788+
if (query.isBlank() && chatResponse.isBlank()) return
789+
790+
sharedViewModel.continueConversation(realmChatId, query, chatResponse, _rev)
791+
}
792+
793+
private fun buildMessagesSlice(startIndex: Int, endIndex: Int): List<ChatMessage> {
794+
val messages = mutableListOf<ChatMessage>()
795+
for (i in startIndex until endIndex) {
796+
val conv = allConversations[i]
797+
conv.query?.let { messages.add(ChatMessage(it, ChatMessage.QUERY)) }
798+
conv.response?.let { messages.add(ChatMessage(it, ChatMessage.RESPONSE, ChatMessage.RESPONSE_SOURCE_SHARED_VIEW_MODEL)) }
799+
}
800+
return messages
801+
}
802+
803+
private fun buildInitialPage(): List<ChatMessage> {
804+
val total = allConversations.size
805+
val startIndex = maxOf(0, total - loadedCount)
806+
val messages = mutableListOf<ChatMessage>()
807+
if (startIndex > 0) messages.add(ChatMessage("", ChatMessage.LOAD_MORE))
808+
messages.addAll(buildMessagesSlice(startIndex, total))
809+
return messages
810+
}
811+
812+
private fun loadMoreConversations() {
813+
val total = allConversations.size
814+
val prevStartIndex = maxOf(0, total - loadedCount)
815+
loadedCount = minOf(loadedCount + PAGE_SIZE, total)
816+
val newStartIndex = maxOf(0, total - loadedCount)
817+
val newMessages = buildMessagesSlice(newStartIndex, prevStartIndex)
818+
mAdapter.prependMessages(newMessages, hasLoadMoreAbove = newStartIndex > 0)
819+
}
820+
770821
private fun clearChatDetail() {
771822
if (newsId == null && sharedViewModel.selectedChatHistory.value.isNullOrEmpty()) {
772823
if (::mAdapter.isInitialized) {
@@ -818,6 +869,7 @@ class ChatDetailFragment : Fragment() {
818869
}
819870

820871
companion object {
872+
private const val PAGE_SIZE = 20
821873
const val ARG_COURSE_TITLE = "course_title"
822874
const val ARG_STEP_TITLE = "step_title"
823875
const val ARG_STEP_DESCRIPTION = "step_description"
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
3+
android:layout_width="match_parent"
4+
android:layout_height="wrap_content"
5+
android:paddingTop="8dp"
6+
android:paddingBottom="4dp">
7+
8+
<TextView
9+
android:id="@+id/btn_load_more"
10+
android:layout_width="wrap_content"
11+
android:layout_height="wrap_content"
12+
android:layout_gravity="center"
13+
android:background="?attr/selectableItemBackground"
14+
android:paddingStart="16dp"
15+
android:paddingEnd="16dp"
16+
android:paddingTop="8dp"
17+
android:paddingBottom="8dp"
18+
android:text="@string/load_earlier_messages"
19+
android:textColor="@color/mainColor"
20+
android:textSize="14sp" />
21+
</FrameLayout>

app/src/main/res/values-ar/strings.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1254,6 +1254,7 @@
12541254
<string name="select_pdf_only">يرجى اختيار ملف PDF</string>
12551255
<string name="file_not_found">الملف غير موجود: %s</string>
12561256
<string name="chat_already_shared_to_destination">تمت مشاركة هذه الدردشة بالفعل إلى هذه الوجهة</string>
1257+
<string name="load_earlier_messages">تحميل الرسائل السابقة</string>
12571258
<string name="already_shared">تمت المشاركة بالفعل</string>
12581259
<string name="guest_visit_limit_warning">لديك محاولة تسجيل دخول واحدة متبقية قبل حظر حسابك.</string>
12591260
<string name="warning_icon">أيقونة التحذير</string>

app/src/main/res/values-es/strings.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1241,6 +1241,7 @@
12411241
<string name="select_pdf_only">Por favor selecciona un archivo PDF</string>
12421242
<string name="file_not_found">Archivo no encontrado: %s</string>
12431243
<string name="chat_already_shared_to_destination">Este chat ya ha sido compartido a este destino</string>
1244+
<string name="load_earlier_messages">Cargar mensajes anteriores</string>
12441245
<string name="already_shared">Ya compartido</string>
12451246
<string name="guest_visit_limit_warning">Tienes 1 intento de inicio de sesión restante antes de ser bloqueado.</string>
12461247
<string name="warning_icon">Icono de advertencia</string>

app/src/main/res/values-fr/strings.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1242,6 +1242,7 @@
12421242
<string name="select_pdf_only">Veuillez sélectionner un fichier PDF</string>
12431243
<string name="file_not_found">Fichier introuvable : %s</string>
12441244
<string name="chat_already_shared_to_destination">Cette discussion a déjà été partagée vers cette destination</string>
1245+
<string name="load_earlier_messages">Charger les messages précédents</string>
12451246
<string name="already_shared">Déjà partagé</string>
12461247
<string name="guest_visit_limit_warning">Vous avez 1 tentative de connexion restante avant d\'être bloqué.</string>
12471248
<string name="warning_icon">Icône d\'avertissement</string>

app/src/main/res/values-ne/strings.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1238,6 +1238,7 @@
12381238
<string name="select_pdf_only">कृपया PDF फाइल छान्नुहोस्</string>
12391239
<string name="file_not_found">फाइल फेला परेन: %s</string>
12401240
<string name="chat_already_shared_to_destination">यो च्याट पहिले नै यस गन्तव्यमा साझेदारी गरिएको छ</string>
1241+
<string name="load_earlier_messages">पहिलेका सन्देशहरू लोड गर्नुहोस्</string>
12411242
<string name="already_shared">पहिले नै साझा गरिएको</string>
12421243
<string name="guest_visit_limit_warning">तपाईंको खाता ब्लक हुनु अघि १ लगइन बाकी छ।</string>
12431244
<string name="warning_icon">चेतावनी आइकन</string>

0 commit comments

Comments
 (0)