From f8b23581e1577a15562a29ba16c88659a8acef13 Mon Sep 17 00:00:00 2001 From: Taras Smakula <tarassmakula@gmail.com> Date: Wed, 6 Dec 2023 15:04:14 +0200 Subject: [PATCH] Change timeline building to flow --- .../timeline/TimelineDialogFragment.kt | 18 ++----- core/build.gradle | 2 +- .../gallery/media/PickMediaItemViewModel.kt | 2 +- .../select_users/SelectUsersDataSource.kt | 5 -- .../feature/timeline/BaseTimelineViewModel.kt | 9 ++-- .../timeline/builder/BaseTimelineBuilder.kt | 2 +- .../data_source/BaseTimelineDataSource.kt | 48 +++++++++++++------ .../data_source/MultiTimelinesDataSource.kt | 12 ++--- .../data_source/SingleTimelineDataSource.kt | 12 ++--- .../feature/gallery/grid/GalleryViewModel.kt | 10 ++-- 10 files changed, 60 insertions(+), 60 deletions(-) diff --git a/app/src/main/java/org/futo/circles/feature/timeline/TimelineDialogFragment.kt b/app/src/main/java/org/futo/circles/feature/timeline/TimelineDialogFragment.kt index e0e6a2c13..f2a19432b 100644 --- a/app/src/main/java/org/futo/circles/feature/timeline/TimelineDialogFragment.kt +++ b/app/src/main/java/org/futo/circles/feature/timeline/TimelineDialogFragment.kt @@ -26,7 +26,6 @@ import org.futo.circles.core.extensions.withConfirmation import org.futo.circles.core.feature.share.ShareProvider import org.futo.circles.core.model.CircleRoomTypeArg 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.PostContentType import org.futo.circles.core.utils.debounce @@ -66,18 +65,7 @@ class TimelineDialogFragment : BaseFullscreenDialogFragment(DialogFragmentTimeli debounce<Unit>( scope = lifecycleScope, destinationFunction = { - if (viewModel.loadMore()) binding.rvTimeline.setIsPageLoading(true) - } - ) - } - - private val submitDataDebounce by lazy { - debounce<List<Post>>( - scope = lifecycleScope, - destinationFunction = { - listAdapter.submitList(it) - binding.rvTimeline.setIsPageLoading(false) - viewModel.markTimelineAsRead(args.roomId, isGroupMode) + binding.rvTimeline.setIsPageLoading(viewModel.loadMore()) } ) } @@ -168,7 +156,9 @@ class TimelineDialogFragment : BaseFullscreenDialogFragment(DialogFragmentTimeli binding.toolbar.title = title } viewModel.timelineEventsLiveData.observeData(this) { - submitDataDebounce(it) + listAdapter.submitList(it) + binding.rvTimeline.setIsPageLoading(false) + viewModel.markTimelineAsRead(args.roomId, isGroupMode) } viewModel.notificationsStateLiveData.observeData(this) { binding.toolbar.subtitle = diff --git a/core/build.gradle b/core/build.gradle index 35b4eb51d..f21acd386 100644 --- a/core/build.gradle +++ b/core/build.gradle @@ -69,7 +69,7 @@ dependencies { kapt "com.google.dagger:hilt-compiler:$rootProject.ext.hilt_version" //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 def retrofit_version = '2.9.0' diff --git a/core/src/main/java/org/futo/circles/core/feature/picker/gallery/media/PickMediaItemViewModel.kt b/core/src/main/java/org/futo/circles/core/feature/picker/gallery/media/PickMediaItemViewModel.kt index b187f428b..3d52fe475 100644 --- a/core/src/main/java/org/futo/circles/core/feature/picker/gallery/media/PickMediaItemViewModel.kt +++ b/core/src/main/java/org/futo/circles/core/feature/picker/gallery/media/PickMediaItemViewModel.kt @@ -26,7 +26,7 @@ class PickMediaItemViewModel @Inject constructor( private val selectedItemsFlow = MutableStateFlow<List<GalleryContentListItem>>(emptyList()) val galleryItemsLiveData = combine( - timelineDataSource.timelineEventsLiveData.asFlow(), + timelineDataSource.getTimelineEventFlow(), selectedItemsFlow ) { items, selectedIds -> items.mapNotNull { post -> diff --git a/core/src/main/java/org/futo/circles/core/feature/select_users/SelectUsersDataSource.kt b/core/src/main/java/org/futo/circles/core/feature/select_users/SelectUsersDataSource.kt index d276949d3..bc477494e 100644 --- a/core/src/main/java/org/futo/circles/core/feature/select_users/SelectUsersDataSource.kt +++ b/core/src/main/java/org/futo/circles/core/feature/select_users/SelectUsersDataSource.kt @@ -1,15 +1,12 @@ package org.futo.circles.core.feature.select_users import androidx.lifecycle.SavedStateHandle -import androidx.lifecycle.asFlow import dagger.hilt.android.scopes.ViewModelScoped import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.distinctUntilChanged 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.mapping.toUserListItem import org.futo.circles.core.model.CirclesUserSummary @@ -20,11 +17,9 @@ import org.futo.circles.core.model.UserListItem import org.futo.circles.core.provider.MatrixSessionProvider import org.futo.circles.core.utils.UserUtils 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.room.members.roomMemberQueryParams 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 javax.inject.Inject diff --git a/core/src/main/java/org/futo/circles/core/feature/timeline/BaseTimelineViewModel.kt b/core/src/main/java/org/futo/circles/core/feature/timeline/BaseTimelineViewModel.kt index d302d53ff..60b44419b 100644 --- a/core/src/main/java/org/futo/circles/core/feature/timeline/BaseTimelineViewModel.kt +++ b/core/src/main/java/org/futo/circles/core/feature/timeline/BaseTimelineViewModel.kt @@ -1,9 +1,10 @@ package org.futo.circles.core.feature.timeline import androidx.lifecycle.ViewModel +import androidx.lifecycle.asLiveData 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.mapping.nameOrId abstract class BaseTimelineViewModel( private val baseTimelineDataSource: BaseTimelineDataSource @@ -12,11 +13,7 @@ abstract class BaseTimelineViewModel( val titleLiveData = baseTimelineDataSource.room.getRoomSummaryLive().map { it.getOrNull()?.nameOrId() ?: "" } - val timelineEventsLiveData = baseTimelineDataSource.timelineEventsLiveData - - init { - baseTimelineDataSource.startTimeline() - } + val timelineEventsLiveData = baseTimelineDataSource.getTimelineEventFlow().asLiveData() override fun onCleared() { baseTimelineDataSource.clearTimeline() diff --git a/core/src/main/java/org/futo/circles/core/feature/timeline/builder/BaseTimelineBuilder.kt b/core/src/main/java/org/futo/circles/core/feature/timeline/builder/BaseTimelineBuilder.kt index 551b588b4..eb4e448d2 100644 --- a/core/src/main/java/org/futo/circles/core/feature/timeline/builder/BaseTimelineBuilder.kt +++ b/core/src/main/java/org/futo/circles/core/feature/timeline/builder/BaseTimelineBuilder.kt @@ -17,7 +17,7 @@ abstract class BaseTimelineBuilder { 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) .processSnapshot(isThread) diff --git a/core/src/main/java/org/futo/circles/core/feature/timeline/data_source/BaseTimelineDataSource.kt b/core/src/main/java/org/futo/circles/core/feature/timeline/data_source/BaseTimelineDataSource.kt index c6de328db..43683402b 100644 --- a/core/src/main/java/org/futo/circles/core/feature/timeline/data_source/BaseTimelineDataSource.kt +++ b/core/src/main/java/org/futo/circles/core/feature/timeline/data_source/BaseTimelineDataSource.kt @@ -1,7 +1,14 @@ package org.futo.circles.core.feature.timeline.data_source -import androidx.lifecycle.MutableLiveData 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.feature.timeline.builder.BaseTimelineBuilder import org.futo.circles.core.feature.timeline.builder.MultiTimelineBuilder @@ -19,7 +26,7 @@ import javax.inject.Inject abstract class BaseTimelineDataSource( savedStateHandle: SavedStateHandle, private val timelineBuilder: BaseTimelineBuilder -) : Timeline.Listener { +) { class Factory @Inject constructor(private val savedStateHandle: SavedStateHandle) { fun create(isMultiTimelines: Boolean): BaseTimelineDataSource = @@ -32,30 +39,45 @@ abstract class BaseTimelineDataSource( protected val session = MatrixSessionProvider.getSessionOrThrow() val room = session.getRoom(roomId) ?: throw IllegalArgumentException("room is not found") - val timelineEventsLiveData = MutableLiveData<List<Post>>() private val isThread: Boolean = threadEventId != null private val listDirection = 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 loadMore(): Boolean - override fun onTimelineUpdated(snapshot: List<TimelineEvent>) { - if (snapshot.isNotEmpty()) - timelineEventsLiveData.value = timelineBuilder.build(snapshot, isThread) - } - - protected fun createAndStartNewTimeline(room: Room) = + protected fun createAndStartNewTimeline(room: Room, listener: Timeline.Listener) = room.timelineService() .createTimeline( null, TimelineSettings(initialSize = MESSAGES_PER_PAGE, rootThreadEventId = threadEventId) ) .apply { - addListener(this@BaseTimelineDataSource) + addListener(listener) start(threadEventId) } @@ -70,10 +92,6 @@ abstract class BaseTimelineDataSource( return hasMore } - protected fun restartTimelineOnFailure(timeline: Timeline) { - if (timeline.getPaginationState(listDirection).inError) timeline.restartWithEventId(null) - } - companion object { private const val MESSAGES_PER_PAGE = 50 } diff --git a/core/src/main/java/org/futo/circles/core/feature/timeline/data_source/MultiTimelinesDataSource.kt b/core/src/main/java/org/futo/circles/core/feature/timeline/data_source/MultiTimelinesDataSource.kt index 9de0a1caa..11f0fcafa 100644 --- a/core/src/main/java/org/futo/circles/core/feature/timeline/data_source/MultiTimelinesDataSource.kt +++ b/core/src/main/java/org/futo/circles/core/feature/timeline/data_source/MultiTimelinesDataSource.kt @@ -16,13 +16,17 @@ class MultiTimelinesDataSource @Inject constructor( private var timelines: MutableList<Timeline> = mutableListOf() - override fun startTimeline() { + override fun startTimeline(listener: Timeline.Listener) { getTimelineRooms().forEach { room -> - val timeline = createAndStartNewTimeline(room) + val timeline = createAndStartNewTimeline(room, listener) timelines.add(timeline) } } + override fun onTimelineFailure(timelineId: String, throwable: Throwable) { + timelines.firstOrNull { it.timelineID == timelineId }?.restartWithEventId(null) + } + override fun clearTimeline() { timelines.forEach { timeline -> closeTimeline(timeline) } timelines.clear() @@ -36,10 +40,6 @@ class MultiTimelinesDataSource @Inject constructor( return hasMore } - override fun onTimelineFailure(throwable: Throwable) { - timelines.forEach { restartTimelineOnFailure(it) } - } - private fun getTimelineRooms(): List<Room> = room.roomSummary()?.spaceChildren?.mapNotNull { session.getRoom(it.childRoomId) } ?: emptyList() diff --git a/core/src/main/java/org/futo/circles/core/feature/timeline/data_source/SingleTimelineDataSource.kt b/core/src/main/java/org/futo/circles/core/feature/timeline/data_source/SingleTimelineDataSource.kt index 519c0de26..87a94383d 100644 --- a/core/src/main/java/org/futo/circles/core/feature/timeline/data_source/SingleTimelineDataSource.kt +++ b/core/src/main/java/org/futo/circles/core/feature/timeline/data_source/SingleTimelineDataSource.kt @@ -14,8 +14,12 @@ class SingleTimelineDataSource @Inject constructor( private var timeline: Timeline? = null - override fun startTimeline() { - timeline = createAndStartNewTimeline(room) + override fun startTimeline(listener: Timeline.Listener) { + timeline = createAndStartNewTimeline(room, listener) + } + + override fun onTimelineFailure(timelineId: String, throwable: Throwable) { + timeline?.restartWithEventId(null) } override fun clearTimeline() { @@ -25,8 +29,4 @@ class SingleTimelineDataSource @Inject constructor( override fun loadMore() = timeline?.let { loadNextPage(it) } ?: false - override fun onTimelineFailure(throwable: Throwable) { - timeline?.let { restartTimelineOnFailure(it) } - } - } \ No newline at end of file diff --git a/gallery/src/main/java/org/futo/circles/gallery/feature/gallery/grid/GalleryViewModel.kt b/gallery/src/main/java/org/futo/circles/gallery/feature/gallery/grid/GalleryViewModel.kt index 4dc518ac7..f557aa3fd 100644 --- a/gallery/src/main/java/org/futo/circles/gallery/feature/gallery/grid/GalleryViewModel.kt +++ b/gallery/src/main/java/org/futo/circles/gallery/feature/gallery/grid/GalleryViewModel.kt @@ -8,16 +8,16 @@ import dagger.hilt.android.lifecycle.HiltViewModel import org.futo.circles.core.base.SingleEventLiveData import org.futo.circles.core.extensions.getOrThrow 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.data_source.AccessLevelDataSource 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.PostOptionsDataSource 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 @HiltViewModel @@ -34,7 +34,7 @@ class GalleryViewModel @Inject constructor( val accessLevelLiveData = accessLevelDataSource.accessLevelFlow.asLiveData() - val galleryItemsLiveData = timelineDataSource.timelineEventsLiveData.map { list -> + val galleryItemsLiveData = timelineDataSource.getTimelineEventFlow().asLiveData().map { list -> list.mapNotNull { post -> (post.content as? MediaContent)?.let { GalleryContentListItem(post.id, post.postInfo, it) -- GitLab