Skip to content
Snippets Groups Projects
Commit f8b23581 authored by Taras's avatar Taras
Browse files

Change timeline building to flow

parent fe94509d
No related branches found
No related tags found
No related merge requests found
Showing
with 60 additions and 60 deletions
...@@ -26,7 +26,6 @@ import org.futo.circles.core.extensions.withConfirmation ...@@ -26,7 +26,6 @@ import org.futo.circles.core.extensions.withConfirmation
import org.futo.circles.core.feature.share.ShareProvider import org.futo.circles.core.feature.share.ShareProvider
import org.futo.circles.core.model.CircleRoomTypeArg import org.futo.circles.core.model.CircleRoomTypeArg
import org.futo.circles.core.model.CreatePollContent import org.futo.circles.core.model.CreatePollContent
import org.futo.circles.core.model.Post
import org.futo.circles.core.model.PostContent import org.futo.circles.core.model.PostContent
import org.futo.circles.core.model.PostContentType import org.futo.circles.core.model.PostContentType
import org.futo.circles.core.utils.debounce import org.futo.circles.core.utils.debounce
...@@ -66,18 +65,7 @@ class TimelineDialogFragment : BaseFullscreenDialogFragment(DialogFragmentTimeli ...@@ -66,18 +65,7 @@ class TimelineDialogFragment : BaseFullscreenDialogFragment(DialogFragmentTimeli
debounce<Unit>( debounce<Unit>(
scope = lifecycleScope, scope = lifecycleScope,
destinationFunction = { destinationFunction = {
if (viewModel.loadMore()) binding.rvTimeline.setIsPageLoading(true) binding.rvTimeline.setIsPageLoading(viewModel.loadMore())
}
)
}
private val submitDataDebounce by lazy {
debounce<List<Post>>(
scope = lifecycleScope,
destinationFunction = {
listAdapter.submitList(it)
binding.rvTimeline.setIsPageLoading(false)
viewModel.markTimelineAsRead(args.roomId, isGroupMode)
} }
) )
} }
...@@ -168,7 +156,9 @@ class TimelineDialogFragment : BaseFullscreenDialogFragment(DialogFragmentTimeli ...@@ -168,7 +156,9 @@ class TimelineDialogFragment : BaseFullscreenDialogFragment(DialogFragmentTimeli
binding.toolbar.title = title binding.toolbar.title = title
} }
viewModel.timelineEventsLiveData.observeData(this) { viewModel.timelineEventsLiveData.observeData(this) {
submitDataDebounce(it) listAdapter.submitList(it)
binding.rvTimeline.setIsPageLoading(false)
viewModel.markTimelineAsRead(args.roomId, isGroupMode)
} }
viewModel.notificationsStateLiveData.observeData(this) { viewModel.notificationsStateLiveData.observeData(this) {
binding.toolbar.subtitle = binding.toolbar.subtitle =
......
...@@ -69,7 +69,7 @@ dependencies { ...@@ -69,7 +69,7 @@ dependencies {
kapt "com.google.dagger:hilt-compiler:$rootProject.ext.hilt_version" kapt "com.google.dagger:hilt-compiler:$rootProject.ext.hilt_version"
//Matrix //Matrix
api 'org.matrix.android:matrix-sdk-android-rustCrypto:1.5.30.36' api 'org.matrix.android:matrix-sdk-android-rustCrypto:1.5.30.37'
//Retrofit2 //Retrofit2
def retrofit_version = '2.9.0' def retrofit_version = '2.9.0'
......
...@@ -26,7 +26,7 @@ class PickMediaItemViewModel @Inject constructor( ...@@ -26,7 +26,7 @@ class PickMediaItemViewModel @Inject constructor(
private val selectedItemsFlow = MutableStateFlow<List<GalleryContentListItem>>(emptyList()) private val selectedItemsFlow = MutableStateFlow<List<GalleryContentListItem>>(emptyList())
val galleryItemsLiveData = combine( val galleryItemsLiveData = combine(
timelineDataSource.timelineEventsLiveData.asFlow(), timelineDataSource.getTimelineEventFlow(),
selectedItemsFlow selectedItemsFlow
) { items, selectedIds -> ) { items, selectedIds ->
items.mapNotNull { post -> items.mapNotNull { post ->
......
package org.futo.circles.core.feature.select_users package org.futo.circles.core.feature.select_users
import androidx.lifecycle.SavedStateHandle import androidx.lifecycle.SavedStateHandle
import androidx.lifecycle.asFlow
import dagger.hilt.android.scopes.ViewModelScoped import dagger.hilt.android.scopes.ViewModelScoped
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.flowOn import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.mapLatest
import org.futo.circles.core.extensions.getOrFetchUser
import org.futo.circles.core.extensions.getUserIdsToExclude import org.futo.circles.core.extensions.getUserIdsToExclude
import org.futo.circles.core.mapping.toUserListItem import org.futo.circles.core.mapping.toUserListItem
import org.futo.circles.core.model.CirclesUserSummary import org.futo.circles.core.model.CirclesUserSummary
...@@ -20,11 +17,9 @@ import org.futo.circles.core.model.UserListItem ...@@ -20,11 +17,9 @@ import org.futo.circles.core.model.UserListItem
import org.futo.circles.core.provider.MatrixSessionProvider import org.futo.circles.core.provider.MatrixSessionProvider
import org.futo.circles.core.utils.UserUtils import org.futo.circles.core.utils.UserUtils
import org.matrix.android.sdk.api.MatrixPatterns import org.matrix.android.sdk.api.MatrixPatterns
import org.matrix.android.sdk.api.session.Session
import org.matrix.android.sdk.api.session.getRoom import org.matrix.android.sdk.api.session.getRoom
import org.matrix.android.sdk.api.session.room.members.roomMemberQueryParams import org.matrix.android.sdk.api.session.room.members.roomMemberQueryParams
import org.matrix.android.sdk.api.session.room.model.Membership import org.matrix.android.sdk.api.session.room.model.Membership
import org.matrix.android.sdk.api.session.room.roomSummaryQueryParams
import org.matrix.android.sdk.api.session.user.model.User import org.matrix.android.sdk.api.session.user.model.User
import javax.inject.Inject import javax.inject.Inject
......
package org.futo.circles.core.feature.timeline package org.futo.circles.core.feature.timeline
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import androidx.lifecycle.asLiveData
import androidx.lifecycle.map import androidx.lifecycle.map
import org.futo.circles.core.mapping.nameOrId
import org.futo.circles.core.feature.timeline.data_source.BaseTimelineDataSource import org.futo.circles.core.feature.timeline.data_source.BaseTimelineDataSource
import org.futo.circles.core.mapping.nameOrId
abstract class BaseTimelineViewModel( abstract class BaseTimelineViewModel(
private val baseTimelineDataSource: BaseTimelineDataSource private val baseTimelineDataSource: BaseTimelineDataSource
...@@ -12,11 +13,7 @@ abstract class BaseTimelineViewModel( ...@@ -12,11 +13,7 @@ abstract class BaseTimelineViewModel(
val titleLiveData = val titleLiveData =
baseTimelineDataSource.room.getRoomSummaryLive().map { it.getOrNull()?.nameOrId() ?: "" } baseTimelineDataSource.room.getRoomSummaryLive().map { it.getOrNull()?.nameOrId() ?: "" }
val timelineEventsLiveData = baseTimelineDataSource.timelineEventsLiveData val timelineEventsLiveData = baseTimelineDataSource.getTimelineEventFlow().asLiveData()
init {
baseTimelineDataSource.startTimeline()
}
override fun onCleared() { override fun onCleared() {
baseTimelineDataSource.clearTimeline() baseTimelineDataSource.clearTimeline()
......
...@@ -17,7 +17,7 @@ abstract class BaseTimelineBuilder { ...@@ -17,7 +17,7 @@ abstract class BaseTimelineBuilder {
abstract fun List<TimelineEvent>.processSnapshot(isThread: Boolean): List<Post> abstract fun List<TimelineEvent>.processSnapshot(isThread: Boolean): List<Post>
fun build(snapshot: List<TimelineEvent>, isThread: Boolean): List<Post> = snapshot suspend fun build(snapshot: List<TimelineEvent>, isThread: Boolean): List<Post> = snapshot
.filterTimelineEvents(isThread) .filterTimelineEvents(isThread)
.processSnapshot(isThread) .processSnapshot(isThread)
......
package org.futo.circles.core.feature.timeline.data_source package org.futo.circles.core.feature.timeline.data_source
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.SavedStateHandle import androidx.lifecycle.SavedStateHandle
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.callbackFlow
import kotlinx.coroutines.flow.debounce
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.mapLatest
import org.futo.circles.core.extensions.getOrThrow import org.futo.circles.core.extensions.getOrThrow
import org.futo.circles.core.feature.timeline.builder.BaseTimelineBuilder import org.futo.circles.core.feature.timeline.builder.BaseTimelineBuilder
import org.futo.circles.core.feature.timeline.builder.MultiTimelineBuilder import org.futo.circles.core.feature.timeline.builder.MultiTimelineBuilder
...@@ -19,7 +26,7 @@ import javax.inject.Inject ...@@ -19,7 +26,7 @@ import javax.inject.Inject
abstract class BaseTimelineDataSource( abstract class BaseTimelineDataSource(
savedStateHandle: SavedStateHandle, savedStateHandle: SavedStateHandle,
private val timelineBuilder: BaseTimelineBuilder private val timelineBuilder: BaseTimelineBuilder
) : Timeline.Listener { ) {
class Factory @Inject constructor(private val savedStateHandle: SavedStateHandle) { class Factory @Inject constructor(private val savedStateHandle: SavedStateHandle) {
fun create(isMultiTimelines: Boolean): BaseTimelineDataSource = fun create(isMultiTimelines: Boolean): BaseTimelineDataSource =
...@@ -32,30 +39,45 @@ abstract class BaseTimelineDataSource( ...@@ -32,30 +39,45 @@ abstract class BaseTimelineDataSource(
protected val session = MatrixSessionProvider.getSessionOrThrow() protected val session = MatrixSessionProvider.getSessionOrThrow()
val room = session.getRoom(roomId) ?: throw IllegalArgumentException("room is not found") val room = session.getRoom(roomId) ?: throw IllegalArgumentException("room is not found")
val timelineEventsLiveData = MutableLiveData<List<Post>>()
private val isThread: Boolean = threadEventId != null private val isThread: Boolean = threadEventId != null
private val listDirection = private val listDirection =
if (isThread) Timeline.Direction.FORWARDS else Timeline.Direction.BACKWARDS if (isThread) Timeline.Direction.FORWARDS else Timeline.Direction.BACKWARDS
abstract fun startTimeline() fun getTimelineEventFlow(): Flow<List<Post>> = callbackFlow {
val listener = object : Timeline.Listener {
override fun onTimelineUpdated(snapshot: List<TimelineEvent>) {
if (snapshot.isNotEmpty()) trySend(snapshot)
}
override fun onTimelineFailure(timelineId: String, throwable: Throwable) {
onTimelineFailure(timelineId, throwable)
}
}
startTimeline(listener)
awaitClose()
}.flowOn(Dispatchers.IO)
.debounce(150)
.mapLatest {
timelineBuilder.build(it, isThread)
}
.distinctUntilChanged()
protected abstract fun startTimeline(listener: Timeline.Listener)
protected abstract fun onTimelineFailure(timelineId: String, throwable: Throwable)
abstract fun clearTimeline() abstract fun clearTimeline()
abstract fun loadMore(): Boolean abstract fun loadMore(): Boolean
override fun onTimelineUpdated(snapshot: List<TimelineEvent>) { protected fun createAndStartNewTimeline(room: Room, listener: Timeline.Listener) =
if (snapshot.isNotEmpty())
timelineEventsLiveData.value = timelineBuilder.build(snapshot, isThread)
}
protected fun createAndStartNewTimeline(room: Room) =
room.timelineService() room.timelineService()
.createTimeline( .createTimeline(
null, null,
TimelineSettings(initialSize = MESSAGES_PER_PAGE, rootThreadEventId = threadEventId) TimelineSettings(initialSize = MESSAGES_PER_PAGE, rootThreadEventId = threadEventId)
) )
.apply { .apply {
addListener(this@BaseTimelineDataSource) addListener(listener)
start(threadEventId) start(threadEventId)
} }
...@@ -70,10 +92,6 @@ abstract class BaseTimelineDataSource( ...@@ -70,10 +92,6 @@ abstract class BaseTimelineDataSource(
return hasMore return hasMore
} }
protected fun restartTimelineOnFailure(timeline: Timeline) {
if (timeline.getPaginationState(listDirection).inError) timeline.restartWithEventId(null)
}
companion object { companion object {
private const val MESSAGES_PER_PAGE = 50 private const val MESSAGES_PER_PAGE = 50
} }
......
...@@ -16,13 +16,17 @@ class MultiTimelinesDataSource @Inject constructor( ...@@ -16,13 +16,17 @@ class MultiTimelinesDataSource @Inject constructor(
private var timelines: MutableList<Timeline> = mutableListOf() private var timelines: MutableList<Timeline> = mutableListOf()
override fun startTimeline() { override fun startTimeline(listener: Timeline.Listener) {
getTimelineRooms().forEach { room -> getTimelineRooms().forEach { room ->
val timeline = createAndStartNewTimeline(room) val timeline = createAndStartNewTimeline(room, listener)
timelines.add(timeline) timelines.add(timeline)
} }
} }
override fun onTimelineFailure(timelineId: String, throwable: Throwable) {
timelines.firstOrNull { it.timelineID == timelineId }?.restartWithEventId(null)
}
override fun clearTimeline() { override fun clearTimeline() {
timelines.forEach { timeline -> closeTimeline(timeline) } timelines.forEach { timeline -> closeTimeline(timeline) }
timelines.clear() timelines.clear()
...@@ -36,10 +40,6 @@ class MultiTimelinesDataSource @Inject constructor( ...@@ -36,10 +40,6 @@ class MultiTimelinesDataSource @Inject constructor(
return hasMore return hasMore
} }
override fun onTimelineFailure(throwable: Throwable) {
timelines.forEach { restartTimelineOnFailure(it) }
}
private fun getTimelineRooms(): List<Room> = room.roomSummary()?.spaceChildren?.mapNotNull { private fun getTimelineRooms(): List<Room> = room.roomSummary()?.spaceChildren?.mapNotNull {
session.getRoom(it.childRoomId) session.getRoom(it.childRoomId)
} ?: emptyList() } ?: emptyList()
......
...@@ -14,8 +14,12 @@ class SingleTimelineDataSource @Inject constructor( ...@@ -14,8 +14,12 @@ class SingleTimelineDataSource @Inject constructor(
private var timeline: Timeline? = null private var timeline: Timeline? = null
override fun startTimeline() { override fun startTimeline(listener: Timeline.Listener) {
timeline = createAndStartNewTimeline(room) timeline = createAndStartNewTimeline(room, listener)
}
override fun onTimelineFailure(timelineId: String, throwable: Throwable) {
timeline?.restartWithEventId(null)
} }
override fun clearTimeline() { override fun clearTimeline() {
...@@ -25,8 +29,4 @@ class SingleTimelineDataSource @Inject constructor( ...@@ -25,8 +29,4 @@ class SingleTimelineDataSource @Inject constructor(
override fun loadMore() = timeline?.let { loadNextPage(it) } ?: false override fun loadMore() = timeline?.let { loadNextPage(it) } ?: false
override fun onTimelineFailure(throwable: Throwable) {
timeline?.let { restartTimelineOnFailure(it) }
}
} }
\ No newline at end of file
...@@ -8,16 +8,16 @@ import dagger.hilt.android.lifecycle.HiltViewModel ...@@ -8,16 +8,16 @@ import dagger.hilt.android.lifecycle.HiltViewModel
import org.futo.circles.core.base.SingleEventLiveData import org.futo.circles.core.base.SingleEventLiveData
import org.futo.circles.core.extensions.getOrThrow import org.futo.circles.core.extensions.getOrThrow
import org.futo.circles.core.extensions.launchBg import org.futo.circles.core.extensions.launchBg
import org.futo.circles.core.model.GalleryContentListItem
import org.futo.circles.core.model.MediaContent
import org.futo.circles.core.model.MediaType
import org.futo.circles.core.model.ShareableContent
import org.futo.circles.core.feature.timeline.BaseTimelineViewModel import org.futo.circles.core.feature.timeline.BaseTimelineViewModel
import org.futo.circles.core.feature.timeline.data_source.AccessLevelDataSource import org.futo.circles.core.feature.timeline.data_source.AccessLevelDataSource
import org.futo.circles.core.feature.timeline.data_source.SingleTimelineDataSource import org.futo.circles.core.feature.timeline.data_source.SingleTimelineDataSource
import org.futo.circles.core.feature.timeline.post.PostContentDataSource import org.futo.circles.core.feature.timeline.post.PostContentDataSource
import org.futo.circles.core.feature.timeline.post.PostOptionsDataSource import org.futo.circles.core.feature.timeline.post.PostOptionsDataSource
import org.futo.circles.core.feature.timeline.post.SendMessageDataSource import org.futo.circles.core.feature.timeline.post.SendMessageDataSource
import org.futo.circles.core.model.GalleryContentListItem
import org.futo.circles.core.model.MediaContent
import org.futo.circles.core.model.MediaType
import org.futo.circles.core.model.ShareableContent
import javax.inject.Inject import javax.inject.Inject
@HiltViewModel @HiltViewModel
...@@ -34,7 +34,7 @@ class GalleryViewModel @Inject constructor( ...@@ -34,7 +34,7 @@ class GalleryViewModel @Inject constructor(
val accessLevelLiveData = accessLevelDataSource.accessLevelFlow.asLiveData() val accessLevelLiveData = accessLevelDataSource.accessLevelFlow.asLiveData()
val galleryItemsLiveData = timelineDataSource.timelineEventsLiveData.map { list -> val galleryItemsLiveData = timelineDataSource.getTimelineEventFlow().asLiveData().map { list ->
list.mapNotNull { post -> list.mapNotNull { post ->
(post.content as? MediaContent)?.let { (post.content as? MediaContent)?.let {
GalleryContentListItem(post.id, post.postInfo, it) GalleryContentListItem(post.id, post.postInfo, it)
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment