Commit 5257d66b authored by Wilko Manger's avatar Wilko Manger

Add ability to scroll up and view history

parent 39de2eeb
Pipeline #268 passed with stages
in 3 minutes and 6 seconds
...@@ -19,6 +19,7 @@ ...@@ -19,6 +19,7 @@
*/ */
package im.pattle.app.data.chat.event package im.pattle.app.data.chat.event
import android.util.Log
import im.pattle.app.data.chat.event.model.* import im.pattle.app.data.chat.event.model.*
import im.pattle.app.data.sync.SyncManager import im.pattle.app.data.sync.SyncManager
import im.pattle.app.sdk.Matrix import im.pattle.app.sdk.Matrix
...@@ -27,6 +28,7 @@ import io.reactivex.Flowable ...@@ -27,6 +28,7 @@ import io.reactivex.Flowable
import org.matrix.androidsdk.data.RoomState import org.matrix.androidsdk.data.RoomState
import org.matrix.androidsdk.data.timeline.EventTimeline import org.matrix.androidsdk.data.timeline.EventTimeline
import org.matrix.androidsdk.listeners.MXEventListener 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.callback.SimpleApiCallback
import org.matrix.androidsdk.rest.model.Event import org.matrix.androidsdk.rest.model.Event
import org.matrix.androidsdk.rest.model.TokensChunkEvents import org.matrix.androidsdk.rest.model.TokensChunkEvents
...@@ -37,7 +39,17 @@ class ChatEventRemoteSource(private val matrix: Matrix, ...@@ -37,7 +39,17 @@ class ChatEventRemoteSource(private val matrix: Matrix,
private val syncManager: SyncManager) private val syncManager: SyncManager)
: ChatEventSource { : ChatEventSource {
/**
* Unsorted collection of alle events that are in memory right now.
*/
private val roomEvents = mutableMapOf<String, MutableList<ChatEvent>>() private val roomEvents = mutableMapOf<String, MutableList<ChatEvent>>()
private fun eventsForRoom(chatId: String): MutableList<ChatEvent> {
if (roomEvents[chatId] == null) {
roomEvents[chatId] = mutableListOf()
}
return roomEvents[chatId]!!
}
fun send(chatId: String, message: Message) { fun send(chatId: String, message: Message) {
...@@ -54,11 +66,7 @@ class ChatEventRemoteSource(private val matrix: Matrix, ...@@ -54,11 +66,7 @@ class ChatEventRemoteSource(private val matrix: Matrix,
@Suppress("UNCHECKED_CAST") @Suppress("UNCHECKED_CAST")
override fun getChatEvents(chatId: String): Flowable<List<ChatEvent>> { override fun getChatEvents(chatId: String): Flowable<List<ChatEvent>> {
val dataHandler = matrix.dataHandler val dataHandler = matrix.dataHandler
val events = eventsForRoom(chatId)
if (roomEvents[chatId] == null) {
roomEvents[chatId] = mutableListOf()
}
val events = roomEvents[chatId]!!
return matrix.waitForInitialSync().andThen( return matrix.waitForInitialSync().andThen(
Flowable.create({ Flowable.create({
...@@ -123,4 +131,45 @@ class ChatEventRemoteSource(private val matrix: Matrix, ...@@ -123,4 +131,45 @@ class ChatEventRemoteSource(private val matrix: Matrix,
}, BackpressureStrategy.BUFFER) }, BackpressureStrategy.BUFFER)
) )
} }
override fun getHistoricChatEvents(chatId: String,
from: ChatEvent): Flowable<List<ChatEvent>> {
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<TokensChunkEvents>() {
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)
}
} }
...@@ -28,4 +28,7 @@ class ChatEventRepository( ...@@ -28,4 +28,7 @@ class ChatEventRepository(
fun send(chatId: String, message: Message) fun send(chatId: String, message: Message)
= remoteSource.send(chatId, message) = remoteSource.send(chatId, message)
fun getHistoricChatEvents(chatId: String, from: ChatEvent)
= remoteSource.getHistoricChatEvents(chatId, from)
} }
\ No newline at end of file
...@@ -25,4 +25,6 @@ import io.reactivex.Flowable ...@@ -25,4 +25,6 @@ import io.reactivex.Flowable
interface ChatEventSource { interface ChatEventSource {
fun getChatEvents(chatId: String): Flowable<List<ChatEvent>> fun getChatEvents(chatId: String): Flowable<List<ChatEvent>>
fun getHistoricChatEvents(chatId: String, from: ChatEvent): Flowable<List<ChatEvent>>
} }
\ No newline at end of file
...@@ -29,6 +29,10 @@ abstract class ChatEvent { ...@@ -29,6 +29,10 @@ abstract class ChatEvent {
abstract val id: String abstract val id: String
abstract val sentAt: Date abstract val sentAt: Date
abstract val sender: User? abstract val sender: User?
/**
* Underlying Matrix SDK event.
*/
abstract val original: Event
override fun equals(other: Any?): Boolean { override fun equals(other: Any?): Boolean {
return when (other) { return when (other) {
...@@ -66,6 +70,7 @@ fun Event.toChatEvent(room: Room): ChatEvent? { ...@@ -66,6 +70,7 @@ fun Event.toChatEvent(room: Room): ChatEvent? {
id = id, id = id,
sentAt = sentAt, sentAt = sentAt,
sender = sender, sender = sender,
original = this,
text = text text = text
) )
} }
...@@ -74,6 +79,7 @@ fun Event.toChatEvent(room: Room): ChatEvent? { ...@@ -74,6 +79,7 @@ fun Event.toChatEvent(room: Room): ChatEvent? {
id = id, id = id,
sentAt = sentAt, sentAt = sentAt,
sender = sender, sender = sender,
original = this,
text = text text = text
) )
} }
...@@ -83,6 +89,7 @@ fun Event.toChatEvent(room: Room): ChatEvent? { ...@@ -83,6 +89,7 @@ fun Event.toChatEvent(room: Room): ChatEvent? {
id = id, id = id,
sentAt = sentAt, sentAt = sentAt,
sender = sender, sender = sender,
original = this,
text = text text = text
) )
} }
...@@ -108,6 +115,7 @@ fun Event.toChatEvent(room: Room): ChatEvent? { ...@@ -108,6 +115,7 @@ fun Event.toChatEvent(room: Room): ChatEvent? {
sentAt = sentAt, sentAt = sentAt,
sender = sender, sender = sender,
subject = subject, subject = subject,
original = this,
type = type type = type
) )
} }
...@@ -136,7 +144,8 @@ fun Event.toChatEvent(room: Room): ChatEvent? { ...@@ -136,7 +144,8 @@ fun Event.toChatEvent(room: Room): ChatEvent? {
EncryptedChatMessage( EncryptedChatMessage(
id = eventId, id = eventId,
sentAt = sentAt, sentAt = sentAt,
sender = sender sender = sender,
original = this
) )
} }
else -> { else -> {
...@@ -144,6 +153,7 @@ fun Event.toChatEvent(room: Room): ChatEvent? { ...@@ -144,6 +153,7 @@ fun Event.toChatEvent(room: Room): ChatEvent? {
override val id = eventId override val id = eventId
override val sentAt = sentAt override val sentAt = sentAt
override val sender = sender override val sender = sender
override val original = this@toChatEvent
} }
} }
} }
......
...@@ -19,6 +19,7 @@ ...@@ -19,6 +19,7 @@
package im.pattle.app.data.chat.event.model package im.pattle.app.data.chat.event.model
import im.pattle.app.data.user.User import im.pattle.app.data.user.User
import org.matrix.androidsdk.rest.model.Event
import java.util.* import java.util.*
enum class MessageType { enum class MessageType {
...@@ -34,6 +35,7 @@ interface Message { ...@@ -34,6 +35,7 @@ interface Message {
open class ChatMessage(override val id: String, open class ChatMessage(override val id: String,
override val sentAt: Date, override val sentAt: Date,
override val sender: User, override val sender: User,
override val original: Event,
override val text: String) : ChatEvent(), Message { override val text: String) : ChatEvent(), Message {
override val type = MessageType.MESSAGE override val type = MessageType.MESSAGE
} }
...@@ -41,4 +43,6 @@ open class ChatMessage(override val id: String, ...@@ -41,4 +43,6 @@ open class ChatMessage(override val id: String,
data class EncryptedChatMessage(override val id: String, data class EncryptedChatMessage(override val id: String,
override val sentAt: Date, override val sentAt: Date,
override val sender: User, override val sender: User,
override val text: String = "") : ChatMessage(id, sentAt, sender, text) override val original: Event,
\ No newline at end of file override val text: String = "")
: ChatMessage(id, sentAt, sender, original, text)
\ No newline at end of file
...@@ -19,12 +19,14 @@ ...@@ -19,12 +19,14 @@
package im.pattle.app.data.chat.event.model package im.pattle.app.data.chat.event.model
import im.pattle.app.data.user.User import im.pattle.app.data.user.User
import org.matrix.androidsdk.rest.model.Event
import java.util.* import java.util.*
data class ChatNotice(override val id: String, data class ChatNotice(override val id: String,
override val sentAt: Date, override val sentAt: Date,
override val sender: User, override val sender: User,
override val original: Event,
override val text: String) override val text: String)
: ChatMessage(id, sentAt, sender, text) { : ChatMessage(id, sentAt, sender, original, text) {
override val type = MessageType.NOTICE override val type = MessageType.NOTICE
} }
\ No newline at end of file
...@@ -19,6 +19,7 @@ ...@@ -19,6 +19,7 @@
package im.pattle.app.data.chat.event.model package im.pattle.app.data.chat.event.model
import im.pattle.app.data.user.User import im.pattle.app.data.user.User
import org.matrix.androidsdk.rest.model.Event
import java.util.* import java.util.*
enum class MemberChangeType { enum class MemberChangeType {
...@@ -32,6 +33,7 @@ enum class MemberChangeType { ...@@ -32,6 +33,7 @@ enum class MemberChangeType {
data class MemberChangeEvent(override val id: String, data class MemberChangeEvent(override val id: String,
override val sentAt: Date, override val sentAt: Date,
override val sender: User, override val sender: User,
override val original: Event,
val subject: User, val subject: User,
val type: MemberChangeType) val type: MemberChangeType)
: ChatEvent() : ChatEvent()
\ No newline at end of file
package im.pattle.app.data.chat.event.model package im.pattle.app.data.chat.event.model
import im.pattle.app.data.user.User import im.pattle.app.data.user.User
import org.matrix.androidsdk.rest.model.Event
import java.util.* import java.util.*
/** /**
...@@ -20,4 +21,9 @@ data class TypingEvent(val users: List<User>) : ChatEvent() { ...@@ -20,4 +21,9 @@ data class TypingEvent(val users: List<User>) : ChatEvent() {
override lateinit var sentAt: Date override lateinit var sentAt: Date
override val sender: User? = null override val sender: User? = null
/**
* Should be ignored.
*/
override lateinit var original: Event
} }
\ No newline at end of file
...@@ -23,6 +23,8 @@ import android.os.Bundle ...@@ -23,6 +23,8 @@ import android.os.Bundle
import android.util.Log import android.util.Log
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.View.GONE
import android.view.View.VISIBLE
import android.view.ViewGroup import android.view.ViewGroup
import android.view.WindowManager import android.view.WindowManager
import androidx.appcompat.app.ActionBar import androidx.appcompat.app.ActionBar
...@@ -30,11 +32,13 @@ import androidx.appcompat.app.AppCompatActivity ...@@ -30,11 +32,13 @@ import androidx.appcompat.app.AppCompatActivity
import androidx.navigation.fragment.findNavController import androidx.navigation.fragment.findNavController
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
import im.pattle.app.R 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.ChatMessage
import im.pattle.app.data.chat.event.model.Message import im.pattle.app.data.chat.event.model.Message
import im.pattle.app.data.chat.event.model.MessageType import im.pattle.app.data.chat.event.model.MessageType
import im.pattle.app.ui.base.BaseFragment import im.pattle.app.ui.base.BaseFragment
import im.pattle.app.ui.util.ImageLoader import im.pattle.app.ui.util.ImageLoader
import im.pattle.app.ui.util.PaginatingScrollListener
import im.pattle.app.ui.util.toPx import im.pattle.app.ui.util.toPx
import io.reactivex.rxkotlin.addTo import io.reactivex.rxkotlin.addTo
import io.reactivex.rxkotlin.subscribeBy import io.reactivex.rxkotlin.subscribeBy
...@@ -67,6 +71,22 @@ class ChatFragment : BaseFragment<ChatViewModel>() { ...@@ -67,6 +71,22 @@ class ChatFragment : BaseFragment<ChatViewModel>() {
Unit Unit
} }
private val onShouldPaginate = {
val from = adapter.items.filterIsInstance<ChatEvent>().first()
progressBar.visibility = VISIBLE
model.getHistoricChatEvents(from)
.subscribe {
runOnUiThread {
adapter.submitList(it)
progressBar.visibility = GONE
}
}
.addTo(compDisposable)
Unit
}
override fun onAttach(context: Context?) { override fun onAttach(context: Context?) {
super.onAttach(context) super.onAttach(context)
...@@ -95,6 +115,7 @@ class ChatFragment : BaseFragment<ChatViewModel>() { ...@@ -95,6 +115,7 @@ class ChatFragment : BaseFragment<ChatViewModel>() {
view.chatEventsView.layoutManager = layoutManager view.chatEventsView.layoutManager = layoutManager
view.chatEventsView.adapter = adapter view.chatEventsView.adapter = adapter
view.chatEventsView.addOnScrollListener(PaginatingScrollListener(onShouldPaginate))
view.toolbar.title = model.chatName view.toolbar.title = model.chatName
...@@ -113,7 +134,7 @@ class ChatFragment : BaseFragment<ChatViewModel>() { ...@@ -113,7 +134,7 @@ class ChatFragment : BaseFragment<ChatViewModel>() {
onNext = { onNext = {
runOnUiThread { runOnUiThread {
adapter.submitList(it) adapter.submitList(it)
view.chatEventsView.smoothScrollToPosition(adapter.itemCount - 1) //view.chatEventsView.smoothScrollToPosition(adapter.itemCount - 1)
} }
} }
) )
......
...@@ -35,5 +35,8 @@ class ChatViewModel(private val eventRepo: ChatEventRepository, ...@@ -35,5 +35,8 @@ class ChatViewModel(private val eventRepo: ChatEventRepository,
fun getChatEvents() = eventRepo.getChatEvents(chatId) fun getChatEvents() = eventRepo.getChatEvents(chatId)
fun getHistoricChatEvents(from: ChatEvent)
= eventRepo.getHistoricChatEvents(chatId, from)
fun send(message: Message) = eventRepo.send(chatId, message) fun send(message: Message) = eventRepo.send(chatId, message)
} }
\ No newline at end of file
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
...@@ -79,4 +79,20 @@ along with Pattle. If not, see <https://www.gnu.org/licenses/>. ...@@ -79,4 +79,20 @@ along with Pattle. If not, see <https://www.gnu.org/licenses/>.
android:inputType="textAutoComplete" /> android:inputType="textAutoComplete" />
</com.google.android.material.textfield.TextInputLayout> </com.google.android.material.textfield.TextInputLayout>
<ProgressBar
android:id="@+id/progressBar"
style="?android:attr/progressBarStyle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginLeft="8dp"
android:layout_marginTop="16dp"
android:layout_marginEnd="8dp"
android:layout_marginRight="8dp"
android:indeterminate="true"
android:visibility="gone"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="@+id/chatEventsView" />
</androidx.constraintlayout.widget.ConstraintLayout> </androidx.constraintlayout.widget.ConstraintLayout>
\ 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