mirror of
https://github.com/element-hq/element-android
synced 2024-11-24 02:15:35 +03:00
Merge remote-tracking branch 'origin/develop' into feature/eric/audio-files-player
# Conflicts: # vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MessageItemFactory.kt
This commit is contained in:
commit
c8a56d63e9
23 changed files with 742 additions and 621 deletions
|
@ -1,3 +1,11 @@
|
|||
Changes in Element v1.4.7 (2022-03-24)
|
||||
======================================
|
||||
|
||||
Bugfixes 🐛
|
||||
----------
|
||||
- Fix inconsistencies between the arrow visibility and the collapse action on the room sections ([#5616](https://github.com/vector-im/element-android/issues/5616))
|
||||
- Fix room list header count flickering
|
||||
|
||||
Changes in Element v1.4.6 (2022-03-23)
|
||||
======================================
|
||||
|
||||
|
@ -37,6 +45,7 @@ SDK API changes ⚠️
|
|||
|
||||
Other changes
|
||||
-------------
|
||||
- Refactoring for safer olm and megolm session usage ([#5380](https://github.com/vector-im/element-android/issues/5380))
|
||||
- Improve headers UI in Rooms/Messages lists ([#4533](https://github.com/vector-im/element-android/issues/4533))
|
||||
- Number of unread messages on space badge now include number of unread DMs ([#5260](https://github.com/vector-im/element-android/issues/5260))
|
||||
- Amend spaces menu to be consistent with iOS version ([#5270](https://github.com/vector-im/element-android/issues/5270))
|
||||
|
|
1
changelog.d/5473.bugfix
Normal file
1
changelog.d/5473.bugfix
Normal file
|
@ -0,0 +1 @@
|
|||
Fixes polls being votable after being ended
|
2
fastlane/metadata/android/en-US/changelogs/40104070.txt
Normal file
2
fastlane/metadata/android/en-US/changelogs/40104070.txt
Normal file
|
@ -0,0 +1,2 @@
|
|||
Main changes in this version: Various bug fixes and stability improvements.
|
||||
Full changelog: https://github.com/vector-im/element-android/releases/tag/v1.4.7
|
|
@ -18,7 +18,6 @@ package org.matrix.android.sdk.api.session.room
|
|||
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.paging.PagedList
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import org.matrix.android.sdk.api.session.events.model.Event
|
||||
import org.matrix.android.sdk.api.session.room.members.ChangeMembershipState
|
||||
import org.matrix.android.sdk.api.session.room.model.Membership
|
||||
|
@ -218,9 +217,10 @@ interface RoomService {
|
|||
sortOrder: RoomSortOrder = RoomSortOrder.ACTIVITY): UpdatableLivePageResult
|
||||
|
||||
/**
|
||||
* Retrieve a flow on the number of rooms.
|
||||
* Return a LiveData on the number of rooms
|
||||
* @param queryParams parameters to query the room summaries. It can be use to keep only joined rooms, for instance.
|
||||
*/
|
||||
fun getRoomCountFlow(queryParams: RoomSummaryQueryParams): Flow<Int>
|
||||
fun getRoomCountLive(queryParams: RoomSummaryQueryParams): LiveData<Int>
|
||||
|
||||
/**
|
||||
* TODO Doc
|
||||
|
|
|
@ -20,7 +20,6 @@ import androidx.lifecycle.LiveData
|
|||
import androidx.lifecycle.Transformations
|
||||
import androidx.paging.PagedList
|
||||
import com.zhuinden.monarchy.Monarchy
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import org.matrix.android.sdk.api.session.events.model.Event
|
||||
import org.matrix.android.sdk.api.session.room.Room
|
||||
import org.matrix.android.sdk.api.session.room.RoomService
|
||||
|
@ -110,8 +109,8 @@ internal class DefaultRoomService @Inject constructor(
|
|||
return roomSummaryDataSource.getUpdatablePagedRoomSummariesLive(queryParams, pagedListConfig, sortOrder)
|
||||
}
|
||||
|
||||
override fun getRoomCountFlow(queryParams: RoomSummaryQueryParams): Flow<Int> {
|
||||
return roomSummaryDataSource.getCountFlow(queryParams)
|
||||
override fun getRoomCountLive(queryParams: RoomSummaryQueryParams): LiveData<Int> {
|
||||
return roomSummaryDataSource.getCountLive(queryParams)
|
||||
}
|
||||
|
||||
override fun getNotificationCountForRooms(queryParams: RoomSummaryQueryParams): RoomAggregateNotificationCount {
|
||||
|
|
|
@ -482,46 +482,39 @@ internal class EventRelationsAggregationProcessor @Inject constructor(
|
|||
roomId: String,
|
||||
isLocalEcho: Boolean) {
|
||||
val pollEventId = content.relatesTo?.eventId ?: return
|
||||
|
||||
val pollOwnerId = getPollEvent(roomId, pollEventId)?.root?.senderId
|
||||
val isPollOwner = pollOwnerId == event.senderId
|
||||
|
||||
val powerLevelsHelper = stateEventDataSource.getStateEvent(roomId, EventType.STATE_ROOM_POWER_LEVELS, QueryStringValue.NoCondition)
|
||||
?.content?.toModel<PowerLevelsContent>()
|
||||
?.let { PowerLevelsHelper(it) }
|
||||
|
||||
if (!isPollOwner && !powerLevelsHelper?.isUserAbleToRedact(event.senderId ?: "").orFalse()) {
|
||||
Timber.v("## Received poll.end event $pollEventId but user ${event.senderId} doesn't have enough power level in room $roomId")
|
||||
return
|
||||
}
|
||||
|
||||
var existing = EventAnnotationsSummaryEntity.where(realm, roomId, pollEventId).findFirst()
|
||||
if (existing == null) {
|
||||
var existingPoll = EventAnnotationsSummaryEntity.where(realm, roomId, pollEventId).findFirst()
|
||||
if (existingPoll == null) {
|
||||
Timber.v("## POLL creating new relation summary for $pollEventId")
|
||||
existing = EventAnnotationsSummaryEntity.create(realm, roomId, pollEventId)
|
||||
existingPoll = EventAnnotationsSummaryEntity.create(realm, roomId, pollEventId)
|
||||
}
|
||||
|
||||
// we have it
|
||||
val existingPollSummary = existing.pollResponseSummary
|
||||
val existingPollSummary = existingPoll.pollResponseSummary
|
||||
?: realm.createObject(PollResponseAggregatedSummaryEntity::class.java).also {
|
||||
existing.pollResponseSummary = it
|
||||
existingPoll.pollResponseSummary = it
|
||||
}
|
||||
|
||||
if (existingPollSummary.closedTime != null) {
|
||||
Timber.v("## Received poll.end event for already ended poll $pollEventId")
|
||||
return
|
||||
}
|
||||
|
||||
val txId = event.unsignedData?.transactionId
|
||||
existingPollSummary.closedTime = event.originServerTs
|
||||
|
||||
// is it a remote echo?
|
||||
if (!isLocalEcho && existingPollSummary.sourceLocalEchoEvents.contains(txId)) {
|
||||
// ok it has already been managed
|
||||
Timber.v("## POLL Receiving remote echo of response eventId:$pollEventId")
|
||||
existingPollSummary.sourceLocalEchoEvents.remove(txId)
|
||||
existingPollSummary.sourceEvents.add(event.eventId)
|
||||
return
|
||||
}
|
||||
|
||||
existingPollSummary.closedTime = event.originServerTs
|
||||
}
|
||||
|
||||
private fun getPollEvent(roomId: String, eventId: String): TimelineEvent? {
|
||||
|
|
|
@ -25,12 +25,7 @@ import androidx.paging.PagedList
|
|||
import com.zhuinden.monarchy.Monarchy
|
||||
import io.realm.Realm
|
||||
import io.realm.RealmQuery
|
||||
import io.realm.kotlin.toFlow
|
||||
import io.realm.kotlin.where
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||
import kotlinx.coroutines.flow.flowOn
|
||||
import kotlinx.coroutines.flow.map
|
||||
import org.matrix.android.sdk.api.MatrixCoroutineDispatchers
|
||||
import org.matrix.android.sdk.api.query.ActiveSpaceFilter
|
||||
import org.matrix.android.sdk.api.query.RoomCategoryFilter
|
||||
|
@ -241,15 +236,14 @@ internal class RoomSummaryDataSource @Inject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
fun getCountFlow(queryParams: RoomSummaryQueryParams): Flow<Int> =
|
||||
realmSessionProvider
|
||||
.withRealm { realm -> roomSummariesQuery(realm, queryParams).findAllAsync() }
|
||||
.toFlow()
|
||||
// need to create the flow on a context dispatcher with a thread with attached Looper
|
||||
.flowOn(coroutineDispatchers.main)
|
||||
.map { it.size }
|
||||
.flowOn(coroutineDispatchers.io)
|
||||
.distinctUntilChanged()
|
||||
fun getCountLive(queryParams: RoomSummaryQueryParams): LiveData<Int> {
|
||||
val liveRooms = monarchy.findAllManagedWithChanges {
|
||||
roomSummariesQuery(it, queryParams)
|
||||
}
|
||||
return Transformations.map(liveRooms) {
|
||||
it.realmResults.where().count().toInt()
|
||||
}
|
||||
}
|
||||
|
||||
fun getNotificationCountForRooms(queryParams: RoomSummaryQueryParams): RoomAggregateNotificationCount {
|
||||
var notificationCount: RoomAggregateNotificationCount? = null
|
||||
|
|
|
@ -184,7 +184,7 @@ import im.vector.app.features.notifications.NotificationDrawerManager
|
|||
import im.vector.app.features.notifications.NotificationUtils
|
||||
import im.vector.app.features.permalink.NavigationInterceptor
|
||||
import im.vector.app.features.permalink.PermalinkHandler
|
||||
import im.vector.app.features.poll.create.PollMode
|
||||
import im.vector.app.features.poll.PollMode
|
||||
import im.vector.app.features.reactions.EmojiReactionPickerActivity
|
||||
import im.vector.app.features.roomprofile.RoomProfileActivity
|
||||
import im.vector.app.features.session.coroutineScope
|
||||
|
|
|
@ -58,7 +58,12 @@ import im.vector.app.features.home.room.detail.timeline.item.MessageVoiceItem
|
|||
import im.vector.app.features.home.room.detail.timeline.item.MessageVoiceItem_
|
||||
import im.vector.app.features.home.room.detail.timeline.item.PollItem
|
||||
import im.vector.app.features.home.room.detail.timeline.item.PollItem_
|
||||
import im.vector.app.features.home.room.detail.timeline.item.PollOptionViewState
|
||||
import im.vector.app.features.home.room.detail.timeline.item.PollOptionViewState.PollEnded
|
||||
import im.vector.app.features.home.room.detail.timeline.item.PollOptionViewState.PollReady
|
||||
import im.vector.app.features.home.room.detail.timeline.item.PollOptionViewState.PollSending
|
||||
import im.vector.app.features.home.room.detail.timeline.item.PollOptionViewState.PollUndisclosed
|
||||
import im.vector.app.features.home.room.detail.timeline.item.PollOptionViewState.PollVoted
|
||||
import im.vector.app.features.home.room.detail.timeline.item.PollResponseData
|
||||
import im.vector.app.features.home.room.detail.timeline.item.RedactedMessageItem
|
||||
import im.vector.app.features.home.room.detail.timeline.item.RedactedMessageItem_
|
||||
import im.vector.app.features.home.room.detail.timeline.item.VerificationRequestItem
|
||||
|
@ -75,6 +80,12 @@ import im.vector.app.features.location.UrlMapProvider
|
|||
import im.vector.app.features.location.toLocationData
|
||||
import im.vector.app.features.media.ImageContentRenderer
|
||||
import im.vector.app.features.media.VideoContentRenderer
|
||||
import im.vector.app.features.poll.PollState
|
||||
import im.vector.app.features.poll.PollState.Ended
|
||||
import im.vector.app.features.poll.PollState.Ready
|
||||
import im.vector.app.features.poll.PollState.Sending
|
||||
import im.vector.app.features.poll.PollState.Undisclosed
|
||||
import im.vector.app.features.poll.PollState.Voted
|
||||
import im.vector.app.features.settings.VectorPreferences
|
||||
import im.vector.app.features.voice.AudioWaveformView
|
||||
import im.vector.lib.core.utils.epoxy.charsequence.toEpoxyCharSequence
|
||||
|
@ -98,6 +109,7 @@ import org.matrix.android.sdk.api.session.room.model.message.MessageTextContent
|
|||
import org.matrix.android.sdk.api.session.room.model.message.MessageType
|
||||
import org.matrix.android.sdk.api.session.room.model.message.MessageVerificationRequestContent
|
||||
import org.matrix.android.sdk.api.session.room.model.message.MessageVideoContent
|
||||
import org.matrix.android.sdk.api.session.room.model.message.PollAnswer
|
||||
import org.matrix.android.sdk.api.session.room.model.message.PollType
|
||||
import org.matrix.android.sdk.api.session.room.model.message.getFileUrl
|
||||
import org.matrix.android.sdk.api.session.room.model.message.getThumbnailUrl
|
||||
|
@ -109,30 +121,30 @@ import org.matrix.android.sdk.internal.database.lightweight.LightweightSettingsS
|
|||
import javax.inject.Inject
|
||||
|
||||
class MessageItemFactory @Inject constructor(
|
||||
private val localFilesHelper: LocalFilesHelper,
|
||||
private val colorProvider: ColorProvider,
|
||||
private val dimensionConverter: DimensionConverter,
|
||||
private val timelineMediaSizeProvider: TimelineMediaSizeProvider,
|
||||
private val htmlRenderer: Lazy<EventHtmlRenderer>,
|
||||
private val htmlCompressor: VectorHtmlCompressor,
|
||||
private val textRendererFactory: EventTextRenderer.Factory,
|
||||
private val stringProvider: StringProvider,
|
||||
private val imageContentRenderer: ImageContentRenderer,
|
||||
private val messageInformationDataFactory: MessageInformationDataFactory,
|
||||
private val messageItemAttributesFactory: MessageItemAttributesFactory,
|
||||
private val contentUploadStateTrackerBinder: ContentUploadStateTrackerBinder,
|
||||
private val contentDownloadStateTrackerBinder: ContentDownloadStateTrackerBinder,
|
||||
private val defaultItemFactory: DefaultItemFactory,
|
||||
private val noticeItemFactory: NoticeItemFactory,
|
||||
private val avatarSizeProvider: AvatarSizeProvider,
|
||||
private val pillsPostProcessorFactory: PillsPostProcessor.Factory,
|
||||
private val lightweightSettingsStorage: LightweightSettingsStorage,
|
||||
private val spanUtils: SpanUtils,
|
||||
private val session: Session,
|
||||
private val audioMessagePlaybackTracker: AudioMessagePlaybackTracker,
|
||||
private val locationPinProvider: LocationPinProvider,
|
||||
private val vectorPreferences: VectorPreferences,
|
||||
private val urlMapProvider: UrlMapProvider,
|
||||
private val localFilesHelper: LocalFilesHelper,
|
||||
private val colorProvider: ColorProvider,
|
||||
private val dimensionConverter: DimensionConverter,
|
||||
private val timelineMediaSizeProvider: TimelineMediaSizeProvider,
|
||||
private val htmlRenderer: Lazy<EventHtmlRenderer>,
|
||||
private val htmlCompressor: VectorHtmlCompressor,
|
||||
private val textRendererFactory: EventTextRenderer.Factory,
|
||||
private val stringProvider: StringProvider,
|
||||
private val imageContentRenderer: ImageContentRenderer,
|
||||
private val messageInformationDataFactory: MessageInformationDataFactory,
|
||||
private val messageItemAttributesFactory: MessageItemAttributesFactory,
|
||||
private val contentUploadStateTrackerBinder: ContentUploadStateTrackerBinder,
|
||||
private val contentDownloadStateTrackerBinder: ContentDownloadStateTrackerBinder,
|
||||
private val defaultItemFactory: DefaultItemFactory,
|
||||
private val noticeItemFactory: NoticeItemFactory,
|
||||
private val avatarSizeProvider: AvatarSizeProvider,
|
||||
private val pillsPostProcessorFactory: PillsPostProcessor.Factory,
|
||||
private val lightweightSettingsStorage: LightweightSettingsStorage,
|
||||
private val spanUtils: SpanUtils,
|
||||
private val session: Session,
|
||||
private val audioMessagePlaybackTracker: AudioMessagePlaybackTracker,
|
||||
private val locationPinProvider: LocationPinProvider,
|
||||
private val vectorPreferences: VectorPreferences,
|
||||
private val urlMapProvider: UrlMapProvider,
|
||||
) {
|
||||
|
||||
// TODO inject this properly?
|
||||
|
@ -167,7 +179,7 @@ class MessageItemFactory @Inject constructor(
|
|||
return defaultItemFactory.create(malformedText, informationData, highlight, callback)
|
||||
}
|
||||
if (messageContent.relatesTo?.type == RelationType.REPLACE ||
|
||||
event.isEncrypted() && event.root.content.toModel<EncryptedEventContent>()?.relatesTo?.type == RelationType.REPLACE
|
||||
event.isEncrypted() && event.root.content.toModel<EncryptedEventContent>()?.relatesTo?.type == RelationType.REPLACE
|
||||
) {
|
||||
// This is an edit event, we should display it when debugging as a notice event
|
||||
return noticeItemFactory.create(params)
|
||||
|
@ -181,36 +193,38 @@ class MessageItemFactory @Inject constructor(
|
|||
// always hide summary when we are on thread timeline
|
||||
val attributes = messageItemAttributesFactory.create(messageContent, informationData, callback, params.reactionsSummaryEvents, threadDetails)
|
||||
|
||||
// val all = event.root.toContent()
|
||||
// val ev = all.toModel<Event>()
|
||||
// val all = event.root.toContent()
|
||||
// val ev = all.toModel<Event>()
|
||||
val messageItem = when (messageContent) {
|
||||
is MessageEmoteContent -> buildEmoteMessageItem(messageContent, informationData, highlight, callback, attributes)
|
||||
is MessageTextContent -> buildItemForTextContent(messageContent, informationData, highlight, callback, attributes)
|
||||
is MessageImageInfoContent -> buildImageMessageItem(messageContent, informationData, highlight, callback, attributes)
|
||||
is MessageNoticeContent -> buildNoticeMessageItem(messageContent, informationData, highlight, callback, attributes)
|
||||
is MessageVideoContent -> buildVideoMessageItem(messageContent, informationData, highlight, callback, attributes)
|
||||
is MessageFileContent -> buildFileMessageItem(messageContent, highlight, attributes)
|
||||
is MessageAudioContent -> buildAudioContent(params, messageContent, informationData, highlight, attributes)
|
||||
is MessageEmoteContent -> buildEmoteMessageItem(messageContent, informationData, highlight, callback, attributes)
|
||||
is MessageTextContent -> buildItemForTextContent(messageContent, informationData, highlight, callback, attributes)
|
||||
is MessageImageInfoContent -> buildImageMessageItem(messageContent, informationData, highlight, callback, attributes)
|
||||
is MessageNoticeContent -> buildNoticeMessageItem(messageContent, informationData, highlight, callback, attributes)
|
||||
is MessageVideoContent -> buildVideoMessageItem(messageContent, informationData, highlight, callback, attributes)
|
||||
is MessageFileContent -> buildFileMessageItem(messageContent, highlight, attributes)
|
||||
is MessageAudioContent -> buildAudioContent(params, messageContent, informationData, highlight, attributes)
|
||||
is MessageVerificationRequestContent -> buildVerificationRequestMessageItem(messageContent, informationData, highlight, callback, attributes)
|
||||
is MessagePollContent -> buildPollItem(messageContent, informationData, highlight, callback, attributes)
|
||||
is MessageLocationContent -> {
|
||||
is MessagePollContent -> buildPollItem(messageContent, informationData, highlight, callback, attributes)
|
||||
is MessageLocationContent -> {
|
||||
if (vectorPreferences.labsRenderLocationsInTimeline()) {
|
||||
buildLocationItem(messageContent, informationData, highlight, attributes)
|
||||
} else {
|
||||
buildMessageTextItem(messageContent.body, false, informationData, highlight, callback, attributes)
|
||||
}
|
||||
}
|
||||
else -> buildNotHandledMessageItem(messageContent, informationData, highlight, callback, attributes)
|
||||
else -> buildNotHandledMessageItem(messageContent, informationData, highlight, callback, attributes)
|
||||
}
|
||||
return messageItem?.apply {
|
||||
layout(informationData.messageLayout.layoutRes)
|
||||
}
|
||||
}
|
||||
|
||||
private fun buildLocationItem(locationContent: MessageLocationContent,
|
||||
informationData: MessageInformationData,
|
||||
highlight: Boolean,
|
||||
attributes: AbsMessageItem.Attributes): MessageLocationItem? {
|
||||
private fun buildLocationItem(
|
||||
locationContent: MessageLocationContent,
|
||||
informationData: MessageInformationData,
|
||||
highlight: Boolean,
|
||||
attributes: AbsMessageItem.Attributes,
|
||||
): MessageLocationItem? {
|
||||
val width = timelineMediaSizeProvider.getMaxSize().first
|
||||
val height = dimensionConverter.dpToPx(200)
|
||||
|
||||
|
@ -221,98 +235,110 @@ class MessageItemFactory @Inject constructor(
|
|||
val userId = if (locationContent.isSelfLocation()) informationData.senderId else null
|
||||
|
||||
return MessageLocationItem_()
|
||||
.attributes(attributes)
|
||||
.locationUrl(locationUrl)
|
||||
.mapWidth(width)
|
||||
.mapHeight(height)
|
||||
.userId(userId)
|
||||
.locationPinProvider(locationPinProvider)
|
||||
.highlighted(highlight)
|
||||
.leftGuideline(avatarSizeProvider.leftGuideline)
|
||||
.attributes(attributes)
|
||||
.locationUrl(locationUrl)
|
||||
.mapWidth(width)
|
||||
.mapHeight(height)
|
||||
.userId(userId)
|
||||
.locationPinProvider(locationPinProvider)
|
||||
.highlighted(highlight)
|
||||
.leftGuideline(avatarSizeProvider.leftGuideline)
|
||||
}
|
||||
|
||||
private fun buildPollItem(pollContent: MessagePollContent,
|
||||
informationData: MessageInformationData,
|
||||
highlight: Boolean,
|
||||
callback: TimelineEventController.Callback?,
|
||||
attributes: AbsMessageItem.Attributes): PollItem? {
|
||||
val optionViewStates = mutableListOf<PollOptionViewState>()
|
||||
|
||||
private fun buildPollItem(
|
||||
pollContent: MessagePollContent,
|
||||
informationData: MessageInformationData,
|
||||
highlight: Boolean,
|
||||
callback: TimelineEventController.Callback?,
|
||||
attributes: AbsMessageItem.Attributes,
|
||||
): PollItem {
|
||||
val pollResponseSummary = informationData.pollResponseAggregatedSummary
|
||||
val isEnded = pollResponseSummary?.isClosed.orFalse()
|
||||
val didUserVoted = pollResponseSummary?.myVote?.isNotEmpty().orFalse()
|
||||
val winnerVoteCount = pollResponseSummary?.winnerVoteCount
|
||||
val isPollSent = informationData.sendState.isSent()
|
||||
val isPollUndisclosed = pollContent.getBestPollCreationInfo()?.kind == PollType.UNDISCLOSED_UNSTABLE
|
||||
|
||||
val totalVotesText = (pollResponseSummary?.totalVotes ?: 0).let {
|
||||
when {
|
||||
isEnded -> stringProvider.getQuantityString(R.plurals.poll_total_vote_count_after_ended, it, it)
|
||||
isPollUndisclosed -> ""
|
||||
didUserVoted -> stringProvider.getQuantityString(R.plurals.poll_total_vote_count_before_ended_and_voted, it, it)
|
||||
else -> if (it == 0) {
|
||||
stringProvider.getString(R.string.poll_no_votes_cast)
|
||||
} else {
|
||||
stringProvider.getQuantityString(R.plurals.poll_total_vote_count_before_ended_and_not_voted, it, it)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pollContent.getBestPollCreationInfo()?.answers?.forEach { option ->
|
||||
val voteSummary = pollResponseSummary?.votes?.get(option.id)
|
||||
val isMyVote = pollResponseSummary?.myVote == option.id
|
||||
val voteCount = voteSummary?.total ?: 0
|
||||
val votePercentage = voteSummary?.percentage ?: 0.0
|
||||
val optionId = option.id ?: ""
|
||||
val optionAnswer = option.getBestAnswer() ?: ""
|
||||
|
||||
optionViewStates.add(
|
||||
if (!isPollSent) {
|
||||
// Poll event is not send yet. Disable option.
|
||||
PollOptionViewState.PollSending(optionId, optionAnswer)
|
||||
} else if (isEnded) {
|
||||
// Poll is ended. Disable option, show votes and mark the winner.
|
||||
val isWinner = winnerVoteCount != 0 && voteCount == winnerVoteCount
|
||||
PollOptionViewState.PollEnded(optionId, optionAnswer, voteCount, votePercentage, isWinner)
|
||||
} else if (isPollUndisclosed) {
|
||||
// Poll is closed. Enable option, hide votes and mark the user's selection.
|
||||
PollOptionViewState.PollUndisclosed(optionId, optionAnswer, isMyVote)
|
||||
} else if (didUserVoted) {
|
||||
// User voted to the poll, but poll is not ended. Enable option, show votes and mark the user's selection.
|
||||
PollOptionViewState.PollVoted(optionId, optionAnswer, voteCount, votePercentage, isMyVote)
|
||||
} else {
|
||||
// User didn't voted yet and poll is not ended yet. Enable options, hide votes.
|
||||
PollOptionViewState.PollReady(optionId, optionAnswer)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
val question = pollContent.getBestPollCreationInfo()?.question?.getBestQuestion() ?: ""
|
||||
val pollState = createPollState(informationData, pollResponseSummary, pollContent)
|
||||
val pollCreationInfo = pollContent.getBestPollCreationInfo()
|
||||
val questionText = pollCreationInfo?.question?.getBestQuestion().orEmpty()
|
||||
val question = createPollQuestion(informationData, questionText, callback)
|
||||
val optionViewStates = pollCreationInfo?.answers?.mapToOptions(pollState, informationData)
|
||||
val totalVotesText = createTotalVotesText(pollState, pollResponseSummary)
|
||||
|
||||
return PollItem_()
|
||||
.attributes(attributes)
|
||||
.eventId(informationData.eventId)
|
||||
.pollQuestion(
|
||||
if (informationData.hasBeenEdited) {
|
||||
annotateWithEdited(question, callback, informationData)
|
||||
} else {
|
||||
question
|
||||
}.toEpoxyCharSequence()
|
||||
)
|
||||
.pollSent(isPollSent)
|
||||
.totalVotesText(totalVotesText)
|
||||
.optionViewStates(optionViewStates)
|
||||
.edited(informationData.hasBeenEdited)
|
||||
.highlighted(highlight)
|
||||
.leftGuideline(avatarSizeProvider.leftGuideline)
|
||||
.callback(callback)
|
||||
.attributes(attributes)
|
||||
.eventId(informationData.eventId)
|
||||
.pollQuestion(question)
|
||||
.canVote(pollState.isVotable())
|
||||
.totalVotesText(totalVotesText)
|
||||
.optionViewStates(optionViewStates)
|
||||
.edited(informationData.hasBeenEdited)
|
||||
.highlighted(highlight)
|
||||
.leftGuideline(avatarSizeProvider.leftGuideline)
|
||||
.callback(callback)
|
||||
}
|
||||
|
||||
private fun buildAudioMessageItem(params: TimelineItemFactoryParams,
|
||||
messageContent: MessageAudioContent,
|
||||
informationData: MessageInformationData,
|
||||
highlight: Boolean,
|
||||
attributes: AbsMessageItem.Attributes): MessageAudioItem {
|
||||
private fun createPollState(
|
||||
informationData: MessageInformationData,
|
||||
pollResponseSummary: PollResponseData?,
|
||||
pollContent: MessagePollContent,
|
||||
): PollState = when {
|
||||
!informationData.sendState.isSent() -> Sending
|
||||
pollResponseSummary?.isClosed.orFalse() -> Ended
|
||||
pollContent.getBestPollCreationInfo()?.kind == PollType.UNDISCLOSED -> Undisclosed
|
||||
pollResponseSummary?.myVote?.isNotEmpty().orFalse() -> Voted(pollResponseSummary?.totalVotes ?: 0)
|
||||
else -> Ready
|
||||
}
|
||||
|
||||
private fun List<PollAnswer>.mapToOptions(
|
||||
pollState: PollState,
|
||||
informationData: MessageInformationData,
|
||||
) = map { answer ->
|
||||
val pollResponseSummary = informationData.pollResponseAggregatedSummary
|
||||
val winnerVoteCount = pollResponseSummary?.winnerVoteCount
|
||||
val optionId = answer.id ?: ""
|
||||
val optionAnswer = answer.getBestAnswer() ?: ""
|
||||
val voteSummary = pollResponseSummary?.votes?.get(answer.id)
|
||||
val voteCount = voteSummary?.total ?: 0
|
||||
val votePercentage = voteSummary?.percentage ?: 0.0
|
||||
val isMyVote = pollResponseSummary?.myVote == answer.id
|
||||
val isWinner = winnerVoteCount != 0 && voteCount == winnerVoteCount
|
||||
|
||||
when (pollState) {
|
||||
Sending -> PollSending(optionId, optionAnswer)
|
||||
Ready -> PollReady(optionId, optionAnswer)
|
||||
is Voted -> PollVoted(optionId, optionAnswer, voteCount, votePercentage, isMyVote)
|
||||
Undisclosed -> PollUndisclosed(optionId, optionAnswer, isMyVote)
|
||||
Ended -> PollEnded(optionId, optionAnswer, voteCount, votePercentage, isWinner)
|
||||
}
|
||||
}
|
||||
|
||||
private fun createPollQuestion(
|
||||
informationData: MessageInformationData,
|
||||
question: String,
|
||||
callback: TimelineEventController.Callback?,
|
||||
) = if (informationData.hasBeenEdited) {
|
||||
annotateWithEdited(question, callback, informationData)
|
||||
} else {
|
||||
question
|
||||
}.toEpoxyCharSequence()
|
||||
|
||||
private fun createTotalVotesText(
|
||||
pollState: PollState,
|
||||
pollResponseSummary: PollResponseData?,
|
||||
): String {
|
||||
val votes = pollResponseSummary?.totalVotes ?: 0
|
||||
return when {
|
||||
pollState is Ended -> stringProvider.getQuantityString(R.plurals.poll_total_vote_count_after_ended, votes, votes)
|
||||
pollState is Undisclosed -> ""
|
||||
pollState is Voted -> stringProvider.getQuantityString(R.plurals.poll_total_vote_count_before_ended_and_voted, votes, votes)
|
||||
votes == 0 -> stringProvider.getString(R.string.poll_no_votes_cast)
|
||||
else -> stringProvider.getQuantityString(R.plurals.poll_total_vote_count_before_ended_and_not_voted, votes, votes)
|
||||
}
|
||||
}
|
||||
|
||||
private fun buildAudioMessageItem(
|
||||
params: TimelineItemFactoryParams,
|
||||
messageContent: MessageAudioContent,
|
||||
informationData: MessageInformationData,
|
||||
highlight: Boolean,
|
||||
attributes: AbsMessageItem.Attributes
|
||||
): MessageAudioItem {
|
||||
val fileUrl = getAudioFileUrl(messageContent, informationData)
|
||||
val playbackControlButtonClickListener = createOnPlaybackButtonClickListener(messageContent, informationData, params)
|
||||
|
||||
|
@ -351,11 +377,13 @@ class MessageItemFactory @Inject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
private fun buildVoiceMessageItem(params: TimelineItemFactoryParams,
|
||||
messageContent: MessageAudioContent,
|
||||
informationData: MessageInformationData,
|
||||
highlight: Boolean,
|
||||
attributes: AbsMessageItem.Attributes): MessageVoiceItem {
|
||||
private fun buildVoiceMessageItem(
|
||||
params: TimelineItemFactoryParams,
|
||||
messageContent: MessageAudioContent,
|
||||
informationData: MessageInformationData,
|
||||
highlight: Boolean,
|
||||
attributes: AbsMessageItem.Attributes
|
||||
): MessageVoiceItem {
|
||||
val fileUrl = getAudioFileUrl(messageContent, informationData)
|
||||
val playbackControlButtonClickListener = createOnPlaybackButtonClickListener(messageContent, informationData, params)
|
||||
|
||||
|
@ -372,26 +400,28 @@ class MessageItemFactory @Inject constructor(
|
|||
}
|
||||
|
||||
return MessageVoiceItem_()
|
||||
.attributes(attributes)
|
||||
.duration(messageContent.audioWaveformInfo?.duration ?: 0)
|
||||
.waveform(messageContent.audioWaveformInfo?.waveform?.toFft().orEmpty())
|
||||
.playbackControlButtonClickListener(playbackControlButtonClickListener)
|
||||
.waveformTouchListener(waveformTouchListener)
|
||||
.audioMessagePlaybackTracker(audioMessagePlaybackTracker)
|
||||
.isLocalFile(localFilesHelper.isLocalFile(fileUrl))
|
||||
.mxcUrl(fileUrl)
|
||||
.contentUploadStateTrackerBinder(contentUploadStateTrackerBinder)
|
||||
.contentDownloadStateTrackerBinder(contentDownloadStateTrackerBinder)
|
||||
.highlighted(highlight)
|
||||
.leftGuideline(avatarSizeProvider.leftGuideline)
|
||||
.attributes(attributes)
|
||||
.duration(messageContent.audioWaveformInfo?.duration ?: 0)
|
||||
.waveform(messageContent.audioWaveformInfo?.waveform?.toFft().orEmpty())
|
||||
.playbackControlButtonClickListener(playbackControlButtonClickListener)
|
||||
.waveformTouchListener(waveformTouchListener)
|
||||
.audioMessagePlaybackTracker(audioMessagePlaybackTracker)
|
||||
.isLocalFile(localFilesHelper.isLocalFile(fileUrl))
|
||||
.mxcUrl(fileUrl)
|
||||
.contentUploadStateTrackerBinder(contentUploadStateTrackerBinder)
|
||||
.contentDownloadStateTrackerBinder(contentDownloadStateTrackerBinder)
|
||||
.highlighted(highlight)
|
||||
.leftGuideline(avatarSizeProvider.leftGuideline)
|
||||
}
|
||||
|
||||
private fun buildVerificationRequestMessageItem(messageContent: MessageVerificationRequestContent,
|
||||
@Suppress("UNUSED_PARAMETER")
|
||||
informationData: MessageInformationData,
|
||||
highlight: Boolean,
|
||||
callback: TimelineEventController.Callback?,
|
||||
attributes: AbsMessageItem.Attributes): VerificationRequestItem? {
|
||||
private fun buildVerificationRequestMessageItem(
|
||||
messageContent: MessageVerificationRequestContent,
|
||||
@Suppress("UNUSED_PARAMETER")
|
||||
informationData: MessageInformationData,
|
||||
highlight: Boolean,
|
||||
callback: TimelineEventController.Callback?,
|
||||
attributes: AbsMessageItem.Attributes,
|
||||
): VerificationRequestItem? {
|
||||
// If this request is not sent by me or sent to me, we should ignore it in timeline
|
||||
val myUserId = session.myUserId
|
||||
if (informationData.senderId != myUserId && messageContent.toUserId != myUserId) {
|
||||
|
@ -405,149 +435,160 @@ class MessageItemFactory @Inject constructor(
|
|||
informationData.memberName
|
||||
}
|
||||
return VerificationRequestItem_()
|
||||
.attributes(
|
||||
VerificationRequestItem.Attributes(
|
||||
otherUserId = otherUserId,
|
||||
otherUserName = otherUserName.toString(),
|
||||
referenceId = informationData.eventId,
|
||||
informationData = informationData,
|
||||
avatarRenderer = attributes.avatarRenderer,
|
||||
messageColorProvider = attributes.messageColorProvider,
|
||||
itemLongClickListener = attributes.itemLongClickListener,
|
||||
itemClickListener = attributes.itemClickListener,
|
||||
reactionPillCallback = attributes.reactionPillCallback,
|
||||
readReceiptsCallback = attributes.readReceiptsCallback,
|
||||
emojiTypeFace = attributes.emojiTypeFace,
|
||||
reactionsSummaryEvents = attributes.reactionsSummaryEvents
|
||||
)
|
||||
.attributes(
|
||||
VerificationRequestItem.Attributes(
|
||||
otherUserId = otherUserId,
|
||||
otherUserName = otherUserName.toString(),
|
||||
referenceId = informationData.eventId,
|
||||
informationData = informationData,
|
||||
avatarRenderer = attributes.avatarRenderer,
|
||||
messageColorProvider = attributes.messageColorProvider,
|
||||
itemLongClickListener = attributes.itemLongClickListener,
|
||||
itemClickListener = attributes.itemClickListener,
|
||||
reactionPillCallback = attributes.reactionPillCallback,
|
||||
readReceiptsCallback = attributes.readReceiptsCallback,
|
||||
emojiTypeFace = attributes.emojiTypeFace,
|
||||
reactionsSummaryEvents = attributes.reactionsSummaryEvents,
|
||||
)
|
||||
.callback(callback)
|
||||
.highlighted(highlight)
|
||||
.leftGuideline(avatarSizeProvider.leftGuideline)
|
||||
)
|
||||
.callback(callback)
|
||||
.highlighted(highlight)
|
||||
.leftGuideline(avatarSizeProvider.leftGuideline)
|
||||
}
|
||||
|
||||
private fun buildFileMessageItem(messageContent: MessageFileContent,
|
||||
highlight: Boolean,
|
||||
attributes: AbsMessageItem.Attributes): MessageFileItem {
|
||||
private fun buildFileMessageItem(
|
||||
messageContent: MessageFileContent,
|
||||
highlight: Boolean,
|
||||
attributes: AbsMessageItem.Attributes,
|
||||
): MessageFileItem {
|
||||
val mxcUrl = messageContent.getFileUrl() ?: ""
|
||||
return MessageFileItem_()
|
||||
.attributes(attributes)
|
||||
.leftGuideline(avatarSizeProvider.leftGuideline)
|
||||
.isLocalFile(localFilesHelper.isLocalFile(messageContent.getFileUrl()))
|
||||
.isDownloaded(session.fileService().isFileInCache(messageContent))
|
||||
.mxcUrl(mxcUrl)
|
||||
.contentUploadStateTrackerBinder(contentUploadStateTrackerBinder)
|
||||
.contentDownloadStateTrackerBinder(contentDownloadStateTrackerBinder)
|
||||
.highlighted(highlight)
|
||||
.filename(messageContent.body)
|
||||
.iconRes(R.drawable.ic_paperclip)
|
||||
.attributes(attributes)
|
||||
.leftGuideline(avatarSizeProvider.leftGuideline)
|
||||
.isLocalFile(localFilesHelper.isLocalFile(messageContent.getFileUrl()))
|
||||
.isDownloaded(session.fileService().isFileInCache(messageContent))
|
||||
.mxcUrl(mxcUrl)
|
||||
.contentUploadStateTrackerBinder(contentUploadStateTrackerBinder)
|
||||
.contentDownloadStateTrackerBinder(contentDownloadStateTrackerBinder)
|
||||
.highlighted(highlight)
|
||||
.filename(messageContent.body)
|
||||
.iconRes(R.drawable.ic_paperclip)
|
||||
}
|
||||
|
||||
private fun buildAudioContent(params: TimelineItemFactoryParams,
|
||||
messageContent: MessageAudioContent,
|
||||
informationData: MessageInformationData,
|
||||
highlight: Boolean,
|
||||
attributes: AbsMessageItem.Attributes) =
|
||||
if (messageContent.voiceMessageIndicator != null) {
|
||||
private fun buildAudioContent(
|
||||
params: TimelineItemFactoryParams,
|
||||
messageContent: MessageAudioContent,
|
||||
informationData: MessageInformationData,
|
||||
highlight: Boolean,
|
||||
attributes: AbsMessageItem.Attributes,
|
||||
) = if (messageContent.voiceMessageIndicator != null) {
|
||||
buildVoiceMessageItem(params, messageContent, informationData, highlight, attributes)
|
||||
} else {
|
||||
buildAudioMessageItem(params, messageContent, informationData, highlight, attributes)
|
||||
}
|
||||
|
||||
private fun buildNotHandledMessageItem(messageContent: MessageContent,
|
||||
informationData: MessageInformationData,
|
||||
highlight: Boolean,
|
||||
callback: TimelineEventController.Callback?,
|
||||
attributes: AbsMessageItem.Attributes): MessageTextItem? {
|
||||
private fun buildNotHandledMessageItem(
|
||||
messageContent: MessageContent,
|
||||
informationData: MessageInformationData,
|
||||
highlight: Boolean,
|
||||
callback: TimelineEventController.Callback?,
|
||||
attributes: AbsMessageItem.Attributes)
|
||||
: MessageTextItem? {
|
||||
// For compatibility reason we should display the body
|
||||
return buildMessageTextItem(messageContent.body, false, informationData, highlight, callback, attributes)
|
||||
}
|
||||
|
||||
private fun buildImageMessageItem(messageContent: MessageImageInfoContent,
|
||||
@Suppress("UNUSED_PARAMETER")
|
||||
informationData: MessageInformationData,
|
||||
highlight: Boolean,
|
||||
callback: TimelineEventController.Callback?,
|
||||
attributes: AbsMessageItem.Attributes): MessageImageVideoItem? {
|
||||
private fun buildImageMessageItem(
|
||||
messageContent: MessageImageInfoContent,
|
||||
@Suppress("UNUSED_PARAMETER")
|
||||
informationData: MessageInformationData,
|
||||
highlight: Boolean,
|
||||
callback: TimelineEventController.Callback?,
|
||||
attributes: AbsMessageItem.Attributes,
|
||||
): MessageImageVideoItem? {
|
||||
val (maxWidth, maxHeight) = timelineMediaSizeProvider.getMaxSize()
|
||||
val data = ImageContentRenderer.Data(
|
||||
eventId = informationData.eventId,
|
||||
filename = messageContent.body,
|
||||
mimeType = messageContent.mimeType,
|
||||
url = messageContent.getFileUrl(),
|
||||
elementToDecrypt = messageContent.encryptedFileInfo?.toElementToDecrypt(),
|
||||
height = messageContent.info?.height,
|
||||
maxHeight = maxHeight,
|
||||
width = messageContent.info?.width,
|
||||
maxWidth = maxWidth,
|
||||
allowNonMxcUrls = informationData.sendState.isSending()
|
||||
eventId = informationData.eventId,
|
||||
filename = messageContent.body,
|
||||
mimeType = messageContent.mimeType,
|
||||
url = messageContent.getFileUrl(),
|
||||
elementToDecrypt = messageContent.encryptedFileInfo?.toElementToDecrypt(),
|
||||
height = messageContent.info?.height,
|
||||
maxHeight = maxHeight,
|
||||
width = messageContent.info?.width,
|
||||
maxWidth = maxWidth,
|
||||
allowNonMxcUrls = informationData.sendState.isSending()
|
||||
)
|
||||
return MessageImageVideoItem_()
|
||||
.attributes(attributes)
|
||||
.leftGuideline(avatarSizeProvider.leftGuideline)
|
||||
.imageContentRenderer(imageContentRenderer)
|
||||
.contentUploadStateTrackerBinder(contentUploadStateTrackerBinder)
|
||||
.playable(messageContent.mimeType == MimeTypes.Gif)
|
||||
.highlighted(highlight)
|
||||
.mediaData(data)
|
||||
.apply {
|
||||
if (messageContent.msgType == MessageType.MSGTYPE_STICKER_LOCAL) {
|
||||
mode(ImageContentRenderer.Mode.STICKER)
|
||||
clickListener { view ->
|
||||
callback?.onImageMessageClicked(messageContent, data, view, listOf(data))
|
||||
}
|
||||
} else {
|
||||
clickListener { view ->
|
||||
callback?.onImageMessageClicked(messageContent, data, view, emptyList())
|
||||
}
|
||||
.attributes(attributes)
|
||||
.leftGuideline(avatarSizeProvider.leftGuideline)
|
||||
.imageContentRenderer(imageContentRenderer)
|
||||
.contentUploadStateTrackerBinder(contentUploadStateTrackerBinder)
|
||||
.playable(messageContent.mimeType == MimeTypes.Gif)
|
||||
.highlighted(highlight)
|
||||
.mediaData(data)
|
||||
.apply {
|
||||
if (messageContent.msgType == MessageType.MSGTYPE_STICKER_LOCAL) {
|
||||
mode(ImageContentRenderer.Mode.STICKER)
|
||||
clickListener { view ->
|
||||
callback?.onImageMessageClicked(messageContent, data, view, listOf(data))
|
||||
}
|
||||
} else {
|
||||
clickListener { view ->
|
||||
callback?.onImageMessageClicked(messageContent, data, view, emptyList())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun buildVideoMessageItem(messageContent: MessageVideoContent,
|
||||
informationData: MessageInformationData,
|
||||
highlight: Boolean,
|
||||
callback: TimelineEventController.Callback?,
|
||||
attributes: AbsMessageItem.Attributes): MessageImageVideoItem? {
|
||||
private fun buildVideoMessageItem(
|
||||
messageContent: MessageVideoContent,
|
||||
informationData: MessageInformationData,
|
||||
highlight: Boolean,
|
||||
callback: TimelineEventController.Callback?,
|
||||
attributes: AbsMessageItem.Attributes,
|
||||
): MessageImageVideoItem? {
|
||||
val (maxWidth, maxHeight) = timelineMediaSizeProvider.getMaxSize()
|
||||
val thumbnailData = ImageContentRenderer.Data(
|
||||
eventId = informationData.eventId,
|
||||
filename = messageContent.body,
|
||||
mimeType = messageContent.mimeType,
|
||||
url = messageContent.videoInfo?.getThumbnailUrl(),
|
||||
elementToDecrypt = messageContent.videoInfo?.thumbnailFile?.toElementToDecrypt(),
|
||||
height = messageContent.videoInfo?.height,
|
||||
maxHeight = maxHeight,
|
||||
width = messageContent.videoInfo?.width,
|
||||
maxWidth = maxWidth,
|
||||
allowNonMxcUrls = informationData.sendState.isSending()
|
||||
eventId = informationData.eventId,
|
||||
filename = messageContent.body,
|
||||
mimeType = messageContent.mimeType,
|
||||
url = messageContent.videoInfo?.getThumbnailUrl(),
|
||||
elementToDecrypt = messageContent.videoInfo?.thumbnailFile?.toElementToDecrypt(),
|
||||
height = messageContent.videoInfo?.height,
|
||||
maxHeight = maxHeight,
|
||||
width = messageContent.videoInfo?.width,
|
||||
maxWidth = maxWidth,
|
||||
allowNonMxcUrls = informationData.sendState.isSending()
|
||||
)
|
||||
|
||||
val videoData = VideoContentRenderer.Data(
|
||||
eventId = informationData.eventId,
|
||||
filename = messageContent.body,
|
||||
mimeType = messageContent.mimeType,
|
||||
url = messageContent.getFileUrl(),
|
||||
elementToDecrypt = messageContent.encryptedFileInfo?.toElementToDecrypt(),
|
||||
thumbnailMediaData = thumbnailData
|
||||
eventId = informationData.eventId,
|
||||
filename = messageContent.body,
|
||||
mimeType = messageContent.mimeType,
|
||||
url = messageContent.getFileUrl(),
|
||||
elementToDecrypt = messageContent.encryptedFileInfo?.toElementToDecrypt(),
|
||||
thumbnailMediaData = thumbnailData
|
||||
)
|
||||
|
||||
return MessageImageVideoItem_()
|
||||
.leftGuideline(avatarSizeProvider.leftGuideline)
|
||||
.attributes(attributes)
|
||||
.imageContentRenderer(imageContentRenderer)
|
||||
.contentUploadStateTrackerBinder(contentUploadStateTrackerBinder)
|
||||
.playable(true)
|
||||
.highlighted(highlight)
|
||||
.mediaData(thumbnailData)
|
||||
.clickListener { view -> callback?.onVideoMessageClicked(messageContent, videoData, view.findViewById(R.id.messageThumbnailView)) }
|
||||
.leftGuideline(avatarSizeProvider.leftGuideline)
|
||||
.attributes(attributes)
|
||||
.imageContentRenderer(imageContentRenderer)
|
||||
.contentUploadStateTrackerBinder(contentUploadStateTrackerBinder)
|
||||
.playable(true)
|
||||
.highlighted(highlight)
|
||||
.mediaData(thumbnailData)
|
||||
.clickListener { view -> callback?.onVideoMessageClicked(messageContent, videoData, view.findViewById(R.id.messageThumbnailView)) }
|
||||
}
|
||||
|
||||
private fun buildItemForTextContent(messageContent: MessageTextContent,
|
||||
informationData: MessageInformationData,
|
||||
highlight: Boolean,
|
||||
callback: TimelineEventController.Callback?,
|
||||
attributes: AbsMessageItem.Attributes): VectorEpoxyModel<*>? {
|
||||
private fun buildItemForTextContent(
|
||||
messageContent: MessageTextContent,
|
||||
informationData: MessageInformationData,
|
||||
highlight: Boolean,
|
||||
callback: TimelineEventController.Callback?,
|
||||
attributes: AbsMessageItem.Attributes,
|
||||
): VectorEpoxyModel<*>? {
|
||||
val matrixFormattedBody = messageContent.matrixFormattedBody
|
||||
return if (matrixFormattedBody != null) {
|
||||
buildFormattedTextItem(matrixFormattedBody, informationData, highlight, callback, attributes)
|
||||
|
@ -556,50 +597,56 @@ class MessageItemFactory @Inject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
private fun buildFormattedTextItem(matrixFormattedBody: String,
|
||||
informationData: MessageInformationData,
|
||||
highlight: Boolean,
|
||||
callback: TimelineEventController.Callback?,
|
||||
attributes: AbsMessageItem.Attributes): MessageTextItem? {
|
||||
private fun buildFormattedTextItem(
|
||||
matrixFormattedBody: String,
|
||||
informationData: MessageInformationData,
|
||||
highlight: Boolean,
|
||||
callback: TimelineEventController.Callback?,
|
||||
attributes: AbsMessageItem.Attributes,
|
||||
): MessageTextItem? {
|
||||
val compressed = htmlCompressor.compress(matrixFormattedBody)
|
||||
val renderedFormattedBody = htmlRenderer.get().render(compressed, pillsPostProcessor) as Spanned
|
||||
return buildMessageTextItem(renderedFormattedBody, true, informationData, highlight, callback, attributes)
|
||||
}
|
||||
|
||||
private fun buildMessageTextItem(body: CharSequence,
|
||||
isFormatted: Boolean,
|
||||
informationData: MessageInformationData,
|
||||
highlight: Boolean,
|
||||
callback: TimelineEventController.Callback?,
|
||||
attributes: AbsMessageItem.Attributes): MessageTextItem? {
|
||||
private fun buildMessageTextItem(
|
||||
body: CharSequence,
|
||||
isFormatted: Boolean,
|
||||
informationData: MessageInformationData,
|
||||
highlight: Boolean,
|
||||
callback: TimelineEventController.Callback?,
|
||||
attributes: AbsMessageItem.Attributes,
|
||||
): MessageTextItem? {
|
||||
val renderedBody = textRenderer.render(body)
|
||||
val bindingOptions = spanUtils.getBindingOptions(renderedBody)
|
||||
val linkifiedBody = renderedBody.linkify(callback)
|
||||
|
||||
return MessageTextItem_()
|
||||
.message(
|
||||
if (informationData.hasBeenEdited) {
|
||||
annotateWithEdited(linkifiedBody, callback, informationData)
|
||||
} else {
|
||||
linkifiedBody
|
||||
}.toEpoxyCharSequence()
|
||||
)
|
||||
.useBigFont(linkifiedBody.length <= MAX_NUMBER_OF_EMOJI_FOR_BIG_FONT * 2 && containsOnlyEmojis(linkifiedBody.toString()))
|
||||
.bindingOptions(bindingOptions)
|
||||
.markwonPlugins(htmlRenderer.get().plugins)
|
||||
.searchForPills(isFormatted)
|
||||
.previewUrlRetriever(callback?.getPreviewUrlRetriever())
|
||||
.imageContentRenderer(imageContentRenderer)
|
||||
.previewUrlCallback(callback)
|
||||
.leftGuideline(avatarSizeProvider.leftGuideline)
|
||||
.attributes(attributes)
|
||||
.highlighted(highlight)
|
||||
.movementMethod(createLinkMovementMethod(callback))
|
||||
.message(
|
||||
if (informationData.hasBeenEdited) {
|
||||
annotateWithEdited(linkifiedBody, callback, informationData)
|
||||
} else {
|
||||
linkifiedBody
|
||||
}.toEpoxyCharSequence()
|
||||
)
|
||||
.useBigFont(linkifiedBody.length <= MAX_NUMBER_OF_EMOJI_FOR_BIG_FONT * 2 && containsOnlyEmojis(linkifiedBody.toString()))
|
||||
.bindingOptions(bindingOptions)
|
||||
.markwonPlugins(htmlRenderer.get().plugins)
|
||||
.searchForPills(isFormatted)
|
||||
.previewUrlRetriever(callback?.getPreviewUrlRetriever())
|
||||
.imageContentRenderer(imageContentRenderer)
|
||||
.previewUrlCallback(callback)
|
||||
.leftGuideline(avatarSizeProvider.leftGuideline)
|
||||
.attributes(attributes)
|
||||
.highlighted(highlight)
|
||||
.movementMethod(createLinkMovementMethod(callback))
|
||||
}
|
||||
|
||||
private fun annotateWithEdited(linkifiedBody: CharSequence,
|
||||
callback: TimelineEventController.Callback?,
|
||||
informationData: MessageInformationData): Spannable {
|
||||
private fun annotateWithEdited(
|
||||
linkifiedBody: CharSequence,
|
||||
callback: TimelineEventController.Callback?,
|
||||
informationData: MessageInformationData,
|
||||
): Spannable {
|
||||
val spannable = SpannableStringBuilder()
|
||||
spannable.append(linkifiedBody)
|
||||
val editedSuffix = stringProvider.getString(R.string.edited_suffix)
|
||||
|
@ -608,17 +655,17 @@ class MessageItemFactory @Inject constructor(
|
|||
val editStart = spannable.lastIndexOf(editedSuffix)
|
||||
val editEnd = editStart + editedSuffix.length
|
||||
spannable.setSpan(
|
||||
ForegroundColorSpan(color),
|
||||
editStart,
|
||||
editEnd,
|
||||
Spanned.SPAN_INCLUSIVE_EXCLUSIVE)
|
||||
ForegroundColorSpan(color),
|
||||
editStart,
|
||||
editEnd,
|
||||
Spanned.SPAN_INCLUSIVE_EXCLUSIVE)
|
||||
|
||||
// Note: text size is set to 14sp
|
||||
spannable.setSpan(
|
||||
AbsoluteSizeSpan(dimensionConverter.spToPx(13)),
|
||||
editStart,
|
||||
editEnd,
|
||||
Spanned.SPAN_INCLUSIVE_EXCLUSIVE)
|
||||
AbsoluteSizeSpan(dimensionConverter.spToPx(13)),
|
||||
editStart,
|
||||
editEnd,
|
||||
Spanned.SPAN_INCLUSIVE_EXCLUSIVE)
|
||||
|
||||
spannable.setSpan(object : ClickableSpan() {
|
||||
override fun onClick(widget: View) {
|
||||
|
@ -629,18 +676,20 @@ class MessageItemFactory @Inject constructor(
|
|||
// nop
|
||||
}
|
||||
},
|
||||
editStart,
|
||||
editEnd,
|
||||
Spanned.SPAN_INCLUSIVE_EXCLUSIVE)
|
||||
editStart,
|
||||
editEnd,
|
||||
Spanned.SPAN_INCLUSIVE_EXCLUSIVE)
|
||||
return spannable
|
||||
}
|
||||
|
||||
private fun buildNoticeMessageItem(messageContent: MessageNoticeContent,
|
||||
@Suppress("UNUSED_PARAMETER")
|
||||
informationData: MessageInformationData,
|
||||
highlight: Boolean,
|
||||
callback: TimelineEventController.Callback?,
|
||||
attributes: AbsMessageItem.Attributes): MessageTextItem? {
|
||||
private fun buildNoticeMessageItem(
|
||||
messageContent: MessageNoticeContent,
|
||||
@Suppress("UNUSED_PARAMETER")
|
||||
informationData: MessageInformationData,
|
||||
highlight: Boolean,
|
||||
callback: TimelineEventController.Callback?,
|
||||
attributes: AbsMessageItem.Attributes,
|
||||
): MessageTextItem? {
|
||||
val htmlBody = messageContent.getHtmlBody()
|
||||
val formattedBody = span {
|
||||
text = htmlBody
|
||||
|
@ -652,22 +701,24 @@ class MessageItemFactory @Inject constructor(
|
|||
val message = formattedBody.linkify(callback)
|
||||
|
||||
return MessageTextItem_()
|
||||
.leftGuideline(avatarSizeProvider.leftGuideline)
|
||||
.previewUrlRetriever(callback?.getPreviewUrlRetriever())
|
||||
.imageContentRenderer(imageContentRenderer)
|
||||
.previewUrlCallback(callback)
|
||||
.attributes(attributes)
|
||||
.message(message.toEpoxyCharSequence())
|
||||
.bindingOptions(bindingOptions)
|
||||
.highlighted(highlight)
|
||||
.movementMethod(createLinkMovementMethod(callback))
|
||||
.leftGuideline(avatarSizeProvider.leftGuideline)
|
||||
.previewUrlRetriever(callback?.getPreviewUrlRetriever())
|
||||
.imageContentRenderer(imageContentRenderer)
|
||||
.previewUrlCallback(callback)
|
||||
.attributes(attributes)
|
||||
.message(message.toEpoxyCharSequence())
|
||||
.bindingOptions(bindingOptions)
|
||||
.highlighted(highlight)
|
||||
.movementMethod(createLinkMovementMethod(callback))
|
||||
}
|
||||
|
||||
private fun buildEmoteMessageItem(messageContent: MessageEmoteContent,
|
||||
informationData: MessageInformationData,
|
||||
highlight: Boolean,
|
||||
callback: TimelineEventController.Callback?,
|
||||
attributes: AbsMessageItem.Attributes): MessageTextItem? {
|
||||
private fun buildEmoteMessageItem(
|
||||
messageContent: MessageEmoteContent,
|
||||
informationData: MessageInformationData,
|
||||
highlight: Boolean,
|
||||
callback: TimelineEventController.Callback?,
|
||||
attributes: AbsMessageItem.Attributes,
|
||||
): MessageTextItem? {
|
||||
val formattedBody = SpannableStringBuilder()
|
||||
formattedBody.append("* ${informationData.memberName} ")
|
||||
formattedBody.append(messageContent.getHtmlBody())
|
||||
|
@ -675,46 +726,48 @@ class MessageItemFactory @Inject constructor(
|
|||
val message = formattedBody.linkify(callback)
|
||||
|
||||
return MessageTextItem_()
|
||||
.message(
|
||||
if (informationData.hasBeenEdited) {
|
||||
annotateWithEdited(message, callback, informationData)
|
||||
} else {
|
||||
message
|
||||
}.toEpoxyCharSequence()
|
||||
)
|
||||
.bindingOptions(bindingOptions)
|
||||
.leftGuideline(avatarSizeProvider.leftGuideline)
|
||||
.previewUrlRetriever(callback?.getPreviewUrlRetriever())
|
||||
.imageContentRenderer(imageContentRenderer)
|
||||
.previewUrlCallback(callback)
|
||||
.attributes(attributes)
|
||||
.highlighted(highlight)
|
||||
.movementMethod(createLinkMovementMethod(callback))
|
||||
.message(
|
||||
if (informationData.hasBeenEdited) {
|
||||
annotateWithEdited(message, callback, informationData)
|
||||
} else {
|
||||
message
|
||||
}.toEpoxyCharSequence()
|
||||
)
|
||||
.bindingOptions(bindingOptions)
|
||||
.leftGuideline(avatarSizeProvider.leftGuideline)
|
||||
.previewUrlRetriever(callback?.getPreviewUrlRetriever())
|
||||
.imageContentRenderer(imageContentRenderer)
|
||||
.previewUrlCallback(callback)
|
||||
.attributes(attributes)
|
||||
.highlighted(highlight)
|
||||
.movementMethod(createLinkMovementMethod(callback))
|
||||
}
|
||||
|
||||
private fun MessageContentWithFormattedBody.getHtmlBody(): CharSequence {
|
||||
return matrixFormattedBody
|
||||
?.let { htmlCompressor.compress(it) }
|
||||
?.let { htmlRenderer.get().render(it, pillsPostProcessor) }
|
||||
?.let { htmlCompressor.compress(it) }
|
||||
?.let { htmlRenderer.get().render(it, pillsPostProcessor) }
|
||||
?: body
|
||||
}
|
||||
|
||||
private fun buildRedactedItem(attributes: AbsMessageItem.Attributes,
|
||||
highlight: Boolean): RedactedMessageItem? {
|
||||
private fun buildRedactedItem(
|
||||
attributes: AbsMessageItem.Attributes,
|
||||
highlight: Boolean,
|
||||
): RedactedMessageItem? {
|
||||
return RedactedMessageItem_()
|
||||
.layout(attributes.informationData.messageLayout.layoutRes)
|
||||
.leftGuideline(avatarSizeProvider.leftGuideline)
|
||||
.attributes(attributes)
|
||||
.highlighted(highlight)
|
||||
.layout(attributes.informationData.messageLayout.layoutRes)
|
||||
.leftGuideline(avatarSizeProvider.leftGuideline)
|
||||
.attributes(attributes)
|
||||
.highlighted(highlight)
|
||||
}
|
||||
|
||||
private fun List<Int?>?.toFft(): List<Int>? {
|
||||
return this
|
||||
?.filterNotNull()
|
||||
?.map {
|
||||
// Value comes from AudioWaveformView.MAX_FFT, and 1024 is the max value in the Matrix spec
|
||||
it * AudioWaveformView.MAX_FFT / 1024
|
||||
}
|
||||
?.filterNotNull()
|
||||
?.map {
|
||||
// Value comes from AudioWaveformView.MAX_FFT, and 1024 is the max value in the Matrix spec
|
||||
it * AudioWaveformView.MAX_FFT / 1024
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
|
|
@ -39,13 +39,13 @@ abstract class PollItem : AbsMessageItem<PollItem.Holder>() {
|
|||
var eventId: String? = null
|
||||
|
||||
@EpoxyAttribute
|
||||
var pollSent: Boolean = false
|
||||
var canVote: Boolean = false
|
||||
|
||||
@EpoxyAttribute
|
||||
var totalVotesText: String? = null
|
||||
|
||||
@EpoxyAttribute
|
||||
var edited: Boolean = false
|
||||
@EpoxyAttribute
|
||||
var edited: Boolean = false
|
||||
|
||||
@EpoxyAttribute
|
||||
lateinit var optionViewStates: List<PollOptionViewState>
|
||||
|
@ -54,7 +54,6 @@ abstract class PollItem : AbsMessageItem<PollItem.Holder>() {
|
|||
|
||||
override fun bind(holder: Holder) {
|
||||
super.bind(holder)
|
||||
val relatedEventId = eventId ?: return
|
||||
|
||||
renderSendState(holder.view, holder.questionTextView)
|
||||
|
||||
|
@ -73,13 +72,19 @@ abstract class PollItem : AbsMessageItem<PollItem.Holder>() {
|
|||
optionViewStates.forEachIndexed { index, optionViewState ->
|
||||
views.getOrNull(index)?.let {
|
||||
it.render(optionViewState)
|
||||
it.setOnClickListener {
|
||||
callback?.onTimelineItemAction(RoomDetailAction.VoteToPoll(relatedEventId, optionViewState.optionId))
|
||||
}
|
||||
it.setOnClickListener { onPollItemClick(optionViewState) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun onPollItemClick(optionViewState: PollOptionViewState) {
|
||||
val relatedEventId = eventId
|
||||
|
||||
if (canVote && relatedEventId != null) {
|
||||
callback?.onTimelineItemAction(RoomDetailAction.VoteToPoll(relatedEventId, optionViewState.optionId))
|
||||
}
|
||||
}
|
||||
|
||||
class Holder : AbsMessageItem.Holder(STUB_ID) {
|
||||
val questionTextView by bind<TextView>(R.id.questionTextView)
|
||||
val optionsContainer by bind<LinearLayout>(R.id.optionsContainer)
|
||||
|
|
|
@ -52,6 +52,7 @@ import im.vector.app.features.home.room.list.actions.RoomListQuickActionsSharedA
|
|||
import im.vector.app.features.home.room.list.widget.NotifsFabMenuView
|
||||
import im.vector.app.features.notifications.NotificationDrawerManager
|
||||
import kotlinx.coroutines.flow.collect
|
||||
import kotlinx.coroutines.flow.filter
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import kotlinx.coroutines.launch
|
||||
|
@ -147,8 +148,10 @@ class RoomListFragment @Inject constructor(
|
|||
}
|
||||
|
||||
private fun refreshCollapseStates() {
|
||||
val sectionsCount = adapterInfosList.count { !it.sectionHeaderAdapter.roomsSectionData.isHidden }
|
||||
roomListViewModel.sections.forEachIndexed { index, roomsSection ->
|
||||
val actualBlock = adapterInfosList[index]
|
||||
val isRoomSectionCollapsable = sectionsCount > 1
|
||||
val isRoomSectionExpanded = roomsSection.isExpanded.value.orTrue()
|
||||
if (actualBlock.section.isExpanded && !isRoomSectionExpanded) {
|
||||
// mark controller as collapsed
|
||||
|
@ -157,12 +160,18 @@ class RoomListFragment @Inject constructor(
|
|||
// we must expand!
|
||||
actualBlock.contentEpoxyController.setCollapsed(false)
|
||||
}
|
||||
actualBlock.section = actualBlock.section.copy(
|
||||
isExpanded = isRoomSectionExpanded
|
||||
)
|
||||
actualBlock.sectionHeaderAdapter.updateSection(
|
||||
actualBlock.sectionHeaderAdapter.roomsSectionData.copy(isExpanded = isRoomSectionExpanded)
|
||||
)
|
||||
actualBlock.section = actualBlock.section.copy(isExpanded = isRoomSectionExpanded)
|
||||
actualBlock.sectionHeaderAdapter.updateSection {
|
||||
it.copy(
|
||||
isExpanded = isRoomSectionExpanded,
|
||||
isCollapsable = isRoomSectionCollapsable
|
||||
)
|
||||
}
|
||||
|
||||
if (!isRoomSectionExpanded && !isRoomSectionCollapsable) {
|
||||
// force expand if the section is not collapsable
|
||||
roomListViewModel.handle(RoomListAction.ToggleSection(roomsSection))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -270,13 +279,12 @@ class RoomListFragment @Inject constructor(
|
|||
|
||||
val concatAdapter = ConcatAdapter()
|
||||
|
||||
roomListViewModel.sections.forEach { section ->
|
||||
val sectionAdapter = SectionHeaderAdapter {
|
||||
roomListViewModel.handle(RoomListAction.ToggleSection(section))
|
||||
}.also {
|
||||
it.updateSection(SectionHeaderAdapter.RoomsSectionData(section.sectionName))
|
||||
roomListViewModel.sections.forEachIndexed { index, section ->
|
||||
val sectionAdapter = SectionHeaderAdapter(SectionHeaderAdapter.RoomsSectionData(section.sectionName)) {
|
||||
if (adapterInfosList[index].sectionHeaderAdapter.roomsSectionData.isCollapsable) {
|
||||
roomListViewModel.handle(RoomListAction.ToggleSection(section))
|
||||
}
|
||||
}
|
||||
|
||||
val contentAdapter =
|
||||
when {
|
||||
section.livePages != null -> {
|
||||
|
@ -284,18 +292,23 @@ class RoomListFragment @Inject constructor(
|
|||
.also { controller ->
|
||||
section.livePages.observe(viewLifecycleOwner) { pl ->
|
||||
controller.submitList(pl)
|
||||
sectionAdapter.updateSection(sectionAdapter.roomsSectionData.copy(
|
||||
isHidden = pl.isEmpty(),
|
||||
isLoading = false
|
||||
))
|
||||
sectionAdapter.updateSection {
|
||||
it.copy(
|
||||
isHidden = pl.isEmpty(),
|
||||
isLoading = false
|
||||
)
|
||||
}
|
||||
refreshCollapseStates()
|
||||
checkEmptyState()
|
||||
}
|
||||
observeItemCount(section, sectionAdapter)
|
||||
section.notificationCount.observe(viewLifecycleOwner) { counts ->
|
||||
sectionAdapter.updateSection(sectionAdapter.roomsSectionData.copy(
|
||||
notificationCount = counts.totalCount,
|
||||
isHighlighted = counts.isHighlight
|
||||
))
|
||||
sectionAdapter.updateSection {
|
||||
it.copy(
|
||||
notificationCount = counts.totalCount,
|
||||
isHighlighted = counts.isHighlight,
|
||||
)
|
||||
}
|
||||
}
|
||||
section.isExpanded.observe(viewLifecycleOwner) { _ ->
|
||||
refreshCollapseStates()
|
||||
|
@ -308,10 +321,13 @@ class RoomListFragment @Inject constructor(
|
|||
.also { controller ->
|
||||
section.liveSuggested.observe(viewLifecycleOwner) { info ->
|
||||
controller.setData(info)
|
||||
sectionAdapter.updateSection(sectionAdapter.roomsSectionData.copy(
|
||||
isHidden = info.rooms.isEmpty(),
|
||||
isLoading = false
|
||||
))
|
||||
sectionAdapter.updateSection {
|
||||
it.copy(
|
||||
isHidden = info.rooms.isEmpty(),
|
||||
isLoading = false
|
||||
)
|
||||
}
|
||||
refreshCollapseStates()
|
||||
checkEmptyState()
|
||||
}
|
||||
observeItemCount(section, sectionAdapter)
|
||||
|
@ -326,17 +342,23 @@ class RoomListFragment @Inject constructor(
|
|||
.also { controller ->
|
||||
section.liveList?.observe(viewLifecycleOwner) { list ->
|
||||
controller.setData(list)
|
||||
sectionAdapter.updateSection(sectionAdapter.roomsSectionData.copy(
|
||||
isHidden = list.isEmpty(),
|
||||
isLoading = false))
|
||||
sectionAdapter.updateSection {
|
||||
it.copy(
|
||||
isHidden = list.isEmpty(),
|
||||
isLoading = false,
|
||||
)
|
||||
}
|
||||
refreshCollapseStates()
|
||||
checkEmptyState()
|
||||
}
|
||||
observeItemCount(section, sectionAdapter)
|
||||
section.notificationCount.observe(viewLifecycleOwner) { counts ->
|
||||
sectionAdapter.updateSection(sectionAdapter.roomsSectionData.copy(
|
||||
notificationCount = counts.totalCount,
|
||||
isHighlighted = counts.isHighlight
|
||||
))
|
||||
sectionAdapter.updateSection {
|
||||
it.copy(
|
||||
notificationCount = counts.totalCount,
|
||||
isHighlighted = counts.isHighlight
|
||||
)
|
||||
}
|
||||
}
|
||||
section.isExpanded.observe(viewLifecycleOwner) { _ ->
|
||||
refreshCollapseStates()
|
||||
|
@ -383,10 +405,11 @@ class RoomListFragment @Inject constructor(
|
|||
lifecycleScope.launch {
|
||||
section.itemCount
|
||||
.flowWithLifecycle(lifecycle, Lifecycle.State.STARTED)
|
||||
.filter { it > 0 }
|
||||
.collect { count ->
|
||||
sectionAdapter.updateSection(
|
||||
sectionAdapter.roomsSectionData.copy(itemCount = count)
|
||||
)
|
||||
sectionAdapter.updateSection {
|
||||
it.copy(itemCount = count)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -70,22 +70,20 @@ class RoomListSectionBuilderGroup(
|
|||
},
|
||||
{ qpm ->
|
||||
val name = stringProvider.getString(R.string.bottom_action_rooms)
|
||||
session.getFilteredPagedRoomSummariesLive(qpm)
|
||||
.let { updatableFilterLivePageResult ->
|
||||
onUpdatable(updatableFilterLivePageResult)
|
||||
val updatableFilterLivePageResult = session.getFilteredPagedRoomSummariesLive(qpm)
|
||||
onUpdatable(updatableFilterLivePageResult)
|
||||
|
||||
val itemCountFlow = updatableFilterLivePageResult.livePagedList.asFlow()
|
||||
.flatMapLatest { session.getRoomCountFlow(updatableFilterLivePageResult.queryParams) }
|
||||
.distinctUntilChanged()
|
||||
val itemCountFlow = updatableFilterLivePageResult.livePagedList.asFlow()
|
||||
.flatMapLatest { session.getRoomCountLive(updatableFilterLivePageResult.queryParams).asFlow() }
|
||||
.distinctUntilChanged()
|
||||
|
||||
sections.add(
|
||||
RoomsSection(
|
||||
sectionName = name,
|
||||
livePages = updatableFilterLivePageResult.livePagedList,
|
||||
itemCount = itemCountFlow
|
||||
)
|
||||
)
|
||||
}
|
||||
sections.add(
|
||||
RoomsSection(
|
||||
sectionName = name,
|
||||
livePages = updatableFilterLivePageResult.livePagedList,
|
||||
itemCount = itemCountFlow
|
||||
)
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
@ -252,37 +250,33 @@ class RoomListSectionBuilderGroup(
|
|||
@StringRes nameRes: Int,
|
||||
notifyOfLocalEcho: Boolean = false,
|
||||
query: (RoomSummaryQueryParams.Builder) -> Unit) {
|
||||
withQueryParams(
|
||||
{ query.invoke(it) },
|
||||
{ roomQueryParams ->
|
||||
val name = stringProvider.getString(nameRes)
|
||||
session.getFilteredPagedRoomSummariesLive(roomQueryParams)
|
||||
.also {
|
||||
activeSpaceUpdaters.add(it)
|
||||
}.livePagedList
|
||||
.let { livePagedList ->
|
||||
// use it also as a source to update count
|
||||
livePagedList.asFlow()
|
||||
.onEach {
|
||||
sections.find { it.sectionName == name }
|
||||
?.notificationCount
|
||||
?.postValue(session.getNotificationCountForRooms(roomQueryParams))
|
||||
}
|
||||
.flowOn(Dispatchers.Default)
|
||||
.launchIn(coroutineScope)
|
||||
withQueryParams(query) { roomQueryParams ->
|
||||
val name = stringProvider.getString(nameRes)
|
||||
session.getFilteredPagedRoomSummariesLive(roomQueryParams)
|
||||
.also {
|
||||
activeSpaceUpdaters.add(it)
|
||||
}.livePagedList
|
||||
.let { livePagedList ->
|
||||
// use it also as a source to update count
|
||||
livePagedList.asFlow()
|
||||
.onEach {
|
||||
sections.find { it.sectionName == name }
|
||||
?.notificationCount
|
||||
?.postValue(session.getNotificationCountForRooms(roomQueryParams))
|
||||
}
|
||||
.flowOn(Dispatchers.Default)
|
||||
.launchIn(coroutineScope)
|
||||
|
||||
sections.add(
|
||||
RoomsSection(
|
||||
sectionName = name,
|
||||
livePages = livePagedList,
|
||||
notifyOfLocalEcho = notifyOfLocalEcho,
|
||||
itemCount = session.getRoomCountFlow(roomQueryParams)
|
||||
)
|
||||
sections.add(
|
||||
RoomsSection(
|
||||
sectionName = name,
|
||||
livePages = livePagedList,
|
||||
notifyOfLocalEcho = notifyOfLocalEcho,
|
||||
itemCount = session.getRoomCountLive(roomQueryParams).asFlow()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun withQueryParams(builder: (RoomSummaryQueryParams.Builder) -> Unit, block: (RoomSummaryQueryParams) -> Unit) {
|
||||
|
|
|
@ -32,6 +32,7 @@ import im.vector.app.features.invite.showInvites
|
|||
import im.vector.app.space
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.combine
|
||||
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||
import kotlinx.coroutines.flow.flatMapLatest
|
||||
|
@ -40,6 +41,7 @@ import kotlinx.coroutines.flow.flowOn
|
|||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import kotlinx.coroutines.flow.update
|
||||
import org.matrix.android.sdk.api.extensions.tryOrNull
|
||||
import org.matrix.android.sdk.api.query.ActiveSpaceFilter
|
||||
import org.matrix.android.sdk.api.query.RoomCategoryFilter
|
||||
|
@ -83,64 +85,10 @@ class RoomListSectionBuilderSpace(
|
|||
}
|
||||
RoomListDisplayMode.FILTERED -> {
|
||||
// Used when searching for rooms
|
||||
withQueryParams(
|
||||
{
|
||||
it.memberships = Membership.activeMemberships()
|
||||
},
|
||||
{ qpm ->
|
||||
val name = stringProvider.getString(R.string.bottom_action_rooms)
|
||||
session.getFilteredPagedRoomSummariesLive(qpm)
|
||||
.let { updatableFilterLivePageResult ->
|
||||
onUpdatable(updatableFilterLivePageResult)
|
||||
|
||||
val itemCountFlow = updatableFilterLivePageResult.livePagedList.asFlow()
|
||||
.flatMapLatest { session.getRoomCountFlow(updatableFilterLivePageResult.queryParams) }
|
||||
.distinctUntilChanged()
|
||||
|
||||
sections.add(
|
||||
RoomsSection(
|
||||
sectionName = name,
|
||||
livePages = updatableFilterLivePageResult.livePagedList,
|
||||
itemCount = itemCountFlow
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
)
|
||||
buildFilteredSection(sections)
|
||||
}
|
||||
RoomListDisplayMode.NOTIFICATIONS -> {
|
||||
if (autoAcceptInvites.showInvites()) {
|
||||
addSection(
|
||||
sections = sections,
|
||||
activeSpaceUpdaters = activeSpaceAwareQueries,
|
||||
nameRes = R.string.invitations_header,
|
||||
notifyOfLocalEcho = true,
|
||||
spaceFilterStrategy = if (onlyOrphansInHome) {
|
||||
RoomListViewModel.SpaceFilterStrategy.ORPHANS_IF_SPACE_NULL
|
||||
} else {
|
||||
RoomListViewModel.SpaceFilterStrategy.ALL_IF_SPACE_NULL
|
||||
},
|
||||
countRoomAsNotif = true
|
||||
) {
|
||||
it.memberships = listOf(Membership.INVITE)
|
||||
it.roomCategoryFilter = RoomCategoryFilter.ALL
|
||||
}
|
||||
}
|
||||
|
||||
addSection(
|
||||
sections = sections,
|
||||
activeSpaceUpdaters = activeSpaceAwareQueries,
|
||||
nameRes = R.string.bottom_action_rooms,
|
||||
notifyOfLocalEcho = false,
|
||||
spaceFilterStrategy = if (onlyOrphansInHome) {
|
||||
RoomListViewModel.SpaceFilterStrategy.ORPHANS_IF_SPACE_NULL
|
||||
} else {
|
||||
RoomListViewModel.SpaceFilterStrategy.ALL_IF_SPACE_NULL
|
||||
}
|
||||
) {
|
||||
it.memberships = listOf(Membership.JOIN)
|
||||
it.roomCategoryFilter = RoomCategoryFilter.ONLY_WITH_NOTIFICATIONS
|
||||
}
|
||||
buildNotificationsSection(sections, activeSpaceAwareQueries)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -332,6 +280,68 @@ class RoomListSectionBuilderSpace(
|
|||
}
|
||||
}
|
||||
|
||||
private fun buildNotificationsSection(sections: MutableList<RoomsSection>,
|
||||
activeSpaceAwareQueries: MutableList<RoomListViewModel.ActiveSpaceQueryUpdater>) {
|
||||
if (autoAcceptInvites.showInvites()) {
|
||||
addSection(
|
||||
sections = sections,
|
||||
activeSpaceUpdaters = activeSpaceAwareQueries,
|
||||
nameRes = R.string.invitations_header,
|
||||
notifyOfLocalEcho = true,
|
||||
spaceFilterStrategy = if (onlyOrphansInHome) {
|
||||
RoomListViewModel.SpaceFilterStrategy.ORPHANS_IF_SPACE_NULL
|
||||
} else {
|
||||
RoomListViewModel.SpaceFilterStrategy.ALL_IF_SPACE_NULL
|
||||
},
|
||||
countRoomAsNotif = true
|
||||
) {
|
||||
it.memberships = listOf(Membership.INVITE)
|
||||
it.roomCategoryFilter = RoomCategoryFilter.ALL
|
||||
}
|
||||
}
|
||||
|
||||
addSection(
|
||||
sections = sections,
|
||||
activeSpaceUpdaters = activeSpaceAwareQueries,
|
||||
nameRes = R.string.bottom_action_rooms,
|
||||
notifyOfLocalEcho = false,
|
||||
spaceFilterStrategy = if (onlyOrphansInHome) {
|
||||
RoomListViewModel.SpaceFilterStrategy.ORPHANS_IF_SPACE_NULL
|
||||
} else {
|
||||
RoomListViewModel.SpaceFilterStrategy.ALL_IF_SPACE_NULL
|
||||
}
|
||||
) {
|
||||
it.memberships = listOf(Membership.JOIN)
|
||||
it.roomCategoryFilter = RoomCategoryFilter.ONLY_WITH_NOTIFICATIONS
|
||||
}
|
||||
}
|
||||
|
||||
private fun buildFilteredSection(sections: MutableList<RoomsSection>) {
|
||||
// Used when searching for rooms
|
||||
withQueryParams(
|
||||
{
|
||||
it.memberships = Membership.activeMemberships()
|
||||
},
|
||||
{ qpm ->
|
||||
val name = stringProvider.getString(R.string.bottom_action_rooms)
|
||||
val updatableFilterLivePageResult = session.getFilteredPagedRoomSummariesLive(qpm)
|
||||
onUpdatable(updatableFilterLivePageResult)
|
||||
|
||||
val itemCountFlow = updatableFilterLivePageResult.livePagedList.asFlow()
|
||||
.flatMapLatest { session.getRoomCountLive(updatableFilterLivePageResult.queryParams).asFlow() }
|
||||
.distinctUntilChanged()
|
||||
|
||||
sections.add(
|
||||
RoomsSection(
|
||||
sectionName = name,
|
||||
livePages = updatableFilterLivePageResult.livePagedList,
|
||||
itemCount = itemCountFlow
|
||||
)
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
private fun addSection(sections: MutableList<RoomsSection>,
|
||||
activeSpaceUpdaters: MutableList<RoomListViewModel.ActiveSpaceQueryUpdater>,
|
||||
@StringRes nameRes: Int,
|
||||
|
@ -339,83 +349,82 @@ class RoomListSectionBuilderSpace(
|
|||
spaceFilterStrategy: RoomListViewModel.SpaceFilterStrategy = RoomListViewModel.SpaceFilterStrategy.NONE,
|
||||
countRoomAsNotif: Boolean = false,
|
||||
query: (RoomSummaryQueryParams.Builder) -> Unit) {
|
||||
withQueryParams(
|
||||
{ query.invoke(it) },
|
||||
{ roomQueryParams ->
|
||||
val name = stringProvider.getString(nameRes)
|
||||
session.getFilteredPagedRoomSummariesLive(
|
||||
roomQueryParams.process(spaceFilterStrategy, appStateHandler.safeActiveSpaceId()),
|
||||
pagedListConfig
|
||||
).also {
|
||||
when (spaceFilterStrategy) {
|
||||
RoomListViewModel.SpaceFilterStrategy.ORPHANS_IF_SPACE_NULL -> {
|
||||
activeSpaceUpdaters.add(object : RoomListViewModel.ActiveSpaceQueryUpdater {
|
||||
override fun updateForSpaceId(roomId: String?) {
|
||||
it.queryParams = roomQueryParams.copy(
|
||||
activeSpaceFilter = ActiveSpaceFilter.ActiveSpace(roomId)
|
||||
)
|
||||
}
|
||||
})
|
||||
}
|
||||
RoomListViewModel.SpaceFilterStrategy.ALL_IF_SPACE_NULL -> {
|
||||
activeSpaceUpdaters.add(object : RoomListViewModel.ActiveSpaceQueryUpdater {
|
||||
override fun updateForSpaceId(roomId: String?) {
|
||||
if (roomId != null) {
|
||||
it.queryParams = roomQueryParams.copy(
|
||||
activeSpaceFilter = ActiveSpaceFilter.ActiveSpace(roomId)
|
||||
)
|
||||
} else {
|
||||
it.queryParams = roomQueryParams.copy(
|
||||
activeSpaceFilter = ActiveSpaceFilter.None
|
||||
)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
RoomListViewModel.SpaceFilterStrategy.NONE -> {
|
||||
// we ignore current space for this one
|
||||
}
|
||||
withQueryParams(query) { roomQueryParams ->
|
||||
val updatedQueryParams = roomQueryParams.process(spaceFilterStrategy, appStateHandler.safeActiveSpaceId())
|
||||
val liveQueryParams = MutableStateFlow(updatedQueryParams)
|
||||
val itemCountFlow = liveQueryParams
|
||||
.flatMapLatest {
|
||||
session.getRoomCountLive(it).asFlow()
|
||||
}
|
||||
.flowOn(Dispatchers.Main)
|
||||
.distinctUntilChanged()
|
||||
|
||||
val name = stringProvider.getString(nameRes)
|
||||
val filteredPagedRoomSummariesLive = session.getFilteredPagedRoomSummariesLive(
|
||||
roomQueryParams.process(spaceFilterStrategy, appStateHandler.safeActiveSpaceId()),
|
||||
pagedListConfig
|
||||
)
|
||||
when (spaceFilterStrategy) {
|
||||
RoomListViewModel.SpaceFilterStrategy.ORPHANS_IF_SPACE_NULL -> {
|
||||
activeSpaceUpdaters.add(object : RoomListViewModel.ActiveSpaceQueryUpdater {
|
||||
override fun updateForSpaceId(roomId: String?) {
|
||||
filteredPagedRoomSummariesLive.queryParams = roomQueryParams.copy(
|
||||
activeSpaceFilter = ActiveSpaceFilter.ActiveSpace(roomId)
|
||||
)
|
||||
liveQueryParams.update { filteredPagedRoomSummariesLive.queryParams }
|
||||
}
|
||||
}.livePagedList
|
||||
.let { livePagedList ->
|
||||
// use it also as a source to update count
|
||||
livePagedList.asFlow()
|
||||
.onEach {
|
||||
Timber.v("Thread space list: ${Thread.currentThread()}")
|
||||
sections.find { it.sectionName == name }
|
||||
?.notificationCount
|
||||
?.postValue(
|
||||
if (countRoomAsNotif) {
|
||||
RoomAggregateNotificationCount(it.size, it.size)
|
||||
} else {
|
||||
session.getNotificationCountForRooms(
|
||||
roomQueryParams.process(spaceFilterStrategy, appStateHandler.safeActiveSpaceId())
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
.flowOn(Dispatchers.Default)
|
||||
.launchIn(viewModelScope)
|
||||
|
||||
val itemCountFlow = livePagedList.asFlow()
|
||||
.flatMapLatest {
|
||||
val queryParams = roomQueryParams.process(spaceFilterStrategy, appStateHandler.safeActiveSpaceId())
|
||||
session.getRoomCountFlow(queryParams)
|
||||
}
|
||||
.distinctUntilChanged()
|
||||
|
||||
sections.add(
|
||||
RoomsSection(
|
||||
sectionName = name,
|
||||
livePages = livePagedList,
|
||||
notifyOfLocalEcho = notifyOfLocalEcho,
|
||||
itemCount = itemCountFlow
|
||||
)
|
||||
})
|
||||
}
|
||||
RoomListViewModel.SpaceFilterStrategy.ALL_IF_SPACE_NULL -> {
|
||||
activeSpaceUpdaters.add(object : RoomListViewModel.ActiveSpaceQueryUpdater {
|
||||
override fun updateForSpaceId(roomId: String?) {
|
||||
if (roomId != null) {
|
||||
filteredPagedRoomSummariesLive.queryParams = roomQueryParams.copy(
|
||||
activeSpaceFilter = ActiveSpaceFilter.ActiveSpace(roomId)
|
||||
)
|
||||
} else {
|
||||
filteredPagedRoomSummariesLive.queryParams = roomQueryParams.copy(
|
||||
activeSpaceFilter = ActiveSpaceFilter.None
|
||||
)
|
||||
}
|
||||
liveQueryParams.update { filteredPagedRoomSummariesLive.queryParams }
|
||||
}
|
||||
})
|
||||
}
|
||||
RoomListViewModel.SpaceFilterStrategy.NONE -> {
|
||||
// we ignore current space for this one
|
||||
}
|
||||
}
|
||||
|
||||
)
|
||||
val livePagedList = filteredPagedRoomSummariesLive.livePagedList
|
||||
// use it also as a source to update count
|
||||
livePagedList.asFlow()
|
||||
.onEach {
|
||||
Timber.v("Thread space list: ${Thread.currentThread()}")
|
||||
sections.find { it.sectionName == name }
|
||||
?.notificationCount
|
||||
?.postValue(
|
||||
if (countRoomAsNotif) {
|
||||
RoomAggregateNotificationCount(it.size, it.size)
|
||||
} else {
|
||||
session.getNotificationCountForRooms(
|
||||
roomQueryParams.process(spaceFilterStrategy, appStateHandler.safeActiveSpaceId())
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
.flowOn(Dispatchers.Default)
|
||||
.launchIn(viewModelScope)
|
||||
|
||||
sections.add(
|
||||
RoomsSection(
|
||||
sectionName = name,
|
||||
livePages = livePagedList,
|
||||
notifyOfLocalEcho = notifyOfLocalEcho,
|
||||
itemCount = itemCountFlow
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun withQueryParams(builder: (RoomSummaryQueryParams.Builder) -> Unit, block: (RoomSummaryQueryParams) -> Unit) {
|
||||
|
|
|
@ -16,6 +16,7 @@
|
|||
|
||||
package im.vector.app.features.home.room.list
|
||||
|
||||
import android.graphics.drawable.Drawable
|
||||
import android.view.LayoutInflater
|
||||
import android.view.ViewGroup
|
||||
import androidx.core.content.ContextCompat
|
||||
|
@ -28,6 +29,7 @@ import im.vector.app.databinding.ItemRoomCategoryBinding
|
|||
import im.vector.app.features.themes.ThemeUtils
|
||||
|
||||
class SectionHeaderAdapter constructor(
|
||||
roomsSectionData: RoomsSectionData,
|
||||
private val onClickAction: ClickListener
|
||||
) : RecyclerView.Adapter<SectionHeaderAdapter.VH>() {
|
||||
|
||||
|
@ -39,14 +41,16 @@ class SectionHeaderAdapter constructor(
|
|||
val isHighlighted: Boolean = false,
|
||||
val isHidden: Boolean = true,
|
||||
// This will be false until real data has been submitted once
|
||||
val isLoading: Boolean = true
|
||||
val isLoading: Boolean = true,
|
||||
val isCollapsable: Boolean = false
|
||||
)
|
||||
|
||||
lateinit var roomsSectionData: RoomsSectionData
|
||||
var roomsSectionData: RoomsSectionData = roomsSectionData
|
||||
private set
|
||||
|
||||
fun updateSection(newRoomsSectionData: RoomsSectionData) {
|
||||
if (!::roomsSectionData.isInitialized || newRoomsSectionData != roomsSectionData) {
|
||||
fun updateSection(block: (RoomsSectionData) -> RoomsSectionData) {
|
||||
val newRoomsSectionData = block(roomsSectionData)
|
||||
if (roomsSectionData != newRoomsSectionData) {
|
||||
roomsSectionData = newRoomsSectionData
|
||||
notifyDataSetChanged()
|
||||
}
|
||||
|
@ -82,11 +86,16 @@ class SectionHeaderAdapter constructor(
|
|||
fun bind(roomsSectionData: RoomsSectionData) {
|
||||
binding.roomCategoryTitleView.text = roomsSectionData.name
|
||||
val tintColor = ThemeUtils.getColor(binding.root.context, R.attr.vctr_content_secondary)
|
||||
val expandedArrowDrawableRes = if (roomsSectionData.isExpanded) R.drawable.ic_expand_more else R.drawable.ic_expand_less
|
||||
val expandedArrowDrawable = ContextCompat.getDrawable(binding.root.context, expandedArrowDrawableRes)?.also {
|
||||
DrawableCompat.setTint(it, tintColor)
|
||||
val collapsableArrowDrawable: Drawable? = if (roomsSectionData.isCollapsable) {
|
||||
val expandedArrowDrawableRes = if (roomsSectionData.isExpanded) R.drawable.ic_expand_more else R.drawable.ic_expand_less
|
||||
ContextCompat.getDrawable(binding.root.context, expandedArrowDrawableRes)?.also {
|
||||
DrawableCompat.setTint(it, tintColor)
|
||||
}
|
||||
} else {
|
||||
null
|
||||
}
|
||||
binding.roomCategoryCounterView.setCompoundDrawablesWithIntrinsicBounds(null, null, expandedArrowDrawable, null)
|
||||
binding.root.isClickable = roomsSectionData.isCollapsable
|
||||
binding.roomCategoryCounterView.setCompoundDrawablesWithIntrinsicBounds(null, null, collapsableArrowDrawable, null)
|
||||
binding.roomCategoryCounterView.text = roomsSectionData.itemCount.takeIf { it > 0 }?.toString().orEmpty()
|
||||
binding.roomCategoryUnreadCounterBadgeView.render(UnreadCounterBadgeView.State(roomsSectionData.notificationCount, roomsSectionData.isHighlighted))
|
||||
}
|
||||
|
|
|
@ -75,9 +75,9 @@ import im.vector.app.features.onboarding.OnboardingActivity
|
|||
import im.vector.app.features.pin.PinActivity
|
||||
import im.vector.app.features.pin.PinArgs
|
||||
import im.vector.app.features.pin.PinMode
|
||||
import im.vector.app.features.poll.PollMode
|
||||
import im.vector.app.features.poll.create.CreatePollActivity
|
||||
import im.vector.app.features.poll.create.CreatePollArgs
|
||||
import im.vector.app.features.poll.create.PollMode
|
||||
import im.vector.app.features.roomdirectory.RoomDirectoryActivity
|
||||
import im.vector.app.features.roomdirectory.RoomDirectoryData
|
||||
import im.vector.app.features.roomdirectory.createroom.CreateRoomActivity
|
||||
|
|
|
@ -31,7 +31,7 @@ import im.vector.app.features.location.LocationSharingMode
|
|||
import im.vector.app.features.login.LoginConfig
|
||||
import im.vector.app.features.media.AttachmentData
|
||||
import im.vector.app.features.pin.PinMode
|
||||
import im.vector.app.features.poll.create.PollMode
|
||||
import im.vector.app.features.poll.PollMode
|
||||
import im.vector.app.features.roomdirectory.RoomDirectoryData
|
||||
import im.vector.app.features.roomdirectory.roompreview.RoomPreviewData
|
||||
import im.vector.app.features.settings.VectorSettingsActivity
|
||||
|
|
|
@ -524,7 +524,7 @@ class OnboardingViewModel @AssistedInject constructor(
|
|||
onDirectLoginError(failure)
|
||||
return
|
||||
}
|
||||
onSessionCreated(data, isAccountCreated = true)
|
||||
onSessionCreated(data, isAccountCreated = false)
|
||||
}
|
||||
|
||||
private fun onDirectLoginError(failure: Throwable) {
|
||||
|
|
|
@ -14,7 +14,7 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package im.vector.app.features.poll.create
|
||||
package im.vector.app.features.poll
|
||||
|
||||
enum class PollMode {
|
||||
CREATE,
|
|
@ -0,0 +1,27 @@
|
|||
/*
|
||||
* Copyright (c) 2022 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.poll
|
||||
|
||||
sealed interface PollState {
|
||||
object Sending : PollState
|
||||
object Ready : PollState
|
||||
data class Voted(val votes: Int) : PollState
|
||||
object Undisclosed : PollState
|
||||
object Ended : PollState
|
||||
|
||||
fun isVotable() = this !is Sending && this !is Ended
|
||||
}
|
|
@ -29,6 +29,7 @@ import im.vector.app.R
|
|||
import im.vector.app.core.extensions.configureWith
|
||||
import im.vector.app.core.platform.VectorBaseFragment
|
||||
import im.vector.app.databinding.FragmentCreatePollBinding
|
||||
import im.vector.app.features.poll.PollMode
|
||||
import im.vector.app.features.poll.create.CreatePollViewModel.Companion.MAX_OPTIONS_COUNT
|
||||
import kotlinx.parcelize.Parcelize
|
||||
import org.matrix.android.sdk.api.session.room.model.message.PollType
|
||||
|
|
|
@ -23,6 +23,7 @@ import dagger.assisted.AssistedInject
|
|||
import im.vector.app.core.di.MavericksAssistedViewModelFactory
|
||||
import im.vector.app.core.di.hiltMavericksViewModelFactory
|
||||
import im.vector.app.core.platform.VectorViewModel
|
||||
import im.vector.app.features.poll.PollMode
|
||||
import org.matrix.android.sdk.api.session.Session
|
||||
import org.matrix.android.sdk.api.session.room.model.message.MessagePollContent
|
||||
import org.matrix.android.sdk.api.session.room.model.message.PollType
|
||||
|
|
|
@ -17,17 +17,18 @@
|
|||
package im.vector.app.features.poll.create
|
||||
|
||||
import com.airbnb.mvrx.MavericksState
|
||||
import im.vector.app.features.poll.PollMode
|
||||
import org.matrix.android.sdk.api.session.room.model.message.PollType
|
||||
|
||||
data class CreatePollViewState(
|
||||
val roomId: String,
|
||||
val editedEventId: String?,
|
||||
val mode: PollMode,
|
||||
val question: String = "",
|
||||
val options: List<String> = List(CreatePollViewModel.MIN_OPTIONS_COUNT) { "" },
|
||||
val canCreatePoll: Boolean = false,
|
||||
val canAddMoreOptions: Boolean = true,
|
||||
val pollType: PollType = PollType.DISCLOSED_UNSTABLE
|
||||
val roomId: String,
|
||||
val editedEventId: String?,
|
||||
val mode: PollMode,
|
||||
val question: String = "",
|
||||
val options: List<String> = List(CreatePollViewModel.MIN_OPTIONS_COUNT) { "" },
|
||||
val canCreatePoll: Boolean = false,
|
||||
val canAddMoreOptions: Boolean = true,
|
||||
val pollType: PollType = PollType.DISCLOSED_UNSTABLE
|
||||
) : MavericksState {
|
||||
|
||||
constructor(args: CreatePollArgs) : this(
|
||||
|
|
|
@ -84,7 +84,7 @@ class SpaceAddRoomsViewModel @AssistedInject constructor(
|
|||
|
||||
val spaceCountFlow: Flow<Int> by lazy {
|
||||
spaceUpdatableLivePageResult.livePagedList.asFlow()
|
||||
.flatMapLatest { session.getRoomCountFlow(spaceUpdatableLivePageResult.queryParams) }
|
||||
.flatMapLatest { session.getRoomCountLive(spaceUpdatableLivePageResult.queryParams).asFlow() }
|
||||
.distinctUntilChanged()
|
||||
}
|
||||
|
||||
|
@ -110,7 +110,7 @@ class SpaceAddRoomsViewModel @AssistedInject constructor(
|
|||
|
||||
val roomCountFlow: Flow<Int> by lazy {
|
||||
roomUpdatableLivePageResult.livePagedList.asFlow()
|
||||
.flatMapLatest { session.getRoomCountFlow(roomUpdatableLivePageResult.queryParams) }
|
||||
.flatMapLatest { session.getRoomCountLive(roomUpdatableLivePageResult.queryParams).asFlow() }
|
||||
.distinctUntilChanged()
|
||||
}
|
||||
|
||||
|
@ -136,7 +136,7 @@ class SpaceAddRoomsViewModel @AssistedInject constructor(
|
|||
|
||||
val dmCountFlow: Flow<Int> by lazy {
|
||||
dmUpdatableLivePageResult.livePagedList.asFlow()
|
||||
.flatMapLatest { session.getRoomCountFlow(dmUpdatableLivePageResult.queryParams) }
|
||||
.flatMapLatest { session.getRoomCountLive(dmUpdatableLivePageResult.queryParams).asFlow() }
|
||||
.distinctUntilChanged()
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue