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 @@
*/
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<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) {
......@@ -54,11 +66,7 @@ class ChatEventRemoteSource(private val matrix: Matrix,
@Suppress("UNCHECKED_CAST")
override fun getChatEvents(chatId: String): Flowable<List<ChatEvent>> {
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<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(
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
......@@ -25,4 +25,6 @@ import io.reactivex.Flowable
interface ChatEventSource {
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 {
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
}
}
}
......
......@@ -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
......@@ -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
......@@ -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
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<User>) : 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
......@@ -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<ChatViewModel>() {
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?) {
super.onAttach(context)
......@@ -95,6 +115,7 @@ class ChatFragment : BaseFragment<ChatViewModel>() {
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<ChatViewModel>() {
onNext = {
runOnUiThread {
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,
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
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/>.
android:inputType="textAutoComplete" />
</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>
\ 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