mirror of
https://github.com/SchildiChat/SchildiChat-android.git
synced 2024-11-24 18:36:21 +03:00
Merge pull request #8069 from vector-im/feature/mna/poll-history-details
[Poll] History list: details screen of a poll (PSG-1041, PSG-1151)
This commit is contained in:
commit
552af673ff
42 changed files with 1929 additions and 134 deletions
2
changelog.d/8056.feature
Normal file
2
changelog.d/8056.feature
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
[Poll] History list: details screen of a poll
|
||||||
|
[Poll] History list: enable the new settings entry in release mode
|
|
@ -3211,6 +3211,7 @@
|
||||||
<string name="room_polls_wait_for_display">Displaying polls</string>
|
<string name="room_polls_wait_for_display">Displaying polls</string>
|
||||||
<string name="room_polls_load_more">Load more polls</string>
|
<string name="room_polls_load_more">Load more polls</string>
|
||||||
<string name="room_polls_loading_error">Error fetching polls.</string>
|
<string name="room_polls_loading_error">Error fetching polls.</string>
|
||||||
|
<string name="room_poll_details_go_to_timeline">View poll in timeline</string>
|
||||||
|
|
||||||
<!-- Location -->
|
<!-- Location -->
|
||||||
<string name="location_activity_title_static_sharing">Share location</string>
|
<string name="location_activity_title_static_sharing">Share location</string>
|
||||||
|
|
|
@ -327,6 +327,7 @@
|
||||||
<activity android:name=".features.settings.devices.v2.details.SessionDetailsActivity" />
|
<activity android:name=".features.settings.devices.v2.details.SessionDetailsActivity" />
|
||||||
<activity android:name=".features.settings.devices.v2.rename.RenameSessionActivity" />
|
<activity android:name=".features.settings.devices.v2.rename.RenameSessionActivity" />
|
||||||
<activity android:name=".features.login.qr.QrCodeLoginActivity" />
|
<activity android:name=".features.login.qr.QrCodeLoginActivity" />
|
||||||
|
<activity android:name=".features.roomprofile.polls.detail.ui.RoomPollDetailActivity" />
|
||||||
|
|
||||||
<!-- Services -->
|
<!-- Services -->
|
||||||
|
|
||||||
|
|
|
@ -85,6 +85,7 @@ import im.vector.app.features.roomprofile.members.RoomMemberListViewModel
|
||||||
import im.vector.app.features.roomprofile.notifications.RoomNotificationSettingsViewModel
|
import im.vector.app.features.roomprofile.notifications.RoomNotificationSettingsViewModel
|
||||||
import im.vector.app.features.roomprofile.permissions.RoomPermissionsViewModel
|
import im.vector.app.features.roomprofile.permissions.RoomPermissionsViewModel
|
||||||
import im.vector.app.features.roomprofile.polls.RoomPollsViewModel
|
import im.vector.app.features.roomprofile.polls.RoomPollsViewModel
|
||||||
|
import im.vector.app.features.roomprofile.polls.detail.ui.RoomPollDetailViewModel
|
||||||
import im.vector.app.features.roomprofile.settings.RoomSettingsViewModel
|
import im.vector.app.features.roomprofile.settings.RoomSettingsViewModel
|
||||||
import im.vector.app.features.roomprofile.settings.joinrule.advanced.RoomJoinRuleChooseRestrictedViewModel
|
import im.vector.app.features.roomprofile.settings.joinrule.advanced.RoomJoinRuleChooseRestrictedViewModel
|
||||||
import im.vector.app.features.roomprofile.uploads.RoomUploadsViewModel
|
import im.vector.app.features.roomprofile.uploads.RoomUploadsViewModel
|
||||||
|
@ -703,4 +704,9 @@ interface MavericksViewModelModule {
|
||||||
@IntoMap
|
@IntoMap
|
||||||
@MavericksViewModelKey(RoomPollsViewModel::class)
|
@MavericksViewModelKey(RoomPollsViewModel::class)
|
||||||
fun roomPollsViewModelFactory(factory: RoomPollsViewModel.Factory): MavericksAssistedViewModelFactory<*, *>
|
fun roomPollsViewModelFactory(factory: RoomPollsViewModel.Factory): MavericksAssistedViewModelFactory<*, *>
|
||||||
|
|
||||||
|
@Binds
|
||||||
|
@IntoMap
|
||||||
|
@MavericksViewModelKey(RoomPollDetailViewModel::class)
|
||||||
|
fun roomPollDetailViewModelFactory(factory: RoomPollDetailViewModel.Factory): MavericksAssistedViewModelFactory<*, *>
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,40 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2023 New Vector Ltd
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package im.vector.app.core.event
|
||||||
|
|
||||||
|
import androidx.lifecycle.asFlow
|
||||||
|
import im.vector.app.core.di.ActiveSessionHolder
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
import kotlinx.coroutines.flow.emptyFlow
|
||||||
|
import org.matrix.android.sdk.api.session.getRoom
|
||||||
|
import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent
|
||||||
|
import org.matrix.android.sdk.flow.unwrap
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
class GetTimelineEventUseCase @Inject constructor(
|
||||||
|
private val activeSessionHolder: ActiveSessionHolder,
|
||||||
|
) {
|
||||||
|
|
||||||
|
fun execute(roomId: String, eventId: String): Flow<TimelineEvent> {
|
||||||
|
return activeSessionHolder.getActiveSession().getRoom(roomId)
|
||||||
|
?.timelineService()
|
||||||
|
?.getTimelineEventLive(eventId)
|
||||||
|
?.asFlow()
|
||||||
|
?.unwrap()
|
||||||
|
?: emptyFlow()
|
||||||
|
}
|
||||||
|
}
|
|
@ -54,6 +54,7 @@ import im.vector.app.features.crypto.verification.SupportedVerificationMethodsPr
|
||||||
import im.vector.app.features.home.room.detail.RoomDetailAction.VoiceBroadcastAction
|
import im.vector.app.features.home.room.detail.RoomDetailAction.VoiceBroadcastAction
|
||||||
import im.vector.app.features.home.room.detail.error.RoomNotFound
|
import im.vector.app.features.home.room.detail.error.RoomNotFound
|
||||||
import im.vector.app.features.home.room.detail.location.RedactLiveLocationShareEventUseCase
|
import im.vector.app.features.home.room.detail.location.RedactLiveLocationShareEventUseCase
|
||||||
|
import im.vector.app.features.home.room.detail.poll.VoteToPollUseCase
|
||||||
import im.vector.app.features.home.room.detail.sticker.StickerPickerActionHandler
|
import im.vector.app.features.home.room.detail.sticker.StickerPickerActionHandler
|
||||||
import im.vector.app.features.home.room.detail.timeline.factory.TimelineFactory
|
import im.vector.app.features.home.room.detail.timeline.factory.TimelineFactory
|
||||||
import im.vector.app.features.home.room.detail.timeline.url.PreviewUrlRetriever
|
import im.vector.app.features.home.room.detail.timeline.url.PreviewUrlRetriever
|
||||||
|
@ -90,7 +91,6 @@ import org.matrix.android.sdk.api.raw.RawService
|
||||||
import org.matrix.android.sdk.api.session.Session
|
import org.matrix.android.sdk.api.session.Session
|
||||||
import org.matrix.android.sdk.api.session.crypto.MXCryptoError
|
import org.matrix.android.sdk.api.session.crypto.MXCryptoError
|
||||||
import org.matrix.android.sdk.api.session.events.model.EventType
|
import org.matrix.android.sdk.api.session.events.model.EventType
|
||||||
import org.matrix.android.sdk.api.session.events.model.LocalEcho
|
|
||||||
import org.matrix.android.sdk.api.session.events.model.RelationType
|
import org.matrix.android.sdk.api.session.events.model.RelationType
|
||||||
import org.matrix.android.sdk.api.session.events.model.content.WithHeldCode
|
import org.matrix.android.sdk.api.session.events.model.content.WithHeldCode
|
||||||
import org.matrix.android.sdk.api.session.events.model.isAttachmentMessage
|
import org.matrix.android.sdk.api.session.events.model.isAttachmentMessage
|
||||||
|
@ -154,6 +154,7 @@ class TimelineViewModel @AssistedInject constructor(
|
||||||
timelineFactory: TimelineFactory,
|
timelineFactory: TimelineFactory,
|
||||||
private val spaceStateHandler: SpaceStateHandler,
|
private val spaceStateHandler: SpaceStateHandler,
|
||||||
private val voiceBroadcastHelper: VoiceBroadcastHelper,
|
private val voiceBroadcastHelper: VoiceBroadcastHelper,
|
||||||
|
private val voteToPollUseCase: VoteToPollUseCase,
|
||||||
) : VectorViewModel<RoomDetailViewState, RoomDetailAction, RoomDetailViewEvents>(initialState),
|
) : VectorViewModel<RoomDetailViewState, RoomDetailAction, RoomDetailViewEvents>(initialState),
|
||||||
Timeline.Listener, ChatEffectManager.Delegate, CallProtocolsChecker.Listener, LocationSharingServiceConnection.Callback {
|
Timeline.Listener, ChatEffectManager.Delegate, CallProtocolsChecker.Listener, LocationSharingServiceConnection.Callback {
|
||||||
|
|
||||||
|
@ -1235,15 +1236,11 @@ class TimelineViewModel @AssistedInject constructor(
|
||||||
|
|
||||||
private fun handleVoteToPoll(action: RoomDetailAction.VoteToPoll) {
|
private fun handleVoteToPoll(action: RoomDetailAction.VoteToPoll) {
|
||||||
if (room == null) return
|
if (room == null) return
|
||||||
// Do not allow to vote unsent local echo of the poll event
|
voteToPollUseCase.execute(
|
||||||
if (LocalEcho.isLocalEchoId(action.eventId)) return
|
roomId = room.roomId,
|
||||||
// Do not allow to vote the same option twice
|
pollEventId = action.eventId,
|
||||||
room.getTimelineEvent(action.eventId)?.let { pollTimelineEvent ->
|
optionId = action.optionKey,
|
||||||
val currentVote = pollTimelineEvent.annotations?.pollResponseSummary?.aggregatedContent?.myVote
|
)
|
||||||
if (currentVote != action.optionKey) {
|
|
||||||
room.sendService().voteToPoll(action.eventId, action.optionKey)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun handleEndPoll(eventId: String) {
|
private fun handleEndPoll(eventId: String) {
|
||||||
|
|
|
@ -0,0 +1,51 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2023 New Vector Ltd
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package im.vector.app.features.home.room.detail.poll
|
||||||
|
|
||||||
|
import im.vector.app.core.di.ActiveSessionHolder
|
||||||
|
import org.matrix.android.sdk.api.session.events.model.LocalEcho
|
||||||
|
import org.matrix.android.sdk.api.session.getRoom
|
||||||
|
import org.matrix.android.sdk.api.session.room.getTimelineEvent
|
||||||
|
import timber.log.Timber
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
class VoteToPollUseCase @Inject constructor(
|
||||||
|
private val activeSessionHolder: ActiveSessionHolder,
|
||||||
|
) {
|
||||||
|
|
||||||
|
fun execute(roomId: String, pollEventId: String, optionId: String) {
|
||||||
|
// Do not allow to vote unsent local echo of the poll event
|
||||||
|
if (LocalEcho.isLocalEchoId(pollEventId)) return
|
||||||
|
|
||||||
|
runCatching {
|
||||||
|
val room = activeSessionHolder.getActiveSession().getRoom(roomId)
|
||||||
|
room?.getTimelineEvent(pollEventId)?.let { pollTimelineEvent ->
|
||||||
|
val currentVote = pollTimelineEvent
|
||||||
|
.annotations
|
||||||
|
?.pollResponseSummary
|
||||||
|
?.aggregatedContent
|
||||||
|
?.myVote
|
||||||
|
if (currentVote != optionId) {
|
||||||
|
room.sendService().voteToPoll(
|
||||||
|
pollEventId = pollEventId,
|
||||||
|
answerId = optionId
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}.onFailure { Timber.w("Failed to vote in poll with id $pollEventId in room with id $roomId") }
|
||||||
|
}
|
||||||
|
}
|
|
@ -255,7 +255,11 @@ class MessageItemFactory @Inject constructor(
|
||||||
attributes: AbsMessageItem.Attributes,
|
attributes: AbsMessageItem.Attributes,
|
||||||
isEnded: Boolean,
|
isEnded: Boolean,
|
||||||
): PollItem {
|
): PollItem {
|
||||||
val pollViewState = pollItemViewStateFactory.create(pollContent, informationData)
|
val pollViewState = pollItemViewStateFactory.create(
|
||||||
|
pollContent = pollContent,
|
||||||
|
pollResponseData = informationData.pollResponseAggregatedSummary,
|
||||||
|
isSent = informationData.sendState.isSent(),
|
||||||
|
)
|
||||||
|
|
||||||
return PollItem_()
|
return PollItem_()
|
||||||
.attributes(attributes)
|
.attributes(attributes)
|
||||||
|
|
|
@ -18,9 +18,8 @@ package im.vector.app.features.home.room.detail.timeline.factory
|
||||||
|
|
||||||
import im.vector.app.R
|
import im.vector.app.R
|
||||||
import im.vector.app.core.resources.StringProvider
|
import im.vector.app.core.resources.StringProvider
|
||||||
import im.vector.app.features.home.room.detail.timeline.item.MessageInformationData
|
|
||||||
import im.vector.app.features.home.room.detail.timeline.item.PollResponseData
|
import im.vector.app.features.home.room.detail.timeline.item.PollResponseData
|
||||||
import im.vector.app.features.poll.PollViewState
|
import im.vector.app.features.poll.PollItemViewState
|
||||||
import org.matrix.android.sdk.api.extensions.orFalse
|
import org.matrix.android.sdk.api.extensions.orFalse
|
||||||
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.PollCreationInfo
|
import org.matrix.android.sdk.api.session.room.model.message.PollCreationInfo
|
||||||
|
@ -33,27 +32,25 @@ class PollItemViewStateFactory @Inject constructor(
|
||||||
|
|
||||||
fun create(
|
fun create(
|
||||||
pollContent: MessagePollContent,
|
pollContent: MessagePollContent,
|
||||||
informationData: MessageInformationData,
|
pollResponseData: PollResponseData?,
|
||||||
): PollViewState {
|
isSent: Boolean,
|
||||||
|
): PollItemViewState {
|
||||||
val pollCreationInfo = pollContent.getBestPollCreationInfo()
|
val pollCreationInfo = pollContent.getBestPollCreationInfo()
|
||||||
|
|
||||||
val question = pollCreationInfo?.question?.getBestQuestion().orEmpty()
|
val question = pollCreationInfo?.question?.getBestQuestion().orEmpty()
|
||||||
|
val totalVotes = pollResponseData?.totalVotes ?: 0
|
||||||
val pollResponseSummary = informationData.pollResponseAggregatedSummary
|
|
||||||
val totalVotes = pollResponseSummary?.totalVotes ?: 0
|
|
||||||
|
|
||||||
return when {
|
return when {
|
||||||
!informationData.sendState.isSent() -> {
|
!isSent -> {
|
||||||
createSendingPollViewState(question, pollCreationInfo)
|
createSendingPollViewState(question, pollCreationInfo)
|
||||||
}
|
}
|
||||||
informationData.pollResponseAggregatedSummary?.isClosed.orFalse() -> {
|
pollResponseData?.isClosed.orFalse() -> {
|
||||||
createEndedPollViewState(question, pollCreationInfo, pollResponseSummary, totalVotes)
|
createEndedPollViewState(question, pollCreationInfo, pollResponseData, totalVotes)
|
||||||
}
|
}
|
||||||
pollContent.getBestPollCreationInfo()?.isUndisclosed().orFalse() -> {
|
pollContent.getBestPollCreationInfo()?.isUndisclosed().orFalse() -> {
|
||||||
createUndisclosedPollViewState(question, pollCreationInfo, pollResponseSummary)
|
createUndisclosedPollViewState(question, pollCreationInfo, pollResponseData)
|
||||||
}
|
}
|
||||||
informationData.pollResponseAggregatedSummary?.myVote?.isNotEmpty().orFalse() -> {
|
pollResponseData?.myVote?.isNotEmpty().orFalse() -> {
|
||||||
createVotedPollViewState(question, pollCreationInfo, pollResponseSummary, totalVotes)
|
createVotedPollViewState(question, pollCreationInfo, pollResponseData, totalVotes)
|
||||||
}
|
}
|
||||||
else -> {
|
else -> {
|
||||||
createReadyPollViewState(question, pollCreationInfo, totalVotes)
|
createReadyPollViewState(question, pollCreationInfo, totalVotes)
|
||||||
|
@ -61,8 +58,8 @@ class PollItemViewStateFactory @Inject constructor(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun createSendingPollViewState(question: String, pollCreationInfo: PollCreationInfo?): PollViewState {
|
private fun createSendingPollViewState(question: String, pollCreationInfo: PollCreationInfo?): PollItemViewState {
|
||||||
return PollViewState(
|
return PollItemViewState(
|
||||||
question = question,
|
question = question,
|
||||||
votesStatus = stringProvider.getString(R.string.poll_no_votes_cast),
|
votesStatus = stringProvider.getString(R.string.poll_no_votes_cast),
|
||||||
canVote = false,
|
canVote = false,
|
||||||
|
@ -73,51 +70,51 @@ class PollItemViewStateFactory @Inject constructor(
|
||||||
private fun createEndedPollViewState(
|
private fun createEndedPollViewState(
|
||||||
question: String,
|
question: String,
|
||||||
pollCreationInfo: PollCreationInfo?,
|
pollCreationInfo: PollCreationInfo?,
|
||||||
pollResponseSummary: PollResponseData?,
|
pollResponseData: PollResponseData?,
|
||||||
totalVotes: Int,
|
totalVotes: Int,
|
||||||
): PollViewState {
|
): PollItemViewState {
|
||||||
val totalVotesText = if (pollResponseSummary?.hasEncryptedRelatedEvents.orFalse()) {
|
val totalVotesText = if (pollResponseData?.hasEncryptedRelatedEvents.orFalse()) {
|
||||||
stringProvider.getString(R.string.unable_to_decrypt_some_events_in_poll)
|
stringProvider.getString(R.string.unable_to_decrypt_some_events_in_poll)
|
||||||
} else {
|
} else {
|
||||||
stringProvider.getQuantityString(R.plurals.poll_total_vote_count_after_ended, totalVotes, totalVotes)
|
stringProvider.getQuantityString(R.plurals.poll_total_vote_count_after_ended, totalVotes, totalVotes)
|
||||||
}
|
}
|
||||||
return PollViewState(
|
return PollItemViewState(
|
||||||
question = question,
|
question = question,
|
||||||
votesStatus = totalVotesText,
|
votesStatus = totalVotesText,
|
||||||
canVote = false,
|
canVote = false,
|
||||||
optionViewStates = pollOptionViewStateFactory.createPollEndedOptions(pollCreationInfo, pollResponseSummary),
|
optionViewStates = pollOptionViewStateFactory.createPollEndedOptions(pollCreationInfo, pollResponseData),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun createUndisclosedPollViewState(
|
private fun createUndisclosedPollViewState(
|
||||||
question: String,
|
question: String,
|
||||||
pollCreationInfo: PollCreationInfo?,
|
pollCreationInfo: PollCreationInfo?,
|
||||||
pollResponseSummary: PollResponseData?
|
pollResponseData: PollResponseData?
|
||||||
): PollViewState {
|
): PollItemViewState {
|
||||||
return PollViewState(
|
return PollItemViewState(
|
||||||
question = question,
|
question = question,
|
||||||
votesStatus = stringProvider.getString(R.string.poll_undisclosed_not_ended),
|
votesStatus = stringProvider.getString(R.string.poll_undisclosed_not_ended),
|
||||||
canVote = true,
|
canVote = true,
|
||||||
optionViewStates = pollOptionViewStateFactory.createPollUndisclosedOptions(pollCreationInfo, pollResponseSummary),
|
optionViewStates = pollOptionViewStateFactory.createPollUndisclosedOptions(pollCreationInfo, pollResponseData),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun createVotedPollViewState(
|
private fun createVotedPollViewState(
|
||||||
question: String,
|
question: String,
|
||||||
pollCreationInfo: PollCreationInfo?,
|
pollCreationInfo: PollCreationInfo?,
|
||||||
pollResponseSummary: PollResponseData?,
|
pollResponseData: PollResponseData?,
|
||||||
totalVotes: Int
|
totalVotes: Int
|
||||||
): PollViewState {
|
): PollItemViewState {
|
||||||
val totalVotesText = if (pollResponseSummary?.hasEncryptedRelatedEvents.orFalse()) {
|
val totalVotesText = if (pollResponseData?.hasEncryptedRelatedEvents.orFalse()) {
|
||||||
stringProvider.getString(R.string.unable_to_decrypt_some_events_in_poll)
|
stringProvider.getString(R.string.unable_to_decrypt_some_events_in_poll)
|
||||||
} else {
|
} else {
|
||||||
stringProvider.getQuantityString(R.plurals.poll_total_vote_count_before_ended_and_voted, totalVotes, totalVotes)
|
stringProvider.getQuantityString(R.plurals.poll_total_vote_count_before_ended_and_voted, totalVotes, totalVotes)
|
||||||
}
|
}
|
||||||
return PollViewState(
|
return PollItemViewState(
|
||||||
question = question,
|
question = question,
|
||||||
votesStatus = totalVotesText,
|
votesStatus = totalVotesText,
|
||||||
canVote = true,
|
canVote = true,
|
||||||
optionViewStates = pollOptionViewStateFactory.createPollVotedOptions(pollCreationInfo, pollResponseSummary),
|
optionViewStates = pollOptionViewStateFactory.createPollVotedOptions(pollCreationInfo, pollResponseData),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -125,13 +122,13 @@ class PollItemViewStateFactory @Inject constructor(
|
||||||
question: String,
|
question: String,
|
||||||
pollCreationInfo: PollCreationInfo?,
|
pollCreationInfo: PollCreationInfo?,
|
||||||
totalVotes: Int
|
totalVotes: Int
|
||||||
): PollViewState {
|
): PollItemViewState {
|
||||||
val totalVotesText = if (totalVotes == 0) {
|
val totalVotesText = if (totalVotes == 0) {
|
||||||
stringProvider.getString(R.string.poll_no_votes_cast)
|
stringProvider.getString(R.string.poll_no_votes_cast)
|
||||||
} else {
|
} else {
|
||||||
stringProvider.getQuantityString(R.plurals.poll_total_vote_count_before_ended_and_not_voted, totalVotes, totalVotes)
|
stringProvider.getQuantityString(R.plurals.poll_total_vote_count_before_ended_and_not_voted, totalVotes, totalVotes)
|
||||||
}
|
}
|
||||||
return PollViewState(
|
return PollItemViewState(
|
||||||
question = question,
|
question = question,
|
||||||
votesStatus = totalVotesText,
|
votesStatus = totalVotesText,
|
||||||
canVote = true,
|
canVote = true,
|
||||||
|
|
|
@ -18,7 +18,7 @@ package im.vector.app.features.poll
|
||||||
|
|
||||||
import im.vector.app.features.home.room.detail.timeline.item.PollOptionViewState
|
import im.vector.app.features.home.room.detail.timeline.item.PollOptionViewState
|
||||||
|
|
||||||
data class PollViewState(
|
data class PollItemViewState(
|
||||||
val question: String,
|
val question: String,
|
||||||
val votesStatus: String,
|
val votesStatus: String,
|
||||||
val canVote: Boolean,
|
val canVote: Boolean,
|
|
@ -18,7 +18,6 @@
|
||||||
package im.vector.app.features.roomprofile
|
package im.vector.app.features.roomprofile
|
||||||
|
|
||||||
import com.airbnb.epoxy.TypedEpoxyController
|
import com.airbnb.epoxy.TypedEpoxyController
|
||||||
import im.vector.app.BuildConfig
|
|
||||||
import im.vector.app.R
|
import im.vector.app.R
|
||||||
import im.vector.app.core.epoxy.expandableTextItem
|
import im.vector.app.core.epoxy.expandableTextItem
|
||||||
import im.vector.app.core.epoxy.profiles.buildProfileAction
|
import im.vector.app.core.epoxy.profiles.buildProfileAction
|
||||||
|
@ -265,15 +264,14 @@ class RoomProfileController @Inject constructor(
|
||||||
action = { callback?.onBannedMemberListClicked() }
|
action = { callback?.onBannedMemberListClicked() }
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
if (BuildConfig.DEBUG) {
|
|
||||||
// WIP, will be in release when related screens will be finished
|
buildProfileAction(
|
||||||
buildProfileAction(
|
id = "poll_history",
|
||||||
id = "poll_history",
|
title = stringProvider.getString(R.string.room_profile_section_more_polls),
|
||||||
title = stringProvider.getString(R.string.room_profile_section_more_polls),
|
icon = R.drawable.ic_attachment_poll,
|
||||||
icon = R.drawable.ic_attachment_poll,
|
action = { callback?.onPollHistoryClicked() }
|
||||||
action = { callback?.onPollHistoryClicked() }
|
)
|
||||||
)
|
|
||||||
}
|
|
||||||
buildProfileAction(
|
buildProfileAction(
|
||||||
id = "uploads",
|
id = "uploads",
|
||||||
title = stringProvider.getString(R.string.room_profile_section_more_uploads),
|
title = stringProvider.getString(R.string.room_profile_section_more_uploads),
|
||||||
|
|
|
@ -64,7 +64,7 @@ import javax.inject.Inject
|
||||||
|
|
||||||
@Parcelize
|
@Parcelize
|
||||||
data class RoomProfileArgs(
|
data class RoomProfileArgs(
|
||||||
val roomId: String
|
val roomId: String,
|
||||||
) : Parcelable
|
) : Parcelable
|
||||||
|
|
||||||
@AndroidEntryPoint
|
@AndroidEntryPoint
|
||||||
|
|
|
@ -0,0 +1,39 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2023 New Vector Ltd
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package im.vector.app.features.roomprofile.polls.detail.domain
|
||||||
|
|
||||||
|
import im.vector.app.core.di.ActiveSessionHolder
|
||||||
|
import org.matrix.android.sdk.api.session.events.model.RelationType
|
||||||
|
import org.matrix.android.sdk.api.session.events.model.isPollEnd
|
||||||
|
import timber.log.Timber
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
class GetEndedPollEventIdUseCase @Inject constructor(
|
||||||
|
private val activeSessionHolder: ActiveSessionHolder,
|
||||||
|
) {
|
||||||
|
|
||||||
|
fun execute(roomId: String, startPollEventId: String): String? {
|
||||||
|
val result = runCatching {
|
||||||
|
activeSessionHolder.getActiveSession().roomService().getRoom(roomId)
|
||||||
|
?.timelineService()
|
||||||
|
?.getTimelineEventsRelatedTo(RelationType.REFERENCE, startPollEventId)
|
||||||
|
?.find { it.root.isPollEnd() }
|
||||||
|
?.eventId
|
||||||
|
}.onFailure { Timber.w("failed to retrieve the ended poll event id for eventId:$startPollEventId") }
|
||||||
|
return result.getOrNull()
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,26 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2023 New Vector Ltd
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package im.vector.app.features.roomprofile.polls.detail.ui
|
||||||
|
|
||||||
|
import im.vector.app.features.poll.PollItemViewState
|
||||||
|
|
||||||
|
data class RoomPollDetail(
|
||||||
|
val creationTimestamp: Long,
|
||||||
|
val isEnded: Boolean,
|
||||||
|
val endedPollEventId: String?,
|
||||||
|
val pollItemViewState: PollItemViewState,
|
||||||
|
)
|
|
@ -0,0 +1,23 @@
|
||||||
|
/*
|
||||||
|
* 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.roomprofile.polls.detail.ui
|
||||||
|
|
||||||
|
import im.vector.app.core.platform.VectorViewModelAction
|
||||||
|
|
||||||
|
sealed interface RoomPollDetailAction : VectorViewModelAction {
|
||||||
|
data class Vote(val pollEventId: String, val optionId: String) : RoomPollDetailAction
|
||||||
|
}
|
|
@ -0,0 +1,61 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2023 New Vector Ltd
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package im.vector.app.features.roomprofile.polls.detail.ui
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.Intent
|
||||||
|
import android.os.Bundle
|
||||||
|
import com.airbnb.mvrx.Mavericks
|
||||||
|
import dagger.hilt.android.AndroidEntryPoint
|
||||||
|
import im.vector.app.core.extensions.addFragment
|
||||||
|
import im.vector.app.core.platform.VectorBaseActivity
|
||||||
|
import im.vector.app.databinding.ActivitySimpleBinding
|
||||||
|
import im.vector.lib.core.utils.compat.getParcelableExtraCompat
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Display the details of a given poll.
|
||||||
|
*/
|
||||||
|
@AndroidEntryPoint
|
||||||
|
class RoomPollDetailActivity : VectorBaseActivity<ActivitySimpleBinding>() {
|
||||||
|
|
||||||
|
override fun getBinding() = ActivitySimpleBinding.inflate(layoutInflater)
|
||||||
|
|
||||||
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
|
super.onCreate(savedInstanceState)
|
||||||
|
|
||||||
|
if (isFirstCreation()) {
|
||||||
|
addFragment(
|
||||||
|
container = views.simpleFragmentContainer,
|
||||||
|
fragmentClass = RoomPollDetailFragment::class.java,
|
||||||
|
params = intent.getParcelableExtraCompat(Mavericks.KEY_ARG)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
fun newIntent(context: Context, pollId: String, roomId: String, isEnded: Boolean): Intent {
|
||||||
|
return Intent(context, RoomPollDetailActivity::class.java).apply {
|
||||||
|
val args = RoomPollDetailArgs(
|
||||||
|
pollId = pollId,
|
||||||
|
roomId = roomId,
|
||||||
|
isEnded = isEnded,
|
||||||
|
)
|
||||||
|
putExtra(Mavericks.KEY_ARG, args)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,64 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2023 New Vector Ltd
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package im.vector.app.features.roomprofile.polls.detail.ui
|
||||||
|
|
||||||
|
import com.airbnb.epoxy.TypedEpoxyController
|
||||||
|
import im.vector.app.core.date.DateFormatKind
|
||||||
|
import im.vector.app.core.date.VectorDateFormatter
|
||||||
|
import java.util.UUID
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
class RoomPollDetailController @Inject constructor(
|
||||||
|
val dateFormatter: VectorDateFormatter,
|
||||||
|
) : TypedEpoxyController<RoomPollDetailViewState>() {
|
||||||
|
|
||||||
|
interface Callback {
|
||||||
|
fun vote(pollEventId: String, optionId: String)
|
||||||
|
fun goToTimelineEvent(eventId: String)
|
||||||
|
}
|
||||||
|
|
||||||
|
var callback: Callback? = null
|
||||||
|
|
||||||
|
override fun buildModels(viewState: RoomPollDetailViewState?) {
|
||||||
|
val pollDetail = viewState?.pollDetail ?: return
|
||||||
|
val pollItemViewState = pollDetail.pollItemViewState
|
||||||
|
val host = this
|
||||||
|
|
||||||
|
roomPollDetailItem {
|
||||||
|
id(viewState.pollId)
|
||||||
|
eventId(viewState.pollId)
|
||||||
|
formattedDate(host.dateFormatter.format(pollDetail.creationTimestamp, DateFormatKind.TIMELINE_DAY_DIVIDER))
|
||||||
|
question(pollItemViewState.question)
|
||||||
|
canVote(pollItemViewState.canVote)
|
||||||
|
votesStatus(pollItemViewState.votesStatus)
|
||||||
|
optionViewStates(pollItemViewState.optionViewStates.orEmpty())
|
||||||
|
callback(host.callback)
|
||||||
|
}
|
||||||
|
|
||||||
|
buildGoToTimelineItem(targetEventId = pollDetail.endedPollEventId ?: viewState.pollId)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun buildGoToTimelineItem(targetEventId: String) {
|
||||||
|
val host = this
|
||||||
|
roomPollGoToTimelineItem {
|
||||||
|
id(UUID.randomUUID().toString())
|
||||||
|
clickListener {
|
||||||
|
host.callback?.goToTimelineEvent(targetEventId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,104 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2023 New Vector Ltd
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package im.vector.app.features.roomprofile.polls.detail.ui
|
||||||
|
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.os.Parcelable
|
||||||
|
import android.view.LayoutInflater
|
||||||
|
import android.view.View
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import com.airbnb.mvrx.args
|
||||||
|
import com.airbnb.mvrx.fragmentViewModel
|
||||||
|
import com.airbnb.mvrx.withState
|
||||||
|
import dagger.hilt.android.AndroidEntryPoint
|
||||||
|
import im.vector.app.R
|
||||||
|
import im.vector.app.core.extensions.cleanup
|
||||||
|
import im.vector.app.core.extensions.configureWith
|
||||||
|
import im.vector.app.core.platform.VectorBaseFragment
|
||||||
|
import im.vector.app.databinding.FragmentRoomPollDetailBinding
|
||||||
|
import kotlinx.parcelize.Parcelize
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
@Parcelize
|
||||||
|
data class RoomPollDetailArgs(
|
||||||
|
val pollId: String,
|
||||||
|
val roomId: String,
|
||||||
|
val isEnded: Boolean,
|
||||||
|
) : Parcelable
|
||||||
|
|
||||||
|
@AndroidEntryPoint
|
||||||
|
class RoomPollDetailFragment :
|
||||||
|
VectorBaseFragment<FragmentRoomPollDetailBinding>(),
|
||||||
|
RoomPollDetailController.Callback {
|
||||||
|
|
||||||
|
@Inject lateinit var viewNavigator: RoomPollDetailNavigator
|
||||||
|
@Inject lateinit var roomPollDetailController: RoomPollDetailController
|
||||||
|
|
||||||
|
private val viewModel: RoomPollDetailViewModel by fragmentViewModel()
|
||||||
|
private val roomPollDetailArgs: RoomPollDetailArgs by args()
|
||||||
|
|
||||||
|
override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentRoomPollDetailBinding {
|
||||||
|
return FragmentRoomPollDetailBinding.inflate(inflater, container, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
|
super.onViewCreated(view, savedInstanceState)
|
||||||
|
setupToolbar(isEnded = roomPollDetailArgs.isEnded)
|
||||||
|
setupDetailView()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onDestroyView() {
|
||||||
|
roomPollDetailController.callback = null
|
||||||
|
views.pollDetailRecyclerView.cleanup()
|
||||||
|
super.onDestroyView()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun setupDetailView() {
|
||||||
|
roomPollDetailController.callback = this
|
||||||
|
views.pollDetailRecyclerView.configureWith(
|
||||||
|
roomPollDetailController,
|
||||||
|
hasFixedSize = true,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun setupToolbar(isEnded: Boolean) {
|
||||||
|
val title = when (isEnded) {
|
||||||
|
true -> getString(R.string.room_polls_ended)
|
||||||
|
false -> getString(R.string.room_polls_active)
|
||||||
|
}
|
||||||
|
|
||||||
|
setupToolbar(views.roomPollDetailToolbar)
|
||||||
|
.setTitle(title)
|
||||||
|
.allowBack(useCross = true)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun invalidate() = withState(viewModel) { state ->
|
||||||
|
roomPollDetailController.setData(state)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun vote(pollEventId: String, optionId: String) {
|
||||||
|
viewModel.handle(RoomPollDetailAction.Vote(pollEventId = pollEventId, optionId = optionId))
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun goToTimelineEvent(eventId: String) = withState(viewModel) { state ->
|
||||||
|
viewNavigator.goToTimelineEvent(
|
||||||
|
context = requireContext(),
|
||||||
|
roomId = state.roomId,
|
||||||
|
eventId = eventId,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,89 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2020 New Vector Ltd
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package im.vector.app.features.roomprofile.polls.detail.ui
|
||||||
|
|
||||||
|
import android.widget.LinearLayout
|
||||||
|
import android.widget.TextView
|
||||||
|
import androidx.core.view.isVisible
|
||||||
|
import com.airbnb.epoxy.EpoxyAttribute
|
||||||
|
import com.airbnb.epoxy.EpoxyModelClass
|
||||||
|
import im.vector.app.R
|
||||||
|
import im.vector.app.core.epoxy.VectorEpoxyHolder
|
||||||
|
import im.vector.app.core.epoxy.VectorEpoxyModel
|
||||||
|
import im.vector.app.features.home.room.detail.timeline.item.PollOptionView
|
||||||
|
import im.vector.app.features.home.room.detail.timeline.item.PollOptionViewState
|
||||||
|
|
||||||
|
@EpoxyModelClass
|
||||||
|
abstract class RoomPollDetailItem : VectorEpoxyModel<RoomPollDetailItem.Holder>(R.layout.item_poll_detail) {
|
||||||
|
|
||||||
|
@EpoxyAttribute
|
||||||
|
lateinit var formattedDate: String
|
||||||
|
|
||||||
|
@EpoxyAttribute
|
||||||
|
var question: String? = null
|
||||||
|
|
||||||
|
@EpoxyAttribute
|
||||||
|
var callback: RoomPollDetailController.Callback? = null
|
||||||
|
|
||||||
|
@EpoxyAttribute
|
||||||
|
var eventId: String? = null
|
||||||
|
|
||||||
|
@EpoxyAttribute
|
||||||
|
var canVote: Boolean = false
|
||||||
|
|
||||||
|
@EpoxyAttribute
|
||||||
|
var votesStatus: String? = null
|
||||||
|
|
||||||
|
@EpoxyAttribute
|
||||||
|
lateinit var optionViewStates: List<PollOptionViewState>
|
||||||
|
|
||||||
|
@EpoxyAttribute
|
||||||
|
var ended: Boolean = false
|
||||||
|
|
||||||
|
override fun bind(holder: Holder) {
|
||||||
|
super.bind(holder)
|
||||||
|
holder.date.text = formattedDate
|
||||||
|
holder.questionTextView.text = question
|
||||||
|
holder.votesStatusTextView.text = votesStatus
|
||||||
|
holder.optionsContainer.removeAllViews()
|
||||||
|
holder.optionsContainer.isVisible = optionViewStates.isNotEmpty()
|
||||||
|
for (option in optionViewStates) {
|
||||||
|
val optionView = PollOptionView(holder.view.context)
|
||||||
|
holder.optionsContainer.addView(optionView)
|
||||||
|
optionView.render(option)
|
||||||
|
optionView.setOnClickListener { onOptionClicked(option) }
|
||||||
|
}
|
||||||
|
|
||||||
|
holder.endedPollTextView.isVisible = false
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun onOptionClicked(optionViewState: PollOptionViewState) {
|
||||||
|
val relatedEventId = eventId
|
||||||
|
|
||||||
|
if (canVote && relatedEventId != null) {
|
||||||
|
callback?.vote(pollEventId = relatedEventId, optionId = optionViewState.optionId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class Holder : VectorEpoxyHolder() {
|
||||||
|
val date by bind<TextView>(R.id.pollDetailDate)
|
||||||
|
val questionTextView by bind<TextView>(R.id.questionTextView)
|
||||||
|
val optionsContainer by bind<LinearLayout>(R.id.optionsContainer)
|
||||||
|
val votesStatusTextView by bind<TextView>(R.id.optionsVotesStatusTextView)
|
||||||
|
val endedPollTextView by bind<TextView>(R.id.endedPollTextView)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,100 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2023 New Vector Ltd
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package im.vector.app.features.roomprofile.polls.detail.ui
|
||||||
|
|
||||||
|
import im.vector.app.core.extensions.getVectorLastMessageContent
|
||||||
|
import im.vector.app.features.home.room.detail.timeline.factory.PollItemViewStateFactory
|
||||||
|
import im.vector.app.features.home.room.detail.timeline.helper.PollResponseDataFactory
|
||||||
|
import im.vector.app.features.home.room.detail.timeline.item.PollResponseData
|
||||||
|
import im.vector.app.features.roomprofile.polls.detail.domain.GetEndedPollEventIdUseCase
|
||||||
|
import org.matrix.android.sdk.api.extensions.orFalse
|
||||||
|
import org.matrix.android.sdk.api.session.room.model.message.MessagePollContent
|
||||||
|
import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent
|
||||||
|
import timber.log.Timber
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
class RoomPollDetailMapper @Inject constructor(
|
||||||
|
private val pollResponseDataFactory: PollResponseDataFactory,
|
||||||
|
private val pollItemViewStateFactory: PollItemViewStateFactory,
|
||||||
|
private val getEndedPollEventIdUseCase: GetEndedPollEventIdUseCase,
|
||||||
|
) {
|
||||||
|
|
||||||
|
fun map(timelineEvent: TimelineEvent): RoomPollDetail? {
|
||||||
|
val eventId = timelineEvent.root.eventId.orEmpty()
|
||||||
|
val result = runCatching {
|
||||||
|
val content = timelineEvent.getVectorLastMessageContent()
|
||||||
|
val pollResponseData = pollResponseDataFactory.create(timelineEvent)
|
||||||
|
val creationTimestamp = timelineEvent.root.originServerTs ?: 0
|
||||||
|
return if (eventId.isNotEmpty() && creationTimestamp > 0 && content is MessagePollContent) {
|
||||||
|
val isPollEnded = pollResponseData?.isClosed.orFalse()
|
||||||
|
val endedPollEventId = getEndedPollEventId(
|
||||||
|
isPollEnded,
|
||||||
|
startPollEventId = eventId,
|
||||||
|
roomId = timelineEvent.roomId,
|
||||||
|
)
|
||||||
|
convertToRoomPollDetail(
|
||||||
|
creationTimestamp = creationTimestamp,
|
||||||
|
content = content,
|
||||||
|
pollResponseData = pollResponseData,
|
||||||
|
isPollEnded = isPollEnded,
|
||||||
|
endedPollEventId = endedPollEventId,
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
Timber.w("missing mandatory info about poll event with id=$eventId")
|
||||||
|
null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (result.isFailure) {
|
||||||
|
Timber.w("failed to map event with id $eventId")
|
||||||
|
}
|
||||||
|
return result.getOrNull()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun convertToRoomPollDetail(
|
||||||
|
creationTimestamp: Long,
|
||||||
|
content: MessagePollContent,
|
||||||
|
pollResponseData: PollResponseData?,
|
||||||
|
isPollEnded: Boolean,
|
||||||
|
endedPollEventId: String?,
|
||||||
|
): RoomPollDetail {
|
||||||
|
// we assume the poll has been sent
|
||||||
|
val pollItemViewState = pollItemViewStateFactory.create(
|
||||||
|
pollContent = content,
|
||||||
|
pollResponseData = pollResponseData,
|
||||||
|
isSent = true,
|
||||||
|
)
|
||||||
|
return RoomPollDetail(
|
||||||
|
creationTimestamp = creationTimestamp,
|
||||||
|
isEnded = isPollEnded,
|
||||||
|
pollItemViewState = pollItemViewState,
|
||||||
|
endedPollEventId = endedPollEventId,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getEndedPollEventId(
|
||||||
|
isPollEnded: Boolean,
|
||||||
|
startPollEventId: String,
|
||||||
|
roomId: String,
|
||||||
|
): String? {
|
||||||
|
return if (isPollEnded) {
|
||||||
|
getEndedPollEventIdUseCase.execute(startPollEventId = startPollEventId, roomId = roomId)
|
||||||
|
} else {
|
||||||
|
null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,35 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2023 New Vector Ltd
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package im.vector.app.features.roomprofile.polls.detail.ui
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import im.vector.app.features.navigation.Navigator
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
class RoomPollDetailNavigator @Inject constructor(
|
||||||
|
private val navigator: Navigator,
|
||||||
|
) {
|
||||||
|
|
||||||
|
fun goToTimelineEvent(context: Context, roomId: String, eventId: String) {
|
||||||
|
navigator.openRoom(
|
||||||
|
context = context,
|
||||||
|
roomId = roomId,
|
||||||
|
eventId = eventId,
|
||||||
|
buildTask = true,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,74 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2023 New Vector Ltd
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package im.vector.app.features.roomprofile.polls.detail.ui
|
||||||
|
|
||||||
|
import com.airbnb.mvrx.MavericksViewModelFactory
|
||||||
|
import dagger.assisted.Assisted
|
||||||
|
import dagger.assisted.AssistedFactory
|
||||||
|
import dagger.assisted.AssistedInject
|
||||||
|
import im.vector.app.core.di.MavericksAssistedViewModelFactory
|
||||||
|
import im.vector.app.core.di.hiltMavericksViewModelFactory
|
||||||
|
import im.vector.app.core.event.GetTimelineEventUseCase
|
||||||
|
import im.vector.app.core.platform.EmptyViewEvents
|
||||||
|
import im.vector.app.core.platform.VectorViewModel
|
||||||
|
import im.vector.app.features.home.room.detail.poll.VoteToPollUseCase
|
||||||
|
import kotlinx.coroutines.flow.launchIn
|
||||||
|
import kotlinx.coroutines.flow.map
|
||||||
|
import kotlinx.coroutines.flow.onEach
|
||||||
|
|
||||||
|
class RoomPollDetailViewModel @AssistedInject constructor(
|
||||||
|
@Assisted initialState: RoomPollDetailViewState,
|
||||||
|
private val getTimelineEventUseCase: GetTimelineEventUseCase,
|
||||||
|
private val roomPollDetailMapper: RoomPollDetailMapper,
|
||||||
|
private val voteToPollUseCase: VoteToPollUseCase,
|
||||||
|
) : VectorViewModel<RoomPollDetailViewState, RoomPollDetailAction, EmptyViewEvents>(initialState) {
|
||||||
|
|
||||||
|
@AssistedFactory
|
||||||
|
interface Factory : MavericksAssistedViewModelFactory<RoomPollDetailViewModel, RoomPollDetailViewState> {
|
||||||
|
override fun create(initialState: RoomPollDetailViewState): RoomPollDetailViewModel
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object : MavericksViewModelFactory<RoomPollDetailViewModel, RoomPollDetailViewState> by hiltMavericksViewModelFactory()
|
||||||
|
|
||||||
|
init {
|
||||||
|
observePollDetails(
|
||||||
|
pollId = initialState.pollId,
|
||||||
|
roomId = initialState.roomId,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun observePollDetails(pollId: String, roomId: String) {
|
||||||
|
getTimelineEventUseCase.execute(roomId = roomId, eventId = pollId)
|
||||||
|
.map { roomPollDetailMapper.map(it) }
|
||||||
|
.onEach { setState { copy(pollDetail = it) } }
|
||||||
|
.launchIn(viewModelScope)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun handle(action: RoomPollDetailAction) {
|
||||||
|
when (action) {
|
||||||
|
is RoomPollDetailAction.Vote -> handleVote(action)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun handleVote(vote: RoomPollDetailAction.Vote) = withState { state ->
|
||||||
|
voteToPollUseCase.execute(
|
||||||
|
roomId = state.roomId,
|
||||||
|
pollEventId = vote.pollEventId,
|
||||||
|
optionId = vote.optionId,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,31 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2023 New Vector Ltd
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package im.vector.app.features.roomprofile.polls.detail.ui
|
||||||
|
|
||||||
|
import com.airbnb.mvrx.MavericksState
|
||||||
|
|
||||||
|
data class RoomPollDetailViewState(
|
||||||
|
val pollId: String,
|
||||||
|
val roomId: String,
|
||||||
|
val pollDetail: RoomPollDetail? = null,
|
||||||
|
) : MavericksState {
|
||||||
|
|
||||||
|
constructor(roomPollDetailArgs: RoomPollDetailArgs) : this(
|
||||||
|
pollId = roomPollDetailArgs.pollId,
|
||||||
|
roomId = roomPollDetailArgs.roomId,
|
||||||
|
)
|
||||||
|
}
|
|
@ -0,0 +1,42 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2023 New Vector Ltd
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package im.vector.app.features.roomprofile.polls.detail.ui
|
||||||
|
|
||||||
|
import android.widget.Button
|
||||||
|
import com.airbnb.epoxy.EpoxyAttribute
|
||||||
|
import com.airbnb.epoxy.EpoxyModelClass
|
||||||
|
import im.vector.app.R
|
||||||
|
import im.vector.app.core.epoxy.ClickListener
|
||||||
|
import im.vector.app.core.epoxy.VectorEpoxyHolder
|
||||||
|
import im.vector.app.core.epoxy.VectorEpoxyModel
|
||||||
|
import im.vector.app.core.epoxy.onClick
|
||||||
|
|
||||||
|
@EpoxyModelClass
|
||||||
|
abstract class RoomPollGoToTimelineItem : VectorEpoxyModel<RoomPollGoToTimelineItem.Holder>(R.layout.item_poll_go_to_timeline) {
|
||||||
|
|
||||||
|
@EpoxyAttribute(EpoxyAttribute.Option.DoNotHash)
|
||||||
|
var clickListener: ClickListener? = null
|
||||||
|
|
||||||
|
override fun bind(holder: Holder) {
|
||||||
|
super.bind(holder)
|
||||||
|
holder.goToTimelineButton.onClick(clickListener)
|
||||||
|
}
|
||||||
|
|
||||||
|
class Holder : VectorEpoxyHolder() {
|
||||||
|
val goToTimelineButton by bind<Button>(R.id.roomPollGoToTimeline)
|
||||||
|
}
|
||||||
|
}
|
|
@ -69,7 +69,9 @@ class PollSummaryMapper @Inject constructor(
|
||||||
creationTimestamp = creationTimestamp,
|
creationTimestamp = creationTimestamp,
|
||||||
title = pollTitle,
|
title = pollTitle,
|
||||||
totalVotes = pollResponseData.totalVotes,
|
totalVotes = pollResponseData.totalVotes,
|
||||||
winnerOptions = pollOptionViewStateFactory.createPollEndedOptions(pollCreationInfo, pollResponseData)
|
winnerOptions = pollOptionViewStateFactory
|
||||||
|
.createPollEndedOptions(pollCreationInfo, pollResponseData)
|
||||||
|
.filter { it.isWinner },
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
PollSummary.ActivePoll(
|
PollSummary.ActivePoll(
|
||||||
|
|
|
@ -35,7 +35,6 @@ import im.vector.app.features.roomprofile.polls.RoomPollsType
|
||||||
import im.vector.app.features.roomprofile.polls.RoomPollsViewEvent
|
import im.vector.app.features.roomprofile.polls.RoomPollsViewEvent
|
||||||
import im.vector.app.features.roomprofile.polls.RoomPollsViewModel
|
import im.vector.app.features.roomprofile.polls.RoomPollsViewModel
|
||||||
import im.vector.app.features.roomprofile.polls.RoomPollsViewState
|
import im.vector.app.features.roomprofile.polls.RoomPollsViewState
|
||||||
import timber.log.Timber
|
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
abstract class RoomPollsListFragment :
|
abstract class RoomPollsListFragment :
|
||||||
|
@ -48,6 +47,9 @@ abstract class RoomPollsListFragment :
|
||||||
@Inject
|
@Inject
|
||||||
lateinit var stringProvider: StringProvider
|
lateinit var stringProvider: StringProvider
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
lateinit var viewNavigator: RoomPollsListNavigator
|
||||||
|
|
||||||
private val viewModel: RoomPollsViewModel by parentFragmentViewModel(RoomPollsViewModel::class)
|
private val viewModel: RoomPollsViewModel by parentFragmentViewModel(RoomPollsViewModel::class)
|
||||||
|
|
||||||
override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentRoomPollsListBinding {
|
override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentRoomPollsListBinding {
|
||||||
|
@ -125,9 +127,13 @@ abstract class RoomPollsListFragment :
|
||||||
views.roomPollsLoadMoreWhenEmptyProgress.isVisible = viewState.hasNoPollsAndCanLoadMore() && viewState.isLoadingMore
|
views.roomPollsLoadMoreWhenEmptyProgress.isVisible = viewState.hasNoPollsAndCanLoadMore() && viewState.isLoadingMore
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onPollClicked(pollId: String) {
|
override fun onPollClicked(pollId: String) = withState(viewModel) {
|
||||||
// TODO navigate to details
|
viewNavigator.goToPollDetails(
|
||||||
Timber.d("poll with id $pollId clicked")
|
context = requireContext(),
|
||||||
|
pollId = pollId,
|
||||||
|
roomId = it.roomId,
|
||||||
|
isEnded = getRoomPollsType() == RoomPollsType.ENDED,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onLoadMoreClicked() {
|
override fun onLoadMoreClicked() {
|
||||||
|
|
|
@ -0,0 +1,35 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2023 New Vector Ltd
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package im.vector.app.features.roomprofile.polls.list.ui
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import im.vector.app.features.roomprofile.polls.detail.ui.RoomPollDetailActivity
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
class RoomPollsListNavigator @Inject constructor() {
|
||||||
|
|
||||||
|
fun goToPollDetails(context: Context, pollId: String, roomId: String, isEnded: Boolean) {
|
||||||
|
context.startActivity(
|
||||||
|
RoomPollDetailActivity.newIntent(
|
||||||
|
context = context,
|
||||||
|
pollId = pollId,
|
||||||
|
roomId = roomId,
|
||||||
|
isEnded = isEnded,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
37
vector/src/main/res/layout/fragment_room_poll_detail.xml
Normal file
37
vector/src/main/res/layout/fragment_room_poll_detail.xml
Normal file
|
@ -0,0 +1,37 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent">
|
||||||
|
|
||||||
|
<com.google.android.material.appbar.AppBarLayout
|
||||||
|
android:id="@+id/appBarLayout"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent">
|
||||||
|
|
||||||
|
<com.google.android.material.appbar.MaterialToolbar
|
||||||
|
android:id="@+id/roomPollDetailToolbar"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="?actionBarSize"
|
||||||
|
tools:title="@string/room_polls_active" />
|
||||||
|
|
||||||
|
</com.google.android.material.appbar.AppBarLayout>
|
||||||
|
|
||||||
|
<androidx.recyclerview.widget.RecyclerView
|
||||||
|
android:id="@+id/pollDetailRecyclerView"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="0dp"
|
||||||
|
android:layout_marginTop="32dp"
|
||||||
|
android:paddingHorizontal="16dp"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/appBarLayout"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
tools:itemCount="1"
|
||||||
|
tools:listitem="@layout/item_timeline_event_poll" />
|
||||||
|
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
29
vector/src/main/res/layout/item_poll_detail.xml
Normal file
29
vector/src/main/res/layout/item_poll_detail.xml
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/pollDetailDate"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:textAppearance="@style/TextAppearance.Vector.Caption"
|
||||||
|
android:textColor="?vctr_content_tertiary"
|
||||||
|
app:layout_constraintBottom_toTopOf="@id/pollDetailContent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
|
tools:text="28/06/22" />
|
||||||
|
|
||||||
|
<include
|
||||||
|
android:id="@+id/pollDetailContent"
|
||||||
|
layout="@layout/item_timeline_event_poll"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="12dp"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/pollDetailDate" />
|
||||||
|
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
20
vector/src/main/res/layout/item_poll_go_to_timeline.xml
Normal file
20
vector/src/main/res/layout/item_poll_go_to_timeline.xml
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content">
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:id="@+id/roomPollGoToTimeline"
|
||||||
|
style="@style/Widget.Vector.Button.Text"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="33dp"
|
||||||
|
android:layout_marginBottom="46dp"
|
||||||
|
android:padding="0dp"
|
||||||
|
android:text="@string/room_poll_details_go_to_timeline"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent" />
|
||||||
|
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
|
@ -0,0 +1,88 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2023 New Vector Ltd
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package im.vector.app.core.event
|
||||||
|
|
||||||
|
import im.vector.app.test.fakes.FakeActiveSessionHolder
|
||||||
|
import im.vector.app.test.fakes.FakeFlowLiveDataConversions
|
||||||
|
import im.vector.app.test.fakes.givenAsFlow
|
||||||
|
import im.vector.app.test.test
|
||||||
|
import io.mockk.every
|
||||||
|
import io.mockk.mockk
|
||||||
|
import io.mockk.unmockkAll
|
||||||
|
import kotlinx.coroutines.flow.firstOrNull
|
||||||
|
import kotlinx.coroutines.test.runCurrent
|
||||||
|
import kotlinx.coroutines.test.runTest
|
||||||
|
import org.amshove.kluent.shouldBeNull
|
||||||
|
import org.junit.After
|
||||||
|
import org.junit.Before
|
||||||
|
import org.junit.Test
|
||||||
|
import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent
|
||||||
|
|
||||||
|
private const val A_ROOM_ID = "room-id"
|
||||||
|
private const val AN_EVENT_ID = "event-id"
|
||||||
|
|
||||||
|
internal class GetTimelineEventUseCaseTest {
|
||||||
|
|
||||||
|
private val fakeFlowLiveDataConversions = FakeFlowLiveDataConversions()
|
||||||
|
private val fakeActiveSessionHolder = FakeActiveSessionHolder()
|
||||||
|
private val getTimelineEventUseCase = GetTimelineEventUseCase(fakeActiveSessionHolder.instance)
|
||||||
|
|
||||||
|
@Before
|
||||||
|
fun setUp() {
|
||||||
|
fakeFlowLiveDataConversions.setup()
|
||||||
|
}
|
||||||
|
|
||||||
|
@After
|
||||||
|
fun tearDown() {
|
||||||
|
unmockkAll()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `given a non existing room id, when execute, then returns an empty flow`() = runTest {
|
||||||
|
// Given
|
||||||
|
every { fakeActiveSessionHolder.instance.getActiveSession().roomService().getRoom(A_ROOM_ID) } returns null
|
||||||
|
|
||||||
|
// When
|
||||||
|
val result = getTimelineEventUseCase.execute(A_ROOM_ID, AN_EVENT_ID).firstOrNull()
|
||||||
|
|
||||||
|
// Then
|
||||||
|
result.shouldBeNull()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `given a LiveData of TimelineEvent, when execute, then returns the expected Flow`() = runTest {
|
||||||
|
// Given
|
||||||
|
val aTimelineEvent1: TimelineEvent = mockk()
|
||||||
|
val aTimelineEvent2: TimelineEvent? = null
|
||||||
|
val timelineService = fakeActiveSessionHolder.fakeSession
|
||||||
|
.fakeRoomService
|
||||||
|
.getRoom(A_ROOM_ID)
|
||||||
|
.timelineService()
|
||||||
|
|
||||||
|
// When
|
||||||
|
timelineService.givenTimelineEventLiveReturns(AN_EVENT_ID, aTimelineEvent1).givenAsFlow()
|
||||||
|
val result1 = getTimelineEventUseCase.execute(A_ROOM_ID, AN_EVENT_ID).test(this)
|
||||||
|
|
||||||
|
timelineService.givenTimelineEventLiveReturns(AN_EVENT_ID, aTimelineEvent2).givenAsFlow()
|
||||||
|
val result2 = getTimelineEventUseCase.execute(A_ROOM_ID, AN_EVENT_ID).test(this)
|
||||||
|
|
||||||
|
// Then
|
||||||
|
runCurrent()
|
||||||
|
result1.assertLatestValue(aTimelineEvent1)
|
||||||
|
result2.assertNoValues()
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,118 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2023 New Vector Ltd
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package im.vector.app.features.home.room.detail.poll
|
||||||
|
|
||||||
|
import im.vector.app.test.fakes.FakeActiveSessionHolder
|
||||||
|
import im.vector.app.test.fixtures.RoomPollFixture
|
||||||
|
import io.mockk.every
|
||||||
|
import io.mockk.verify
|
||||||
|
import org.junit.Test
|
||||||
|
import org.matrix.android.sdk.api.session.getRoom
|
||||||
|
import org.matrix.android.sdk.api.session.room.getTimelineEvent
|
||||||
|
|
||||||
|
private const val A_LOCAL_EVENT_ID = "\$local.event-id"
|
||||||
|
private const val AN_EVENT_ID = "event-id"
|
||||||
|
private const val A_ROOM_ID = "room-id"
|
||||||
|
private const val AN_EXISTING_OPTION_ID = "an-existing-option-id"
|
||||||
|
private const val AN_OPTION_ID = "option-id"
|
||||||
|
private const val AN_EVENT_TIMESTAMP = 123L
|
||||||
|
|
||||||
|
internal class VoteToPollUseCaseTest {
|
||||||
|
|
||||||
|
private val fakeActiveSessionHolder = FakeActiveSessionHolder()
|
||||||
|
private val voteToPollUseCase = VoteToPollUseCase(fakeActiveSessionHolder.instance)
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `given a local echo poll event when voting for an option then the vote is aborted`() {
|
||||||
|
// Given
|
||||||
|
val aPollEventId = A_LOCAL_EVENT_ID
|
||||||
|
val aVoteId = AN_OPTION_ID
|
||||||
|
givenAPollTimelineEvent(aPollEventId)
|
||||||
|
|
||||||
|
// When
|
||||||
|
voteToPollUseCase.execute(A_ROOM_ID, aPollEventId, aVoteId)
|
||||||
|
|
||||||
|
// Then
|
||||||
|
verify(exactly = 0) {
|
||||||
|
fakeActiveSessionHolder.fakeSession
|
||||||
|
.getRoom(A_ROOM_ID)
|
||||||
|
?.sendService()
|
||||||
|
?.voteToPoll(aPollEventId, aVoteId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `given a poll event when voting for a different option then the vote is sent`() {
|
||||||
|
// Given
|
||||||
|
val aPollEventId = AN_EVENT_ID
|
||||||
|
val aVoteId = AN_OPTION_ID
|
||||||
|
givenAPollTimelineEvent(aPollEventId)
|
||||||
|
|
||||||
|
// When
|
||||||
|
voteToPollUseCase.execute(A_ROOM_ID, aPollEventId, aVoteId)
|
||||||
|
|
||||||
|
// Then
|
||||||
|
verify(exactly = 1) {
|
||||||
|
fakeActiveSessionHolder.fakeSession
|
||||||
|
.getRoom(A_ROOM_ID)
|
||||||
|
?.sendService()
|
||||||
|
?.voteToPoll(aPollEventId, aVoteId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `given a poll event when voting for the same option then the vote is aborted`() {
|
||||||
|
// Given
|
||||||
|
val aPollEventId = AN_EVENT_ID
|
||||||
|
val aVoteId = AN_EXISTING_OPTION_ID
|
||||||
|
givenAPollTimelineEvent(aPollEventId)
|
||||||
|
|
||||||
|
// When
|
||||||
|
voteToPollUseCase.execute(A_ROOM_ID, aPollEventId, aVoteId)
|
||||||
|
|
||||||
|
// Then
|
||||||
|
verify(exactly = 0) {
|
||||||
|
fakeActiveSessionHolder.fakeSession
|
||||||
|
.getRoom(A_ROOM_ID)
|
||||||
|
?.sendService()
|
||||||
|
?.voteToPoll(aPollEventId, aVoteId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun givenAPollTimelineEvent(eventId: String) {
|
||||||
|
val pollCreationInfo = RoomPollFixture.givenPollCreationInfo("pollTitle")
|
||||||
|
val messageContent = RoomPollFixture.givenAMessagePollContent(pollCreationInfo)
|
||||||
|
val timelineEvent = RoomPollFixture.givenATimelineEvent(
|
||||||
|
eventId,
|
||||||
|
A_ROOM_ID,
|
||||||
|
AN_EVENT_TIMESTAMP,
|
||||||
|
messageContent
|
||||||
|
)
|
||||||
|
every {
|
||||||
|
timelineEvent.annotations
|
||||||
|
?.pollResponseSummary
|
||||||
|
?.aggregatedContent
|
||||||
|
?.myVote
|
||||||
|
} returns AN_EXISTING_OPTION_ID
|
||||||
|
|
||||||
|
every {
|
||||||
|
fakeActiveSessionHolder.fakeSession
|
||||||
|
.getRoom(A_ROOM_ID)
|
||||||
|
?.getTimelineEvent(eventId)
|
||||||
|
} returns timelineEvent
|
||||||
|
}
|
||||||
|
}
|
|
@ -19,9 +19,8 @@ package im.vector.app.features.home.room.detail.timeline.factory
|
||||||
import im.vector.app.R
|
import im.vector.app.R
|
||||||
import im.vector.app.features.home.room.detail.timeline.item.PollOptionViewState
|
import im.vector.app.features.home.room.detail.timeline.item.PollOptionViewState
|
||||||
import im.vector.app.features.home.room.detail.timeline.item.PollVoteSummaryData
|
import im.vector.app.features.home.room.detail.timeline.item.PollVoteSummaryData
|
||||||
import im.vector.app.features.poll.PollViewState
|
import im.vector.app.features.poll.PollItemViewState
|
||||||
import im.vector.app.test.fakes.FakeStringProvider
|
import im.vector.app.test.fakes.FakeStringProvider
|
||||||
import im.vector.app.test.fixtures.PollFixture.A_MESSAGE_INFORMATION_DATA
|
|
||||||
import im.vector.app.test.fixtures.PollFixture.A_POLL_CONTENT
|
import im.vector.app.test.fixtures.PollFixture.A_POLL_CONTENT
|
||||||
import im.vector.app.test.fixtures.PollFixture.A_POLL_OPTION_IDS
|
import im.vector.app.test.fixtures.PollFixture.A_POLL_OPTION_IDS
|
||||||
import im.vector.app.test.fixtures.PollFixture.A_POLL_RESPONSE_DATA
|
import im.vector.app.test.fixtures.PollFixture.A_POLL_RESPONSE_DATA
|
||||||
|
@ -31,7 +30,6 @@ import io.mockk.verify
|
||||||
import org.amshove.kluent.shouldBeEqualTo
|
import org.amshove.kluent.shouldBeEqualTo
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
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.send.SendState
|
|
||||||
|
|
||||||
class PollItemViewStateFactoryTest {
|
class PollItemViewStateFactoryTest {
|
||||||
|
|
||||||
|
@ -46,18 +44,18 @@ class PollItemViewStateFactoryTest {
|
||||||
@Test
|
@Test
|
||||||
fun `given a sending poll state then poll is not votable and option states are PollSending`() {
|
fun `given a sending poll state then poll is not votable and option states are PollSending`() {
|
||||||
// Given
|
// Given
|
||||||
val sendingPollInformationData = A_MESSAGE_INFORMATION_DATA.copy(sendState = SendState.SENDING)
|
|
||||||
val optionViewStates = listOf(PollOptionViewState.PollSending(optionId = "", optionAnswer = ""))
|
val optionViewStates = listOf(PollOptionViewState.PollSending(optionId = "", optionAnswer = ""))
|
||||||
every { fakePollOptionViewStateFactory.createPollSendingOptions(A_POLL_CONTENT.getBestPollCreationInfo()) } returns optionViewStates
|
every { fakePollOptionViewStateFactory.createPollSendingOptions(A_POLL_CONTENT.getBestPollCreationInfo()) } returns optionViewStates
|
||||||
|
|
||||||
// When
|
// When
|
||||||
val pollViewState = pollItemViewStateFactory.create(
|
val pollViewState = pollItemViewStateFactory.create(
|
||||||
pollContent = A_POLL_CONTENT,
|
pollContent = A_POLL_CONTENT,
|
||||||
informationData = sendingPollInformationData,
|
pollResponseData = null,
|
||||||
|
isSent = false,
|
||||||
)
|
)
|
||||||
|
|
||||||
// Then
|
// Then
|
||||||
pollViewState shouldBeEqualTo PollViewState(
|
pollViewState shouldBeEqualTo PollItemViewState(
|
||||||
question = A_POLL_CONTENT.getBestPollCreationInfo()?.question?.getBestQuestion() ?: "",
|
question = A_POLL_CONTENT.getBestPollCreationInfo()?.question?.getBestQuestion() ?: "",
|
||||||
votesStatus = fakeStringProvider.instance.getString(R.string.poll_no_votes_cast),
|
votesStatus = fakeStringProvider.instance.getString(R.string.poll_no_votes_cast),
|
||||||
canVote = false,
|
canVote = false,
|
||||||
|
@ -70,7 +68,6 @@ class PollItemViewStateFactoryTest {
|
||||||
fun `given a sent poll state when poll is closed then poll is not votable and option states are Ended`() {
|
fun `given a sent poll state when poll is closed then poll is not votable and option states are Ended`() {
|
||||||
// Given
|
// Given
|
||||||
val closedPollSummary = A_POLL_RESPONSE_DATA.copy(isClosed = true)
|
val closedPollSummary = A_POLL_RESPONSE_DATA.copy(isClosed = true)
|
||||||
val closedPollInformationData = A_MESSAGE_INFORMATION_DATA.copy(pollResponseAggregatedSummary = closedPollSummary)
|
|
||||||
val optionViewStates = listOf(
|
val optionViewStates = listOf(
|
||||||
PollOptionViewState.PollEnded(
|
PollOptionViewState.PollEnded(
|
||||||
optionId = "", optionAnswer = "", voteCount = 0, votePercentage = 0.0, isWinner = false
|
optionId = "", optionAnswer = "", voteCount = 0, votePercentage = 0.0, isWinner = false
|
||||||
|
@ -79,18 +76,19 @@ class PollItemViewStateFactoryTest {
|
||||||
every {
|
every {
|
||||||
fakePollOptionViewStateFactory.createPollEndedOptions(
|
fakePollOptionViewStateFactory.createPollEndedOptions(
|
||||||
A_POLL_CONTENT.getBestPollCreationInfo(),
|
A_POLL_CONTENT.getBestPollCreationInfo(),
|
||||||
closedPollInformationData.pollResponseAggregatedSummary,
|
closedPollSummary,
|
||||||
)
|
)
|
||||||
} returns optionViewStates
|
} returns optionViewStates
|
||||||
|
|
||||||
// When
|
// When
|
||||||
val pollViewState = pollItemViewStateFactory.create(
|
val pollViewState = pollItemViewStateFactory.create(
|
||||||
pollContent = A_POLL_CONTENT,
|
pollContent = A_POLL_CONTENT,
|
||||||
informationData = closedPollInformationData,
|
pollResponseData = closedPollSummary,
|
||||||
|
isSent = true,
|
||||||
)
|
)
|
||||||
|
|
||||||
// Then
|
// Then
|
||||||
pollViewState shouldBeEqualTo PollViewState(
|
pollViewState shouldBeEqualTo PollItemViewState(
|
||||||
question = A_POLL_CONTENT.getBestPollCreationInfo()?.question?.getBestQuestion() ?: "",
|
question = A_POLL_CONTENT.getBestPollCreationInfo()?.question?.getBestQuestion() ?: "",
|
||||||
votesStatus = fakeStringProvider.instance.getQuantityString(R.plurals.poll_total_vote_count_after_ended, 0, 0),
|
votesStatus = fakeStringProvider.instance.getQuantityString(R.plurals.poll_total_vote_count_after_ended, 0, 0),
|
||||||
canVote = false,
|
canVote = false,
|
||||||
|
@ -99,7 +97,7 @@ class PollItemViewStateFactoryTest {
|
||||||
verify {
|
verify {
|
||||||
fakePollOptionViewStateFactory.createPollEndedOptions(
|
fakePollOptionViewStateFactory.createPollEndedOptions(
|
||||||
A_POLL_CONTENT.getBestPollCreationInfo(),
|
A_POLL_CONTENT.getBestPollCreationInfo(),
|
||||||
closedPollInformationData.pollResponseAggregatedSummary,
|
closedPollSummary,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -108,7 +106,6 @@ class PollItemViewStateFactoryTest {
|
||||||
fun `given a sent poll state with some decryption error when poll is closed then warning message is displayed`() {
|
fun `given a sent poll state with some decryption error when poll is closed then warning message is displayed`() {
|
||||||
// Given
|
// Given
|
||||||
val closedPollSummary = A_POLL_RESPONSE_DATA.copy(isClosed = true, hasEncryptedRelatedEvents = true)
|
val closedPollSummary = A_POLL_RESPONSE_DATA.copy(isClosed = true, hasEncryptedRelatedEvents = true)
|
||||||
val closedPollInformationData = A_MESSAGE_INFORMATION_DATA.copy(pollResponseAggregatedSummary = closedPollSummary)
|
|
||||||
val optionViewStates = listOf(
|
val optionViewStates = listOf(
|
||||||
PollOptionViewState.PollEnded(
|
PollOptionViewState.PollEnded(
|
||||||
optionId = "", optionAnswer = "", voteCount = 0, votePercentage = 0.0, isWinner = false
|
optionId = "", optionAnswer = "", voteCount = 0, votePercentage = 0.0, isWinner = false
|
||||||
|
@ -117,14 +114,15 @@ class PollItemViewStateFactoryTest {
|
||||||
every {
|
every {
|
||||||
fakePollOptionViewStateFactory.createPollEndedOptions(
|
fakePollOptionViewStateFactory.createPollEndedOptions(
|
||||||
A_POLL_CONTENT.getBestPollCreationInfo(),
|
A_POLL_CONTENT.getBestPollCreationInfo(),
|
||||||
closedPollInformationData.pollResponseAggregatedSummary,
|
closedPollSummary,
|
||||||
)
|
)
|
||||||
} returns optionViewStates
|
} returns optionViewStates
|
||||||
|
|
||||||
// When
|
// When
|
||||||
val pollViewState = pollItemViewStateFactory.create(
|
val pollViewState = pollItemViewStateFactory.create(
|
||||||
pollContent = A_POLL_CONTENT,
|
pollContent = A_POLL_CONTENT,
|
||||||
informationData = closedPollInformationData,
|
pollResponseData = closedPollSummary,
|
||||||
|
isSent = true,
|
||||||
)
|
)
|
||||||
|
|
||||||
// Then
|
// Then
|
||||||
|
@ -134,6 +132,7 @@ class PollItemViewStateFactoryTest {
|
||||||
@Test
|
@Test
|
||||||
fun `given a sent poll when undisclosed poll type is selected then poll is votable and option states are PollUndisclosed`() {
|
fun `given a sent poll when undisclosed poll type is selected then poll is votable and option states are PollUndisclosed`() {
|
||||||
// Given
|
// Given
|
||||||
|
val pollResponseData = A_POLL_RESPONSE_DATA
|
||||||
val optionViewStates = listOf(
|
val optionViewStates = listOf(
|
||||||
PollOptionViewState.PollUndisclosed(
|
PollOptionViewState.PollUndisclosed(
|
||||||
optionId = "",
|
optionId = "",
|
||||||
|
@ -144,18 +143,19 @@ class PollItemViewStateFactoryTest {
|
||||||
every {
|
every {
|
||||||
fakePollOptionViewStateFactory.createPollUndisclosedOptions(
|
fakePollOptionViewStateFactory.createPollUndisclosedOptions(
|
||||||
A_POLL_CONTENT.getBestPollCreationInfo(),
|
A_POLL_CONTENT.getBestPollCreationInfo(),
|
||||||
A_MESSAGE_INFORMATION_DATA.pollResponseAggregatedSummary,
|
pollResponseData,
|
||||||
)
|
)
|
||||||
} returns optionViewStates
|
} returns optionViewStates
|
||||||
|
|
||||||
// When
|
// When
|
||||||
val pollViewState = pollItemViewStateFactory.create(
|
val pollViewState = pollItemViewStateFactory.create(
|
||||||
pollContent = A_POLL_CONTENT,
|
pollContent = A_POLL_CONTENT,
|
||||||
informationData = A_MESSAGE_INFORMATION_DATA,
|
pollResponseData = pollResponseData,
|
||||||
|
isSent = true,
|
||||||
)
|
)
|
||||||
|
|
||||||
// Then
|
// Then
|
||||||
pollViewState shouldBeEqualTo PollViewState(
|
pollViewState shouldBeEqualTo PollItemViewState(
|
||||||
question = A_POLL_CONTENT.getBestPollCreationInfo()?.question?.getBestQuestion() ?: "",
|
question = A_POLL_CONTENT.getBestPollCreationInfo()?.question?.getBestQuestion() ?: "",
|
||||||
votesStatus = fakeStringProvider.instance.getString(R.string.poll_undisclosed_not_ended),
|
votesStatus = fakeStringProvider.instance.getString(R.string.poll_undisclosed_not_ended),
|
||||||
canVote = true,
|
canVote = true,
|
||||||
|
@ -164,7 +164,7 @@ class PollItemViewStateFactoryTest {
|
||||||
verify {
|
verify {
|
||||||
fakePollOptionViewStateFactory.createPollUndisclosedOptions(
|
fakePollOptionViewStateFactory.createPollUndisclosedOptions(
|
||||||
A_POLL_CONTENT.getBestPollCreationInfo(),
|
A_POLL_CONTENT.getBestPollCreationInfo(),
|
||||||
A_MESSAGE_INFORMATION_DATA.pollResponseAggregatedSummary,
|
A_POLL_RESPONSE_DATA,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -180,7 +180,6 @@ class PollItemViewStateFactoryTest {
|
||||||
kind = PollType.DISCLOSED_UNSTABLE
|
kind = PollType.DISCLOSED_UNSTABLE
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
val votedInformationData = A_MESSAGE_INFORMATION_DATA.copy(pollResponseAggregatedSummary = votedPollData)
|
|
||||||
val optionViewStates = listOf(
|
val optionViewStates = listOf(
|
||||||
PollOptionViewState.PollVoted(
|
PollOptionViewState.PollVoted(
|
||||||
optionId = "",
|
optionId = "",
|
||||||
|
@ -193,18 +192,19 @@ class PollItemViewStateFactoryTest {
|
||||||
every {
|
every {
|
||||||
fakePollOptionViewStateFactory.createPollVotedOptions(
|
fakePollOptionViewStateFactory.createPollVotedOptions(
|
||||||
disclosedPollContent.getBestPollCreationInfo(),
|
disclosedPollContent.getBestPollCreationInfo(),
|
||||||
votedInformationData.pollResponseAggregatedSummary,
|
votedPollData,
|
||||||
)
|
)
|
||||||
} returns optionViewStates
|
} returns optionViewStates
|
||||||
|
|
||||||
// When
|
// When
|
||||||
val pollViewState = pollItemViewStateFactory.create(
|
val pollViewState = pollItemViewStateFactory.create(
|
||||||
pollContent = disclosedPollContent,
|
pollContent = disclosedPollContent,
|
||||||
informationData = votedInformationData,
|
pollResponseData = votedPollData,
|
||||||
|
isSent = true,
|
||||||
)
|
)
|
||||||
|
|
||||||
// Then
|
// Then
|
||||||
pollViewState shouldBeEqualTo PollViewState(
|
pollViewState shouldBeEqualTo PollItemViewState(
|
||||||
question = A_POLL_CONTENT.getBestPollCreationInfo()?.question?.getBestQuestion() ?: "",
|
question = A_POLL_CONTENT.getBestPollCreationInfo()?.question?.getBestQuestion() ?: "",
|
||||||
votesStatus = fakeStringProvider.instance.getQuantityString(R.plurals.poll_total_vote_count_before_ended_and_voted, 1, 1),
|
votesStatus = fakeStringProvider.instance.getQuantityString(R.plurals.poll_total_vote_count_before_ended_and_voted, 1, 1),
|
||||||
canVote = true,
|
canVote = true,
|
||||||
|
@ -213,7 +213,7 @@ class PollItemViewStateFactoryTest {
|
||||||
verify {
|
verify {
|
||||||
fakePollOptionViewStateFactory.createPollVotedOptions(
|
fakePollOptionViewStateFactory.createPollVotedOptions(
|
||||||
disclosedPollContent.getBestPollCreationInfo(),
|
disclosedPollContent.getBestPollCreationInfo(),
|
||||||
votedInformationData.pollResponseAggregatedSummary,
|
votedPollData,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -232,7 +232,6 @@ class PollItemViewStateFactoryTest {
|
||||||
kind = PollType.DISCLOSED_UNSTABLE
|
kind = PollType.DISCLOSED_UNSTABLE
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
val votedInformationData = A_MESSAGE_INFORMATION_DATA.copy(pollResponseAggregatedSummary = votedPollData)
|
|
||||||
val optionViewStates = listOf(
|
val optionViewStates = listOf(
|
||||||
PollOptionViewState.PollVoted(
|
PollOptionViewState.PollVoted(
|
||||||
optionId = "",
|
optionId = "",
|
||||||
|
@ -245,14 +244,15 @@ class PollItemViewStateFactoryTest {
|
||||||
every {
|
every {
|
||||||
fakePollOptionViewStateFactory.createPollVotedOptions(
|
fakePollOptionViewStateFactory.createPollVotedOptions(
|
||||||
disclosedPollContent.getBestPollCreationInfo(),
|
disclosedPollContent.getBestPollCreationInfo(),
|
||||||
votedInformationData.pollResponseAggregatedSummary,
|
votedPollData,
|
||||||
)
|
)
|
||||||
} returns optionViewStates
|
} returns optionViewStates
|
||||||
|
|
||||||
// When
|
// When
|
||||||
val pollViewState = pollItemViewStateFactory.create(
|
val pollViewState = pollItemViewStateFactory.create(
|
||||||
pollContent = disclosedPollContent,
|
pollContent = disclosedPollContent,
|
||||||
informationData = votedInformationData,
|
pollResponseData = votedPollData,
|
||||||
|
isSent = true,
|
||||||
)
|
)
|
||||||
|
|
||||||
// Then
|
// Then
|
||||||
|
@ -262,6 +262,7 @@ class PollItemViewStateFactoryTest {
|
||||||
@Test
|
@Test
|
||||||
fun `given a sent poll when poll type is disclosed then poll is votable and option view states are PollReady`() {
|
fun `given a sent poll when poll type is disclosed then poll is votable and option view states are PollReady`() {
|
||||||
// Given
|
// Given
|
||||||
|
val pollResponseData = A_POLL_RESPONSE_DATA
|
||||||
val disclosedPollContent = A_POLL_CONTENT.copy(
|
val disclosedPollContent = A_POLL_CONTENT.copy(
|
||||||
unstablePollCreationInfo = A_POLL_CONTENT.getBestPollCreationInfo()?.copy(
|
unstablePollCreationInfo = A_POLL_CONTENT.getBestPollCreationInfo()?.copy(
|
||||||
kind = PollType.DISCLOSED_UNSTABLE
|
kind = PollType.DISCLOSED_UNSTABLE
|
||||||
|
@ -282,11 +283,12 @@ class PollItemViewStateFactoryTest {
|
||||||
// When
|
// When
|
||||||
val pollViewState = pollItemViewStateFactory.create(
|
val pollViewState = pollItemViewStateFactory.create(
|
||||||
pollContent = disclosedPollContent,
|
pollContent = disclosedPollContent,
|
||||||
informationData = A_MESSAGE_INFORMATION_DATA,
|
pollResponseData = pollResponseData,
|
||||||
|
isSent = true,
|
||||||
)
|
)
|
||||||
|
|
||||||
// Then
|
// Then
|
||||||
pollViewState shouldBeEqualTo PollViewState(
|
pollViewState shouldBeEqualTo PollItemViewState(
|
||||||
question = A_POLL_CONTENT.getBestPollCreationInfo()?.question?.getBestQuestion() ?: "",
|
question = A_POLL_CONTENT.getBestPollCreationInfo()?.question?.getBestQuestion() ?: "",
|
||||||
votesStatus = fakeStringProvider.instance.getString(R.string.poll_no_votes_cast),
|
votesStatus = fakeStringProvider.instance.getString(R.string.poll_no_votes_cast),
|
||||||
canVote = true,
|
canVote = true,
|
||||||
|
|
|
@ -0,0 +1,136 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2023 New Vector Ltd
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package im.vector.app.features.roomprofile.polls.detail.domain
|
||||||
|
|
||||||
|
import im.vector.app.test.fakes.FakeActiveSessionHolder
|
||||||
|
import io.mockk.every
|
||||||
|
import io.mockk.mockk
|
||||||
|
import io.mockk.mockkStatic
|
||||||
|
import io.mockk.unmockkAll
|
||||||
|
import org.amshove.kluent.shouldBe
|
||||||
|
import org.junit.After
|
||||||
|
import org.junit.Before
|
||||||
|
import org.junit.Test
|
||||||
|
import org.matrix.android.sdk.api.session.events.model.Event
|
||||||
|
import org.matrix.android.sdk.api.session.events.model.RelationType
|
||||||
|
import org.matrix.android.sdk.api.session.events.model.isPollEnd
|
||||||
|
import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent
|
||||||
|
|
||||||
|
private const val A_ROOM_ID = "room-id"
|
||||||
|
private const val A_START_POLL_EVENT_ID = "start-poll-id"
|
||||||
|
private const val AN_END_POLL_EVENT_ID = "end-poll-id"
|
||||||
|
|
||||||
|
internal class GetEndedPollEventIdUseCaseTest {
|
||||||
|
|
||||||
|
private val fakeActiveSessionHolder = FakeActiveSessionHolder()
|
||||||
|
|
||||||
|
private val getEndedPollEventIdUseCase = GetEndedPollEventIdUseCase(
|
||||||
|
activeSessionHolder = fakeActiveSessionHolder.instance
|
||||||
|
)
|
||||||
|
|
||||||
|
@Before
|
||||||
|
fun setup() {
|
||||||
|
mockkStatic("org.matrix.android.sdk.api.session.events.model.EventKt")
|
||||||
|
}
|
||||||
|
|
||||||
|
@After
|
||||||
|
fun tearDown() {
|
||||||
|
unmockkAll()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `given existing related end event when execute then result is id of the end event`() {
|
||||||
|
// Given
|
||||||
|
val timelineEvent = givenEvent(eventId = AN_END_POLL_EVENT_ID, isPollEnd = true)
|
||||||
|
givenARelatedEvent(timelineEvent = timelineEvent)
|
||||||
|
|
||||||
|
// When
|
||||||
|
val result = getEndedPollEventIdUseCase.execute(A_ROOM_ID, A_START_POLL_EVENT_ID)
|
||||||
|
|
||||||
|
// Then
|
||||||
|
result shouldBe AN_END_POLL_EVENT_ID
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `given existing related event but not end poll event when execute then result is null`() {
|
||||||
|
// Given
|
||||||
|
val timelineEvent = givenEvent(eventId = AN_END_POLL_EVENT_ID, isPollEnd = false)
|
||||||
|
givenARelatedEvent(timelineEvent = timelineEvent)
|
||||||
|
|
||||||
|
// When
|
||||||
|
val result = getEndedPollEventIdUseCase.execute(A_ROOM_ID, A_START_POLL_EVENT_ID)
|
||||||
|
|
||||||
|
// Then
|
||||||
|
result shouldBe null
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `given no existing related event when execute then result is null`() {
|
||||||
|
// Given
|
||||||
|
givenARelatedEvent(timelineEvent = null)
|
||||||
|
|
||||||
|
// When
|
||||||
|
val result = getEndedPollEventIdUseCase.execute(A_ROOM_ID, A_START_POLL_EVENT_ID)
|
||||||
|
|
||||||
|
// Then
|
||||||
|
result shouldBe null
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `given error occurred when execute then result is null`() {
|
||||||
|
// Given
|
||||||
|
givenErrorDuringRequest(error = Exception())
|
||||||
|
|
||||||
|
// When
|
||||||
|
val result = getEndedPollEventIdUseCase.execute(A_ROOM_ID, A_START_POLL_EVENT_ID)
|
||||||
|
|
||||||
|
// Then
|
||||||
|
result shouldBe null
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun givenEvent(eventId: String, isPollEnd: Boolean): TimelineEvent {
|
||||||
|
val timelineEvent = mockk<TimelineEvent>()
|
||||||
|
val event = mockk<Event>()
|
||||||
|
every { timelineEvent.root } returns event
|
||||||
|
every { timelineEvent.eventId } returns eventId
|
||||||
|
every { event.isPollEnd() } returns isPollEnd
|
||||||
|
return timelineEvent
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun givenARelatedEvent(timelineEvent: TimelineEvent?) {
|
||||||
|
val result: List<TimelineEvent> = timelineEvent?.let { listOf(it) } ?: emptyList()
|
||||||
|
every {
|
||||||
|
fakeActiveSessionHolder.instance
|
||||||
|
.getActiveSession()
|
||||||
|
.roomService()
|
||||||
|
.getRoom(A_ROOM_ID)
|
||||||
|
?.timelineService()
|
||||||
|
?.getTimelineEventsRelatedTo(RelationType.REFERENCE, A_START_POLL_EVENT_ID)
|
||||||
|
} returns result
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun givenErrorDuringRequest(error: Exception) {
|
||||||
|
every {
|
||||||
|
fakeActiveSessionHolder.instance
|
||||||
|
.getActiveSession()
|
||||||
|
.roomService()
|
||||||
|
.getRoom(A_ROOM_ID)
|
||||||
|
?.timelineService()
|
||||||
|
?.getTimelineEventsRelatedTo(RelationType.REFERENCE, A_START_POLL_EVENT_ID)
|
||||||
|
} throws error
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,186 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2023 New Vector Ltd
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package im.vector.app.features.roomprofile.polls.detail.ui
|
||||||
|
|
||||||
|
import im.vector.app.features.home.room.detail.timeline.factory.PollItemViewStateFactory
|
||||||
|
import im.vector.app.features.home.room.detail.timeline.helper.PollResponseDataFactory
|
||||||
|
import im.vector.app.features.poll.PollItemViewState
|
||||||
|
import im.vector.app.features.roomprofile.polls.detail.domain.GetEndedPollEventIdUseCase
|
||||||
|
import im.vector.app.test.fixtures.RoomPollFixture
|
||||||
|
import io.mockk.every
|
||||||
|
import io.mockk.mockk
|
||||||
|
import io.mockk.mockkStatic
|
||||||
|
import io.mockk.unmockkAll
|
||||||
|
import org.amshove.kluent.shouldBe
|
||||||
|
import org.amshove.kluent.shouldBeEqualTo
|
||||||
|
import org.junit.After
|
||||||
|
import org.junit.Before
|
||||||
|
import org.junit.Test
|
||||||
|
import org.matrix.android.sdk.api.session.room.model.message.MessageTextContent
|
||||||
|
import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent
|
||||||
|
|
||||||
|
private const val AN_EVENT_ID = "event-id"
|
||||||
|
private const val A_ROOM_ID = "room-id"
|
||||||
|
private const val AN_EVENT_TIMESTAMP = 123L
|
||||||
|
|
||||||
|
internal class RoomPollDetailMapperTest {
|
||||||
|
|
||||||
|
private val fakePollResponseDataFactory = mockk<PollResponseDataFactory>()
|
||||||
|
private val fakePollItemViewStateFactory = mockk<PollItemViewStateFactory>()
|
||||||
|
private val fakeGetEndedPollEventIdUseCase = mockk<GetEndedPollEventIdUseCase>()
|
||||||
|
|
||||||
|
private val roomPollDetailMapper = RoomPollDetailMapper(
|
||||||
|
pollResponseDataFactory = fakePollResponseDataFactory,
|
||||||
|
pollItemViewStateFactory = fakePollItemViewStateFactory,
|
||||||
|
getEndedPollEventIdUseCase = fakeGetEndedPollEventIdUseCase,
|
||||||
|
)
|
||||||
|
|
||||||
|
@Before
|
||||||
|
fun setup() {
|
||||||
|
mockkStatic("im.vector.app.core.extensions.TimelineEventKt")
|
||||||
|
}
|
||||||
|
|
||||||
|
@After
|
||||||
|
fun tearDown() {
|
||||||
|
unmockkAll()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `given a not ended poll event when mapping to model then result contains correct poll details`() {
|
||||||
|
// Given
|
||||||
|
val aPollItemViewState = givenAPollItemViewState()
|
||||||
|
val aPollEvent = givenAPollTimelineEvent(
|
||||||
|
eventId = AN_EVENT_ID,
|
||||||
|
creationTimestamp = AN_EVENT_TIMESTAMP,
|
||||||
|
isClosed = false,
|
||||||
|
pollItemViewState = aPollItemViewState,
|
||||||
|
)
|
||||||
|
val expectedResult = RoomPollDetail(
|
||||||
|
creationTimestamp = AN_EVENT_TIMESTAMP,
|
||||||
|
isEnded = false,
|
||||||
|
endedPollEventId = null,
|
||||||
|
pollItemViewState = aPollItemViewState,
|
||||||
|
)
|
||||||
|
|
||||||
|
// When
|
||||||
|
val result = roomPollDetailMapper.map(aPollEvent)
|
||||||
|
|
||||||
|
// Then
|
||||||
|
result shouldBeEqualTo expectedResult
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `given an ended poll event when mapping to model then result contains correct poll details`() {
|
||||||
|
// Given
|
||||||
|
val aPollItemViewState = givenAPollItemViewState()
|
||||||
|
val aPollEvent = givenAPollTimelineEvent(
|
||||||
|
eventId = AN_EVENT_ID,
|
||||||
|
creationTimestamp = AN_EVENT_TIMESTAMP,
|
||||||
|
isClosed = true,
|
||||||
|
pollItemViewState = aPollItemViewState,
|
||||||
|
)
|
||||||
|
val endedPollEventId = givenEndedPollEventId()
|
||||||
|
val expectedResult = RoomPollDetail(
|
||||||
|
creationTimestamp = AN_EVENT_TIMESTAMP,
|
||||||
|
isEnded = true,
|
||||||
|
endedPollEventId = endedPollEventId,
|
||||||
|
pollItemViewState = aPollItemViewState,
|
||||||
|
)
|
||||||
|
|
||||||
|
// When
|
||||||
|
val result = roomPollDetailMapper.map(aPollEvent)
|
||||||
|
|
||||||
|
// Then
|
||||||
|
result shouldBeEqualTo expectedResult
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `given missing data in event when mapping to model then result is null`() {
|
||||||
|
// Given
|
||||||
|
val aPollItemViewState = givenAPollItemViewState()
|
||||||
|
val noIdPollEvent = givenAPollTimelineEvent(
|
||||||
|
eventId = "",
|
||||||
|
creationTimestamp = AN_EVENT_TIMESTAMP,
|
||||||
|
isClosed = false,
|
||||||
|
pollItemViewState = aPollItemViewState,
|
||||||
|
)
|
||||||
|
val noTimestampPollEvent = givenAPollTimelineEvent(
|
||||||
|
eventId = AN_EVENT_ID,
|
||||||
|
creationTimestamp = 0,
|
||||||
|
isClosed = false,
|
||||||
|
pollItemViewState = aPollItemViewState,
|
||||||
|
)
|
||||||
|
val notAPollEvent = RoomPollFixture.givenATimelineEvent(
|
||||||
|
eventId = AN_EVENT_ID,
|
||||||
|
roomId = "room-id",
|
||||||
|
creationTimestamp = 0,
|
||||||
|
content = mockk<MessageTextContent>(),
|
||||||
|
)
|
||||||
|
|
||||||
|
// When
|
||||||
|
val result1 = roomPollDetailMapper.map(noIdPollEvent)
|
||||||
|
val result2 = roomPollDetailMapper.map(noTimestampPollEvent)
|
||||||
|
val result3 = roomPollDetailMapper.map(notAPollEvent)
|
||||||
|
|
||||||
|
// Then
|
||||||
|
result1 shouldBe null
|
||||||
|
result2 shouldBe null
|
||||||
|
result3 shouldBe null
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun givenAPollItemViewState(): PollItemViewState {
|
||||||
|
return PollItemViewState(
|
||||||
|
question = "",
|
||||||
|
votesStatus = "",
|
||||||
|
canVote = true,
|
||||||
|
optionViewStates = emptyList(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun givenAPollTimelineEvent(
|
||||||
|
eventId: String,
|
||||||
|
creationTimestamp: Long,
|
||||||
|
isClosed: Boolean,
|
||||||
|
pollItemViewState: PollItemViewState,
|
||||||
|
): TimelineEvent {
|
||||||
|
val pollCreationInfo = RoomPollFixture.givenPollCreationInfo("pollTitle")
|
||||||
|
val messageContent = RoomPollFixture.givenAMessagePollContent(pollCreationInfo)
|
||||||
|
val timelineEvent = RoomPollFixture.givenATimelineEvent(eventId, A_ROOM_ID, creationTimestamp, messageContent)
|
||||||
|
val pollResponseData = RoomPollFixture.givenAPollResponseData(isClosed, totalVotes = 1)
|
||||||
|
every { fakePollResponseDataFactory.create(timelineEvent) } returns pollResponseData
|
||||||
|
every {
|
||||||
|
fakePollItemViewStateFactory.create(
|
||||||
|
pollContent = messageContent,
|
||||||
|
pollResponseData = pollResponseData,
|
||||||
|
isSent = true
|
||||||
|
)
|
||||||
|
} returns pollItemViewState
|
||||||
|
|
||||||
|
return timelineEvent
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun givenEndedPollEventId(): String {
|
||||||
|
val eventId = "ended-poll-event-id"
|
||||||
|
every {
|
||||||
|
fakeGetEndedPollEventIdUseCase.execute(
|
||||||
|
startPollEventId = AN_EVENT_ID,
|
||||||
|
roomId = A_ROOM_ID,
|
||||||
|
)
|
||||||
|
} returns eventId
|
||||||
|
return eventId
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,60 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2023 New Vector Ltd
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package im.vector.app.features.roomprofile.polls.detail.ui
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import im.vector.app.test.fakes.FakeNavigator
|
||||||
|
import io.mockk.mockk
|
||||||
|
import org.junit.Test
|
||||||
|
|
||||||
|
internal class RoomPollDetailNavigatorTest {
|
||||||
|
|
||||||
|
private val fakeNavigator = FakeNavigator()
|
||||||
|
|
||||||
|
private val roomPollDetailNavigator = RoomPollDetailNavigator(
|
||||||
|
navigator = fakeNavigator.instance,
|
||||||
|
)
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `given main navigator when goToTimelineEvent then correct method main navigator is called`() {
|
||||||
|
// Given
|
||||||
|
val aContext = mockk<Context>()
|
||||||
|
val aRoomId = "roomId"
|
||||||
|
val anEventId = "eventId"
|
||||||
|
fakeNavigator.givenOpenRoomSuccess(
|
||||||
|
context = aContext,
|
||||||
|
roomId = aRoomId,
|
||||||
|
eventId = anEventId,
|
||||||
|
buildTask = true,
|
||||||
|
isInviteAlreadyAccepted = false,
|
||||||
|
trigger = null,
|
||||||
|
)
|
||||||
|
|
||||||
|
// When
|
||||||
|
roomPollDetailNavigator.goToTimelineEvent(aContext, aRoomId, anEventId)
|
||||||
|
|
||||||
|
// Then
|
||||||
|
fakeNavigator.verifyOpenRoom(
|
||||||
|
context = aContext,
|
||||||
|
roomId = aRoomId,
|
||||||
|
eventId = anEventId,
|
||||||
|
buildTask = true,
|
||||||
|
isInviteAlreadyAccepted = false,
|
||||||
|
trigger = null,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,126 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2023 New Vector Ltd
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package im.vector.app.features.roomprofile.polls.detail.ui
|
||||||
|
|
||||||
|
import com.airbnb.mvrx.test.MavericksTestRule
|
||||||
|
import im.vector.app.core.event.GetTimelineEventUseCase
|
||||||
|
import im.vector.app.features.home.room.detail.poll.VoteToPollUseCase
|
||||||
|
import im.vector.app.test.test
|
||||||
|
import im.vector.app.test.testDispatcher
|
||||||
|
import io.mockk.every
|
||||||
|
import io.mockk.justRun
|
||||||
|
import io.mockk.mockk
|
||||||
|
import io.mockk.verify
|
||||||
|
import kotlinx.coroutines.flow.flowOf
|
||||||
|
import org.junit.Rule
|
||||||
|
import org.junit.Test
|
||||||
|
import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent
|
||||||
|
|
||||||
|
private const val A_POLL_ID = "poll-id"
|
||||||
|
private const val A_ROOM_ID = "room-id"
|
||||||
|
|
||||||
|
internal class RoomPollDetailViewModelTest {
|
||||||
|
|
||||||
|
@get:Rule
|
||||||
|
val mavericksTestRule = MavericksTestRule(testDispatcher = testDispatcher)
|
||||||
|
|
||||||
|
private val initialState = RoomPollDetailViewState(pollId = A_POLL_ID, roomId = A_ROOM_ID)
|
||||||
|
private val fakeGetTimelineEventUseCase = mockk<GetTimelineEventUseCase>()
|
||||||
|
private val fakeRoomPollDetailMapper = mockk<RoomPollDetailMapper>()
|
||||||
|
private val fakeVoteToPollUseCase = mockk<VoteToPollUseCase>()
|
||||||
|
|
||||||
|
private fun createViewModel(): RoomPollDetailViewModel {
|
||||||
|
return RoomPollDetailViewModel(
|
||||||
|
initialState = initialState,
|
||||||
|
getTimelineEventUseCase = fakeGetTimelineEventUseCase,
|
||||||
|
roomPollDetailMapper = fakeRoomPollDetailMapper,
|
||||||
|
voteToPollUseCase = fakeVoteToPollUseCase,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `given viewModel when created then poll detail is observed and viewState is updated`() {
|
||||||
|
// Given
|
||||||
|
val aPollEvent = givenAPollEvent()
|
||||||
|
val pollDetail = givenAPollDetail()
|
||||||
|
every { fakeGetTimelineEventUseCase.execute(A_ROOM_ID, A_POLL_ID) } returns flowOf(aPollEvent)
|
||||||
|
every { fakeRoomPollDetailMapper.map(aPollEvent) } returns pollDetail
|
||||||
|
val expectedViewState = initialState.copy(pollDetail = pollDetail)
|
||||||
|
|
||||||
|
// When
|
||||||
|
val viewModel = createViewModel()
|
||||||
|
val viewModelTest = viewModel.test()
|
||||||
|
|
||||||
|
// Then
|
||||||
|
viewModelTest
|
||||||
|
.assertLatestState(expectedViewState)
|
||||||
|
.finish()
|
||||||
|
verify {
|
||||||
|
fakeGetTimelineEventUseCase.execute(A_ROOM_ID, A_POLL_ID)
|
||||||
|
fakeRoomPollDetailMapper.map(aPollEvent)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `given viewModel when handle vote action then correct use case is called`() {
|
||||||
|
// Given
|
||||||
|
val aPollEvent = givenAPollEvent()
|
||||||
|
val pollDetail = givenAPollDetail()
|
||||||
|
every { fakeGetTimelineEventUseCase.execute(A_ROOM_ID, A_POLL_ID) } returns flowOf(aPollEvent)
|
||||||
|
every { fakeRoomPollDetailMapper.map(aPollEvent) } returns pollDetail
|
||||||
|
val viewModel = createViewModel()
|
||||||
|
val optionId = "option-id"
|
||||||
|
justRun {
|
||||||
|
fakeVoteToPollUseCase.execute(
|
||||||
|
roomId = A_ROOM_ID,
|
||||||
|
pollEventId = A_POLL_ID,
|
||||||
|
optionId = optionId,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
val action = RoomPollDetailAction.Vote(
|
||||||
|
pollEventId = A_POLL_ID,
|
||||||
|
optionId = optionId,
|
||||||
|
)
|
||||||
|
|
||||||
|
// When
|
||||||
|
val viewModelTest = viewModel.test()
|
||||||
|
viewModel.handle(action)
|
||||||
|
|
||||||
|
// Then
|
||||||
|
viewModelTest.finish()
|
||||||
|
verify {
|
||||||
|
fakeVoteToPollUseCase.execute(
|
||||||
|
roomId = A_ROOM_ID,
|
||||||
|
pollEventId = A_POLL_ID,
|
||||||
|
optionId = optionId,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun givenAPollEvent(): TimelineEvent {
|
||||||
|
return mockk()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun givenAPollDetail(): RoomPollDetail {
|
||||||
|
return RoomPollDetail(
|
||||||
|
creationTimestamp = 123L,
|
||||||
|
isEnded = false,
|
||||||
|
endedPollEventId = null,
|
||||||
|
pollItemViewState = mockk(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
|
@ -16,11 +16,10 @@
|
||||||
|
|
||||||
package im.vector.app.features.roomprofile.polls.list.ui
|
package im.vector.app.features.roomprofile.polls.list.ui
|
||||||
|
|
||||||
import im.vector.app.core.extensions.getVectorLastMessageContent
|
|
||||||
import im.vector.app.features.home.room.detail.timeline.factory.PollOptionViewStateFactory
|
import im.vector.app.features.home.room.detail.timeline.factory.PollOptionViewStateFactory
|
||||||
import im.vector.app.features.home.room.detail.timeline.helper.PollResponseDataFactory
|
import im.vector.app.features.home.room.detail.timeline.helper.PollResponseDataFactory
|
||||||
import im.vector.app.features.home.room.detail.timeline.item.PollOptionViewState
|
import im.vector.app.features.home.room.detail.timeline.item.PollOptionViewState
|
||||||
import im.vector.app.features.home.room.detail.timeline.item.PollResponseData
|
import im.vector.app.test.fixtures.RoomPollFixture
|
||||||
import io.mockk.every
|
import io.mockk.every
|
||||||
import io.mockk.mockk
|
import io.mockk.mockk
|
||||||
import io.mockk.mockkStatic
|
import io.mockk.mockkStatic
|
||||||
|
@ -30,11 +29,7 @@ import org.amshove.kluent.shouldBeEqualTo
|
||||||
import org.junit.After
|
import org.junit.After
|
||||||
import org.junit.Before
|
import org.junit.Before
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
import org.matrix.android.sdk.api.session.room.model.message.MessageContent
|
|
||||||
import org.matrix.android.sdk.api.session.room.model.message.MessagePollContent
|
|
||||||
import org.matrix.android.sdk.api.session.room.model.message.MessageTextContent
|
import org.matrix.android.sdk.api.session.room.model.message.MessageTextContent
|
||||||
import org.matrix.android.sdk.api.session.room.model.message.PollCreationInfo
|
|
||||||
import org.matrix.android.sdk.api.session.room.model.message.PollQuestion
|
|
||||||
import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent
|
import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent
|
||||||
|
|
||||||
private const val AN_EVENT_ID = "event-id"
|
private const val AN_EVENT_ID = "event-id"
|
||||||
|
@ -84,10 +79,12 @@ internal class PollSummaryMapperTest {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `given an ended poll event when mapping to model then result is ended poll`() {
|
fun `given an ended poll event when mapping to model then result is ended poll with only winner options`() {
|
||||||
// Given
|
// Given
|
||||||
val totalVotes = 10
|
val totalVotes = 10
|
||||||
val winnerOptions = listOf<PollOptionViewState.PollEnded>()
|
val option1 = givenAPollEndedOption(isWinner = false)
|
||||||
|
val option2 = givenAPollEndedOption(isWinner = true)
|
||||||
|
val winnerOptions = listOf(option1, option2)
|
||||||
val endedPollEvent = givenAPollTimelineEvent(
|
val endedPollEvent = givenAPollTimelineEvent(
|
||||||
eventId = AN_EVENT_ID,
|
eventId = AN_EVENT_ID,
|
||||||
creationTimestamp = AN_EVENT_TIMESTAMP,
|
creationTimestamp = AN_EVENT_TIMESTAMP,
|
||||||
|
@ -101,7 +98,7 @@ internal class PollSummaryMapperTest {
|
||||||
creationTimestamp = AN_EVENT_TIMESTAMP,
|
creationTimestamp = AN_EVENT_TIMESTAMP,
|
||||||
title = A_POLL_TITLE,
|
title = A_POLL_TITLE,
|
||||||
totalVotes = totalVotes,
|
totalVotes = totalVotes,
|
||||||
winnerOptions = winnerOptions,
|
winnerOptions = listOf(option2),
|
||||||
)
|
)
|
||||||
|
|
||||||
// When
|
// When
|
||||||
|
@ -126,10 +123,11 @@ internal class PollSummaryMapperTest {
|
||||||
pollTitle = A_POLL_TITLE,
|
pollTitle = A_POLL_TITLE,
|
||||||
isClosed = false,
|
isClosed = false,
|
||||||
)
|
)
|
||||||
val notAPollEvent = givenATimelineEvent(
|
val notAPollEvent = RoomPollFixture.givenATimelineEvent(
|
||||||
eventId = AN_EVENT_ID,
|
eventId = AN_EVENT_ID,
|
||||||
|
roomId = "room-id",
|
||||||
creationTimestamp = 0,
|
creationTimestamp = 0,
|
||||||
content = mockk<MessageTextContent>()
|
content = mockk<MessageTextContent>(),
|
||||||
)
|
)
|
||||||
|
|
||||||
// When
|
// When
|
||||||
|
@ -143,18 +141,6 @@ internal class PollSummaryMapperTest {
|
||||||
result3 shouldBe null
|
result3 shouldBe null
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun givenATimelineEvent(
|
|
||||||
eventId: String,
|
|
||||||
creationTimestamp: Long,
|
|
||||||
content: MessageContent,
|
|
||||||
): TimelineEvent {
|
|
||||||
val timelineEvent = mockk<TimelineEvent>()
|
|
||||||
every { timelineEvent.root.eventId } returns eventId
|
|
||||||
every { timelineEvent.root.originServerTs } returns creationTimestamp
|
|
||||||
every { timelineEvent.getVectorLastMessageContent() } returns content
|
|
||||||
return timelineEvent
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun givenAPollTimelineEvent(
|
private fun givenAPollTimelineEvent(
|
||||||
eventId: String,
|
eventId: String,
|
||||||
creationTimestamp: Long,
|
creationTimestamp: Long,
|
||||||
|
@ -163,10 +149,10 @@ internal class PollSummaryMapperTest {
|
||||||
totalVotes: Int = 0,
|
totalVotes: Int = 0,
|
||||||
winnerOptions: List<PollOptionViewState.PollEnded> = emptyList(),
|
winnerOptions: List<PollOptionViewState.PollEnded> = emptyList(),
|
||||||
): TimelineEvent {
|
): TimelineEvent {
|
||||||
val pollCreationInfo = givenPollCreationInfo(pollTitle)
|
val pollCreationInfo = RoomPollFixture.givenPollCreationInfo(pollTitle)
|
||||||
val messageContent = givenAMessagePollContent(pollCreationInfo)
|
val messageContent = RoomPollFixture.givenAMessagePollContent(pollCreationInfo)
|
||||||
val timelineEvent = givenATimelineEvent(eventId, creationTimestamp, messageContent)
|
val timelineEvent = RoomPollFixture.givenATimelineEvent(eventId, "room-id", creationTimestamp, messageContent)
|
||||||
val pollResponseData = givenAPollResponseData(isClosed, totalVotes)
|
val pollResponseData = RoomPollFixture.givenAPollResponseData(isClosed, totalVotes)
|
||||||
every { fakePollResponseDataFactory.create(timelineEvent) } returns pollResponseData
|
every { fakePollResponseDataFactory.create(timelineEvent) } returns pollResponseData
|
||||||
every {
|
every {
|
||||||
fakePollOptionViewStateFactory.createPollEndedOptions(
|
fakePollOptionViewStateFactory.createPollEndedOptions(
|
||||||
|
@ -178,24 +164,9 @@ internal class PollSummaryMapperTest {
|
||||||
return timelineEvent
|
return timelineEvent
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun givenAMessagePollContent(pollCreationInfo: PollCreationInfo): MessagePollContent {
|
private fun givenAPollEndedOption(isWinner: Boolean): PollOptionViewState.PollEnded {
|
||||||
return MessagePollContent(
|
return mockk<PollOptionViewState.PollEnded>().also {
|
||||||
unstablePollCreationInfo = pollCreationInfo,
|
every { it.isWinner } returns isWinner
|
||||||
)
|
}
|
||||||
}
|
|
||||||
|
|
||||||
private fun givenPollCreationInfo(pollTitle: String): PollCreationInfo {
|
|
||||||
return PollCreationInfo(
|
|
||||||
question = PollQuestion(unstableQuestion = pollTitle),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun givenAPollResponseData(isClosed: Boolean, totalVotes: Int): PollResponseData {
|
|
||||||
return PollResponseData(
|
|
||||||
myVote = "",
|
|
||||||
votes = emptyMap(),
|
|
||||||
isClosed = isClosed,
|
|
||||||
totalVotes = totalVotes,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,66 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2023 New Vector Ltd
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package im.vector.app.features.roomprofile.polls.list.ui
|
||||||
|
|
||||||
|
import android.content.Intent
|
||||||
|
import im.vector.app.features.roomprofile.polls.detail.ui.RoomPollDetailActivity
|
||||||
|
import im.vector.app.test.fakes.FakeContext
|
||||||
|
import io.mockk.every
|
||||||
|
import io.mockk.mockk
|
||||||
|
import io.mockk.mockkObject
|
||||||
|
import io.mockk.unmockkAll
|
||||||
|
import org.junit.After
|
||||||
|
import org.junit.Before
|
||||||
|
import org.junit.Test
|
||||||
|
|
||||||
|
internal class RoomPollsListNavigatorTest {
|
||||||
|
|
||||||
|
private val fakeContext = FakeContext()
|
||||||
|
private val roomPollsListNavigator = RoomPollsListNavigator()
|
||||||
|
|
||||||
|
@Before
|
||||||
|
fun setUp() {
|
||||||
|
mockkObject(RoomPollDetailActivity.Companion)
|
||||||
|
}
|
||||||
|
|
||||||
|
@After
|
||||||
|
fun tearDown() {
|
||||||
|
unmockkAll()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `given info about poll when goToPollDetails then it starts the correct activity`() {
|
||||||
|
// Given
|
||||||
|
val aPollId = "pollId"
|
||||||
|
val aRoomId = "roomId"
|
||||||
|
val isEnded = true
|
||||||
|
val intent = givenIntentForPollDetails(aPollId, aRoomId, isEnded)
|
||||||
|
fakeContext.givenStartActivity(intent)
|
||||||
|
|
||||||
|
// When
|
||||||
|
roomPollsListNavigator.goToPollDetails(fakeContext.instance, aPollId, aRoomId, isEnded)
|
||||||
|
|
||||||
|
// Then
|
||||||
|
fakeContext.verifyStartActivity(intent)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun givenIntentForPollDetails(pollId: String, roomId: String, isEnded: Boolean): Intent {
|
||||||
|
val intent = mockk<Intent>()
|
||||||
|
every { RoomPollDetailActivity.newIntent(fakeContext.instance, pollId, roomId, isEnded) } returns intent
|
||||||
|
return intent
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,51 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2023 New Vector Ltd
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package im.vector.app.test.fakes
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import im.vector.app.features.analytics.plan.ViewRoom
|
||||||
|
import im.vector.app.features.navigation.Navigator
|
||||||
|
import io.mockk.justRun
|
||||||
|
import io.mockk.mockk
|
||||||
|
import io.mockk.verify
|
||||||
|
|
||||||
|
class FakeNavigator {
|
||||||
|
|
||||||
|
val instance: Navigator = mockk()
|
||||||
|
|
||||||
|
fun givenOpenRoomSuccess(
|
||||||
|
context: Context,
|
||||||
|
roomId: String,
|
||||||
|
eventId: String?,
|
||||||
|
buildTask: Boolean,
|
||||||
|
isInviteAlreadyAccepted: Boolean,
|
||||||
|
trigger: ViewRoom.Trigger?,
|
||||||
|
) {
|
||||||
|
justRun { instance.openRoom(context, roomId, eventId, buildTask, isInviteAlreadyAccepted, trigger) }
|
||||||
|
}
|
||||||
|
|
||||||
|
fun verifyOpenRoom(
|
||||||
|
context: Context,
|
||||||
|
roomId: String,
|
||||||
|
eventId: String?,
|
||||||
|
buildTask: Boolean,
|
||||||
|
isInviteAlreadyAccepted: Boolean,
|
||||||
|
trigger: ViewRoom.Trigger?,
|
||||||
|
) {
|
||||||
|
verify { instance.openRoom(context, roomId, eventId, buildTask, isInviteAlreadyAccepted, trigger) }
|
||||||
|
}
|
||||||
|
}
|
|
@ -16,14 +16,26 @@
|
||||||
|
|
||||||
package im.vector.app.test.fakes
|
package im.vector.app.test.fakes
|
||||||
|
|
||||||
|
import androidx.lifecycle.LiveData
|
||||||
|
import androidx.lifecycle.MutableLiveData
|
||||||
import io.mockk.every
|
import io.mockk.every
|
||||||
import io.mockk.mockk
|
import io.mockk.mockk
|
||||||
import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent
|
import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent
|
||||||
import org.matrix.android.sdk.api.session.room.timeline.TimelineService
|
import org.matrix.android.sdk.api.session.room.timeline.TimelineService
|
||||||
|
import org.matrix.android.sdk.api.util.Optional
|
||||||
|
|
||||||
class FakeTimelineService : TimelineService by mockk() {
|
class FakeTimelineService : TimelineService by mockk() {
|
||||||
|
|
||||||
fun givenTimelineEventReturns(eventId: String, event: TimelineEvent?) {
|
fun givenTimelineEventReturns(eventId: String, event: TimelineEvent?) {
|
||||||
every { getTimelineEvent(eventId) } returns event
|
every { getTimelineEvent(eventId) } returns event
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun givenTimelineEventLiveReturns(
|
||||||
|
eventId: String,
|
||||||
|
event: TimelineEvent?
|
||||||
|
): LiveData<Optional<TimelineEvent>> {
|
||||||
|
return MutableLiveData(Optional(event)).also {
|
||||||
|
every { getTimelineEventLive(eventId) } returns it
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
65
vector/src/test/java/im/vector/app/test/fixtures/RoomPollFixture.kt
vendored
Normal file
65
vector/src/test/java/im/vector/app/test/fixtures/RoomPollFixture.kt
vendored
Normal file
|
@ -0,0 +1,65 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2023 New Vector Ltd
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package im.vector.app.test.fixtures
|
||||||
|
|
||||||
|
import im.vector.app.core.extensions.getVectorLastMessageContent
|
||||||
|
import im.vector.app.features.home.room.detail.timeline.item.PollResponseData
|
||||||
|
import io.mockk.every
|
||||||
|
import io.mockk.mockk
|
||||||
|
import org.matrix.android.sdk.api.session.room.model.message.MessageContent
|
||||||
|
import org.matrix.android.sdk.api.session.room.model.message.MessagePollContent
|
||||||
|
import org.matrix.android.sdk.api.session.room.model.message.PollCreationInfo
|
||||||
|
import org.matrix.android.sdk.api.session.room.model.message.PollQuestion
|
||||||
|
import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent
|
||||||
|
|
||||||
|
object RoomPollFixture {
|
||||||
|
|
||||||
|
fun givenATimelineEvent(
|
||||||
|
eventId: String,
|
||||||
|
roomId: String,
|
||||||
|
creationTimestamp: Long,
|
||||||
|
content: MessageContent,
|
||||||
|
): TimelineEvent {
|
||||||
|
val timelineEvent = mockk<TimelineEvent>()
|
||||||
|
every { timelineEvent.root.eventId } returns eventId
|
||||||
|
every { timelineEvent.roomId } returns roomId
|
||||||
|
every { timelineEvent.root.originServerTs } returns creationTimestamp
|
||||||
|
every { timelineEvent.getVectorLastMessageContent() } returns content
|
||||||
|
return timelineEvent
|
||||||
|
}
|
||||||
|
|
||||||
|
fun givenAMessagePollContent(pollCreationInfo: PollCreationInfo): MessagePollContent {
|
||||||
|
return MessagePollContent(
|
||||||
|
unstablePollCreationInfo = pollCreationInfo,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun givenPollCreationInfo(pollTitle: String): PollCreationInfo {
|
||||||
|
return PollCreationInfo(
|
||||||
|
question = PollQuestion(unstableQuestion = pollTitle),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun givenAPollResponseData(isClosed: Boolean, totalVotes: Int): PollResponseData {
|
||||||
|
return PollResponseData(
|
||||||
|
myVote = "",
|
||||||
|
votes = emptyMap(),
|
||||||
|
isClosed = isClosed,
|
||||||
|
totalVotes = totalVotes,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue