diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineViewModel.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineViewModel.kt index 72d9fc8a16..5d5aae66bb 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineViewModel.kt @@ -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.error.RoomNotFound 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.timeline.factory.TimelineFactory 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.crypto.MXCryptoError 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.content.WithHeldCode import org.matrix.android.sdk.api.session.events.model.isAttachmentMessage @@ -154,6 +154,7 @@ class TimelineViewModel @AssistedInject constructor( timelineFactory: TimelineFactory, private val spaceStateHandler: SpaceStateHandler, private val voiceBroadcastHelper: VoiceBroadcastHelper, + private val voteToPollUseCase: VoteToPollUseCase, ) : VectorViewModel(initialState), Timeline.Listener, ChatEffectManager.Delegate, CallProtocolsChecker.Listener, LocationSharingServiceConnection.Callback { @@ -1235,15 +1236,11 @@ class TimelineViewModel @AssistedInject constructor( private fun handleVoteToPoll(action: RoomDetailAction.VoteToPoll) { if (room == null) return - // Do not allow to vote unsent local echo of the poll event - if (LocalEcho.isLocalEchoId(action.eventId)) return - // Do not allow to vote the same option twice - room.getTimelineEvent(action.eventId)?.let { pollTimelineEvent -> - val currentVote = pollTimelineEvent.annotations?.pollResponseSummary?.aggregatedContent?.myVote - if (currentVote != action.optionKey) { - room.sendService().voteToPoll(action.eventId, action.optionKey) - } - } + voteToPollUseCase.execute( + roomId = room.roomId, + pollEventId = action.eventId, + optionId = action.optionKey, + ) } private fun handleEndPoll(eventId: String) { diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/poll/VoteToPollUseCase.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/poll/VoteToPollUseCase.kt new file mode 100644 index 0000000000..78c647bc63 --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/poll/VoteToPollUseCase.kt @@ -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.room.getTimelineEvent +import javax.inject.Inject + +// TODO add unit tests +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 + + val room = activeSessionHolder.getActiveSession() + .roomService() + .getRoom(roomId) + + room?.getTimelineEvent(pollEventId)?.let { pollTimelineEvent -> + val currentVote = pollTimelineEvent + .annotations + ?.pollResponseSummary + ?.aggregatedContent + ?.myVote + if (currentVote != optionId) { + room.sendService().voteToPoll( + pollEventId = pollEventId, + answerId = optionId + ) + } + } + } +} diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/ui/RoomPollDetailAction.kt b/vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/ui/RoomPollDetailAction.kt new file mode 100644 index 0000000000..dbf8436399 --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/ui/RoomPollDetailAction.kt @@ -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 +} diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/ui/RoomPollDetailController.kt b/vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/ui/RoomPollDetailController.kt index a4318151be..61ef41b14f 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/ui/RoomPollDetailController.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/ui/RoomPollDetailController.kt @@ -22,7 +22,7 @@ import javax.inject.Inject class RoomPollDetailController @Inject constructor() : TypedEpoxyController() { interface Callback { - fun vote(pollId: String, optionId: String) + fun vote(pollEventId: String, optionId: String) } var callback: Callback? = null diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/ui/RoomPollDetailFragment.kt b/vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/ui/RoomPollDetailFragment.kt index 7d27963023..8340c677ef 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/ui/RoomPollDetailFragment.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/ui/RoomPollDetailFragment.kt @@ -40,7 +40,9 @@ data class RoomPollDetailArgs( ) : Parcelable @AndroidEntryPoint -class RoomPollDetailFragment : VectorBaseFragment() { +class RoomPollDetailFragment : + VectorBaseFragment(), + RoomPollDetailController.Callback { @Inject lateinit var roomPollDetailController: RoomPollDetailController @@ -54,17 +56,22 @@ class RoomPollDetailFragment : VectorBaseFragment override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) setupToolbar() + setupDetailView() + // TODO add link to go to timeline message + create a ViewNavigator + } + override fun onDestroyView() { + roomPollDetailController.callback = null + views.pollDetailRecyclerView.cleanup() + super.onDestroyView() + } + + private fun setupDetailView() { + roomPollDetailController.callback = this views.pollDetailRecyclerView.configureWith( roomPollDetailController, hasFixedSize = true, ) - // TODO setup callback in controller for vote action - } - - override fun onDestroyView() { - views.pollDetailRecyclerView.cleanup() - super.onDestroyView() } private fun setupToolbar(isEnded: Boolean? = null) { @@ -85,4 +92,8 @@ class RoomPollDetailFragment : VectorBaseFragment setupToolbar(state.pollDetail.isEnded) roomPollDetailController.setData(state) } + + override fun vote(pollEventId: String, optionId: String) { + viewModel.handle(RoomPollDetailAction.Vote(pollEventId = pollEventId, optionId = optionId)) + } } diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/ui/RoomPollDetailItem.kt b/vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/ui/RoomPollDetailItem.kt index 9a80c32640..4420776a47 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/ui/RoomPollDetailItem.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/ui/RoomPollDetailItem.kt @@ -72,7 +72,7 @@ abstract class RoomPollDetailItem : VectorEpoxyModel( val relatedEventId = eventId if (canVote && relatedEventId != null) { - callback?.vote(pollId = relatedEventId, optionId = optionViewState.optionId) + callback?.vote(pollEventId = relatedEventId, optionId = optionViewState.optionId) } } diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/ui/RoomPollDetailViewModel.kt b/vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/ui/RoomPollDetailViewModel.kt index e3b7631cce..d76d0f7279 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/ui/RoomPollDetailViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/ui/RoomPollDetailViewModel.kt @@ -23,19 +23,19 @@ 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.EmptyAction import im.vector.app.core.platform.VectorViewModel -import im.vector.app.features.auth.ReAuthState -import im.vector.app.features.auth.ReAuthViewModel +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 +// TODO add unit tests class RoomPollDetailViewModel @AssistedInject constructor( @Assisted initialState: RoomPollDetailViewState, private val getTimelineEventUseCase: GetTimelineEventUseCase, private val roomPollDetailMapper: RoomPollDetailMapper, -) : VectorViewModel(initialState) { + private val voteToPollUseCase: VoteToPollUseCase, +) : VectorViewModel(initialState) { @AssistedFactory interface Factory : MavericksAssistedViewModelFactory { @@ -58,7 +58,17 @@ class RoomPollDetailViewModel @AssistedInject constructor( .launchIn(viewModelScope) } - override fun handle(action: EmptyAction) { - // do nothing for now + 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, + ) } }