mirror of
https://github.com/SchildiChat/SchildiChat-android.git
synced 2025-02-16 20:10:04 +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)
|
Changes in Element v1.4.6 (2022-03-23)
|
||||||
======================================
|
======================================
|
||||||
|
|
||||||
|
@ -37,6 +45,7 @@ SDK API changes ⚠️
|
||||||
|
|
||||||
Other 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))
|
- 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))
|
- 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))
|
- 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.lifecycle.LiveData
|
||||||
import androidx.paging.PagedList
|
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.events.model.Event
|
||||||
import org.matrix.android.sdk.api.session.room.members.ChangeMembershipState
|
import org.matrix.android.sdk.api.session.room.members.ChangeMembershipState
|
||||||
import org.matrix.android.sdk.api.session.room.model.Membership
|
import org.matrix.android.sdk.api.session.room.model.Membership
|
||||||
|
@ -218,9 +217,10 @@ interface RoomService {
|
||||||
sortOrder: RoomSortOrder = RoomSortOrder.ACTIVITY): UpdatableLivePageResult
|
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
|
* TODO Doc
|
||||||
|
|
|
@ -20,7 +20,6 @@ import androidx.lifecycle.LiveData
|
||||||
import androidx.lifecycle.Transformations
|
import androidx.lifecycle.Transformations
|
||||||
import androidx.paging.PagedList
|
import androidx.paging.PagedList
|
||||||
import com.zhuinden.monarchy.Monarchy
|
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.events.model.Event
|
||||||
import org.matrix.android.sdk.api.session.room.Room
|
import org.matrix.android.sdk.api.session.room.Room
|
||||||
import org.matrix.android.sdk.api.session.room.RoomService
|
import org.matrix.android.sdk.api.session.room.RoomService
|
||||||
|
@ -110,8 +109,8 @@ internal class DefaultRoomService @Inject constructor(
|
||||||
return roomSummaryDataSource.getUpdatablePagedRoomSummariesLive(queryParams, pagedListConfig, sortOrder)
|
return roomSummaryDataSource.getUpdatablePagedRoomSummariesLive(queryParams, pagedListConfig, sortOrder)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getRoomCountFlow(queryParams: RoomSummaryQueryParams): Flow<Int> {
|
override fun getRoomCountLive(queryParams: RoomSummaryQueryParams): LiveData<Int> {
|
||||||
return roomSummaryDataSource.getCountFlow(queryParams)
|
return roomSummaryDataSource.getCountLive(queryParams)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getNotificationCountForRooms(queryParams: RoomSummaryQueryParams): RoomAggregateNotificationCount {
|
override fun getNotificationCountForRooms(queryParams: RoomSummaryQueryParams): RoomAggregateNotificationCount {
|
||||||
|
|
|
@ -482,46 +482,39 @@ internal class EventRelationsAggregationProcessor @Inject constructor(
|
||||||
roomId: String,
|
roomId: String,
|
||||||
isLocalEcho: Boolean) {
|
isLocalEcho: Boolean) {
|
||||||
val pollEventId = content.relatesTo?.eventId ?: return
|
val pollEventId = content.relatesTo?.eventId ?: return
|
||||||
|
|
||||||
val pollOwnerId = getPollEvent(roomId, pollEventId)?.root?.senderId
|
val pollOwnerId = getPollEvent(roomId, pollEventId)?.root?.senderId
|
||||||
val isPollOwner = pollOwnerId == event.senderId
|
val isPollOwner = pollOwnerId == event.senderId
|
||||||
|
|
||||||
val powerLevelsHelper = stateEventDataSource.getStateEvent(roomId, EventType.STATE_ROOM_POWER_LEVELS, QueryStringValue.NoCondition)
|
val powerLevelsHelper = stateEventDataSource.getStateEvent(roomId, EventType.STATE_ROOM_POWER_LEVELS, QueryStringValue.NoCondition)
|
||||||
?.content?.toModel<PowerLevelsContent>()
|
?.content?.toModel<PowerLevelsContent>()
|
||||||
?.let { PowerLevelsHelper(it) }
|
?.let { PowerLevelsHelper(it) }
|
||||||
|
|
||||||
if (!isPollOwner && !powerLevelsHelper?.isUserAbleToRedact(event.senderId ?: "").orFalse()) {
|
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")
|
Timber.v("## Received poll.end event $pollEventId but user ${event.senderId} doesn't have enough power level in room $roomId")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
var existing = EventAnnotationsSummaryEntity.where(realm, roomId, pollEventId).findFirst()
|
var existingPoll = EventAnnotationsSummaryEntity.where(realm, roomId, pollEventId).findFirst()
|
||||||
if (existing == null) {
|
if (existingPoll == null) {
|
||||||
Timber.v("## POLL creating new relation summary for $pollEventId")
|
Timber.v("## POLL creating new relation summary for $pollEventId")
|
||||||
existing = EventAnnotationsSummaryEntity.create(realm, roomId, pollEventId)
|
existingPoll = EventAnnotationsSummaryEntity.create(realm, roomId, pollEventId)
|
||||||
}
|
}
|
||||||
|
|
||||||
// we have it
|
// we have it
|
||||||
val existingPollSummary = existing.pollResponseSummary
|
val existingPollSummary = existingPoll.pollResponseSummary
|
||||||
?: realm.createObject(PollResponseAggregatedSummaryEntity::class.java).also {
|
?: 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
|
val txId = event.unsignedData?.transactionId
|
||||||
|
existingPollSummary.closedTime = event.originServerTs
|
||||||
|
|
||||||
// is it a remote echo?
|
// is it a remote echo?
|
||||||
if (!isLocalEcho && existingPollSummary.sourceLocalEchoEvents.contains(txId)) {
|
if (!isLocalEcho && existingPollSummary.sourceLocalEchoEvents.contains(txId)) {
|
||||||
// ok it has already been managed
|
// ok it has already been managed
|
||||||
Timber.v("## POLL Receiving remote echo of response eventId:$pollEventId")
|
Timber.v("## POLL Receiving remote echo of response eventId:$pollEventId")
|
||||||
existingPollSummary.sourceLocalEchoEvents.remove(txId)
|
existingPollSummary.sourceLocalEchoEvents.remove(txId)
|
||||||
existingPollSummary.sourceEvents.add(event.eventId)
|
existingPollSummary.sourceEvents.add(event.eventId)
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
existingPollSummary.closedTime = event.originServerTs
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getPollEvent(roomId: String, eventId: String): TimelineEvent? {
|
private fun getPollEvent(roomId: String, eventId: String): TimelineEvent? {
|
||||||
|
|
|
@ -25,12 +25,7 @@ import androidx.paging.PagedList
|
||||||
import com.zhuinden.monarchy.Monarchy
|
import com.zhuinden.monarchy.Monarchy
|
||||||
import io.realm.Realm
|
import io.realm.Realm
|
||||||
import io.realm.RealmQuery
|
import io.realm.RealmQuery
|
||||||
import io.realm.kotlin.toFlow
|
|
||||||
import io.realm.kotlin.where
|
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.MatrixCoroutineDispatchers
|
||||||
import org.matrix.android.sdk.api.query.ActiveSpaceFilter
|
import org.matrix.android.sdk.api.query.ActiveSpaceFilter
|
||||||
import org.matrix.android.sdk.api.query.RoomCategoryFilter
|
import org.matrix.android.sdk.api.query.RoomCategoryFilter
|
||||||
|
@ -241,15 +236,14 @@ internal class RoomSummaryDataSource @Inject constructor(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getCountFlow(queryParams: RoomSummaryQueryParams): Flow<Int> =
|
fun getCountLive(queryParams: RoomSummaryQueryParams): LiveData<Int> {
|
||||||
realmSessionProvider
|
val liveRooms = monarchy.findAllManagedWithChanges {
|
||||||
.withRealm { realm -> roomSummariesQuery(realm, queryParams).findAllAsync() }
|
roomSummariesQuery(it, queryParams)
|
||||||
.toFlow()
|
}
|
||||||
// need to create the flow on a context dispatcher with a thread with attached Looper
|
return Transformations.map(liveRooms) {
|
||||||
.flowOn(coroutineDispatchers.main)
|
it.realmResults.where().count().toInt()
|
||||||
.map { it.size }
|
}
|
||||||
.flowOn(coroutineDispatchers.io)
|
}
|
||||||
.distinctUntilChanged()
|
|
||||||
|
|
||||||
fun getNotificationCountForRooms(queryParams: RoomSummaryQueryParams): RoomAggregateNotificationCount {
|
fun getNotificationCountForRooms(queryParams: RoomSummaryQueryParams): RoomAggregateNotificationCount {
|
||||||
var notificationCount: RoomAggregateNotificationCount? = null
|
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.notifications.NotificationUtils
|
||||||
import im.vector.app.features.permalink.NavigationInterceptor
|
import im.vector.app.features.permalink.NavigationInterceptor
|
||||||
import im.vector.app.features.permalink.PermalinkHandler
|
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.reactions.EmojiReactionPickerActivity
|
||||||
import im.vector.app.features.roomprofile.RoomProfileActivity
|
import im.vector.app.features.roomprofile.RoomProfileActivity
|
||||||
import im.vector.app.features.session.coroutineScope
|
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.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.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.RedactedMessageItem_
|
import im.vector.app.features.home.room.detail.timeline.item.RedactedMessageItem_
|
||||||
import im.vector.app.features.home.room.detail.timeline.item.VerificationRequestItem
|
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.location.toLocationData
|
||||||
import im.vector.app.features.media.ImageContentRenderer
|
import im.vector.app.features.media.ImageContentRenderer
|
||||||
import im.vector.app.features.media.VideoContentRenderer
|
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.settings.VectorPreferences
|
||||||
import im.vector.app.features.voice.AudioWaveformView
|
import im.vector.app.features.voice.AudioWaveformView
|
||||||
import im.vector.lib.core.utils.epoxy.charsequence.toEpoxyCharSequence
|
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.MessageType
|
||||||
import org.matrix.android.sdk.api.session.room.model.message.MessageVerificationRequestContent
|
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.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.PollType
|
||||||
import org.matrix.android.sdk.api.session.room.model.message.getFileUrl
|
import org.matrix.android.sdk.api.session.room.model.message.getFileUrl
|
||||||
import org.matrix.android.sdk.api.session.room.model.message.getThumbnailUrl
|
import org.matrix.android.sdk.api.session.room.model.message.getThumbnailUrl
|
||||||
|
@ -181,8 +193,8 @@ class MessageItemFactory @Inject constructor(
|
||||||
// always hide summary when we are on thread timeline
|
// always hide summary when we are on thread timeline
|
||||||
val attributes = messageItemAttributesFactory.create(messageContent, informationData, callback, params.reactionsSummaryEvents, threadDetails)
|
val attributes = messageItemAttributesFactory.create(messageContent, informationData, callback, params.reactionsSummaryEvents, threadDetails)
|
||||||
|
|
||||||
// val all = event.root.toContent()
|
// val all = event.root.toContent()
|
||||||
// val ev = all.toModel<Event>()
|
// val ev = all.toModel<Event>()
|
||||||
val messageItem = when (messageContent) {
|
val messageItem = when (messageContent) {
|
||||||
is MessageEmoteContent -> buildEmoteMessageItem(messageContent, informationData, highlight, callback, attributes)
|
is MessageEmoteContent -> buildEmoteMessageItem(messageContent, informationData, highlight, callback, attributes)
|
||||||
is MessageTextContent -> buildItemForTextContent(messageContent, informationData, highlight, callback, attributes)
|
is MessageTextContent -> buildItemForTextContent(messageContent, informationData, highlight, callback, attributes)
|
||||||
|
@ -207,10 +219,12 @@ class MessageItemFactory @Inject constructor(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun buildLocationItem(locationContent: MessageLocationContent,
|
private fun buildLocationItem(
|
||||||
|
locationContent: MessageLocationContent,
|
||||||
informationData: MessageInformationData,
|
informationData: MessageInformationData,
|
||||||
highlight: Boolean,
|
highlight: Boolean,
|
||||||
attributes: AbsMessageItem.Attributes): MessageLocationItem? {
|
attributes: AbsMessageItem.Attributes,
|
||||||
|
): MessageLocationItem? {
|
||||||
val width = timelineMediaSizeProvider.getMaxSize().first
|
val width = timelineMediaSizeProvider.getMaxSize().first
|
||||||
val height = dimensionConverter.dpToPx(200)
|
val height = dimensionConverter.dpToPx(200)
|
||||||
|
|
||||||
|
@ -231,75 +245,26 @@ class MessageItemFactory @Inject constructor(
|
||||||
.leftGuideline(avatarSizeProvider.leftGuideline)
|
.leftGuideline(avatarSizeProvider.leftGuideline)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun buildPollItem(pollContent: MessagePollContent,
|
private fun buildPollItem(
|
||||||
|
pollContent: MessagePollContent,
|
||||||
informationData: MessageInformationData,
|
informationData: MessageInformationData,
|
||||||
highlight: Boolean,
|
highlight: Boolean,
|
||||||
callback: TimelineEventController.Callback?,
|
callback: TimelineEventController.Callback?,
|
||||||
attributes: AbsMessageItem.Attributes): PollItem? {
|
attributes: AbsMessageItem.Attributes,
|
||||||
val optionViewStates = mutableListOf<PollOptionViewState>()
|
): PollItem {
|
||||||
|
|
||||||
val pollResponseSummary = informationData.pollResponseAggregatedSummary
|
val pollResponseSummary = informationData.pollResponseAggregatedSummary
|
||||||
val isEnded = pollResponseSummary?.isClosed.orFalse()
|
val pollState = createPollState(informationData, pollResponseSummary, pollContent)
|
||||||
val didUserVoted = pollResponseSummary?.myVote?.isNotEmpty().orFalse()
|
val pollCreationInfo = pollContent.getBestPollCreationInfo()
|
||||||
val winnerVoteCount = pollResponseSummary?.winnerVoteCount
|
val questionText = pollCreationInfo?.question?.getBestQuestion().orEmpty()
|
||||||
val isPollSent = informationData.sendState.isSent()
|
val question = createPollQuestion(informationData, questionText, callback)
|
||||||
val isPollUndisclosed = pollContent.getBestPollCreationInfo()?.kind == PollType.UNDISCLOSED_UNSTABLE
|
val optionViewStates = pollCreationInfo?.answers?.mapToOptions(pollState, informationData)
|
||||||
|
val totalVotesText = createTotalVotesText(pollState, pollResponseSummary)
|
||||||
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() ?: ""
|
|
||||||
|
|
||||||
return PollItem_()
|
return PollItem_()
|
||||||
.attributes(attributes)
|
.attributes(attributes)
|
||||||
.eventId(informationData.eventId)
|
.eventId(informationData.eventId)
|
||||||
.pollQuestion(
|
.pollQuestion(question)
|
||||||
if (informationData.hasBeenEdited) {
|
.canVote(pollState.isVotable())
|
||||||
annotateWithEdited(question, callback, informationData)
|
|
||||||
} else {
|
|
||||||
question
|
|
||||||
}.toEpoxyCharSequence()
|
|
||||||
)
|
|
||||||
.pollSent(isPollSent)
|
|
||||||
.totalVotesText(totalVotesText)
|
.totalVotesText(totalVotesText)
|
||||||
.optionViewStates(optionViewStates)
|
.optionViewStates(optionViewStates)
|
||||||
.edited(informationData.hasBeenEdited)
|
.edited(informationData.hasBeenEdited)
|
||||||
|
@ -308,11 +273,72 @@ class MessageItemFactory @Inject constructor(
|
||||||
.callback(callback)
|
.callback(callback)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun buildAudioMessageItem(params: TimelineItemFactoryParams,
|
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,
|
messageContent: MessageAudioContent,
|
||||||
informationData: MessageInformationData,
|
informationData: MessageInformationData,
|
||||||
highlight: Boolean,
|
highlight: Boolean,
|
||||||
attributes: AbsMessageItem.Attributes): MessageAudioItem {
|
attributes: AbsMessageItem.Attributes
|
||||||
|
): MessageAudioItem {
|
||||||
val fileUrl = getAudioFileUrl(messageContent, informationData)
|
val fileUrl = getAudioFileUrl(messageContent, informationData)
|
||||||
val playbackControlButtonClickListener = createOnPlaybackButtonClickListener(messageContent, informationData, params)
|
val playbackControlButtonClickListener = createOnPlaybackButtonClickListener(messageContent, informationData, params)
|
||||||
|
|
||||||
|
@ -351,11 +377,13 @@ class MessageItemFactory @Inject constructor(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun buildVoiceMessageItem(params: TimelineItemFactoryParams,
|
private fun buildVoiceMessageItem(
|
||||||
|
params: TimelineItemFactoryParams,
|
||||||
messageContent: MessageAudioContent,
|
messageContent: MessageAudioContent,
|
||||||
informationData: MessageInformationData,
|
informationData: MessageInformationData,
|
||||||
highlight: Boolean,
|
highlight: Boolean,
|
||||||
attributes: AbsMessageItem.Attributes): MessageVoiceItem {
|
attributes: AbsMessageItem.Attributes
|
||||||
|
): MessageVoiceItem {
|
||||||
val fileUrl = getAudioFileUrl(messageContent, informationData)
|
val fileUrl = getAudioFileUrl(messageContent, informationData)
|
||||||
val playbackControlButtonClickListener = createOnPlaybackButtonClickListener(messageContent, informationData, params)
|
val playbackControlButtonClickListener = createOnPlaybackButtonClickListener(messageContent, informationData, params)
|
||||||
|
|
||||||
|
@ -386,12 +414,14 @@ class MessageItemFactory @Inject constructor(
|
||||||
.leftGuideline(avatarSizeProvider.leftGuideline)
|
.leftGuideline(avatarSizeProvider.leftGuideline)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun buildVerificationRequestMessageItem(messageContent: MessageVerificationRequestContent,
|
private fun buildVerificationRequestMessageItem(
|
||||||
|
messageContent: MessageVerificationRequestContent,
|
||||||
@Suppress("UNUSED_PARAMETER")
|
@Suppress("UNUSED_PARAMETER")
|
||||||
informationData: MessageInformationData,
|
informationData: MessageInformationData,
|
||||||
highlight: Boolean,
|
highlight: Boolean,
|
||||||
callback: TimelineEventController.Callback?,
|
callback: TimelineEventController.Callback?,
|
||||||
attributes: AbsMessageItem.Attributes): VerificationRequestItem? {
|
attributes: AbsMessageItem.Attributes,
|
||||||
|
): VerificationRequestItem? {
|
||||||
// If this request is not sent by me or sent to me, we should ignore it in timeline
|
// If this request is not sent by me or sent to me, we should ignore it in timeline
|
||||||
val myUserId = session.myUserId
|
val myUserId = session.myUserId
|
||||||
if (informationData.senderId != myUserId && messageContent.toUserId != myUserId) {
|
if (informationData.senderId != myUserId && messageContent.toUserId != myUserId) {
|
||||||
|
@ -418,7 +448,7 @@ class MessageItemFactory @Inject constructor(
|
||||||
reactionPillCallback = attributes.reactionPillCallback,
|
reactionPillCallback = attributes.reactionPillCallback,
|
||||||
readReceiptsCallback = attributes.readReceiptsCallback,
|
readReceiptsCallback = attributes.readReceiptsCallback,
|
||||||
emojiTypeFace = attributes.emojiTypeFace,
|
emojiTypeFace = attributes.emojiTypeFace,
|
||||||
reactionsSummaryEvents = attributes.reactionsSummaryEvents
|
reactionsSummaryEvents = attributes.reactionsSummaryEvents,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
.callback(callback)
|
.callback(callback)
|
||||||
|
@ -426,9 +456,11 @@ class MessageItemFactory @Inject constructor(
|
||||||
.leftGuideline(avatarSizeProvider.leftGuideline)
|
.leftGuideline(avatarSizeProvider.leftGuideline)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun buildFileMessageItem(messageContent: MessageFileContent,
|
private fun buildFileMessageItem(
|
||||||
|
messageContent: MessageFileContent,
|
||||||
highlight: Boolean,
|
highlight: Boolean,
|
||||||
attributes: AbsMessageItem.Attributes): MessageFileItem {
|
attributes: AbsMessageItem.Attributes,
|
||||||
|
): MessageFileItem {
|
||||||
val mxcUrl = messageContent.getFileUrl() ?: ""
|
val mxcUrl = messageContent.getFileUrl() ?: ""
|
||||||
return MessageFileItem_()
|
return MessageFileItem_()
|
||||||
.attributes(attributes)
|
.attributes(attributes)
|
||||||
|
@ -443,32 +475,37 @@ class MessageItemFactory @Inject constructor(
|
||||||
.iconRes(R.drawable.ic_paperclip)
|
.iconRes(R.drawable.ic_paperclip)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun buildAudioContent(params: TimelineItemFactoryParams,
|
private fun buildAudioContent(
|
||||||
|
params: TimelineItemFactoryParams,
|
||||||
messageContent: MessageAudioContent,
|
messageContent: MessageAudioContent,
|
||||||
informationData: MessageInformationData,
|
informationData: MessageInformationData,
|
||||||
highlight: Boolean,
|
highlight: Boolean,
|
||||||
attributes: AbsMessageItem.Attributes) =
|
attributes: AbsMessageItem.Attributes,
|
||||||
if (messageContent.voiceMessageIndicator != null) {
|
) = if (messageContent.voiceMessageIndicator != null) {
|
||||||
buildVoiceMessageItem(params, messageContent, informationData, highlight, attributes)
|
buildVoiceMessageItem(params, messageContent, informationData, highlight, attributes)
|
||||||
} else {
|
} else {
|
||||||
buildAudioMessageItem(params, messageContent, informationData, highlight, attributes)
|
buildAudioMessageItem(params, messageContent, informationData, highlight, attributes)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun buildNotHandledMessageItem(messageContent: MessageContent,
|
private fun buildNotHandledMessageItem(
|
||||||
|
messageContent: MessageContent,
|
||||||
informationData: MessageInformationData,
|
informationData: MessageInformationData,
|
||||||
highlight: Boolean,
|
highlight: Boolean,
|
||||||
callback: TimelineEventController.Callback?,
|
callback: TimelineEventController.Callback?,
|
||||||
attributes: AbsMessageItem.Attributes): MessageTextItem? {
|
attributes: AbsMessageItem.Attributes)
|
||||||
|
: MessageTextItem? {
|
||||||
// For compatibility reason we should display the body
|
// For compatibility reason we should display the body
|
||||||
return buildMessageTextItem(messageContent.body, false, informationData, highlight, callback, attributes)
|
return buildMessageTextItem(messageContent.body, false, informationData, highlight, callback, attributes)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun buildImageMessageItem(messageContent: MessageImageInfoContent,
|
private fun buildImageMessageItem(
|
||||||
|
messageContent: MessageImageInfoContent,
|
||||||
@Suppress("UNUSED_PARAMETER")
|
@Suppress("UNUSED_PARAMETER")
|
||||||
informationData: MessageInformationData,
|
informationData: MessageInformationData,
|
||||||
highlight: Boolean,
|
highlight: Boolean,
|
||||||
callback: TimelineEventController.Callback?,
|
callback: TimelineEventController.Callback?,
|
||||||
attributes: AbsMessageItem.Attributes): MessageImageVideoItem? {
|
attributes: AbsMessageItem.Attributes,
|
||||||
|
): MessageImageVideoItem? {
|
||||||
val (maxWidth, maxHeight) = timelineMediaSizeProvider.getMaxSize()
|
val (maxWidth, maxHeight) = timelineMediaSizeProvider.getMaxSize()
|
||||||
val data = ImageContentRenderer.Data(
|
val data = ImageContentRenderer.Data(
|
||||||
eventId = informationData.eventId,
|
eventId = informationData.eventId,
|
||||||
|
@ -504,11 +541,13 @@ class MessageItemFactory @Inject constructor(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun buildVideoMessageItem(messageContent: MessageVideoContent,
|
private fun buildVideoMessageItem(
|
||||||
|
messageContent: MessageVideoContent,
|
||||||
informationData: MessageInformationData,
|
informationData: MessageInformationData,
|
||||||
highlight: Boolean,
|
highlight: Boolean,
|
||||||
callback: TimelineEventController.Callback?,
|
callback: TimelineEventController.Callback?,
|
||||||
attributes: AbsMessageItem.Attributes): MessageImageVideoItem? {
|
attributes: AbsMessageItem.Attributes,
|
||||||
|
): MessageImageVideoItem? {
|
||||||
val (maxWidth, maxHeight) = timelineMediaSizeProvider.getMaxSize()
|
val (maxWidth, maxHeight) = timelineMediaSizeProvider.getMaxSize()
|
||||||
val thumbnailData = ImageContentRenderer.Data(
|
val thumbnailData = ImageContentRenderer.Data(
|
||||||
eventId = informationData.eventId,
|
eventId = informationData.eventId,
|
||||||
|
@ -543,11 +582,13 @@ class MessageItemFactory @Inject constructor(
|
||||||
.clickListener { view -> callback?.onVideoMessageClicked(messageContent, videoData, view.findViewById(R.id.messageThumbnailView)) }
|
.clickListener { view -> callback?.onVideoMessageClicked(messageContent, videoData, view.findViewById(R.id.messageThumbnailView)) }
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun buildItemForTextContent(messageContent: MessageTextContent,
|
private fun buildItemForTextContent(
|
||||||
|
messageContent: MessageTextContent,
|
||||||
informationData: MessageInformationData,
|
informationData: MessageInformationData,
|
||||||
highlight: Boolean,
|
highlight: Boolean,
|
||||||
callback: TimelineEventController.Callback?,
|
callback: TimelineEventController.Callback?,
|
||||||
attributes: AbsMessageItem.Attributes): VectorEpoxyModel<*>? {
|
attributes: AbsMessageItem.Attributes,
|
||||||
|
): VectorEpoxyModel<*>? {
|
||||||
val matrixFormattedBody = messageContent.matrixFormattedBody
|
val matrixFormattedBody = messageContent.matrixFormattedBody
|
||||||
return if (matrixFormattedBody != null) {
|
return if (matrixFormattedBody != null) {
|
||||||
buildFormattedTextItem(matrixFormattedBody, informationData, highlight, callback, attributes)
|
buildFormattedTextItem(matrixFormattedBody, informationData, highlight, callback, attributes)
|
||||||
|
@ -556,22 +597,26 @@ class MessageItemFactory @Inject constructor(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun buildFormattedTextItem(matrixFormattedBody: String,
|
private fun buildFormattedTextItem(
|
||||||
|
matrixFormattedBody: String,
|
||||||
informationData: MessageInformationData,
|
informationData: MessageInformationData,
|
||||||
highlight: Boolean,
|
highlight: Boolean,
|
||||||
callback: TimelineEventController.Callback?,
|
callback: TimelineEventController.Callback?,
|
||||||
attributes: AbsMessageItem.Attributes): MessageTextItem? {
|
attributes: AbsMessageItem.Attributes,
|
||||||
|
): MessageTextItem? {
|
||||||
val compressed = htmlCompressor.compress(matrixFormattedBody)
|
val compressed = htmlCompressor.compress(matrixFormattedBody)
|
||||||
val renderedFormattedBody = htmlRenderer.get().render(compressed, pillsPostProcessor) as Spanned
|
val renderedFormattedBody = htmlRenderer.get().render(compressed, pillsPostProcessor) as Spanned
|
||||||
return buildMessageTextItem(renderedFormattedBody, true, informationData, highlight, callback, attributes)
|
return buildMessageTextItem(renderedFormattedBody, true, informationData, highlight, callback, attributes)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun buildMessageTextItem(body: CharSequence,
|
private fun buildMessageTextItem(
|
||||||
|
body: CharSequence,
|
||||||
isFormatted: Boolean,
|
isFormatted: Boolean,
|
||||||
informationData: MessageInformationData,
|
informationData: MessageInformationData,
|
||||||
highlight: Boolean,
|
highlight: Boolean,
|
||||||
callback: TimelineEventController.Callback?,
|
callback: TimelineEventController.Callback?,
|
||||||
attributes: AbsMessageItem.Attributes): MessageTextItem? {
|
attributes: AbsMessageItem.Attributes,
|
||||||
|
): MessageTextItem? {
|
||||||
val renderedBody = textRenderer.render(body)
|
val renderedBody = textRenderer.render(body)
|
||||||
val bindingOptions = spanUtils.getBindingOptions(renderedBody)
|
val bindingOptions = spanUtils.getBindingOptions(renderedBody)
|
||||||
val linkifiedBody = renderedBody.linkify(callback)
|
val linkifiedBody = renderedBody.linkify(callback)
|
||||||
|
@ -597,9 +642,11 @@ class MessageItemFactory @Inject constructor(
|
||||||
.movementMethod(createLinkMovementMethod(callback))
|
.movementMethod(createLinkMovementMethod(callback))
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun annotateWithEdited(linkifiedBody: CharSequence,
|
private fun annotateWithEdited(
|
||||||
|
linkifiedBody: CharSequence,
|
||||||
callback: TimelineEventController.Callback?,
|
callback: TimelineEventController.Callback?,
|
||||||
informationData: MessageInformationData): Spannable {
|
informationData: MessageInformationData,
|
||||||
|
): Spannable {
|
||||||
val spannable = SpannableStringBuilder()
|
val spannable = SpannableStringBuilder()
|
||||||
spannable.append(linkifiedBody)
|
spannable.append(linkifiedBody)
|
||||||
val editedSuffix = stringProvider.getString(R.string.edited_suffix)
|
val editedSuffix = stringProvider.getString(R.string.edited_suffix)
|
||||||
|
@ -635,12 +682,14 @@ class MessageItemFactory @Inject constructor(
|
||||||
return spannable
|
return spannable
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun buildNoticeMessageItem(messageContent: MessageNoticeContent,
|
private fun buildNoticeMessageItem(
|
||||||
|
messageContent: MessageNoticeContent,
|
||||||
@Suppress("UNUSED_PARAMETER")
|
@Suppress("UNUSED_PARAMETER")
|
||||||
informationData: MessageInformationData,
|
informationData: MessageInformationData,
|
||||||
highlight: Boolean,
|
highlight: Boolean,
|
||||||
callback: TimelineEventController.Callback?,
|
callback: TimelineEventController.Callback?,
|
||||||
attributes: AbsMessageItem.Attributes): MessageTextItem? {
|
attributes: AbsMessageItem.Attributes,
|
||||||
|
): MessageTextItem? {
|
||||||
val htmlBody = messageContent.getHtmlBody()
|
val htmlBody = messageContent.getHtmlBody()
|
||||||
val formattedBody = span {
|
val formattedBody = span {
|
||||||
text = htmlBody
|
text = htmlBody
|
||||||
|
@ -663,11 +712,13 @@ class MessageItemFactory @Inject constructor(
|
||||||
.movementMethod(createLinkMovementMethod(callback))
|
.movementMethod(createLinkMovementMethod(callback))
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun buildEmoteMessageItem(messageContent: MessageEmoteContent,
|
private fun buildEmoteMessageItem(
|
||||||
|
messageContent: MessageEmoteContent,
|
||||||
informationData: MessageInformationData,
|
informationData: MessageInformationData,
|
||||||
highlight: Boolean,
|
highlight: Boolean,
|
||||||
callback: TimelineEventController.Callback?,
|
callback: TimelineEventController.Callback?,
|
||||||
attributes: AbsMessageItem.Attributes): MessageTextItem? {
|
attributes: AbsMessageItem.Attributes,
|
||||||
|
): MessageTextItem? {
|
||||||
val formattedBody = SpannableStringBuilder()
|
val formattedBody = SpannableStringBuilder()
|
||||||
formattedBody.append("* ${informationData.memberName} ")
|
formattedBody.append("* ${informationData.memberName} ")
|
||||||
formattedBody.append(messageContent.getHtmlBody())
|
formattedBody.append(messageContent.getHtmlBody())
|
||||||
|
@ -699,8 +750,10 @@ class MessageItemFactory @Inject constructor(
|
||||||
?: body
|
?: body
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun buildRedactedItem(attributes: AbsMessageItem.Attributes,
|
private fun buildRedactedItem(
|
||||||
highlight: Boolean): RedactedMessageItem? {
|
attributes: AbsMessageItem.Attributes,
|
||||||
|
highlight: Boolean,
|
||||||
|
): RedactedMessageItem? {
|
||||||
return RedactedMessageItem_()
|
return RedactedMessageItem_()
|
||||||
.layout(attributes.informationData.messageLayout.layoutRes)
|
.layout(attributes.informationData.messageLayout.layoutRes)
|
||||||
.leftGuideline(avatarSizeProvider.leftGuideline)
|
.leftGuideline(avatarSizeProvider.leftGuideline)
|
||||||
|
|
|
@ -39,7 +39,7 @@ abstract class PollItem : AbsMessageItem<PollItem.Holder>() {
|
||||||
var eventId: String? = null
|
var eventId: String? = null
|
||||||
|
|
||||||
@EpoxyAttribute
|
@EpoxyAttribute
|
||||||
var pollSent: Boolean = false
|
var canVote: Boolean = false
|
||||||
|
|
||||||
@EpoxyAttribute
|
@EpoxyAttribute
|
||||||
var totalVotesText: String? = null
|
var totalVotesText: String? = null
|
||||||
|
@ -54,7 +54,6 @@ abstract class PollItem : AbsMessageItem<PollItem.Holder>() {
|
||||||
|
|
||||||
override fun bind(holder: Holder) {
|
override fun bind(holder: Holder) {
|
||||||
super.bind(holder)
|
super.bind(holder)
|
||||||
val relatedEventId = eventId ?: return
|
|
||||||
|
|
||||||
renderSendState(holder.view, holder.questionTextView)
|
renderSendState(holder.view, holder.questionTextView)
|
||||||
|
|
||||||
|
@ -73,12 +72,18 @@ abstract class PollItem : AbsMessageItem<PollItem.Holder>() {
|
||||||
optionViewStates.forEachIndexed { index, optionViewState ->
|
optionViewStates.forEachIndexed { index, optionViewState ->
|
||||||
views.getOrNull(index)?.let {
|
views.getOrNull(index)?.let {
|
||||||
it.render(optionViewState)
|
it.render(optionViewState)
|
||||||
it.setOnClickListener {
|
it.setOnClickListener { onPollItemClick(optionViewState) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun onPollItemClick(optionViewState: PollOptionViewState) {
|
||||||
|
val relatedEventId = eventId
|
||||||
|
|
||||||
|
if (canVote && relatedEventId != null) {
|
||||||
callback?.onTimelineItemAction(RoomDetailAction.VoteToPoll(relatedEventId, optionViewState.optionId))
|
callback?.onTimelineItemAction(RoomDetailAction.VoteToPoll(relatedEventId, optionViewState.optionId))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class Holder : AbsMessageItem.Holder(STUB_ID) {
|
class Holder : AbsMessageItem.Holder(STUB_ID) {
|
||||||
val questionTextView by bind<TextView>(R.id.questionTextView)
|
val questionTextView by bind<TextView>(R.id.questionTextView)
|
||||||
|
|
|
@ -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.home.room.list.widget.NotifsFabMenuView
|
||||||
import im.vector.app.features.notifications.NotificationDrawerManager
|
import im.vector.app.features.notifications.NotificationDrawerManager
|
||||||
import kotlinx.coroutines.flow.collect
|
import kotlinx.coroutines.flow.collect
|
||||||
|
import kotlinx.coroutines.flow.filter
|
||||||
import kotlinx.coroutines.flow.launchIn
|
import kotlinx.coroutines.flow.launchIn
|
||||||
import kotlinx.coroutines.flow.onEach
|
import kotlinx.coroutines.flow.onEach
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
@ -147,8 +148,10 @@ class RoomListFragment @Inject constructor(
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun refreshCollapseStates() {
|
private fun refreshCollapseStates() {
|
||||||
|
val sectionsCount = adapterInfosList.count { !it.sectionHeaderAdapter.roomsSectionData.isHidden }
|
||||||
roomListViewModel.sections.forEachIndexed { index, roomsSection ->
|
roomListViewModel.sections.forEachIndexed { index, roomsSection ->
|
||||||
val actualBlock = adapterInfosList[index]
|
val actualBlock = adapterInfosList[index]
|
||||||
|
val isRoomSectionCollapsable = sectionsCount > 1
|
||||||
val isRoomSectionExpanded = roomsSection.isExpanded.value.orTrue()
|
val isRoomSectionExpanded = roomsSection.isExpanded.value.orTrue()
|
||||||
if (actualBlock.section.isExpanded && !isRoomSectionExpanded) {
|
if (actualBlock.section.isExpanded && !isRoomSectionExpanded) {
|
||||||
// mark controller as collapsed
|
// mark controller as collapsed
|
||||||
|
@ -157,13 +160,19 @@ class RoomListFragment @Inject constructor(
|
||||||
// we must expand!
|
// we must expand!
|
||||||
actualBlock.contentEpoxyController.setCollapsed(false)
|
actualBlock.contentEpoxyController.setCollapsed(false)
|
||||||
}
|
}
|
||||||
actualBlock.section = actualBlock.section.copy(
|
actualBlock.section = actualBlock.section.copy(isExpanded = isRoomSectionExpanded)
|
||||||
isExpanded = isRoomSectionExpanded
|
actualBlock.sectionHeaderAdapter.updateSection {
|
||||||
)
|
it.copy(
|
||||||
actualBlock.sectionHeaderAdapter.updateSection(
|
isExpanded = isRoomSectionExpanded,
|
||||||
actualBlock.sectionHeaderAdapter.roomsSectionData.copy(isExpanded = isRoomSectionExpanded)
|
isCollapsable = isRoomSectionCollapsable
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!isRoomSectionExpanded && !isRoomSectionCollapsable) {
|
||||||
|
// force expand if the section is not collapsable
|
||||||
|
roomListViewModel.handle(RoomListAction.ToggleSection(roomsSection))
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun showFailure(throwable: Throwable) {
|
override fun showFailure(throwable: Throwable) {
|
||||||
|
@ -270,13 +279,12 @@ class RoomListFragment @Inject constructor(
|
||||||
|
|
||||||
val concatAdapter = ConcatAdapter()
|
val concatAdapter = ConcatAdapter()
|
||||||
|
|
||||||
roomListViewModel.sections.forEach { section ->
|
roomListViewModel.sections.forEachIndexed { index, section ->
|
||||||
val sectionAdapter = SectionHeaderAdapter {
|
val sectionAdapter = SectionHeaderAdapter(SectionHeaderAdapter.RoomsSectionData(section.sectionName)) {
|
||||||
|
if (adapterInfosList[index].sectionHeaderAdapter.roomsSectionData.isCollapsable) {
|
||||||
roomListViewModel.handle(RoomListAction.ToggleSection(section))
|
roomListViewModel.handle(RoomListAction.ToggleSection(section))
|
||||||
}.also {
|
|
||||||
it.updateSection(SectionHeaderAdapter.RoomsSectionData(section.sectionName))
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
val contentAdapter =
|
val contentAdapter =
|
||||||
when {
|
when {
|
||||||
section.livePages != null -> {
|
section.livePages != null -> {
|
||||||
|
@ -284,18 +292,23 @@ class RoomListFragment @Inject constructor(
|
||||||
.also { controller ->
|
.also { controller ->
|
||||||
section.livePages.observe(viewLifecycleOwner) { pl ->
|
section.livePages.observe(viewLifecycleOwner) { pl ->
|
||||||
controller.submitList(pl)
|
controller.submitList(pl)
|
||||||
sectionAdapter.updateSection(sectionAdapter.roomsSectionData.copy(
|
sectionAdapter.updateSection {
|
||||||
|
it.copy(
|
||||||
isHidden = pl.isEmpty(),
|
isHidden = pl.isEmpty(),
|
||||||
isLoading = false
|
isLoading = false
|
||||||
))
|
)
|
||||||
|
}
|
||||||
|
refreshCollapseStates()
|
||||||
checkEmptyState()
|
checkEmptyState()
|
||||||
}
|
}
|
||||||
observeItemCount(section, sectionAdapter)
|
observeItemCount(section, sectionAdapter)
|
||||||
section.notificationCount.observe(viewLifecycleOwner) { counts ->
|
section.notificationCount.observe(viewLifecycleOwner) { counts ->
|
||||||
sectionAdapter.updateSection(sectionAdapter.roomsSectionData.copy(
|
sectionAdapter.updateSection {
|
||||||
|
it.copy(
|
||||||
notificationCount = counts.totalCount,
|
notificationCount = counts.totalCount,
|
||||||
isHighlighted = counts.isHighlight
|
isHighlighted = counts.isHighlight,
|
||||||
))
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
section.isExpanded.observe(viewLifecycleOwner) { _ ->
|
section.isExpanded.observe(viewLifecycleOwner) { _ ->
|
||||||
refreshCollapseStates()
|
refreshCollapseStates()
|
||||||
|
@ -308,10 +321,13 @@ class RoomListFragment @Inject constructor(
|
||||||
.also { controller ->
|
.also { controller ->
|
||||||
section.liveSuggested.observe(viewLifecycleOwner) { info ->
|
section.liveSuggested.observe(viewLifecycleOwner) { info ->
|
||||||
controller.setData(info)
|
controller.setData(info)
|
||||||
sectionAdapter.updateSection(sectionAdapter.roomsSectionData.copy(
|
sectionAdapter.updateSection {
|
||||||
|
it.copy(
|
||||||
isHidden = info.rooms.isEmpty(),
|
isHidden = info.rooms.isEmpty(),
|
||||||
isLoading = false
|
isLoading = false
|
||||||
))
|
)
|
||||||
|
}
|
||||||
|
refreshCollapseStates()
|
||||||
checkEmptyState()
|
checkEmptyState()
|
||||||
}
|
}
|
||||||
observeItemCount(section, sectionAdapter)
|
observeItemCount(section, sectionAdapter)
|
||||||
|
@ -326,17 +342,23 @@ class RoomListFragment @Inject constructor(
|
||||||
.also { controller ->
|
.also { controller ->
|
||||||
section.liveList?.observe(viewLifecycleOwner) { list ->
|
section.liveList?.observe(viewLifecycleOwner) { list ->
|
||||||
controller.setData(list)
|
controller.setData(list)
|
||||||
sectionAdapter.updateSection(sectionAdapter.roomsSectionData.copy(
|
sectionAdapter.updateSection {
|
||||||
|
it.copy(
|
||||||
isHidden = list.isEmpty(),
|
isHidden = list.isEmpty(),
|
||||||
isLoading = false))
|
isLoading = false,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
refreshCollapseStates()
|
||||||
checkEmptyState()
|
checkEmptyState()
|
||||||
}
|
}
|
||||||
observeItemCount(section, sectionAdapter)
|
observeItemCount(section, sectionAdapter)
|
||||||
section.notificationCount.observe(viewLifecycleOwner) { counts ->
|
section.notificationCount.observe(viewLifecycleOwner) { counts ->
|
||||||
sectionAdapter.updateSection(sectionAdapter.roomsSectionData.copy(
|
sectionAdapter.updateSection {
|
||||||
|
it.copy(
|
||||||
notificationCount = counts.totalCount,
|
notificationCount = counts.totalCount,
|
||||||
isHighlighted = counts.isHighlight
|
isHighlighted = counts.isHighlight
|
||||||
))
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
section.isExpanded.observe(viewLifecycleOwner) { _ ->
|
section.isExpanded.observe(viewLifecycleOwner) { _ ->
|
||||||
refreshCollapseStates()
|
refreshCollapseStates()
|
||||||
|
@ -383,10 +405,11 @@ class RoomListFragment @Inject constructor(
|
||||||
lifecycleScope.launch {
|
lifecycleScope.launch {
|
||||||
section.itemCount
|
section.itemCount
|
||||||
.flowWithLifecycle(lifecycle, Lifecycle.State.STARTED)
|
.flowWithLifecycle(lifecycle, Lifecycle.State.STARTED)
|
||||||
|
.filter { it > 0 }
|
||||||
.collect { count ->
|
.collect { count ->
|
||||||
sectionAdapter.updateSection(
|
sectionAdapter.updateSection {
|
||||||
sectionAdapter.roomsSectionData.copy(itemCount = count)
|
it.copy(itemCount = count)
|
||||||
)
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -70,12 +70,11 @@ class RoomListSectionBuilderGroup(
|
||||||
},
|
},
|
||||||
{ qpm ->
|
{ qpm ->
|
||||||
val name = stringProvider.getString(R.string.bottom_action_rooms)
|
val name = stringProvider.getString(R.string.bottom_action_rooms)
|
||||||
session.getFilteredPagedRoomSummariesLive(qpm)
|
val updatableFilterLivePageResult = session.getFilteredPagedRoomSummariesLive(qpm)
|
||||||
.let { updatableFilterLivePageResult ->
|
|
||||||
onUpdatable(updatableFilterLivePageResult)
|
onUpdatable(updatableFilterLivePageResult)
|
||||||
|
|
||||||
val itemCountFlow = updatableFilterLivePageResult.livePagedList.asFlow()
|
val itemCountFlow = updatableFilterLivePageResult.livePagedList.asFlow()
|
||||||
.flatMapLatest { session.getRoomCountFlow(updatableFilterLivePageResult.queryParams) }
|
.flatMapLatest { session.getRoomCountLive(updatableFilterLivePageResult.queryParams).asFlow() }
|
||||||
.distinctUntilChanged()
|
.distinctUntilChanged()
|
||||||
|
|
||||||
sections.add(
|
sections.add(
|
||||||
|
@ -86,7 +85,6 @@ class RoomListSectionBuilderGroup(
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
RoomListDisplayMode.NOTIFICATIONS -> {
|
RoomListDisplayMode.NOTIFICATIONS -> {
|
||||||
|
@ -252,9 +250,7 @@ class RoomListSectionBuilderGroup(
|
||||||
@StringRes nameRes: Int,
|
@StringRes nameRes: Int,
|
||||||
notifyOfLocalEcho: Boolean = false,
|
notifyOfLocalEcho: Boolean = false,
|
||||||
query: (RoomSummaryQueryParams.Builder) -> Unit) {
|
query: (RoomSummaryQueryParams.Builder) -> Unit) {
|
||||||
withQueryParams(
|
withQueryParams(query) { roomQueryParams ->
|
||||||
{ query.invoke(it) },
|
|
||||||
{ roomQueryParams ->
|
|
||||||
val name = stringProvider.getString(nameRes)
|
val name = stringProvider.getString(nameRes)
|
||||||
session.getFilteredPagedRoomSummariesLive(roomQueryParams)
|
session.getFilteredPagedRoomSummariesLive(roomQueryParams)
|
||||||
.also {
|
.also {
|
||||||
|
@ -276,13 +272,11 @@ class RoomListSectionBuilderGroup(
|
||||||
sectionName = name,
|
sectionName = name,
|
||||||
livePages = livePagedList,
|
livePages = livePagedList,
|
||||||
notifyOfLocalEcho = notifyOfLocalEcho,
|
notifyOfLocalEcho = notifyOfLocalEcho,
|
||||||
itemCount = session.getRoomCountFlow(roomQueryParams)
|
itemCount = session.getRoomCountLive(roomQueryParams).asFlow()
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun withQueryParams(builder: (RoomSummaryQueryParams.Builder) -> Unit, block: (RoomSummaryQueryParams) -> Unit) {
|
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 im.vector.app.space
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
import kotlinx.coroutines.flow.combine
|
import kotlinx.coroutines.flow.combine
|
||||||
import kotlinx.coroutines.flow.distinctUntilChanged
|
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||||
import kotlinx.coroutines.flow.flatMapLatest
|
import kotlinx.coroutines.flow.flatMapLatest
|
||||||
|
@ -40,6 +41,7 @@ import kotlinx.coroutines.flow.flowOn
|
||||||
import kotlinx.coroutines.flow.launchIn
|
import kotlinx.coroutines.flow.launchIn
|
||||||
import kotlinx.coroutines.flow.map
|
import kotlinx.coroutines.flow.map
|
||||||
import kotlinx.coroutines.flow.onEach
|
import kotlinx.coroutines.flow.onEach
|
||||||
|
import kotlinx.coroutines.flow.update
|
||||||
import org.matrix.android.sdk.api.extensions.tryOrNull
|
import org.matrix.android.sdk.api.extensions.tryOrNull
|
||||||
import org.matrix.android.sdk.api.query.ActiveSpaceFilter
|
import org.matrix.android.sdk.api.query.ActiveSpaceFilter
|
||||||
import org.matrix.android.sdk.api.query.RoomCategoryFilter
|
import org.matrix.android.sdk.api.query.RoomCategoryFilter
|
||||||
|
@ -83,64 +85,10 @@ class RoomListSectionBuilderSpace(
|
||||||
}
|
}
|
||||||
RoomListDisplayMode.FILTERED -> {
|
RoomListDisplayMode.FILTERED -> {
|
||||||
// Used when searching for rooms
|
// Used when searching for rooms
|
||||||
withQueryParams(
|
buildFilteredSection(sections)
|
||||||
{
|
|
||||||
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
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
RoomListDisplayMode.NOTIFICATIONS -> {
|
RoomListDisplayMode.NOTIFICATIONS -> {
|
||||||
if (autoAcceptInvites.showInvites()) {
|
buildNotificationsSection(sections, activeSpaceAwareQueries)
|
||||||
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
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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>,
|
private fun addSection(sections: MutableList<RoomsSection>,
|
||||||
activeSpaceUpdaters: MutableList<RoomListViewModel.ActiveSpaceQueryUpdater>,
|
activeSpaceUpdaters: MutableList<RoomListViewModel.ActiveSpaceQueryUpdater>,
|
||||||
@StringRes nameRes: Int,
|
@StringRes nameRes: Int,
|
||||||
|
@ -339,21 +349,29 @@ class RoomListSectionBuilderSpace(
|
||||||
spaceFilterStrategy: RoomListViewModel.SpaceFilterStrategy = RoomListViewModel.SpaceFilterStrategy.NONE,
|
spaceFilterStrategy: RoomListViewModel.SpaceFilterStrategy = RoomListViewModel.SpaceFilterStrategy.NONE,
|
||||||
countRoomAsNotif: Boolean = false,
|
countRoomAsNotif: Boolean = false,
|
||||||
query: (RoomSummaryQueryParams.Builder) -> Unit) {
|
query: (RoomSummaryQueryParams.Builder) -> Unit) {
|
||||||
withQueryParams(
|
withQueryParams(query) { roomQueryParams ->
|
||||||
{ query.invoke(it) },
|
val updatedQueryParams = roomQueryParams.process(spaceFilterStrategy, appStateHandler.safeActiveSpaceId())
|
||||||
{ roomQueryParams ->
|
val liveQueryParams = MutableStateFlow(updatedQueryParams)
|
||||||
|
val itemCountFlow = liveQueryParams
|
||||||
|
.flatMapLatest {
|
||||||
|
session.getRoomCountLive(it).asFlow()
|
||||||
|
}
|
||||||
|
.flowOn(Dispatchers.Main)
|
||||||
|
.distinctUntilChanged()
|
||||||
|
|
||||||
val name = stringProvider.getString(nameRes)
|
val name = stringProvider.getString(nameRes)
|
||||||
session.getFilteredPagedRoomSummariesLive(
|
val filteredPagedRoomSummariesLive = session.getFilteredPagedRoomSummariesLive(
|
||||||
roomQueryParams.process(spaceFilterStrategy, appStateHandler.safeActiveSpaceId()),
|
roomQueryParams.process(spaceFilterStrategy, appStateHandler.safeActiveSpaceId()),
|
||||||
pagedListConfig
|
pagedListConfig
|
||||||
).also {
|
)
|
||||||
when (spaceFilterStrategy) {
|
when (spaceFilterStrategy) {
|
||||||
RoomListViewModel.SpaceFilterStrategy.ORPHANS_IF_SPACE_NULL -> {
|
RoomListViewModel.SpaceFilterStrategy.ORPHANS_IF_SPACE_NULL -> {
|
||||||
activeSpaceUpdaters.add(object : RoomListViewModel.ActiveSpaceQueryUpdater {
|
activeSpaceUpdaters.add(object : RoomListViewModel.ActiveSpaceQueryUpdater {
|
||||||
override fun updateForSpaceId(roomId: String?) {
|
override fun updateForSpaceId(roomId: String?) {
|
||||||
it.queryParams = roomQueryParams.copy(
|
filteredPagedRoomSummariesLive.queryParams = roomQueryParams.copy(
|
||||||
activeSpaceFilter = ActiveSpaceFilter.ActiveSpace(roomId)
|
activeSpaceFilter = ActiveSpaceFilter.ActiveSpace(roomId)
|
||||||
)
|
)
|
||||||
|
liveQueryParams.update { filteredPagedRoomSummariesLive.queryParams }
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -361,14 +379,15 @@ class RoomListSectionBuilderSpace(
|
||||||
activeSpaceUpdaters.add(object : RoomListViewModel.ActiveSpaceQueryUpdater {
|
activeSpaceUpdaters.add(object : RoomListViewModel.ActiveSpaceQueryUpdater {
|
||||||
override fun updateForSpaceId(roomId: String?) {
|
override fun updateForSpaceId(roomId: String?) {
|
||||||
if (roomId != null) {
|
if (roomId != null) {
|
||||||
it.queryParams = roomQueryParams.copy(
|
filteredPagedRoomSummariesLive.queryParams = roomQueryParams.copy(
|
||||||
activeSpaceFilter = ActiveSpaceFilter.ActiveSpace(roomId)
|
activeSpaceFilter = ActiveSpaceFilter.ActiveSpace(roomId)
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
it.queryParams = roomQueryParams.copy(
|
filteredPagedRoomSummariesLive.queryParams = roomQueryParams.copy(
|
||||||
activeSpaceFilter = ActiveSpaceFilter.None
|
activeSpaceFilter = ActiveSpaceFilter.None
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
liveQueryParams.update { filteredPagedRoomSummariesLive.queryParams }
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -376,8 +395,8 @@ class RoomListSectionBuilderSpace(
|
||||||
// we ignore current space for this one
|
// we ignore current space for this one
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}.livePagedList
|
|
||||||
.let { livePagedList ->
|
val livePagedList = filteredPagedRoomSummariesLive.livePagedList
|
||||||
// use it also as a source to update count
|
// use it also as a source to update count
|
||||||
livePagedList.asFlow()
|
livePagedList.asFlow()
|
||||||
.onEach {
|
.onEach {
|
||||||
|
@ -397,13 +416,6 @@ class RoomListSectionBuilderSpace(
|
||||||
.flowOn(Dispatchers.Default)
|
.flowOn(Dispatchers.Default)
|
||||||
.launchIn(viewModelScope)
|
.launchIn(viewModelScope)
|
||||||
|
|
||||||
val itemCountFlow = livePagedList.asFlow()
|
|
||||||
.flatMapLatest {
|
|
||||||
val queryParams = roomQueryParams.process(spaceFilterStrategy, appStateHandler.safeActiveSpaceId())
|
|
||||||
session.getRoomCountFlow(queryParams)
|
|
||||||
}
|
|
||||||
.distinctUntilChanged()
|
|
||||||
|
|
||||||
sections.add(
|
sections.add(
|
||||||
RoomsSection(
|
RoomsSection(
|
||||||
sectionName = name,
|
sectionName = name,
|
||||||
|
@ -415,9 +427,6 @@ class RoomListSectionBuilderSpace(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun withQueryParams(builder: (RoomSummaryQueryParams.Builder) -> Unit, block: (RoomSummaryQueryParams) -> Unit) {
|
private fun withQueryParams(builder: (RoomSummaryQueryParams.Builder) -> Unit, block: (RoomSummaryQueryParams) -> Unit) {
|
||||||
RoomSummaryQueryParams.Builder()
|
RoomSummaryQueryParams.Builder()
|
||||||
.apply { builder.invoke(this) }
|
.apply { builder.invoke(this) }
|
||||||
|
|
|
@ -16,6 +16,7 @@
|
||||||
|
|
||||||
package im.vector.app.features.home.room.list
|
package im.vector.app.features.home.room.list
|
||||||
|
|
||||||
|
import android.graphics.drawable.Drawable
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import androidx.core.content.ContextCompat
|
import androidx.core.content.ContextCompat
|
||||||
|
@ -28,6 +29,7 @@ import im.vector.app.databinding.ItemRoomCategoryBinding
|
||||||
import im.vector.app.features.themes.ThemeUtils
|
import im.vector.app.features.themes.ThemeUtils
|
||||||
|
|
||||||
class SectionHeaderAdapter constructor(
|
class SectionHeaderAdapter constructor(
|
||||||
|
roomsSectionData: RoomsSectionData,
|
||||||
private val onClickAction: ClickListener
|
private val onClickAction: ClickListener
|
||||||
) : RecyclerView.Adapter<SectionHeaderAdapter.VH>() {
|
) : RecyclerView.Adapter<SectionHeaderAdapter.VH>() {
|
||||||
|
|
||||||
|
@ -39,14 +41,16 @@ class SectionHeaderAdapter constructor(
|
||||||
val isHighlighted: Boolean = false,
|
val isHighlighted: Boolean = false,
|
||||||
val isHidden: Boolean = true,
|
val isHidden: Boolean = true,
|
||||||
// This will be false until real data has been submitted once
|
// 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
|
private set
|
||||||
|
|
||||||
fun updateSection(newRoomsSectionData: RoomsSectionData) {
|
fun updateSection(block: (RoomsSectionData) -> RoomsSectionData) {
|
||||||
if (!::roomsSectionData.isInitialized || newRoomsSectionData != roomsSectionData) {
|
val newRoomsSectionData = block(roomsSectionData)
|
||||||
|
if (roomsSectionData != newRoomsSectionData) {
|
||||||
roomsSectionData = newRoomsSectionData
|
roomsSectionData = newRoomsSectionData
|
||||||
notifyDataSetChanged()
|
notifyDataSetChanged()
|
||||||
}
|
}
|
||||||
|
@ -82,11 +86,16 @@ class SectionHeaderAdapter constructor(
|
||||||
fun bind(roomsSectionData: RoomsSectionData) {
|
fun bind(roomsSectionData: RoomsSectionData) {
|
||||||
binding.roomCategoryTitleView.text = roomsSectionData.name
|
binding.roomCategoryTitleView.text = roomsSectionData.name
|
||||||
val tintColor = ThemeUtils.getColor(binding.root.context, R.attr.vctr_content_secondary)
|
val tintColor = ThemeUtils.getColor(binding.root.context, R.attr.vctr_content_secondary)
|
||||||
|
val collapsableArrowDrawable: Drawable? = if (roomsSectionData.isCollapsable) {
|
||||||
val expandedArrowDrawableRes = if (roomsSectionData.isExpanded) R.drawable.ic_expand_more else R.drawable.ic_expand_less
|
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 {
|
ContextCompat.getDrawable(binding.root.context, expandedArrowDrawableRes)?.also {
|
||||||
DrawableCompat.setTint(it, tintColor)
|
DrawableCompat.setTint(it, tintColor)
|
||||||
}
|
}
|
||||||
binding.roomCategoryCounterView.setCompoundDrawablesWithIntrinsicBounds(null, null, expandedArrowDrawable, null)
|
} else {
|
||||||
|
null
|
||||||
|
}
|
||||||
|
binding.root.isClickable = roomsSectionData.isCollapsable
|
||||||
|
binding.roomCategoryCounterView.setCompoundDrawablesWithIntrinsicBounds(null, null, collapsableArrowDrawable, null)
|
||||||
binding.roomCategoryCounterView.text = roomsSectionData.itemCount.takeIf { it > 0 }?.toString().orEmpty()
|
binding.roomCategoryCounterView.text = roomsSectionData.itemCount.takeIf { it > 0 }?.toString().orEmpty()
|
||||||
binding.roomCategoryUnreadCounterBadgeView.render(UnreadCounterBadgeView.State(roomsSectionData.notificationCount, roomsSectionData.isHighlighted))
|
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.PinActivity
|
||||||
import im.vector.app.features.pin.PinArgs
|
import im.vector.app.features.pin.PinArgs
|
||||||
import im.vector.app.features.pin.PinMode
|
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.CreatePollActivity
|
||||||
import im.vector.app.features.poll.create.CreatePollArgs
|
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.RoomDirectoryActivity
|
||||||
import im.vector.app.features.roomdirectory.RoomDirectoryData
|
import im.vector.app.features.roomdirectory.RoomDirectoryData
|
||||||
import im.vector.app.features.roomdirectory.createroom.CreateRoomActivity
|
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.login.LoginConfig
|
||||||
import im.vector.app.features.media.AttachmentData
|
import im.vector.app.features.media.AttachmentData
|
||||||
import im.vector.app.features.pin.PinMode
|
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.RoomDirectoryData
|
||||||
import im.vector.app.features.roomdirectory.roompreview.RoomPreviewData
|
import im.vector.app.features.roomdirectory.roompreview.RoomPreviewData
|
||||||
import im.vector.app.features.settings.VectorSettingsActivity
|
import im.vector.app.features.settings.VectorSettingsActivity
|
||||||
|
|
|
@ -524,7 +524,7 @@ class OnboardingViewModel @AssistedInject constructor(
|
||||||
onDirectLoginError(failure)
|
onDirectLoginError(failure)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
onSessionCreated(data, isAccountCreated = true)
|
onSessionCreated(data, isAccountCreated = false)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun onDirectLoginError(failure: Throwable) {
|
private fun onDirectLoginError(failure: Throwable) {
|
||||||
|
|
|
@ -14,7 +14,7 @@
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package im.vector.app.features.poll.create
|
package im.vector.app.features.poll
|
||||||
|
|
||||||
enum class PollMode {
|
enum class PollMode {
|
||||||
CREATE,
|
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.extensions.configureWith
|
||||||
import im.vector.app.core.platform.VectorBaseFragment
|
import im.vector.app.core.platform.VectorBaseFragment
|
||||||
import im.vector.app.databinding.FragmentCreatePollBinding
|
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 im.vector.app.features.poll.create.CreatePollViewModel.Companion.MAX_OPTIONS_COUNT
|
||||||
import kotlinx.parcelize.Parcelize
|
import kotlinx.parcelize.Parcelize
|
||||||
import org.matrix.android.sdk.api.session.room.model.message.PollType
|
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.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.poll.PollMode
|
||||||
import org.matrix.android.sdk.api.session.Session
|
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.MessagePollContent
|
||||||
import org.matrix.android.sdk.api.session.room.model.message.PollType
|
import org.matrix.android.sdk.api.session.room.model.message.PollType
|
||||||
|
|
|
@ -17,6 +17,7 @@
|
||||||
package im.vector.app.features.poll.create
|
package im.vector.app.features.poll.create
|
||||||
|
|
||||||
import com.airbnb.mvrx.MavericksState
|
import com.airbnb.mvrx.MavericksState
|
||||||
|
import im.vector.app.features.poll.PollMode
|
||||||
import org.matrix.android.sdk.api.session.room.model.message.PollType
|
import org.matrix.android.sdk.api.session.room.model.message.PollType
|
||||||
|
|
||||||
data class CreatePollViewState(
|
data class CreatePollViewState(
|
||||||
|
|
|
@ -84,7 +84,7 @@ class SpaceAddRoomsViewModel @AssistedInject constructor(
|
||||||
|
|
||||||
val spaceCountFlow: Flow<Int> by lazy {
|
val spaceCountFlow: Flow<Int> by lazy {
|
||||||
spaceUpdatableLivePageResult.livePagedList.asFlow()
|
spaceUpdatableLivePageResult.livePagedList.asFlow()
|
||||||
.flatMapLatest { session.getRoomCountFlow(spaceUpdatableLivePageResult.queryParams) }
|
.flatMapLatest { session.getRoomCountLive(spaceUpdatableLivePageResult.queryParams).asFlow() }
|
||||||
.distinctUntilChanged()
|
.distinctUntilChanged()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -110,7 +110,7 @@ class SpaceAddRoomsViewModel @AssistedInject constructor(
|
||||||
|
|
||||||
val roomCountFlow: Flow<Int> by lazy {
|
val roomCountFlow: Flow<Int> by lazy {
|
||||||
roomUpdatableLivePageResult.livePagedList.asFlow()
|
roomUpdatableLivePageResult.livePagedList.asFlow()
|
||||||
.flatMapLatest { session.getRoomCountFlow(roomUpdatableLivePageResult.queryParams) }
|
.flatMapLatest { session.getRoomCountLive(roomUpdatableLivePageResult.queryParams).asFlow() }
|
||||||
.distinctUntilChanged()
|
.distinctUntilChanged()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -136,7 +136,7 @@ class SpaceAddRoomsViewModel @AssistedInject constructor(
|
||||||
|
|
||||||
val dmCountFlow: Flow<Int> by lazy {
|
val dmCountFlow: Flow<Int> by lazy {
|
||||||
dmUpdatableLivePageResult.livePagedList.asFlow()
|
dmUpdatableLivePageResult.livePagedList.asFlow()
|
||||||
.flatMapLatest { session.getRoomCountFlow(dmUpdatableLivePageResult.queryParams) }
|
.flatMapLatest { session.getRoomCountLive(dmUpdatableLivePageResult.queryParams).asFlow() }
|
||||||
.distinctUntilChanged()
|
.distinctUntilChanged()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Add table
Reference in a new issue