mirror of
https://github.com/SchildiChat/SchildiChat-android.git
synced 2025-02-16 12:00:03 +03:00
Observation of the local events to render UI
This commit is contained in:
parent
7ca532a5f6
commit
96252ec2af
9 changed files with 153 additions and 26 deletions
|
@ -17,7 +17,7 @@
|
||||||
package org.matrix.android.sdk.api.session.room.poll
|
package org.matrix.android.sdk.api.session.room.poll
|
||||||
|
|
||||||
import androidx.lifecycle.LiveData
|
import androidx.lifecycle.LiveData
|
||||||
import org.matrix.android.sdk.api.session.room.model.PollResponseAggregatedSummary
|
import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Expose methods to get history of polls in rooms.
|
* Expose methods to get history of polls in rooms.
|
||||||
|
@ -43,7 +43,7 @@ interface PollHistoryService {
|
||||||
suspend fun syncPolls()
|
suspend fun syncPolls()
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get currently loaded list of polls. See [loadMore].
|
* Get currently loaded list of poll events. See [loadMore].
|
||||||
*/
|
*/
|
||||||
fun getPolls(): LiveData<List<PollResponseAggregatedSummary>>
|
fun getPollEvents(): LiveData<List<TimelineEvent>>
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,14 +17,28 @@
|
||||||
package org.matrix.android.sdk.internal.session.room.poll
|
package org.matrix.android.sdk.internal.session.room.poll
|
||||||
|
|
||||||
import androidx.lifecycle.LiveData
|
import androidx.lifecycle.LiveData
|
||||||
|
import androidx.lifecycle.Transformations
|
||||||
|
import com.zhuinden.monarchy.Monarchy
|
||||||
import dagger.assisted.Assisted
|
import dagger.assisted.Assisted
|
||||||
import dagger.assisted.AssistedFactory
|
import dagger.assisted.AssistedFactory
|
||||||
import dagger.assisted.AssistedInject
|
import dagger.assisted.AssistedInject
|
||||||
|
import io.realm.kotlin.where
|
||||||
import kotlinx.coroutines.delay
|
import kotlinx.coroutines.delay
|
||||||
|
import org.matrix.android.sdk.api.session.events.model.EventType
|
||||||
import org.matrix.android.sdk.api.session.room.model.PollResponseAggregatedSummary
|
import org.matrix.android.sdk.api.session.room.model.PollResponseAggregatedSummary
|
||||||
import org.matrix.android.sdk.api.session.room.poll.LoadedPollsStatus
|
import org.matrix.android.sdk.api.session.room.poll.LoadedPollsStatus
|
||||||
import org.matrix.android.sdk.api.session.room.poll.PollHistoryService
|
import org.matrix.android.sdk.api.session.room.poll.PollHistoryService
|
||||||
|
import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent
|
||||||
|
import org.matrix.android.sdk.internal.database.mapper.PollResponseAggregatedSummaryEntityMapper
|
||||||
|
import org.matrix.android.sdk.internal.database.mapper.TimelineEventMapper
|
||||||
|
import org.matrix.android.sdk.internal.database.model.PollHistoryStatusEntity
|
||||||
|
import org.matrix.android.sdk.internal.database.model.PollHistoryStatusEntityFields
|
||||||
|
import org.matrix.android.sdk.internal.database.model.PollResponseAggregatedSummaryEntity
|
||||||
|
import org.matrix.android.sdk.internal.database.model.TimelineEventEntity
|
||||||
|
import org.matrix.android.sdk.internal.database.model.TimelineEventEntityFields
|
||||||
|
import org.matrix.android.sdk.internal.di.SessionDatabase
|
||||||
import org.matrix.android.sdk.internal.util.time.Clock
|
import org.matrix.android.sdk.internal.util.time.Clock
|
||||||
|
import timber.log.Timber
|
||||||
|
|
||||||
private const val LOADING_PERIOD_IN_DAYS = 30
|
private const val LOADING_PERIOD_IN_DAYS = 30
|
||||||
private const val EVENTS_PAGE_SIZE = 250
|
private const val EVENTS_PAGE_SIZE = 250
|
||||||
|
@ -32,9 +46,11 @@ private const val EVENTS_PAGE_SIZE = 250
|
||||||
// TODO add unit tests
|
// TODO add unit tests
|
||||||
internal class DefaultPollHistoryService @AssistedInject constructor(
|
internal class DefaultPollHistoryService @AssistedInject constructor(
|
||||||
@Assisted private val roomId: String,
|
@Assisted private val roomId: String,
|
||||||
|
@SessionDatabase private val monarchy: Monarchy,
|
||||||
private val clock: Clock,
|
private val clock: Clock,
|
||||||
private val loadMorePollsTask: LoadMorePollsTask,
|
private val loadMorePollsTask: LoadMorePollsTask,
|
||||||
private val getLoadedPollsStatusTask: GetLoadedPollsStatusTask,
|
private val getLoadedPollsStatusTask: GetLoadedPollsStatusTask,
|
||||||
|
private val timelineEventMapper: TimelineEventMapper,
|
||||||
) : PollHistoryService {
|
) : PollHistoryService {
|
||||||
|
|
||||||
@AssistedFactory
|
@AssistedFactory
|
||||||
|
@ -68,7 +84,38 @@ internal class DefaultPollHistoryService @AssistedInject constructor(
|
||||||
delay(1000)
|
delay(1000)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getPolls(): LiveData<List<PollResponseAggregatedSummary>> {
|
override fun getPollEvents(): LiveData<List<TimelineEvent>> {
|
||||||
TODO("listen database and update query depending on latest PollHistoryStatusEntity.oldestTimestampReachedMs")
|
val pollHistoryStatusLiveData = getPollHistoryStatus()
|
||||||
|
|
||||||
|
return Transformations.switchMap(pollHistoryStatusLiveData) { results ->
|
||||||
|
val oldestTimestamp = results.firstOrNull()?.oldestTimestampReachedMs ?: clock.epochMillis()
|
||||||
|
Timber.d("oldestTimestamp=$oldestTimestamp")
|
||||||
|
|
||||||
|
monarchy.findAllMappedWithChanges(
|
||||||
|
{ realm ->
|
||||||
|
val pollTypes = EventType.POLL_START.values.toTypedArray()
|
||||||
|
realm.where<TimelineEventEntity>()
|
||||||
|
.equalTo(TimelineEventEntityFields.ROOM_ID, roomId)
|
||||||
|
.`in`(TimelineEventEntityFields.ROOT.TYPE, pollTypes)
|
||||||
|
.greaterThan(TimelineEventEntityFields.ROOT.ORIGIN_SERVER_TS, oldestTimestamp)
|
||||||
|
},
|
||||||
|
{ result ->
|
||||||
|
timelineEventMapper.map(result, buildReadReceipts = false)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getPollHistoryStatus(): LiveData<List<PollHistoryStatusEntity>> {
|
||||||
|
return monarchy.findAllMappedWithChanges(
|
||||||
|
{ realm ->
|
||||||
|
realm.where<PollHistoryStatusEntity>()
|
||||||
|
.equalTo(PollHistoryStatusEntityFields.ROOM_ID, roomId)
|
||||||
|
},
|
||||||
|
{ result ->
|
||||||
|
// make a copy of the Realm object since it will be used in another transformations
|
||||||
|
result.copy()
|
||||||
|
}
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -137,7 +137,9 @@ internal class DefaultLoadMorePollsTask @Inject constructor(
|
||||||
if (paginationResponse.end == null) {
|
if (paginationResponse.end == null) {
|
||||||
// start of the timeline is reached, there are no more events
|
// start of the timeline is reached, there are no more events
|
||||||
status.isEndOfPollsBackward = true
|
status.isEndOfPollsBackward = true
|
||||||
status.oldestTimestampReachedMs = oldestEventTimestamp
|
if(oldestEventTimestamp != null && oldestEventTimestamp > 0) {
|
||||||
|
status.oldestTimestampReachedMs = oldestEventTimestamp
|
||||||
|
}
|
||||||
} else if (oldestEventTimestamp != null && currentTargetTimestamp != null && oldestEventTimestamp <= currentTargetTimestamp) {
|
} else if (oldestEventTimestamp != null && currentTargetTimestamp != null && oldestEventTimestamp <= currentTargetTimestamp) {
|
||||||
// target has been reached
|
// target has been reached
|
||||||
status.oldestTimestampReachedMs = oldestEventTimestamp
|
status.oldestTimestampReachedMs = oldestEventTimestamp
|
||||||
|
|
|
@ -92,6 +92,7 @@ class PollItemViewStateFactory @Inject constructor(
|
||||||
question = question,
|
question = question,
|
||||||
votesStatus = totalVotesText,
|
votesStatus = totalVotesText,
|
||||||
canVote = false,
|
canVote = false,
|
||||||
|
// TODO extract into helper method or mapper
|
||||||
optionViewStates = pollCreationInfo?.answers?.map { answer ->
|
optionViewStates = pollCreationInfo?.answers?.map { answer ->
|
||||||
val voteSummary = pollResponseSummary?.getVoteSummaryOfAnOption(answer.id ?: "")
|
val voteSummary = pollResponseSummary?.getVoteSummaryOfAnOption(answer.id ?: "")
|
||||||
PollOptionViewState.PollEnded(
|
PollOptionViewState.PollEnded(
|
||||||
|
|
|
@ -23,20 +23,21 @@ import dagger.assisted.AssistedInject
|
||||||
import im.vector.app.core.di.MavericksAssistedViewModelFactory
|
import im.vector.app.core.di.MavericksAssistedViewModelFactory
|
||||||
import im.vector.app.core.di.hiltMavericksViewModelFactory
|
import im.vector.app.core.di.hiltMavericksViewModelFactory
|
||||||
import im.vector.app.core.platform.VectorViewModel
|
import im.vector.app.core.platform.VectorViewModel
|
||||||
import im.vector.app.features.roomprofile.polls.list.domain.GetLoadedPollsStatusUseCase
|
|
||||||
import im.vector.app.features.roomprofile.polls.list.domain.GetPollsUseCase
|
import im.vector.app.features.roomprofile.polls.list.domain.GetPollsUseCase
|
||||||
import im.vector.app.features.roomprofile.polls.list.domain.LoadMorePollsUseCase
|
import im.vector.app.features.roomprofile.polls.list.domain.LoadMorePollsUseCase
|
||||||
import im.vector.app.features.roomprofile.polls.list.domain.SyncPollsUseCase
|
import im.vector.app.features.roomprofile.polls.list.domain.SyncPollsUseCase
|
||||||
|
import im.vector.app.features.roomprofile.polls.list.ui.PollSummaryMapper
|
||||||
import kotlinx.coroutines.flow.launchIn
|
import kotlinx.coroutines.flow.launchIn
|
||||||
|
import kotlinx.coroutines.flow.map
|
||||||
import kotlinx.coroutines.flow.onEach
|
import kotlinx.coroutines.flow.onEach
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
class RoomPollsViewModel @AssistedInject constructor(
|
class RoomPollsViewModel @AssistedInject constructor(
|
||||||
@Assisted initialState: RoomPollsViewState,
|
@Assisted initialState: RoomPollsViewState,
|
||||||
private val getPollsUseCase: GetPollsUseCase,
|
private val getPollsUseCase: GetPollsUseCase,
|
||||||
private val getLoadedPollsStatusUseCase: GetLoadedPollsStatusUseCase,
|
|
||||||
private val loadMorePollsUseCase: LoadMorePollsUseCase,
|
private val loadMorePollsUseCase: LoadMorePollsUseCase,
|
||||||
private val syncPollsUseCase: SyncPollsUseCase,
|
private val syncPollsUseCase: SyncPollsUseCase,
|
||||||
|
private val pollSummaryMapper: PollSummaryMapper,
|
||||||
) : VectorViewModel<RoomPollsViewState, RoomPollsAction, RoomPollsViewEvent>(initialState) {
|
) : VectorViewModel<RoomPollsViewState, RoomPollsAction, RoomPollsViewEvent>(initialState) {
|
||||||
|
|
||||||
@AssistedFactory
|
@AssistedFactory
|
||||||
|
@ -73,6 +74,7 @@ class RoomPollsViewModel @AssistedInject constructor(
|
||||||
|
|
||||||
private fun observePolls(roomId: String) {
|
private fun observePolls(roomId: String) {
|
||||||
getPollsUseCase.execute(roomId)
|
getPollsUseCase.execute(roomId)
|
||||||
|
.map { it.map { event -> pollSummaryMapper.map(event) } }
|
||||||
.onEach { setState { copy(polls = it) } }
|
.onEach { setState { copy(polls = it) } }
|
||||||
.launchIn(viewModelScope)
|
.launchIn(viewModelScope)
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,15 +16,13 @@
|
||||||
|
|
||||||
package im.vector.app.features.roomprofile.polls.list.data
|
package im.vector.app.features.roomprofile.polls.list.data
|
||||||
|
|
||||||
|
import androidx.lifecycle.asFlow
|
||||||
import im.vector.app.core.di.ActiveSessionHolder
|
import im.vector.app.core.di.ActiveSessionHolder
|
||||||
import im.vector.app.features.roomprofile.polls.list.ui.PollSummary
|
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
|
||||||
import kotlinx.coroutines.flow.asSharedFlow
|
|
||||||
import org.matrix.android.sdk.api.session.getRoom
|
import org.matrix.android.sdk.api.session.getRoom
|
||||||
import org.matrix.android.sdk.api.session.room.poll.LoadedPollsStatus
|
import org.matrix.android.sdk.api.session.room.poll.LoadedPollsStatus
|
||||||
import org.matrix.android.sdk.api.session.room.poll.PollHistoryService
|
import org.matrix.android.sdk.api.session.room.poll.PollHistoryService
|
||||||
import timber.log.Timber
|
import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
// TODO add unit tests
|
// TODO add unit tests
|
||||||
|
@ -32,8 +30,6 @@ class RoomPollDataSource @Inject constructor(
|
||||||
private val activeSessionHolder: ActiveSessionHolder,
|
private val activeSessionHolder: ActiveSessionHolder,
|
||||||
) {
|
) {
|
||||||
|
|
||||||
private val pollsFlow = MutableSharedFlow<List<PollSummary>>(replay = 1)
|
|
||||||
|
|
||||||
private fun getPollHistoryService(roomId: String): PollHistoryService {
|
private fun getPollHistoryService(roomId: String): PollHistoryService {
|
||||||
return activeSessionHolder
|
return activeSessionHolder
|
||||||
.getSafeActiveSession()
|
.getSafeActiveSession()
|
||||||
|
@ -42,12 +38,8 @@ class RoomPollDataSource @Inject constructor(
|
||||||
?: throw PollHistoryError.UnknownRoomError
|
?: throw PollHistoryError.UnknownRoomError
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO
|
fun getPolls(roomId: String): Flow<List<TimelineEvent>> {
|
||||||
// unmock using SDK service
|
return getPollHistoryService(roomId).getPollEvents().asFlow()
|
||||||
// after unmock, expose domain layer model (entity) and do the mapping to PollSummary in the UI layer
|
|
||||||
fun getPolls(roomId: String): Flow<List<PollSummary>> {
|
|
||||||
Timber.d("roomId=$roomId")
|
|
||||||
return pollsFlow.asSharedFlow()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun getLoadedPollsStatus(roomId: String): LoadedPollsStatus {
|
suspend fun getLoadedPollsStatus(roomId: String): LoadedPollsStatus {
|
||||||
|
|
|
@ -16,17 +16,16 @@
|
||||||
|
|
||||||
package im.vector.app.features.roomprofile.polls.list.data
|
package im.vector.app.features.roomprofile.polls.list.data
|
||||||
|
|
||||||
import im.vector.app.features.roomprofile.polls.list.ui.PollSummary
|
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
import org.matrix.android.sdk.api.session.room.poll.LoadedPollsStatus
|
import org.matrix.android.sdk.api.session.room.poll.LoadedPollsStatus
|
||||||
|
import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
class RoomPollRepository @Inject constructor(
|
class RoomPollRepository @Inject constructor(
|
||||||
private val roomPollDataSource: RoomPollDataSource,
|
private val roomPollDataSource: RoomPollDataSource,
|
||||||
) {
|
) {
|
||||||
|
|
||||||
// TODO after unmock, expose domain layer model (entity) and do the mapping to PollSummary in the UI layer
|
fun getPolls(roomId: String): Flow<List<TimelineEvent>> {
|
||||||
fun getPolls(roomId: String): Flow<List<PollSummary>> {
|
|
||||||
return roomPollDataSource.getPolls(roomId)
|
return roomPollDataSource.getPolls(roomId)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -17,17 +17,17 @@
|
||||||
package im.vector.app.features.roomprofile.polls.list.domain
|
package im.vector.app.features.roomprofile.polls.list.domain
|
||||||
|
|
||||||
import im.vector.app.features.roomprofile.polls.list.data.RoomPollRepository
|
import im.vector.app.features.roomprofile.polls.list.data.RoomPollRepository
|
||||||
import im.vector.app.features.roomprofile.polls.list.ui.PollSummary
|
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
import kotlinx.coroutines.flow.map
|
import kotlinx.coroutines.flow.map
|
||||||
|
import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
class GetPollsUseCase @Inject constructor(
|
class GetPollsUseCase @Inject constructor(
|
||||||
private val roomPollRepository: RoomPollRepository,
|
private val roomPollRepository: RoomPollRepository,
|
||||||
) {
|
) {
|
||||||
|
|
||||||
fun execute(roomId: String): Flow<List<PollSummary>> {
|
fun execute(roomId: String): Flow<List<TimelineEvent>> {
|
||||||
return roomPollRepository.getPolls(roomId)
|
return roomPollRepository.getPolls(roomId)
|
||||||
.map { it.sortedByDescending { poll -> poll.creationTimestamp } }
|
.map { it.sortedByDescending { event -> event.root.originServerTs } }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,84 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2023 New Vector Ltd
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package im.vector.app.features.roomprofile.polls.list.ui
|
||||||
|
|
||||||
|
import im.vector.app.core.extensions.getVectorLastMessageContent
|
||||||
|
import im.vector.app.features.home.room.detail.timeline.helper.PollResponseDataFactory
|
||||||
|
import im.vector.app.features.home.room.detail.timeline.item.PollOptionViewState
|
||||||
|
import im.vector.app.features.home.room.detail.timeline.item.PollResponseData
|
||||||
|
import org.matrix.android.sdk.api.session.room.model.message.MessagePollContent
|
||||||
|
import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
// TODO add unit tests
|
||||||
|
class PollSummaryMapper @Inject constructor(
|
||||||
|
private val pollResponseDataFactory: PollResponseDataFactory,
|
||||||
|
) {
|
||||||
|
|
||||||
|
fun map(timelineEvent: TimelineEvent): PollSummary {
|
||||||
|
val content = timelineEvent.getVectorLastMessageContent()
|
||||||
|
val pollResponseData = pollResponseDataFactory.create(timelineEvent)
|
||||||
|
val eventId = timelineEvent.root.eventId.orEmpty()
|
||||||
|
val creationTimestamp = timelineEvent.root.originServerTs ?: 0
|
||||||
|
if (eventId.isNotEmpty() && creationTimestamp > 0 && content is MessagePollContent && pollResponseData != null) {
|
||||||
|
return convertToPollSummary(
|
||||||
|
eventId = eventId,
|
||||||
|
creationTimestamp = creationTimestamp,
|
||||||
|
messagePollContent = content,
|
||||||
|
pollResponseData = pollResponseData
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
throw IllegalStateException("expected MessagePollContent")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun convertToPollSummary(
|
||||||
|
eventId: String,
|
||||||
|
creationTimestamp: Long,
|
||||||
|
messagePollContent: MessagePollContent,
|
||||||
|
pollResponseData: PollResponseData
|
||||||
|
): PollSummary {
|
||||||
|
val pollCreationInfo = messagePollContent.getBestPollCreationInfo()
|
||||||
|
val pollTitle = pollCreationInfo?.question?.getBestQuestion().orEmpty()
|
||||||
|
return if (pollResponseData.isClosed) {
|
||||||
|
val winnerVoteCount = pollResponseData.winnerVoteCount
|
||||||
|
PollSummary.EndedPoll(
|
||||||
|
id = eventId,
|
||||||
|
creationTimestamp = creationTimestamp,
|
||||||
|
title = pollTitle,
|
||||||
|
totalVotes = pollResponseData.totalVotes,
|
||||||
|
// TODO mutualise this with PollItemViewStateFactory
|
||||||
|
winnerOptions = pollCreationInfo?.answers?.map { answer ->
|
||||||
|
val voteSummary = pollResponseData.getVoteSummaryOfAnOption(answer.id ?: "")
|
||||||
|
PollOptionViewState.PollEnded(
|
||||||
|
optionId = answer.id ?: "",
|
||||||
|
optionAnswer = answer.getBestAnswer() ?: "",
|
||||||
|
voteCount = voteSummary?.total ?: 0,
|
||||||
|
votePercentage = voteSummary?.percentage ?: 0.0,
|
||||||
|
isWinner = winnerVoteCount != 0 && voteSummary?.total == winnerVoteCount
|
||||||
|
)
|
||||||
|
} ?: emptyList()
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
PollSummary.ActivePoll(
|
||||||
|
id = eventId,
|
||||||
|
creationTimestamp = creationTimestamp,
|
||||||
|
title = pollTitle,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Add table
Reference in a new issue