diff --git a/app/src/main/kotlin/im/pattle/app/data/chat/event/ChatEventRemoteSource.kt b/app/src/main/kotlin/im/pattle/app/data/chat/event/ChatEventRemoteSource.kt index fd9092cc6f5a56b7d458e0beedf5696e626e5628..f19f22af2166f7f668431f7aae71336cd8cdfe7a 100644 --- a/app/src/main/kotlin/im/pattle/app/data/chat/event/ChatEventRemoteSource.kt +++ b/app/src/main/kotlin/im/pattle/app/data/chat/event/ChatEventRemoteSource.kt @@ -19,6 +19,7 @@ */ package im.pattle.app.data.chat.event +import android.util.Log import im.pattle.app.data.chat.event.model.* import im.pattle.app.data.sync.SyncManager import im.pattle.app.sdk.Matrix @@ -27,6 +28,7 @@ import io.reactivex.Flowable import org.matrix.androidsdk.data.RoomState import org.matrix.androidsdk.data.timeline.EventTimeline import org.matrix.androidsdk.listeners.MXEventListener +import org.matrix.androidsdk.rest.callback.ApiCallback import org.matrix.androidsdk.rest.callback.SimpleApiCallback import org.matrix.androidsdk.rest.model.Event import org.matrix.androidsdk.rest.model.TokensChunkEvents @@ -37,7 +39,17 @@ class ChatEventRemoteSource(private val matrix: Matrix, private val syncManager: SyncManager) : ChatEventSource { + /** + * Unsorted collection of alle events that are in memory right now. + */ private val roomEvents = mutableMapOf>() + private fun eventsForRoom(chatId: String): MutableList { + if (roomEvents[chatId] == null) { + roomEvents[chatId] = mutableListOf() + } + return roomEvents[chatId]!! + } + fun send(chatId: String, message: Message) { @@ -54,11 +66,7 @@ class ChatEventRemoteSource(private val matrix: Matrix, @Suppress("UNCHECKED_CAST") override fun getChatEvents(chatId: String): Flowable> { val dataHandler = matrix.dataHandler - - if (roomEvents[chatId] == null) { - roomEvents[chatId] = mutableListOf() - } - val events = roomEvents[chatId]!! + val events = eventsForRoom(chatId) return matrix.waitForInitialSync().andThen( Flowable.create({ @@ -123,4 +131,45 @@ class ChatEventRemoteSource(private val matrix: Matrix, }, BackpressureStrategy.BUFFER) ) } + + override fun getHistoricChatEvents(chatId: String, + from: ChatEvent): Flowable> { + return Flowable.create({ + + val room = matrix.dataHandler.getRoom(chatId) + val token = from.original.mToken + + if (token != null) { + val events = eventsForRoom(chatId) + + room.requestServerRoomHistory(token, 20, + object : SimpleApiCallback() { + override fun onSuccess(response: TokensChunkEvents?) { + if (response != null) { + for (event in response.chunk) { + // TODO: Reuse code + val chatEvent = event.toChatEvent(room) + + if (chatEvent != null) { + val old = events.find { it.id == chatEvent.id } + if (old != null) { + events.remove(old) + } + + events.add(chatEvent) + } + } + + it.onNext(events) + } + } + + }) + } else { + Log.e("ChatEventRemoteSource", "token is null") + } + + + }, BackpressureStrategy.BUFFER) + } } diff --git a/app/src/main/kotlin/im/pattle/app/data/chat/event/ChatEventRepository.kt b/app/src/main/kotlin/im/pattle/app/data/chat/event/ChatEventRepository.kt index f37e9874c76724c0a4e540718185d37483bd1309..a0a1fa8394aef2eba9cbf54f57b715bd0431b7c0 100644 --- a/app/src/main/kotlin/im/pattle/app/data/chat/event/ChatEventRepository.kt +++ b/app/src/main/kotlin/im/pattle/app/data/chat/event/ChatEventRepository.kt @@ -28,4 +28,7 @@ class ChatEventRepository( fun send(chatId: String, message: Message) = remoteSource.send(chatId, message) + + fun getHistoricChatEvents(chatId: String, from: ChatEvent) + = remoteSource.getHistoricChatEvents(chatId, from) } \ No newline at end of file diff --git a/app/src/main/kotlin/im/pattle/app/data/chat/event/ChatEventSource.kt b/app/src/main/kotlin/im/pattle/app/data/chat/event/ChatEventSource.kt index 2d8cb5f035681c7484a5c950242370237c662436..e4715dd479741fa6428b86884f5b85a6b9118e39 100644 --- a/app/src/main/kotlin/im/pattle/app/data/chat/event/ChatEventSource.kt +++ b/app/src/main/kotlin/im/pattle/app/data/chat/event/ChatEventSource.kt @@ -25,4 +25,6 @@ import io.reactivex.Flowable interface ChatEventSource { fun getChatEvents(chatId: String): Flowable> + + fun getHistoricChatEvents(chatId: String, from: ChatEvent): Flowable> } \ No newline at end of file diff --git a/app/src/main/kotlin/im/pattle/app/data/chat/event/model/ChatEvent.kt b/app/src/main/kotlin/im/pattle/app/data/chat/event/model/ChatEvent.kt index ddc6b1555054d1704a2b1c7eab5268480fb104e8..cba09fe640e042b1cd14bd62922726b1d1df14f4 100644 --- a/app/src/main/kotlin/im/pattle/app/data/chat/event/model/ChatEvent.kt +++ b/app/src/main/kotlin/im/pattle/app/data/chat/event/model/ChatEvent.kt @@ -29,6 +29,10 @@ abstract class ChatEvent { abstract val id: String abstract val sentAt: Date abstract val sender: User? + /** + * Underlying Matrix SDK event. + */ + abstract val original: Event override fun equals(other: Any?): Boolean { return when (other) { @@ -66,6 +70,7 @@ fun Event.toChatEvent(room: Room): ChatEvent? { id = id, sentAt = sentAt, sender = sender, + original = this, text = text ) } @@ -74,6 +79,7 @@ fun Event.toChatEvent(room: Room): ChatEvent? { id = id, sentAt = sentAt, sender = sender, + original = this, text = text ) } @@ -83,6 +89,7 @@ fun Event.toChatEvent(room: Room): ChatEvent? { id = id, sentAt = sentAt, sender = sender, + original = this, text = text ) } @@ -108,6 +115,7 @@ fun Event.toChatEvent(room: Room): ChatEvent? { sentAt = sentAt, sender = sender, subject = subject, + original = this, type = type ) } @@ -136,7 +144,8 @@ fun Event.toChatEvent(room: Room): ChatEvent? { EncryptedChatMessage( id = eventId, sentAt = sentAt, - sender = sender + sender = sender, + original = this ) } else -> { @@ -144,6 +153,7 @@ fun Event.toChatEvent(room: Room): ChatEvent? { override val id = eventId override val sentAt = sentAt override val sender = sender + override val original = this@toChatEvent } } } diff --git a/app/src/main/kotlin/im/pattle/app/data/chat/event/model/ChatMessage.kt b/app/src/main/kotlin/im/pattle/app/data/chat/event/model/ChatMessage.kt index 8721e32e9952e5c35cc58ca9c560c5654e121933..839a9c87a766f0611a0da8be2a43a2652cff1f5f 100644 --- a/app/src/main/kotlin/im/pattle/app/data/chat/event/model/ChatMessage.kt +++ b/app/src/main/kotlin/im/pattle/app/data/chat/event/model/ChatMessage.kt @@ -19,6 +19,7 @@ package im.pattle.app.data.chat.event.model import im.pattle.app.data.user.User +import org.matrix.androidsdk.rest.model.Event import java.util.* enum class MessageType { @@ -34,6 +35,7 @@ interface Message { open class ChatMessage(override val id: String, override val sentAt: Date, override val sender: User, + override val original: Event, override val text: String) : ChatEvent(), Message { override val type = MessageType.MESSAGE } @@ -41,4 +43,6 @@ open class ChatMessage(override val id: String, data class EncryptedChatMessage(override val id: String, override val sentAt: Date, override val sender: User, - override val text: String = "") : ChatMessage(id, sentAt, sender, text) \ No newline at end of file + override val original: Event, + override val text: String = "") + : ChatMessage(id, sentAt, sender, original, text) \ No newline at end of file diff --git a/app/src/main/kotlin/im/pattle/app/data/chat/event/model/ChatNotice.kt b/app/src/main/kotlin/im/pattle/app/data/chat/event/model/ChatNotice.kt index 60cb08191b27646f2a216497bea562e3c9a940c0..dfa18c38b30fab1d406c8b45e0d3b13835eac31e 100644 --- a/app/src/main/kotlin/im/pattle/app/data/chat/event/model/ChatNotice.kt +++ b/app/src/main/kotlin/im/pattle/app/data/chat/event/model/ChatNotice.kt @@ -19,12 +19,14 @@ package im.pattle.app.data.chat.event.model import im.pattle.app.data.user.User +import org.matrix.androidsdk.rest.model.Event import java.util.* data class ChatNotice(override val id: String, override val sentAt: Date, override val sender: User, + override val original: Event, override val text: String) - : ChatMessage(id, sentAt, sender, text) { + : ChatMessage(id, sentAt, sender, original, text) { override val type = MessageType.NOTICE } \ No newline at end of file diff --git a/app/src/main/kotlin/im/pattle/app/data/chat/event/model/MemberChangeEvent.kt b/app/src/main/kotlin/im/pattle/app/data/chat/event/model/MemberChangeEvent.kt index 8591f695412af79e7d86b7bf6e77184a0b89af6a..3a152105398e16748e72469ea1d59aa0664e1ad9 100644 --- a/app/src/main/kotlin/im/pattle/app/data/chat/event/model/MemberChangeEvent.kt +++ b/app/src/main/kotlin/im/pattle/app/data/chat/event/model/MemberChangeEvent.kt @@ -19,6 +19,7 @@ package im.pattle.app.data.chat.event.model import im.pattle.app.data.user.User +import org.matrix.androidsdk.rest.model.Event import java.util.* enum class MemberChangeType { @@ -32,6 +33,7 @@ enum class MemberChangeType { data class MemberChangeEvent(override val id: String, override val sentAt: Date, override val sender: User, + override val original: Event, val subject: User, val type: MemberChangeType) : ChatEvent() \ No newline at end of file diff --git a/app/src/main/kotlin/im/pattle/app/data/chat/event/model/TypingEvent.kt b/app/src/main/kotlin/im/pattle/app/data/chat/event/model/TypingEvent.kt index c5fd36028fdbfaa91eb567703671db48b4c16c06..e896c4a0c98ebbb0adb843a28737c549d3b05832 100644 --- a/app/src/main/kotlin/im/pattle/app/data/chat/event/model/TypingEvent.kt +++ b/app/src/main/kotlin/im/pattle/app/data/chat/event/model/TypingEvent.kt @@ -1,6 +1,7 @@ package im.pattle.app.data.chat.event.model import im.pattle.app.data.user.User +import org.matrix.androidsdk.rest.model.Event import java.util.* /** @@ -20,4 +21,9 @@ data class TypingEvent(val users: List) : ChatEvent() { override lateinit var sentAt: Date override val sender: User? = null + + /** + * Should be ignored. + */ + override lateinit var original: Event } \ No newline at end of file diff --git a/app/src/main/kotlin/im/pattle/app/ui/main/chat/ChatFragment.kt b/app/src/main/kotlin/im/pattle/app/ui/main/chat/ChatFragment.kt index 632d46dd9753ef6028ec5d2ac1c77db7ba3dc772..a6b13f9b505eaa0277f8b48ec023ead5b3519a24 100644 --- a/app/src/main/kotlin/im/pattle/app/ui/main/chat/ChatFragment.kt +++ b/app/src/main/kotlin/im/pattle/app/ui/main/chat/ChatFragment.kt @@ -23,6 +23,8 @@ import android.os.Bundle import android.util.Log import android.view.LayoutInflater import android.view.View +import android.view.View.GONE +import android.view.View.VISIBLE import android.view.ViewGroup import android.view.WindowManager import androidx.appcompat.app.ActionBar @@ -30,11 +32,13 @@ import androidx.appcompat.app.AppCompatActivity import androidx.navigation.fragment.findNavController import androidx.recyclerview.widget.LinearLayoutManager import im.pattle.app.R +import im.pattle.app.data.chat.event.model.ChatEvent import im.pattle.app.data.chat.event.model.ChatMessage import im.pattle.app.data.chat.event.model.Message import im.pattle.app.data.chat.event.model.MessageType import im.pattle.app.ui.base.BaseFragment import im.pattle.app.ui.util.ImageLoader +import im.pattle.app.ui.util.PaginatingScrollListener import im.pattle.app.ui.util.toPx import io.reactivex.rxkotlin.addTo import io.reactivex.rxkotlin.subscribeBy @@ -67,6 +71,22 @@ class ChatFragment : BaseFragment() { Unit } + private val onShouldPaginate = { + val from = adapter.items.filterIsInstance().first() + + progressBar.visibility = VISIBLE + model.getHistoricChatEvents(from) + .subscribe { + runOnUiThread { + adapter.submitList(it) + progressBar.visibility = GONE + } + } + .addTo(compDisposable) + + Unit + } + override fun onAttach(context: Context?) { super.onAttach(context) @@ -95,6 +115,7 @@ class ChatFragment : BaseFragment() { view.chatEventsView.layoutManager = layoutManager view.chatEventsView.adapter = adapter + view.chatEventsView.addOnScrollListener(PaginatingScrollListener(onShouldPaginate)) view.toolbar.title = model.chatName @@ -113,7 +134,7 @@ class ChatFragment : BaseFragment() { onNext = { runOnUiThread { adapter.submitList(it) - view.chatEventsView.smoothScrollToPosition(adapter.itemCount - 1) + //view.chatEventsView.smoothScrollToPosition(adapter.itemCount - 1) } } ) diff --git a/app/src/main/kotlin/im/pattle/app/ui/main/chat/ChatViewModel.kt b/app/src/main/kotlin/im/pattle/app/ui/main/chat/ChatViewModel.kt index 11f30adde8cdbd62c7c4a536e1787ae47b1349d4..76d25a722f6e6877a623551727566cad37dcc377 100644 --- a/app/src/main/kotlin/im/pattle/app/ui/main/chat/ChatViewModel.kt +++ b/app/src/main/kotlin/im/pattle/app/ui/main/chat/ChatViewModel.kt @@ -35,5 +35,8 @@ class ChatViewModel(private val eventRepo: ChatEventRepository, fun getChatEvents() = eventRepo.getChatEvents(chatId) + fun getHistoricChatEvents(from: ChatEvent) + = eventRepo.getHistoricChatEvents(chatId, from) + fun send(message: Message) = eventRepo.send(chatId, message) } \ No newline at end of file diff --git a/app/src/main/kotlin/im/pattle/app/ui/util/PaginatingScrollListener.kt b/app/src/main/kotlin/im/pattle/app/ui/util/PaginatingScrollListener.kt new file mode 100644 index 0000000000000000000000000000000000000000..bd6dadcac4cdd0e56b684c573b5daf818cfe5004 --- /dev/null +++ b/app/src/main/kotlin/im/pattle/app/ui/util/PaginatingScrollListener.kt @@ -0,0 +1,32 @@ +package im.pattle.app.ui.util + +import android.util.Log +import androidx.recyclerview.widget.LinearLayoutManager +import androidx.recyclerview.widget.RecyclerView +import androidx.recyclerview.widget.RecyclerView.SCROLL_STATE_DRAGGING +import androidx.recyclerview.widget.RecyclerView.SCROLL_STATE_SETTLING + +class PaginatingScrollListener(val onShouldPaginate: () -> Unit) : RecyclerView.OnScrollListener() { + + private var scrollingUp = false + + override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) { + super.onScrolled(recyclerView, dx, dy) + + scrollingUp = dy < 0 + } + + override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) { + super.onScrollStateChanged(recyclerView, newState) + + val layoutManager = recyclerView.layoutManager as LinearLayoutManager + + if (layoutManager.findFirstCompletelyVisibleItemPosition() == 0 + && (newState == SCROLL_STATE_DRAGGING + || newState == SCROLL_STATE_SETTLING) + && scrollingUp) { + onShouldPaginate() + Log.d("PAGINATE!", "please") + } + } +} \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_chat.xml b/app/src/main/res/layout/fragment_chat.xml index fc44348a672fa8492122ae523f5de932e6ac11e9..b88124a381daabf365ecf805eff2c8c920df89d6 100644 --- a/app/src/main/res/layout/fragment_chat.xml +++ b/app/src/main/res/layout/fragment_chat.xml @@ -79,4 +79,20 @@ along with Pattle. If not, see . android:inputType="textAutoComplete" /> + + \ No newline at end of file