From 783596723eebc136e1868adb9173153f7ba94904 Mon Sep 17 00:00:00 2001 From: Onuray Sahin Date: Tue, 24 Jan 2023 17:04:53 +0300 Subject: [PATCH 01/33] Implement poll detail layout. --- .../res/layout/fragment_room_poll_detail.xml | 35 +++++++++++++++++++ 1 file changed, 35 insertions(+) create mode 100644 vector/src/main/res/layout/fragment_room_poll_detail.xml diff --git a/vector/src/main/res/layout/fragment_room_poll_detail.xml b/vector/src/main/res/layout/fragment_room_poll_detail.xml new file mode 100644 index 0000000000..7a7334bf8d --- /dev/null +++ b/vector/src/main/res/layout/fragment_room_poll_detail.xml @@ -0,0 +1,35 @@ + + + + + + + + + + + + From c9dc570f83003687fddcecb8e52d7cc39571dbbc Mon Sep 17 00:00:00 2001 From: Onuray Sahin Date: Fri, 27 Jan 2023 16:31:30 +0300 Subject: [PATCH 02/33] Navigate to poll detail fragment. --- .../roomprofile/RoomProfileActivity.kt | 6 +++ .../roomprofile/RoomProfileSharedAction.kt | 1 + .../roomprofile/polls/RoomPollsAction.kt | 1 + .../roomprofile/polls/RoomPollsViewEvent.kt | 1 + .../roomprofile/polls/RoomPollsViewModel.kt | 6 +++ .../roomprofile/polls/RoomPollsViewState.kt | 2 + .../polls/detail/RoomPollDetailFragment.kt | 38 +++++++++++++++++++ .../roomprofile/polls/list/ui/PollSummary.kt | 3 ++ .../polls/list/ui/PollSummaryMapper.kt | 4 +- .../polls/list/ui/RoomPollsListFragment.kt | 9 +++-- 10 files changed, 67 insertions(+), 4 deletions(-) create mode 100644 vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/RoomPollDetailFragment.kt diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileActivity.kt b/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileActivity.kt index 3ee1ed867c..16bece891c 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileActivity.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileActivity.kt @@ -37,6 +37,7 @@ import im.vector.app.features.roomprofile.members.RoomMemberListFragment import im.vector.app.features.roomprofile.notifications.RoomNotificationSettingsFragment import im.vector.app.features.roomprofile.permissions.RoomPermissionsFragment import im.vector.app.features.roomprofile.polls.RoomPollsFragment +import im.vector.app.features.roomprofile.polls.detail.RoomPollDetailFragment import im.vector.app.features.roomprofile.settings.RoomSettingsFragment import im.vector.app.features.roomprofile.uploads.RoomUploadsFragment import im.vector.lib.core.utils.compat.getParcelableCompat @@ -105,6 +106,7 @@ class RoomProfileActivity : RoomProfileSharedAction.OpenRoomUploads -> openRoomUploads() RoomProfileSharedAction.OpenBannedRoomMembers -> openBannedRoomMembers() RoomProfileSharedAction.OpenRoomNotificationSettings -> openRoomNotificationSettings() + is RoomProfileSharedAction.OpenPollDetails -> handleOpenPollDetails() } } .launchIn(lifecycleScope) @@ -130,6 +132,10 @@ class RoomProfileActivity : finish() } + private fun handleOpenPollDetails() { + addFragmentToBackstack(views.simpleFragmentContainer, RoomPollDetailFragment::class.java, roomProfileArgs) + } + private fun openRoomPolls() { addFragmentToBackstack(views.simpleFragmentContainer, RoomPollsFragment::class.java, roomProfileArgs) } diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileSharedAction.kt b/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileSharedAction.kt index b243ceb206..63e2506c9a 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileSharedAction.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileSharedAction.kt @@ -26,6 +26,7 @@ sealed class RoomProfileSharedAction : VectorSharedAction { object OpenRoomAliasesSettings : RoomProfileSharedAction() object OpenRoomPermissionsSettings : RoomProfileSharedAction() object OpenRoomPolls : RoomProfileSharedAction() + object OpenPollDetails : RoomProfileSharedAction() object OpenRoomUploads : RoomProfileSharedAction() object OpenRoomMembers : RoomProfileSharedAction() object OpenBannedRoomMembers : RoomProfileSharedAction() diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/polls/RoomPollsAction.kt b/vector/src/main/java/im/vector/app/features/roomprofile/polls/RoomPollsAction.kt index 3fedbfc4a8..b3ef4f45e2 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/polls/RoomPollsAction.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/polls/RoomPollsAction.kt @@ -20,4 +20,5 @@ import im.vector.app.core.platform.VectorViewModelAction sealed interface RoomPollsAction : VectorViewModelAction { object LoadMorePolls : RoomPollsAction + data class OnPollSelected(val selectedPollId: String) : RoomPollsAction } diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/polls/RoomPollsViewEvent.kt b/vector/src/main/java/im/vector/app/features/roomprofile/polls/RoomPollsViewEvent.kt index cb2069d824..58b388186e 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/polls/RoomPollsViewEvent.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/polls/RoomPollsViewEvent.kt @@ -20,4 +20,5 @@ import im.vector.app.core.platform.VectorViewEvents sealed class RoomPollsViewEvent : VectorViewEvents { object LoadingError : RoomPollsViewEvent() + object NavigateToPollDetail : RoomPollsViewEvent() } diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/polls/RoomPollsViewModel.kt b/vector/src/main/java/im/vector/app/features/roomprofile/polls/RoomPollsViewModel.kt index 2beda47816..df891e08e4 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/polls/RoomPollsViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/polls/RoomPollsViewModel.kt @@ -89,9 +89,15 @@ class RoomPollsViewModel @AssistedInject constructor( override fun handle(action: RoomPollsAction) { when (action) { RoomPollsAction.LoadMorePolls -> handleLoadMore() + is RoomPollsAction.OnPollSelected -> handleOnPollSelected(action) } } + private fun handleOnPollSelected(action: RoomPollsAction.OnPollSelected) { + setState { copy(selectedPollId = action.selectedPollId) } + _viewEvents.post(RoomPollsViewEvent.NavigateToPollDetail) + } + private fun handleLoadMore() = withState { viewState -> viewModelScope.launch { setState { copy(isLoadingMore = true) } diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/polls/RoomPollsViewState.kt b/vector/src/main/java/im/vector/app/features/roomprofile/polls/RoomPollsViewState.kt index 4a5c138b6a..dca016e529 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/polls/RoomPollsViewState.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/polls/RoomPollsViewState.kt @@ -27,10 +27,12 @@ data class RoomPollsViewState( val canLoadMore: Boolean = true, val nbSyncedDays: Int = 0, val isSyncing: Boolean = false, + val selectedPollId: String? = null, ) : MavericksState { constructor(roomProfileArgs: RoomProfileArgs) : this(roomId = roomProfileArgs.roomId) fun hasNoPolls() = polls.isEmpty() fun hasNoPollsAndCanLoadMore() = !isSyncing && hasNoPolls() && canLoadMore + fun getSelectedPoll() = polls.find { it.id == selectedPollId } } diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/RoomPollDetailFragment.kt b/vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/RoomPollDetailFragment.kt new file mode 100644 index 0000000000..67e6307860 --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/RoomPollDetailFragment.kt @@ -0,0 +1,38 @@ +/* + * 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 + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import im.vector.app.core.platform.VectorBaseFragment +import im.vector.app.databinding.FragmentRoomPollDetailBinding + +class RoomPollDetailFragment : VectorBaseFragment() { + + 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(views.roomPollDetailToolbar) + .allowBack() + } +} diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/polls/list/ui/PollSummary.kt b/vector/src/main/java/im/vector/app/features/roomprofile/polls/list/ui/PollSummary.kt index 5c1eee0d00..542ac68ad2 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/polls/list/ui/PollSummary.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/polls/list/ui/PollSummary.kt @@ -22,11 +22,13 @@ sealed interface PollSummary { val id: String val creationTimestamp: Long val title: String + val optionViewStates: List data class ActivePoll( override val id: String, override val creationTimestamp: Long, override val title: String, + override val optionViewStates: List, ) : PollSummary data class EndedPoll( @@ -35,5 +37,6 @@ sealed interface PollSummary { override val title: String, val totalVotes: Int, val winnerOptions: List, + override val optionViewStates: List, ) : PollSummary } diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/polls/list/ui/PollSummaryMapper.kt b/vector/src/main/java/im/vector/app/features/roomprofile/polls/list/ui/PollSummaryMapper.kt index 64c712e61f..791f62d414 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/polls/list/ui/PollSummaryMapper.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/polls/list/ui/PollSummaryMapper.kt @@ -69,13 +69,15 @@ class PollSummaryMapper @Inject constructor( creationTimestamp = creationTimestamp, title = pollTitle, totalVotes = pollResponseData.totalVotes, - winnerOptions = pollOptionViewStateFactory.createPollEndedOptions(pollCreationInfo, pollResponseData) + winnerOptions = pollOptionViewStateFactory.createPollEndedOptions(pollCreationInfo, pollResponseData), + optionViewStates = pollOptionViewStateFactory.createPollSendingOptions(pollCreationInfo), ) } else { PollSummary.ActivePoll( id = eventId, creationTimestamp = creationTimestamp, title = pollTitle, + optionViewStates = pollOptionViewStateFactory.createPollSendingOptions(pollCreationInfo), ) } } diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/polls/list/ui/RoomPollsListFragment.kt b/vector/src/main/java/im/vector/app/features/roomprofile/polls/list/ui/RoomPollsListFragment.kt index 1c33959824..21f55629ec 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/polls/list/ui/RoomPollsListFragment.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/polls/list/ui/RoomPollsListFragment.kt @@ -29,13 +29,14 @@ import im.vector.app.core.extensions.configureWith import im.vector.app.core.platform.VectorBaseFragment import im.vector.app.core.resources.StringProvider import im.vector.app.databinding.FragmentRoomPollsListBinding +import im.vector.app.features.roomprofile.RoomProfileSharedAction +import im.vector.app.features.roomprofile.RoomProfileSharedActionViewModel import im.vector.app.features.roomprofile.polls.RoomPollsAction import im.vector.app.features.roomprofile.polls.RoomPollsLoadingError import im.vector.app.features.roomprofile.polls.RoomPollsType import im.vector.app.features.roomprofile.polls.RoomPollsViewEvent import im.vector.app.features.roomprofile.polls.RoomPollsViewModel import im.vector.app.features.roomprofile.polls.RoomPollsViewState -import timber.log.Timber import javax.inject.Inject abstract class RoomPollsListFragment : @@ -49,6 +50,7 @@ abstract class RoomPollsListFragment : lateinit var stringProvider: StringProvider private val viewModel: RoomPollsViewModel by parentFragmentViewModel(RoomPollsViewModel::class) + private lateinit var sharedActionViewModel: RoomProfileSharedActionViewModel override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentRoomPollsListBinding { return FragmentRoomPollsListBinding.inflate(inflater, container, false) @@ -56,6 +58,7 @@ abstract class RoomPollsListFragment : override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) + sharedActionViewModel = activityViewModelProvider[RoomProfileSharedActionViewModel::class.java] observeViewEvents() setupList() setupLoadMoreButton() @@ -65,6 +68,7 @@ abstract class RoomPollsListFragment : viewModel.observeViewEvents { viewEvent -> when (viewEvent) { RoomPollsViewEvent.LoadingError -> showErrorInSnackbar(RoomPollsLoadingError()) + RoomPollsViewEvent.NavigateToPollDetail -> sharedActionViewModel.post(RoomProfileSharedAction.OpenPollDetails) } } } @@ -126,8 +130,7 @@ abstract class RoomPollsListFragment : } override fun onPollClicked(pollId: String) { - // TODO navigate to details - Timber.d("poll with id $pollId clicked") + viewModel.handle(RoomPollsAction.OnPollSelected(pollId)) } override fun onLoadMoreClicked() { From b86f6a41bd5ea451dda146577aaa63793383b594 Mon Sep 17 00:00:00 2001 From: Onuray Sahin Date: Tue, 31 Jan 2023 11:13:14 +0300 Subject: [PATCH 03/33] Open poll detail screen. --- .../roomprofile/RoomProfileActivity.kt | 10 ++++-- .../roomprofile/RoomProfileFragment.kt | 3 +- .../roomprofile/RoomProfileSharedAction.kt | 2 +- .../roomprofile/polls/RoomPollsAction.kt | 1 + .../roomprofile/polls/RoomPollsFragment.kt | 33 +++++++++++++++++-- .../roomprofile/polls/RoomPollsViewEvent.kt | 2 +- .../roomprofile/polls/RoomPollsViewModel.kt | 7 +++- .../roomprofile/polls/RoomPollsViewState.kt | 1 + .../polls/detail/RoomPollDetailFragment.kt | 27 +++++++++++++++ .../polls/list/ui/RoomPollsListFragment.kt | 4 ++- 10 files changed, 80 insertions(+), 10 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileActivity.kt b/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileActivity.kt index 16bece891c..97636acb70 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileActivity.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileActivity.kt @@ -106,7 +106,7 @@ class RoomProfileActivity : RoomProfileSharedAction.OpenRoomUploads -> openRoomUploads() RoomProfileSharedAction.OpenBannedRoomMembers -> openBannedRoomMembers() RoomProfileSharedAction.OpenRoomNotificationSettings -> openRoomNotificationSettings() - is RoomProfileSharedAction.OpenPollDetails -> handleOpenPollDetails() + is RoomProfileSharedAction.OpenPollDetails -> handleOpenPollDetails(sharedAction) } } .launchIn(lifecycleScope) @@ -132,8 +132,12 @@ class RoomProfileActivity : finish() } - private fun handleOpenPollDetails() { - addFragmentToBackstack(views.simpleFragmentContainer, RoomPollDetailFragment::class.java, roomProfileArgs) + private fun handleOpenPollDetails(sharedAction: RoomProfileSharedAction.OpenPollDetails) { + addFragmentToBackstack( + views.simpleFragmentContainer, + RoomPollDetailFragment::class.java, + roomProfileArgs.copy(selectedPollId = sharedAction.pollId) + ) } private fun openRoomPolls() { diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileFragment.kt b/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileFragment.kt index 91f57d33e9..f90dd76d29 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileFragment.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileFragment.kt @@ -64,7 +64,8 @@ import javax.inject.Inject @Parcelize data class RoomProfileArgs( - val roomId: String + val roomId: String, + val selectedPollId: String? = null, ) : Parcelable @AndroidEntryPoint diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileSharedAction.kt b/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileSharedAction.kt index 63e2506c9a..7948b0dcd9 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileSharedAction.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileSharedAction.kt @@ -26,7 +26,7 @@ sealed class RoomProfileSharedAction : VectorSharedAction { object OpenRoomAliasesSettings : RoomProfileSharedAction() object OpenRoomPermissionsSettings : RoomProfileSharedAction() object OpenRoomPolls : RoomProfileSharedAction() - object OpenPollDetails : RoomProfileSharedAction() + data class OpenPollDetails(val pollId: String) : RoomProfileSharedAction() object OpenRoomUploads : RoomProfileSharedAction() object OpenRoomMembers : RoomProfileSharedAction() object OpenBannedRoomMembers : RoomProfileSharedAction() diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/polls/RoomPollsAction.kt b/vector/src/main/java/im/vector/app/features/roomprofile/polls/RoomPollsAction.kt index b3ef4f45e2..297e0048d7 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/polls/RoomPollsAction.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/polls/RoomPollsAction.kt @@ -20,5 +20,6 @@ import im.vector.app.core.platform.VectorViewModelAction sealed interface RoomPollsAction : VectorViewModelAction { object LoadMorePolls : RoomPollsAction + data class OnRoomPollsTypeChange(val roomPollsType: RoomPollsType) : RoomPollsAction data class OnPollSelected(val selectedPollId: String) : RoomPollsAction } diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/polls/RoomPollsFragment.kt b/vector/src/main/java/im/vector/app/features/roomprofile/polls/RoomPollsFragment.kt index 9f7e704135..a20c5cd350 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/polls/RoomPollsFragment.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/polls/RoomPollsFragment.kt @@ -20,6 +20,9 @@ import android.os.Bundle import android.view.LayoutInflater import android.view.View import android.view.ViewGroup +import androidx.lifecycle.DefaultLifecycleObserver +import androidx.lifecycle.LifecycleOwner +import androidx.viewpager2.widget.ViewPager2 import com.airbnb.mvrx.args import com.airbnb.mvrx.fragmentViewModel import com.google.android.material.tabs.TabLayoutMediator @@ -66,9 +69,35 @@ class RoomPollsFragment : VectorBaseFragment() { tabLayoutMediator = TabLayoutMediator(views.roomPollsTabs, views.roomPollsViewPager) { tab, position -> when (position) { - RoomPollsType.ACTIVE.ordinal -> tab.text = getString(R.string.room_polls_active) - RoomPollsType.ENDED.ordinal -> tab.text = getString(R.string.room_polls_ended) + RoomPollsType.ACTIVE.ordinal -> { + tab.text = getString(R.string.room_polls_active) + } + RoomPollsType.ENDED.ordinal -> { + tab.text = getString(R.string.room_polls_ended) + } } }.also { it.attach() } + + val onPageChangeCallback = object : ViewPager2.OnPageChangeCallback() { + override fun onPageSelected(position: Int) { + when (position) { + RoomPollsType.ACTIVE.ordinal -> { + viewModel.handle(RoomPollsAction.OnRoomPollsTypeChange(RoomPollsType.ACTIVE)) + } + RoomPollsType.ENDED.ordinal -> { + viewModel.handle(RoomPollsAction.OnRoomPollsTypeChange(RoomPollsType.ENDED)) + } + } + } + } + + viewLifecycleOwner.lifecycle.addObserver(object : DefaultLifecycleObserver { + override fun onCreate(owner: LifecycleOwner) { + views.roomPollsViewPager.registerOnPageChangeCallback(onPageChangeCallback) + } + override fun onDestroy(owner: LifecycleOwner) { + views.roomPollsViewPager.unregisterOnPageChangeCallback(onPageChangeCallback) + } + }) } } diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/polls/RoomPollsViewEvent.kt b/vector/src/main/java/im/vector/app/features/roomprofile/polls/RoomPollsViewEvent.kt index 58b388186e..df60d86613 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/polls/RoomPollsViewEvent.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/polls/RoomPollsViewEvent.kt @@ -20,5 +20,5 @@ import im.vector.app.core.platform.VectorViewEvents sealed class RoomPollsViewEvent : VectorViewEvents { object LoadingError : RoomPollsViewEvent() - object NavigateToPollDetail : RoomPollsViewEvent() + data class NavigateToPollDetail(val selectedPollId: String) : RoomPollsViewEvent() } diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/polls/RoomPollsViewModel.kt b/vector/src/main/java/im/vector/app/features/roomprofile/polls/RoomPollsViewModel.kt index df891e08e4..6a94f11c14 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/polls/RoomPollsViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/polls/RoomPollsViewModel.kt @@ -90,12 +90,17 @@ class RoomPollsViewModel @AssistedInject constructor( when (action) { RoomPollsAction.LoadMorePolls -> handleLoadMore() is RoomPollsAction.OnPollSelected -> handleOnPollSelected(action) + is RoomPollsAction.OnRoomPollsTypeChange -> handleOnRoomPollsTypeChange(action) } } + private fun handleOnRoomPollsTypeChange(action: RoomPollsAction.OnRoomPollsTypeChange) { + setState { copy(selectedRoomPollsType = action.roomPollsType) } + } + private fun handleOnPollSelected(action: RoomPollsAction.OnPollSelected) { setState { copy(selectedPollId = action.selectedPollId) } - _viewEvents.post(RoomPollsViewEvent.NavigateToPollDetail) + _viewEvents.post(RoomPollsViewEvent.NavigateToPollDetail(action.selectedPollId)) } private fun handleLoadMore() = withState { viewState -> diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/polls/RoomPollsViewState.kt b/vector/src/main/java/im/vector/app/features/roomprofile/polls/RoomPollsViewState.kt index dca016e529..6be16d14b9 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/polls/RoomPollsViewState.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/polls/RoomPollsViewState.kt @@ -28,6 +28,7 @@ data class RoomPollsViewState( val nbSyncedDays: Int = 0, val isSyncing: Boolean = false, val selectedPollId: String? = null, + val selectedRoomPollsType: RoomPollsType = RoomPollsType.ACTIVE, ) : MavericksState { constructor(roomProfileArgs: RoomProfileArgs) : this(roomId = roomProfileArgs.roomId) diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/RoomPollDetailFragment.kt b/vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/RoomPollDetailFragment.kt index 67e6307860..684ba45892 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/RoomPollDetailFragment.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/RoomPollDetailFragment.kt @@ -20,11 +20,24 @@ import android.os.Bundle import android.view.LayoutInflater import android.view.View import android.view.ViewGroup +import com.airbnb.mvrx.activityViewModel +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.platform.VectorBaseFragment import im.vector.app.databinding.FragmentRoomPollDetailBinding +import im.vector.app.features.roomprofile.RoomProfileArgs +import im.vector.app.features.roomprofile.polls.RoomPollsType +import im.vector.app.features.roomprofile.polls.RoomPollsViewModel +@AndroidEntryPoint class RoomPollDetailFragment : VectorBaseFragment() { + private val viewModel: RoomPollsViewModel by activityViewModel() + private val roomProfileArgs: RoomProfileArgs by args() + override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentRoomPollDetailBinding { return FragmentRoomPollDetailBinding.inflate(inflater, container, false) } @@ -32,7 +45,21 @@ class RoomPollDetailFragment : VectorBaseFragment override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) + initToolbar() + } + + private fun initToolbar() = withState(viewModel) { state -> + val title = if (state.selectedRoomPollsType == RoomPollsType.ACTIVE) getString(R.string.room_polls_active) + else getString(R.string.room_polls_ended) + setupToolbar(views.roomPollDetailToolbar) + .setTitle(title) .allowBack() } + + override fun invalidate() = withState(viewModel) { state -> + state.getSelectedPoll()?.let { _ -> + } + Unit + } } diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/polls/list/ui/RoomPollsListFragment.kt b/vector/src/main/java/im/vector/app/features/roomprofile/polls/list/ui/RoomPollsListFragment.kt index 21f55629ec..e4e64d229a 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/polls/list/ui/RoomPollsListFragment.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/polls/list/ui/RoomPollsListFragment.kt @@ -68,7 +68,9 @@ abstract class RoomPollsListFragment : viewModel.observeViewEvents { viewEvent -> when (viewEvent) { RoomPollsViewEvent.LoadingError -> showErrorInSnackbar(RoomPollsLoadingError()) - RoomPollsViewEvent.NavigateToPollDetail -> sharedActionViewModel.post(RoomProfileSharedAction.OpenPollDetails) + is RoomPollsViewEvent.NavigateToPollDetail -> { + sharedActionViewModel.post(RoomProfileSharedAction.OpenPollDetails(viewEvent.selectedPollId)) + } } } } From ec4226b5d3d23740426882a7f2c5b8e6acc4dbfe Mon Sep 17 00:00:00 2001 From: Onuray Sahin Date: Tue, 31 Jan 2023 12:19:30 +0300 Subject: [PATCH 04/33] Render poll detail. --- .../roomprofile/polls/RoomPollsViewState.kt | 1 + .../polls/detail/RoomPollDetailController.kt | 40 +++++++++++++++++++ .../polls/detail/RoomPollDetailFragment.kt | 16 ++++++-- .../res/layout/fragment_room_poll_detail.xml | 1 + 4 files changed, 55 insertions(+), 3 deletions(-) create mode 100644 vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/RoomPollDetailController.kt diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/polls/RoomPollsViewState.kt b/vector/src/main/java/im/vector/app/features/roomprofile/polls/RoomPollsViewState.kt index 6be16d14b9..63638257b0 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/polls/RoomPollsViewState.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/polls/RoomPollsViewState.kt @@ -36,4 +36,5 @@ data class RoomPollsViewState( fun hasNoPolls() = polls.isEmpty() fun hasNoPollsAndCanLoadMore() = !isSyncing && hasNoPolls() && canLoadMore fun getSelectedPoll() = polls.find { it.id == selectedPollId } + fun canVoteSelectedPoll() = selectedRoomPollsType == RoomPollsType.ACTIVE } diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/RoomPollDetailController.kt b/vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/RoomPollDetailController.kt new file mode 100644 index 0000000000..72ce1b2f56 --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/RoomPollDetailController.kt @@ -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.features.roomprofile.polls.detail + +import com.airbnb.epoxy.TypedEpoxyController +import im.vector.app.features.home.room.detail.timeline.item.PollItem_ +import im.vector.app.features.roomprofile.polls.RoomPollsViewState +import im.vector.lib.core.utils.epoxy.charsequence.toEpoxyCharSequence +import javax.inject.Inject + +class RoomPollDetailController @Inject constructor( + +) : TypedEpoxyController() { + + override fun buildModels(viewState: RoomPollsViewState?) { + viewState ?: return + val pollSummary = viewState.getSelectedPoll() ?: return + + PollItem_() + .eventId(pollSummary.id) + .pollQuestion(pollSummary.title.toEpoxyCharSequence()) + .canVote(viewState.canVoteSelectedPoll()) + .optionViewStates(pollSummary.optionViewStates) + .ended(viewState.canVoteSelectedPoll().not()) + } +} diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/RoomPollDetailFragment.kt b/vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/RoomPollDetailFragment.kt index 684ba45892..ec8e8ea004 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/RoomPollDetailFragment.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/RoomPollDetailFragment.kt @@ -26,15 +26,19 @@ 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.configureWith import im.vector.app.core.platform.VectorBaseFragment import im.vector.app.databinding.FragmentRoomPollDetailBinding import im.vector.app.features.roomprofile.RoomProfileArgs import im.vector.app.features.roomprofile.polls.RoomPollsType import im.vector.app.features.roomprofile.polls.RoomPollsViewModel +import javax.inject.Inject @AndroidEntryPoint class RoomPollDetailFragment : VectorBaseFragment() { + @Inject lateinit var roomPollDetailController: RoomPollDetailController + private val viewModel: RoomPollsViewModel by activityViewModel() private val roomProfileArgs: RoomProfileArgs by args() @@ -45,11 +49,14 @@ class RoomPollDetailFragment : VectorBaseFragment override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) - initToolbar() + views.pollDetailRecyclerView.configureWith( + roomPollDetailController, + hasFixedSize = true, + ) } - private fun initToolbar() = withState(viewModel) { state -> - val title = if (state.selectedRoomPollsType == RoomPollsType.ACTIVE) getString(R.string.room_polls_active) + private fun setupToolbar(roomPollsType: RoomPollsType) { + val title = if (roomPollsType == RoomPollsType.ACTIVE) getString(R.string.room_polls_active) else getString(R.string.room_polls_ended) setupToolbar(views.roomPollDetailToolbar) @@ -58,7 +65,10 @@ class RoomPollDetailFragment : VectorBaseFragment } override fun invalidate() = withState(viewModel) { state -> + setupToolbar(state.selectedRoomPollsType) + state.getSelectedPoll()?.let { _ -> + roomPollDetailController.setData(state) } Unit } diff --git a/vector/src/main/res/layout/fragment_room_poll_detail.xml b/vector/src/main/res/layout/fragment_room_poll_detail.xml index 7a7334bf8d..7ed8d1125e 100644 --- a/vector/src/main/res/layout/fragment_room_poll_detail.xml +++ b/vector/src/main/res/layout/fragment_room_poll_detail.xml @@ -22,6 +22,7 @@ Date: Tue, 31 Jan 2023 15:01:07 +0300 Subject: [PATCH 05/33] Implement new view state. --- .../roomprofile/RoomProfileFragment.kt | 1 - .../polls/detail/RoomPollDetail.kt | 29 +++++++++++++++++++ .../polls/detail/RoomPollDetailFragment.kt | 14 ++++++--- .../polls/detail/RoomPollDetailViewState.kt | 29 +++++++++++++++++++ 4 files changed, 68 insertions(+), 5 deletions(-) create mode 100644 vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/RoomPollDetail.kt create mode 100644 vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/RoomPollDetailViewState.kt diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileFragment.kt b/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileFragment.kt index f90dd76d29..9436bafc03 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileFragment.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileFragment.kt @@ -65,7 +65,6 @@ import javax.inject.Inject @Parcelize data class RoomProfileArgs( val roomId: String, - val selectedPollId: String? = null, ) : Parcelable @AndroidEntryPoint diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/RoomPollDetail.kt b/vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/RoomPollDetail.kt new file mode 100644 index 0000000000..c149daaef2 --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/RoomPollDetail.kt @@ -0,0 +1,29 @@ +/* + * 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 + +import im.vector.app.features.home.room.detail.timeline.item.PollOptionViewState + +data class RoomPollDetail( + val eventId: String, + val question: String, + val canVote: Boolean, + val votesStatusSummary: String, + val optionViewStates: List, + val hasBeenEdited: Boolean, + val isEnded: Boolean, +) diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/RoomPollDetailFragment.kt b/vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/RoomPollDetailFragment.kt index ec8e8ea004..96eea5bdd8 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/RoomPollDetailFragment.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/RoomPollDetailFragment.kt @@ -17,10 +17,10 @@ package im.vector.app.features.roomprofile.polls.detail import android.os.Bundle +import android.os.Parcelable import android.view.LayoutInflater import android.view.View import android.view.ViewGroup -import com.airbnb.mvrx.activityViewModel import com.airbnb.mvrx.args import com.airbnb.mvrx.fragmentViewModel import com.airbnb.mvrx.withState @@ -29,18 +29,24 @@ import im.vector.app.R import im.vector.app.core.extensions.configureWith import im.vector.app.core.platform.VectorBaseFragment import im.vector.app.databinding.FragmentRoomPollDetailBinding -import im.vector.app.features.roomprofile.RoomProfileArgs import im.vector.app.features.roomprofile.polls.RoomPollsType import im.vector.app.features.roomprofile.polls.RoomPollsViewModel +import kotlinx.parcelize.Parcelize import javax.inject.Inject +@Parcelize +data class RoomPollDetailArgs( + val roomId: String, + val pollId: String, +) : Parcelable + @AndroidEntryPoint class RoomPollDetailFragment : VectorBaseFragment() { @Inject lateinit var roomPollDetailController: RoomPollDetailController - private val viewModel: RoomPollsViewModel by activityViewModel() - private val roomProfileArgs: RoomProfileArgs by args() + private val viewModel: RoomPollsViewModel by fragmentViewModel() + private val roomPollDetailArgs: RoomPollDetailArgs by args() override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentRoomPollDetailBinding { return FragmentRoomPollDetailBinding.inflate(inflater, container, false) diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/RoomPollDetailViewState.kt b/vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/RoomPollDetailViewState.kt new file mode 100644 index 0000000000..d7e31a0424 --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/RoomPollDetailViewState.kt @@ -0,0 +1,29 @@ +/* + * 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 + +import com.airbnb.mvrx.MavericksState + +data class RoomPollDetailViewState( + val roomId: String, + val pollId: String, + val pollDetail: RoomPollDetail? = null, +) : MavericksState { + + constructor(roomPollDetailArgs: RoomPollDetailArgs) + : this(roomId = roomPollDetailArgs.roomId, pollId = roomPollDetailArgs.pollId) +} From 429a71964d64200da7cf0b212097b8c438e179b5 Mon Sep 17 00:00:00 2001 From: Onuray Sahin Date: Tue, 31 Jan 2023 15:25:37 +0300 Subject: [PATCH 06/33] Create separate view model for poll detail. --- .../polls/detail/RoomPollDetailAction.kt | 23 +++++++++++++ .../polls/detail/RoomPollDetailController.kt | 7 ++-- .../polls/detail/RoomPollDetailFragment.kt | 14 +++++--- .../polls/detail/RoomPollDetailViewEvent.kt | 23 +++++++++++++ .../polls/detail/RoomPollDetailViewModel.kt | 34 +++++++++++++++++++ 5 files changed, 93 insertions(+), 8 deletions(-) create mode 100644 vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/RoomPollDetailAction.kt create mode 100644 vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/RoomPollDetailViewEvent.kt create mode 100644 vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/RoomPollDetailViewModel.kt diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/RoomPollDetailAction.kt b/vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/RoomPollDetailAction.kt new file mode 100644 index 0000000000..58a059b7a5 --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/RoomPollDetailAction.kt @@ -0,0 +1,23 @@ +/* + * 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 + +import im.vector.app.core.platform.VectorViewModelAction + +sealed interface RoomPollDetailAction : VectorViewModelAction { + +} diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/RoomPollDetailController.kt b/vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/RoomPollDetailController.kt index 72ce1b2f56..647c036513 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/RoomPollDetailController.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/RoomPollDetailController.kt @@ -24,17 +24,18 @@ import javax.inject.Inject class RoomPollDetailController @Inject constructor( -) : TypedEpoxyController() { +) : TypedEpoxyController() { - override fun buildModels(viewState: RoomPollsViewState?) { + override fun buildModels(viewState: RoomPollDetailViewState?) { viewState ?: return - val pollSummary = viewState.getSelectedPoll() ?: return PollItem_() + /* .eventId(pollSummary.id) .pollQuestion(pollSummary.title.toEpoxyCharSequence()) .canVote(viewState.canVoteSelectedPoll()) .optionViewStates(pollSummary.optionViewStates) .ended(viewState.canVoteSelectedPoll().not()) + */ } } diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/RoomPollDetailFragment.kt b/vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/RoomPollDetailFragment.kt index 96eea5bdd8..cfab132947 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/RoomPollDetailFragment.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/RoomPollDetailFragment.kt @@ -45,7 +45,7 @@ class RoomPollDetailFragment : VectorBaseFragment @Inject lateinit var roomPollDetailController: RoomPollDetailController - private val viewModel: RoomPollsViewModel by fragmentViewModel() + private val viewModel: RoomPollDetailViewModel by fragmentViewModel() private val roomPollDetailArgs: RoomPollDetailArgs by args() override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentRoomPollDetailBinding { @@ -61,9 +61,9 @@ class RoomPollDetailFragment : VectorBaseFragment ) } - private fun setupToolbar(roomPollsType: RoomPollsType) { - val title = if (roomPollsType == RoomPollsType.ACTIVE) getString(R.string.room_polls_active) - else getString(R.string.room_polls_ended) + private fun setupToolbar(isEnded: Boolean) { + val title = if (isEnded) getString(R.string.room_polls_ended) + else getString(R.string.room_polls_active) setupToolbar(views.roomPollDetailToolbar) .setTitle(title) @@ -71,11 +71,15 @@ class RoomPollDetailFragment : VectorBaseFragment } override fun invalidate() = withState(viewModel) { state -> - setupToolbar(state.selectedRoomPollsType) + state.pollDetail ?: return@withState + setupToolbar(state.pollDetail.isEnded) + + /* state.getSelectedPoll()?.let { _ -> roomPollDetailController.setData(state) } Unit + */ } } diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/RoomPollDetailViewEvent.kt b/vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/RoomPollDetailViewEvent.kt new file mode 100644 index 0000000000..1ee405e0c3 --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/RoomPollDetailViewEvent.kt @@ -0,0 +1,23 @@ +/* + * 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 + +import im.vector.app.core.platform.VectorViewEvents + +sealed class RoomPollDetailViewEvent : VectorViewEvents { + +} diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/RoomPollDetailViewModel.kt b/vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/RoomPollDetailViewModel.kt new file mode 100644 index 0000000000..5d65db78b4 --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/RoomPollDetailViewModel.kt @@ -0,0 +1,34 @@ +/* + * 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 + +import dagger.assisted.Assisted +import dagger.assisted.AssistedInject +import im.vector.app.core.platform.VectorViewModel + +class RoomPollDetailViewModel @AssistedInject constructor( + @Assisted initialState: RoomPollDetailViewState, +) : VectorViewModel(initialState) { + + init { + // Subscribe to the poll event and map it + } + + override fun handle(action: RoomPollDetailAction) { + + } +} From 8aa89f1dfd03dfb1927900ddcc6d003a3e91fd57 Mon Sep 17 00:00:00 2001 From: Maxime NATUREL <46314705+mnaturel@users.noreply.github.com> Date: Wed, 1 Feb 2023 11:45:10 +0100 Subject: [PATCH 07/33] Adding changelog entry --- changelog.d/8056.wip | 1 + 1 file changed, 1 insertion(+) create mode 100644 changelog.d/8056.wip diff --git a/changelog.d/8056.wip b/changelog.d/8056.wip new file mode 100644 index 0000000000..5dca2d001d --- /dev/null +++ b/changelog.d/8056.wip @@ -0,0 +1 @@ +[Poll] History list: details screen of a poll From fb445628249d165e73f5e4e60339723c69ae88b0 Mon Sep 17 00:00:00 2001 From: Maxime NATUREL <46314705+mnaturel@users.noreply.github.com> Date: Wed, 1 Feb 2023 14:53:05 +0100 Subject: [PATCH 08/33] Using navigator and new activity for the new screen --- vector/src/main/AndroidManifest.xml | 1 + .../roomprofile/RoomProfileActivity.kt | 10 ---- .../roomprofile/RoomProfileSharedAction.kt | 1 - .../roomprofile/polls/RoomPollsAction.kt | 2 - .../roomprofile/polls/RoomPollsFragment.kt | 33 +---------- .../roomprofile/polls/RoomPollsViewEvent.kt | 1 - .../roomprofile/polls/RoomPollsViewModel.kt | 11 ---- .../roomprofile/polls/RoomPollsViewState.kt | 4 -- .../polls/detail/RoomPollDetailActivity.kt | 56 +++++++++++++++++++ .../polls/detail/RoomPollDetailController.kt | 5 +- .../polls/detail/RoomPollDetailFragment.kt | 1 - .../polls/detail/RoomPollDetailViewModel.kt | 5 +- .../polls/detail/RoomPollDetailViewState.kt | 8 +-- .../polls/list/ui/RoomPollsListFragment.kt | 12 ++-- .../polls/list/ui/RoomPollsListNavigator.kt | 29 ++++++++++ 15 files changed, 100 insertions(+), 79 deletions(-) create mode 100644 vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/RoomPollDetailActivity.kt create mode 100644 vector/src/main/java/im/vector/app/features/roomprofile/polls/list/ui/RoomPollsListNavigator.kt diff --git a/vector/src/main/AndroidManifest.xml b/vector/src/main/AndroidManifest.xml index 9c8186b2d4..ed9800b4f7 100644 --- a/vector/src/main/AndroidManifest.xml +++ b/vector/src/main/AndroidManifest.xml @@ -327,6 +327,7 @@ + diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileActivity.kt b/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileActivity.kt index 97636acb70..3ee1ed867c 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileActivity.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileActivity.kt @@ -37,7 +37,6 @@ import im.vector.app.features.roomprofile.members.RoomMemberListFragment import im.vector.app.features.roomprofile.notifications.RoomNotificationSettingsFragment import im.vector.app.features.roomprofile.permissions.RoomPermissionsFragment import im.vector.app.features.roomprofile.polls.RoomPollsFragment -import im.vector.app.features.roomprofile.polls.detail.RoomPollDetailFragment import im.vector.app.features.roomprofile.settings.RoomSettingsFragment import im.vector.app.features.roomprofile.uploads.RoomUploadsFragment import im.vector.lib.core.utils.compat.getParcelableCompat @@ -106,7 +105,6 @@ class RoomProfileActivity : RoomProfileSharedAction.OpenRoomUploads -> openRoomUploads() RoomProfileSharedAction.OpenBannedRoomMembers -> openBannedRoomMembers() RoomProfileSharedAction.OpenRoomNotificationSettings -> openRoomNotificationSettings() - is RoomProfileSharedAction.OpenPollDetails -> handleOpenPollDetails(sharedAction) } } .launchIn(lifecycleScope) @@ -132,14 +130,6 @@ class RoomProfileActivity : finish() } - private fun handleOpenPollDetails(sharedAction: RoomProfileSharedAction.OpenPollDetails) { - addFragmentToBackstack( - views.simpleFragmentContainer, - RoomPollDetailFragment::class.java, - roomProfileArgs.copy(selectedPollId = sharedAction.pollId) - ) - } - private fun openRoomPolls() { addFragmentToBackstack(views.simpleFragmentContainer, RoomPollsFragment::class.java, roomProfileArgs) } diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileSharedAction.kt b/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileSharedAction.kt index 7948b0dcd9..b243ceb206 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileSharedAction.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileSharedAction.kt @@ -26,7 +26,6 @@ sealed class RoomProfileSharedAction : VectorSharedAction { object OpenRoomAliasesSettings : RoomProfileSharedAction() object OpenRoomPermissionsSettings : RoomProfileSharedAction() object OpenRoomPolls : RoomProfileSharedAction() - data class OpenPollDetails(val pollId: String) : RoomProfileSharedAction() object OpenRoomUploads : RoomProfileSharedAction() object OpenRoomMembers : RoomProfileSharedAction() object OpenBannedRoomMembers : RoomProfileSharedAction() diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/polls/RoomPollsAction.kt b/vector/src/main/java/im/vector/app/features/roomprofile/polls/RoomPollsAction.kt index 297e0048d7..3fedbfc4a8 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/polls/RoomPollsAction.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/polls/RoomPollsAction.kt @@ -20,6 +20,4 @@ import im.vector.app.core.platform.VectorViewModelAction sealed interface RoomPollsAction : VectorViewModelAction { object LoadMorePolls : RoomPollsAction - data class OnRoomPollsTypeChange(val roomPollsType: RoomPollsType) : RoomPollsAction - data class OnPollSelected(val selectedPollId: String) : RoomPollsAction } diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/polls/RoomPollsFragment.kt b/vector/src/main/java/im/vector/app/features/roomprofile/polls/RoomPollsFragment.kt index a20c5cd350..9f7e704135 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/polls/RoomPollsFragment.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/polls/RoomPollsFragment.kt @@ -20,9 +20,6 @@ import android.os.Bundle import android.view.LayoutInflater import android.view.View import android.view.ViewGroup -import androidx.lifecycle.DefaultLifecycleObserver -import androidx.lifecycle.LifecycleOwner -import androidx.viewpager2.widget.ViewPager2 import com.airbnb.mvrx.args import com.airbnb.mvrx.fragmentViewModel import com.google.android.material.tabs.TabLayoutMediator @@ -69,35 +66,9 @@ class RoomPollsFragment : VectorBaseFragment() { tabLayoutMediator = TabLayoutMediator(views.roomPollsTabs, views.roomPollsViewPager) { tab, position -> when (position) { - RoomPollsType.ACTIVE.ordinal -> { - tab.text = getString(R.string.room_polls_active) - } - RoomPollsType.ENDED.ordinal -> { - tab.text = getString(R.string.room_polls_ended) - } + RoomPollsType.ACTIVE.ordinal -> tab.text = getString(R.string.room_polls_active) + RoomPollsType.ENDED.ordinal -> tab.text = getString(R.string.room_polls_ended) } }.also { it.attach() } - - val onPageChangeCallback = object : ViewPager2.OnPageChangeCallback() { - override fun onPageSelected(position: Int) { - when (position) { - RoomPollsType.ACTIVE.ordinal -> { - viewModel.handle(RoomPollsAction.OnRoomPollsTypeChange(RoomPollsType.ACTIVE)) - } - RoomPollsType.ENDED.ordinal -> { - viewModel.handle(RoomPollsAction.OnRoomPollsTypeChange(RoomPollsType.ENDED)) - } - } - } - } - - viewLifecycleOwner.lifecycle.addObserver(object : DefaultLifecycleObserver { - override fun onCreate(owner: LifecycleOwner) { - views.roomPollsViewPager.registerOnPageChangeCallback(onPageChangeCallback) - } - override fun onDestroy(owner: LifecycleOwner) { - views.roomPollsViewPager.unregisterOnPageChangeCallback(onPageChangeCallback) - } - }) } } diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/polls/RoomPollsViewEvent.kt b/vector/src/main/java/im/vector/app/features/roomprofile/polls/RoomPollsViewEvent.kt index df60d86613..cb2069d824 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/polls/RoomPollsViewEvent.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/polls/RoomPollsViewEvent.kt @@ -20,5 +20,4 @@ import im.vector.app.core.platform.VectorViewEvents sealed class RoomPollsViewEvent : VectorViewEvents { object LoadingError : RoomPollsViewEvent() - data class NavigateToPollDetail(val selectedPollId: String) : RoomPollsViewEvent() } diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/polls/RoomPollsViewModel.kt b/vector/src/main/java/im/vector/app/features/roomprofile/polls/RoomPollsViewModel.kt index 6a94f11c14..2beda47816 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/polls/RoomPollsViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/polls/RoomPollsViewModel.kt @@ -89,20 +89,9 @@ class RoomPollsViewModel @AssistedInject constructor( override fun handle(action: RoomPollsAction) { when (action) { RoomPollsAction.LoadMorePolls -> handleLoadMore() - is RoomPollsAction.OnPollSelected -> handleOnPollSelected(action) - is RoomPollsAction.OnRoomPollsTypeChange -> handleOnRoomPollsTypeChange(action) } } - private fun handleOnRoomPollsTypeChange(action: RoomPollsAction.OnRoomPollsTypeChange) { - setState { copy(selectedRoomPollsType = action.roomPollsType) } - } - - private fun handleOnPollSelected(action: RoomPollsAction.OnPollSelected) { - setState { copy(selectedPollId = action.selectedPollId) } - _viewEvents.post(RoomPollsViewEvent.NavigateToPollDetail(action.selectedPollId)) - } - private fun handleLoadMore() = withState { viewState -> viewModelScope.launch { setState { copy(isLoadingMore = true) } diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/polls/RoomPollsViewState.kt b/vector/src/main/java/im/vector/app/features/roomprofile/polls/RoomPollsViewState.kt index 63638257b0..4a5c138b6a 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/polls/RoomPollsViewState.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/polls/RoomPollsViewState.kt @@ -27,14 +27,10 @@ data class RoomPollsViewState( val canLoadMore: Boolean = true, val nbSyncedDays: Int = 0, val isSyncing: Boolean = false, - val selectedPollId: String? = null, - val selectedRoomPollsType: RoomPollsType = RoomPollsType.ACTIVE, ) : MavericksState { constructor(roomProfileArgs: RoomProfileArgs) : this(roomId = roomProfileArgs.roomId) fun hasNoPolls() = polls.isEmpty() fun hasNoPollsAndCanLoadMore() = !isSyncing && hasNoPolls() && canLoadMore - fun getSelectedPoll() = polls.find { it.id == selectedPollId } - fun canVoteSelectedPoll() = selectedRoomPollsType == RoomPollsType.ACTIVE } diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/RoomPollDetailActivity.kt b/vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/RoomPollDetailActivity.kt new file mode 100644 index 0000000000..6995340a9e --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/RoomPollDetailActivity.kt @@ -0,0 +1,56 @@ +/* + * Copyright 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 + +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() { + + 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): Intent { + return Intent(context, RoomPollDetailActivity::class.java).apply { + putExtra(Mavericks.KEY_ARG, RoomPollDetailArgs(pollId)) + } + } + } +} diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/RoomPollDetailController.kt b/vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/RoomPollDetailController.kt index 647c036513..cd2ca1c711 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/RoomPollDetailController.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/RoomPollDetailController.kt @@ -22,9 +22,8 @@ import im.vector.app.features.roomprofile.polls.RoomPollsViewState import im.vector.lib.core.utils.epoxy.charsequence.toEpoxyCharSequence import javax.inject.Inject -class RoomPollDetailController @Inject constructor( - -) : TypedEpoxyController() { +class RoomPollDetailController @Inject constructor() + : TypedEpoxyController() { override fun buildModels(viewState: RoomPollDetailViewState?) { viewState ?: return diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/RoomPollDetailFragment.kt b/vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/RoomPollDetailFragment.kt index cfab132947..dd7b40560f 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/RoomPollDetailFragment.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/RoomPollDetailFragment.kt @@ -36,7 +36,6 @@ import javax.inject.Inject @Parcelize data class RoomPollDetailArgs( - val roomId: String, val pollId: String, ) : Parcelable diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/RoomPollDetailViewModel.kt b/vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/RoomPollDetailViewModel.kt index 5d65db78b4..7368d28c8f 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/RoomPollDetailViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/RoomPollDetailViewModel.kt @@ -25,10 +25,11 @@ class RoomPollDetailViewModel @AssistedInject constructor( ) : VectorViewModel(initialState) { init { - // Subscribe to the poll event and map it + // TODO observe poll using TimelineService.getTimelineEventLive + // TODO create a dedicated useCase and mapper } override fun handle(action: RoomPollDetailAction) { - + // TODO handle go to timeline action } } diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/RoomPollDetailViewState.kt b/vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/RoomPollDetailViewState.kt index d7e31a0424..2d7303f555 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/RoomPollDetailViewState.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/RoomPollDetailViewState.kt @@ -19,11 +19,9 @@ package im.vector.app.features.roomprofile.polls.detail import com.airbnb.mvrx.MavericksState data class RoomPollDetailViewState( - val roomId: String, - val pollId: String, - val pollDetail: RoomPollDetail? = null, + val pollId: String, + val pollDetail: RoomPollDetail? = null, ) : MavericksState { - constructor(roomPollDetailArgs: RoomPollDetailArgs) - : this(roomId = roomPollDetailArgs.roomId, pollId = roomPollDetailArgs.pollId) + constructor(roomPollDetailArgs: RoomPollDetailArgs) : this(pollId = roomPollDetailArgs.pollId) } diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/polls/list/ui/RoomPollsListFragment.kt b/vector/src/main/java/im/vector/app/features/roomprofile/polls/list/ui/RoomPollsListFragment.kt index e4e64d229a..1c6ae518da 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/polls/list/ui/RoomPollsListFragment.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/polls/list/ui/RoomPollsListFragment.kt @@ -29,8 +29,6 @@ import im.vector.app.core.extensions.configureWith import im.vector.app.core.platform.VectorBaseFragment import im.vector.app.core.resources.StringProvider import im.vector.app.databinding.FragmentRoomPollsListBinding -import im.vector.app.features.roomprofile.RoomProfileSharedAction -import im.vector.app.features.roomprofile.RoomProfileSharedActionViewModel import im.vector.app.features.roomprofile.polls.RoomPollsAction import im.vector.app.features.roomprofile.polls.RoomPollsLoadingError import im.vector.app.features.roomprofile.polls.RoomPollsType @@ -49,8 +47,10 @@ abstract class RoomPollsListFragment : @Inject lateinit var stringProvider: StringProvider + @Inject + lateinit var viewNavigator: RoomPollsListNavigator + private val viewModel: RoomPollsViewModel by parentFragmentViewModel(RoomPollsViewModel::class) - private lateinit var sharedActionViewModel: RoomProfileSharedActionViewModel override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentRoomPollsListBinding { return FragmentRoomPollsListBinding.inflate(inflater, container, false) @@ -58,7 +58,6 @@ abstract class RoomPollsListFragment : override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) - sharedActionViewModel = activityViewModelProvider[RoomProfileSharedActionViewModel::class.java] observeViewEvents() setupList() setupLoadMoreButton() @@ -68,9 +67,6 @@ abstract class RoomPollsListFragment : viewModel.observeViewEvents { viewEvent -> when (viewEvent) { RoomPollsViewEvent.LoadingError -> showErrorInSnackbar(RoomPollsLoadingError()) - is RoomPollsViewEvent.NavigateToPollDetail -> { - sharedActionViewModel.post(RoomProfileSharedAction.OpenPollDetails(viewEvent.selectedPollId)) - } } } } @@ -132,7 +128,7 @@ abstract class RoomPollsListFragment : } override fun onPollClicked(pollId: String) { - viewModel.handle(RoomPollsAction.OnPollSelected(pollId)) + viewNavigator.goToPollDetails(requireContext(), pollId) } override fun onLoadMoreClicked() { diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/polls/list/ui/RoomPollsListNavigator.kt b/vector/src/main/java/im/vector/app/features/roomprofile/polls/list/ui/RoomPollsListNavigator.kt new file mode 100644 index 0000000000..35440e89a9 --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/roomprofile/polls/list/ui/RoomPollsListNavigator.kt @@ -0,0 +1,29 @@ +/* + * 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.RoomPollDetailActivity +import javax.inject.Inject + +// TODO add unit tests +class RoomPollsListNavigator @Inject constructor() { + + fun goToPollDetails(context: Context, pollId: String) { + context.startActivity(RoomPollDetailActivity.newIntent(context, pollId)) + } +} From 2ce15a1923d36158f53dc5f6e2ad8240a587d699 Mon Sep 17 00:00:00 2001 From: Maxime NATUREL <46314705+mnaturel@users.noreply.github.com> Date: Wed, 1 Feb 2023 14:59:31 +0100 Subject: [PATCH 09/33] Set empty toolbar by default --- .../polls/detail/RoomPollDetailFragment.kt | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/RoomPollDetailFragment.kt b/vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/RoomPollDetailFragment.kt index dd7b40560f..3027f30460 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/RoomPollDetailFragment.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/RoomPollDetailFragment.kt @@ -26,11 +26,10 @@ 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 im.vector.app.features.roomprofile.polls.RoomPollsType -import im.vector.app.features.roomprofile.polls.RoomPollsViewModel import kotlinx.parcelize.Parcelize import javax.inject.Inject @@ -53,6 +52,7 @@ class RoomPollDetailFragment : VectorBaseFragment override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) + setupToolbar() views.pollDetailRecyclerView.configureWith( roomPollDetailController, @@ -60,9 +60,17 @@ class RoomPollDetailFragment : VectorBaseFragment ) } - private fun setupToolbar(isEnded: Boolean) { - val title = if (isEnded) getString(R.string.room_polls_ended) - else getString(R.string.room_polls_active) + override fun onDestroyView() { + views.pollDetailRecyclerView.cleanup() + super.onDestroyView() + } + + private fun setupToolbar(isEnded: Boolean? = null) { + val title = when (isEnded) { + true -> getString(R.string.room_polls_ended) + false -> getString(R.string.room_polls_active) + else -> "" + } setupToolbar(views.roomPollDetailToolbar) .setTitle(title) From 753875ba0c87f313f67f848f2ede3e79c701a2dc Mon Sep 17 00:00:00 2001 From: Maxime NATUREL <46314705+mnaturel@users.noreply.github.com> Date: Wed, 1 Feb 2023 15:04:46 +0100 Subject: [PATCH 10/33] Creating subpackage ui --- vector/src/main/AndroidManifest.xml | 2 +- .../roomprofile/polls/detail/{ => ui}/RoomPollDetail.kt | 2 +- .../roomprofile/polls/detail/{ => ui}/RoomPollDetailAction.kt | 2 +- .../polls/detail/{ => ui}/RoomPollDetailActivity.kt | 4 ++-- .../polls/detail/{ => ui}/RoomPollDetailController.kt | 4 +--- .../polls/detail/{ => ui}/RoomPollDetailFragment.kt | 2 +- .../polls/detail/{ => ui}/RoomPollDetailViewEvent.kt | 2 +- .../polls/detail/{ => ui}/RoomPollDetailViewModel.kt | 2 +- .../polls/detail/{ => ui}/RoomPollDetailViewState.kt | 2 +- .../roomprofile/polls/list/ui/RoomPollsListNavigator.kt | 2 +- 10 files changed, 11 insertions(+), 13 deletions(-) rename vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/{ => ui}/RoomPollDetail.kt (94%) rename vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/{ => ui}/RoomPollDetailAction.kt (92%) rename vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/{ => ui}/RoomPollDetailActivity.kt (95%) rename vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/{ => ui}/RoomPollDetailController.kt (87%) rename vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/{ => ui}/RoomPollDetailFragment.kt (97%) rename vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/{ => ui}/RoomPollDetailViewEvent.kt (92%) rename vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/{ => ui}/RoomPollDetailViewModel.kt (95%) rename vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/{ => ui}/RoomPollDetailViewState.kt (93%) diff --git a/vector/src/main/AndroidManifest.xml b/vector/src/main/AndroidManifest.xml index ed9800b4f7..922d4ca292 100644 --- a/vector/src/main/AndroidManifest.xml +++ b/vector/src/main/AndroidManifest.xml @@ -327,7 +327,7 @@ - + diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/RoomPollDetail.kt b/vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/ui/RoomPollDetail.kt similarity index 94% rename from vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/RoomPollDetail.kt rename to vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/ui/RoomPollDetail.kt index c149daaef2..cbbd09f065 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/RoomPollDetail.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/ui/RoomPollDetail.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package im.vector.app.features.roomprofile.polls.detail +package im.vector.app.features.roomprofile.polls.detail.ui import im.vector.app.features.home.room.detail.timeline.item.PollOptionViewState diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/RoomPollDetailAction.kt b/vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/ui/RoomPollDetailAction.kt similarity index 92% rename from vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/RoomPollDetailAction.kt rename to vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/ui/RoomPollDetailAction.kt index 58a059b7a5..efb20f9144 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/RoomPollDetailAction.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/ui/RoomPollDetailAction.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package im.vector.app.features.roomprofile.polls.detail +package im.vector.app.features.roomprofile.polls.detail.ui import im.vector.app.core.platform.VectorViewModelAction diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/RoomPollDetailActivity.kt b/vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/ui/RoomPollDetailActivity.kt similarity index 95% rename from vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/RoomPollDetailActivity.kt rename to vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/ui/RoomPollDetailActivity.kt index 6995340a9e..90090f7830 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/RoomPollDetailActivity.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/ui/RoomPollDetailActivity.kt @@ -1,5 +1,5 @@ /* - * Copyright 2022 New Vector Ltd + * 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. @@ -14,7 +14,7 @@ * limitations under the License. */ -package im.vector.app.features.roomprofile.polls.detail +package im.vector.app.features.roomprofile.polls.detail.ui import android.content.Context import android.content.Intent diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/RoomPollDetailController.kt b/vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/ui/RoomPollDetailController.kt similarity index 87% rename from vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/RoomPollDetailController.kt rename to vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/ui/RoomPollDetailController.kt index cd2ca1c711..08db09a5b0 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/RoomPollDetailController.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/ui/RoomPollDetailController.kt @@ -14,12 +14,10 @@ * limitations under the License. */ -package im.vector.app.features.roomprofile.polls.detail +package im.vector.app.features.roomprofile.polls.detail.ui import com.airbnb.epoxy.TypedEpoxyController import im.vector.app.features.home.room.detail.timeline.item.PollItem_ -import im.vector.app.features.roomprofile.polls.RoomPollsViewState -import im.vector.lib.core.utils.epoxy.charsequence.toEpoxyCharSequence import javax.inject.Inject class RoomPollDetailController @Inject constructor() diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/RoomPollDetailFragment.kt b/vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/ui/RoomPollDetailFragment.kt similarity index 97% rename from vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/RoomPollDetailFragment.kt rename to vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/ui/RoomPollDetailFragment.kt index 3027f30460..a3ff6e0451 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/RoomPollDetailFragment.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/ui/RoomPollDetailFragment.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package im.vector.app.features.roomprofile.polls.detail +package im.vector.app.features.roomprofile.polls.detail.ui import android.os.Bundle import android.os.Parcelable diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/RoomPollDetailViewEvent.kt b/vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/ui/RoomPollDetailViewEvent.kt similarity index 92% rename from vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/RoomPollDetailViewEvent.kt rename to vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/ui/RoomPollDetailViewEvent.kt index 1ee405e0c3..01fb4698fb 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/RoomPollDetailViewEvent.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/ui/RoomPollDetailViewEvent.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package im.vector.app.features.roomprofile.polls.detail +package im.vector.app.features.roomprofile.polls.detail.ui import im.vector.app.core.platform.VectorViewEvents diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/RoomPollDetailViewModel.kt b/vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/ui/RoomPollDetailViewModel.kt similarity index 95% rename from vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/RoomPollDetailViewModel.kt rename to vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/ui/RoomPollDetailViewModel.kt index 7368d28c8f..a186ca3b3b 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/RoomPollDetailViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/ui/RoomPollDetailViewModel.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package im.vector.app.features.roomprofile.polls.detail +package im.vector.app.features.roomprofile.polls.detail.ui import dagger.assisted.Assisted import dagger.assisted.AssistedInject diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/RoomPollDetailViewState.kt b/vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/ui/RoomPollDetailViewState.kt similarity index 93% rename from vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/RoomPollDetailViewState.kt rename to vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/ui/RoomPollDetailViewState.kt index 2d7303f555..6a68cdeb01 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/RoomPollDetailViewState.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/ui/RoomPollDetailViewState.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package im.vector.app.features.roomprofile.polls.detail +package im.vector.app.features.roomprofile.polls.detail.ui import com.airbnb.mvrx.MavericksState diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/polls/list/ui/RoomPollsListNavigator.kt b/vector/src/main/java/im/vector/app/features/roomprofile/polls/list/ui/RoomPollsListNavigator.kt index 35440e89a9..f04c9bf601 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/polls/list/ui/RoomPollsListNavigator.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/polls/list/ui/RoomPollsListNavigator.kt @@ -17,7 +17,7 @@ package im.vector.app.features.roomprofile.polls.list.ui import android.content.Context -import im.vector.app.features.roomprofile.polls.detail.RoomPollDetailActivity +import im.vector.app.features.roomprofile.polls.detail.ui.RoomPollDetailActivity import javax.inject.Inject // TODO add unit tests From 60d3ae6cc597577413989a2bdfcc0850fbb318c0 Mon Sep 17 00:00:00 2001 From: Maxime NATUREL <46314705+mnaturel@users.noreply.github.com> Date: Wed, 1 Feb 2023 15:47:13 +0100 Subject: [PATCH 11/33] Removing new added fields in PollSummary --- .../app/features/roomprofile/polls/list/ui/PollSummary.kt | 3 --- .../features/roomprofile/polls/list/ui/PollSummaryMapper.kt | 2 -- 2 files changed, 5 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/polls/list/ui/PollSummary.kt b/vector/src/main/java/im/vector/app/features/roomprofile/polls/list/ui/PollSummary.kt index 542ac68ad2..5c1eee0d00 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/polls/list/ui/PollSummary.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/polls/list/ui/PollSummary.kt @@ -22,13 +22,11 @@ sealed interface PollSummary { val id: String val creationTimestamp: Long val title: String - val optionViewStates: List data class ActivePoll( override val id: String, override val creationTimestamp: Long, override val title: String, - override val optionViewStates: List, ) : PollSummary data class EndedPoll( @@ -37,6 +35,5 @@ sealed interface PollSummary { override val title: String, val totalVotes: Int, val winnerOptions: List, - override val optionViewStates: List, ) : PollSummary } diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/polls/list/ui/PollSummaryMapper.kt b/vector/src/main/java/im/vector/app/features/roomprofile/polls/list/ui/PollSummaryMapper.kt index 791f62d414..ea22d7d5a3 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/polls/list/ui/PollSummaryMapper.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/polls/list/ui/PollSummaryMapper.kt @@ -70,14 +70,12 @@ class PollSummaryMapper @Inject constructor( title = pollTitle, totalVotes = pollResponseData.totalVotes, winnerOptions = pollOptionViewStateFactory.createPollEndedOptions(pollCreationInfo, pollResponseData), - optionViewStates = pollOptionViewStateFactory.createPollSendingOptions(pollCreationInfo), ) } else { PollSummary.ActivePoll( id = eventId, creationTimestamp = creationTimestamp, title = pollTitle, - optionViewStates = pollOptionViewStateFactory.createPollSendingOptions(pollCreationInfo), ) } } From afe036dd9de4de1032eb9a006fe621333397db93 Mon Sep 17 00:00:00 2001 From: Maxime NATUREL <46314705+mnaturel@users.noreply.github.com> Date: Wed, 1 Feb 2023 15:47:40 +0100 Subject: [PATCH 12/33] Observe timeline event of the selected poll --- .../app/core/event/GetTimelineEventUseCase.kt | 43 +++++++++++++++++++ .../polls/detail/ui/RoomPollDetailActivity.kt | 8 +++- .../polls/detail/ui/RoomPollDetailFragment.kt | 9 ++-- .../detail/ui/RoomPollDetailViewModel.kt | 14 +++++- .../detail/ui/RoomPollDetailViewState.kt | 6 ++- .../polls/list/ui/RoomPollsListFragment.kt | 8 +++- .../polls/list/ui/RoomPollsListNavigator.kt | 10 ++++- 7 files changed, 83 insertions(+), 15 deletions(-) create mode 100644 vector/src/main/java/im/vector/app/core/event/GetTimelineEventUseCase.kt diff --git a/vector/src/main/java/im/vector/app/core/event/GetTimelineEventUseCase.kt b/vector/src/main/java/im/vector/app/core/event/GetTimelineEventUseCase.kt new file mode 100644 index 0000000000..9745701589 --- /dev/null +++ b/vector/src/main/java/im/vector/app/core/event/GetTimelineEventUseCase.kt @@ -0,0 +1,43 @@ +/* + * 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.room.timeline.TimelineEvent +import org.matrix.android.sdk.flow.unwrap +import javax.inject.Inject + +// TODO add unit tests +class GetTimelineEventUseCase @Inject constructor( + private val activeSessionHolder: ActiveSessionHolder, +) { + + fun execute(roomId: String, eventId: String): Flow { + return activeSessionHolder + .getActiveSession() + .roomService() + .getRoom(roomId) + ?.timelineService() + ?.getTimelineEventLive(eventId) + ?.asFlow() + ?.unwrap() + ?: emptyFlow() + } +} diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/ui/RoomPollDetailActivity.kt b/vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/ui/RoomPollDetailActivity.kt index 90090f7830..ed89526349 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/ui/RoomPollDetailActivity.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/ui/RoomPollDetailActivity.kt @@ -47,9 +47,13 @@ class RoomPollDetailActivity : VectorBaseActivity() { } companion object { - fun newIntent(context: Context, pollId: String): Intent { + fun newIntent(context: Context, pollId: String, roomId: String): Intent { return Intent(context, RoomPollDetailActivity::class.java).apply { - putExtra(Mavericks.KEY_ARG, RoomPollDetailArgs(pollId)) + val args = RoomPollDetailArgs( + pollId = pollId, + roomId = roomId, + ) + putExtra(Mavericks.KEY_ARG, args) } } } 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 a3ff6e0451..1589954b35 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 @@ -36,6 +36,7 @@ import javax.inject.Inject @Parcelize data class RoomPollDetailArgs( val pollId: String, + val roomId: String, ) : Parcelable @AndroidEntryPoint @@ -80,13 +81,9 @@ class RoomPollDetailFragment : VectorBaseFragment override fun invalidate() = withState(viewModel) { state -> state.pollDetail ?: return@withState + // TODO should we update the title when the poll status changes? setupToolbar(state.pollDetail.isEnded) - /* - state.getSelectedPoll()?.let { _ -> - roomPollDetailController.setData(state) - } - Unit - */ + // TODO update data of the controller } } 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 a186ca3b3b..8227a55881 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 @@ -18,15 +18,25 @@ package im.vector.app.features.roomprofile.polls.detail.ui import dagger.assisted.Assisted import dagger.assisted.AssistedInject +import im.vector.app.core.event.GetTimelineEventUseCase import im.vector.app.core.platform.VectorViewModel +import kotlinx.coroutines.flow.launchIn class RoomPollDetailViewModel @AssistedInject constructor( @Assisted initialState: RoomPollDetailViewState, + private val getTimelineEventUseCase: GetTimelineEventUseCase, ) : VectorViewModel(initialState) { init { - // TODO observe poll using TimelineService.getTimelineEventLive - // TODO create a dedicated useCase and mapper + observePollDetails( + pollId = initialState.pollId, + roomId = initialState.roomId, + ) + } + + private fun observePollDetails(pollId: String, roomId: String) { + getTimelineEventUseCase.execute(roomId = roomId, eventId = pollId) + .launchIn(viewModelScope) } override fun handle(action: RoomPollDetailAction) { diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/ui/RoomPollDetailViewState.kt b/vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/ui/RoomPollDetailViewState.kt index 6a68cdeb01..a2906dc88f 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/ui/RoomPollDetailViewState.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/ui/RoomPollDetailViewState.kt @@ -20,8 +20,12 @@ 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) + constructor(roomPollDetailArgs: RoomPollDetailArgs) : this( + pollId = roomPollDetailArgs.pollId, + roomId = roomPollDetailArgs.roomId, + ) } diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/polls/list/ui/RoomPollsListFragment.kt b/vector/src/main/java/im/vector/app/features/roomprofile/polls/list/ui/RoomPollsListFragment.kt index 1c6ae518da..bbf058d908 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/polls/list/ui/RoomPollsListFragment.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/polls/list/ui/RoomPollsListFragment.kt @@ -127,8 +127,12 @@ abstract class RoomPollsListFragment : views.roomPollsLoadMoreWhenEmptyProgress.isVisible = viewState.hasNoPollsAndCanLoadMore() && viewState.isLoadingMore } - override fun onPollClicked(pollId: String) { - viewNavigator.goToPollDetails(requireContext(), pollId) + override fun onPollClicked(pollId: String) = withState(viewModel) { + viewNavigator.goToPollDetails( + context = requireContext(), + pollId = pollId, + roomId = it.roomId, + ) } override fun onLoadMoreClicked() { diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/polls/list/ui/RoomPollsListNavigator.kt b/vector/src/main/java/im/vector/app/features/roomprofile/polls/list/ui/RoomPollsListNavigator.kt index f04c9bf601..3cba2fc355 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/polls/list/ui/RoomPollsListNavigator.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/polls/list/ui/RoomPollsListNavigator.kt @@ -23,7 +23,13 @@ import javax.inject.Inject // TODO add unit tests class RoomPollsListNavigator @Inject constructor() { - fun goToPollDetails(context: Context, pollId: String) { - context.startActivity(RoomPollDetailActivity.newIntent(context, pollId)) + fun goToPollDetails(context: Context, pollId: String, roomId: String) { + context.startActivity( + RoomPollDetailActivity.newIntent( + context = context, + pollId = pollId, + roomId = roomId, + ) + ) } } From d3df58c607b747563996f3db3c06e3a8bc02c5ba Mon Sep 17 00:00:00 2001 From: Maxime NATUREL <46314705+mnaturel@users.noreply.github.com> Date: Wed, 1 Feb 2023 17:44:48 +0100 Subject: [PATCH 13/33] Render the details of the poll --- .../app/core/di/MavericksViewModelModule.kt | 6 ++ .../timeline/factory/MessageItemFactory.kt | 6 +- .../factory/PollItemViewStateFactory.kt | 59 +++++++------ ...{PollViewState.kt => PollItemViewState.kt} | 2 +- .../polls/detail/ui/RoomPollDetail.kt | 9 +- .../polls/detail/ui/RoomPollDetailAction.kt | 23 ----- .../detail/ui/RoomPollDetailController.kt | 31 ++++--- .../polls/detail/ui/RoomPollDetailFragment.kt | 7 +- .../polls/detail/ui/RoomPollDetailItem.kt | 85 +++++++++++++++++++ .../polls/detail/ui/RoomPollDetailMapper.kt | 60 +++++++++++++ .../detail/ui/RoomPollDetailViewModel.kt | 25 +++++- .../detail/ui/RoomPollDetailViewState.kt | 1 + .../res/layout/fragment_room_poll_detail.xml | 3 +- .../factory/PollItemViewStateFactoryTest.kt | 12 +-- 14 files changed, 241 insertions(+), 88 deletions(-) rename vector/src/main/java/im/vector/app/features/poll/{PollViewState.kt => PollItemViewState.kt} (96%) delete mode 100644 vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/ui/RoomPollDetailAction.kt create mode 100644 vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/ui/RoomPollDetailItem.kt create mode 100644 vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/ui/RoomPollDetailMapper.kt diff --git a/vector/src/main/java/im/vector/app/core/di/MavericksViewModelModule.kt b/vector/src/main/java/im/vector/app/core/di/MavericksViewModelModule.kt index 911bbfa4a3..c2e2f9f695 100644 --- a/vector/src/main/java/im/vector/app/core/di/MavericksViewModelModule.kt +++ b/vector/src/main/java/im/vector/app/core/di/MavericksViewModelModule.kt @@ -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.permissions.RoomPermissionsViewModel 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.joinrule.advanced.RoomJoinRuleChooseRestrictedViewModel import im.vector.app.features.roomprofile.uploads.RoomUploadsViewModel @@ -703,4 +704,9 @@ interface MavericksViewModelModule { @IntoMap @MavericksViewModelKey(RoomPollsViewModel::class) fun roomPollsViewModelFactory(factory: RoomPollsViewModel.Factory): MavericksAssistedViewModelFactory<*, *> + + @Binds + @IntoMap + @MavericksViewModelKey(RoomPollDetailViewModel::class) + fun roomPollDetailViewModelFactory(factory: RoomPollDetailViewModel.Factory): MavericksAssistedViewModelFactory<*, *> } diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MessageItemFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MessageItemFactory.kt index 9cb1608415..e988e9818c 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MessageItemFactory.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MessageItemFactory.kt @@ -255,7 +255,11 @@ class MessageItemFactory @Inject constructor( attributes: AbsMessageItem.Attributes, isEnded: Boolean, ): PollItem { - val pollViewState = pollItemViewStateFactory.create(pollContent, informationData) + val pollViewState = pollItemViewStateFactory.create( + pollContent = pollContent, + pollResponseData = informationData.pollResponseAggregatedSummary, + isSent = informationData.sendState.isSent(), + ) return PollItem_() .attributes(attributes) diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/PollItemViewStateFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/PollItemViewStateFactory.kt index 3c1a1cfd85..63e41d7ad2 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/PollItemViewStateFactory.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/PollItemViewStateFactory.kt @@ -18,9 +18,8 @@ package im.vector.app.features.home.room.detail.timeline.factory import im.vector.app.R 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.poll.PollViewState +import im.vector.app.features.poll.PollItemViewState 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.PollCreationInfo @@ -33,27 +32,27 @@ class PollItemViewStateFactory @Inject constructor( fun create( pollContent: MessagePollContent, - informationData: MessageInformationData, - ): PollViewState { + pollResponseData: PollResponseData?, + isSent: Boolean, + ): PollItemViewState { val pollCreationInfo = pollContent.getBestPollCreationInfo() val question = pollCreationInfo?.question?.getBestQuestion().orEmpty() - val pollResponseSummary = informationData.pollResponseAggregatedSummary - val totalVotes = pollResponseSummary?.totalVotes ?: 0 + val totalVotes = pollResponseData?.totalVotes ?: 0 return when { - !informationData.sendState.isSent() -> { + !isSent -> { createSendingPollViewState(question, pollCreationInfo) } - informationData.pollResponseAggregatedSummary?.isClosed.orFalse() -> { - createEndedPollViewState(question, pollCreationInfo, pollResponseSummary, totalVotes) + pollResponseData?.isClosed.orFalse() -> { + createEndedPollViewState(question, pollCreationInfo, pollResponseData, totalVotes) } pollContent.getBestPollCreationInfo()?.isUndisclosed().orFalse() -> { - createUndisclosedPollViewState(question, pollCreationInfo, pollResponseSummary) + createUndisclosedPollViewState(question, pollCreationInfo, pollResponseData) } - informationData.pollResponseAggregatedSummary?.myVote?.isNotEmpty().orFalse() -> { - createVotedPollViewState(question, pollCreationInfo, pollResponseSummary, totalVotes) + pollResponseData?.myVote?.isNotEmpty().orFalse() -> { + createVotedPollViewState(question, pollCreationInfo, pollResponseData, totalVotes) } else -> { createReadyPollViewState(question, pollCreationInfo, totalVotes) @@ -61,8 +60,8 @@ class PollItemViewStateFactory @Inject constructor( } } - private fun createSendingPollViewState(question: String, pollCreationInfo: PollCreationInfo?): PollViewState { - return PollViewState( + private fun createSendingPollViewState(question: String, pollCreationInfo: PollCreationInfo?): PollItemViewState { + return PollItemViewState( question = question, votesStatus = stringProvider.getString(R.string.poll_no_votes_cast), canVote = false, @@ -73,51 +72,51 @@ class PollItemViewStateFactory @Inject constructor( private fun createEndedPollViewState( question: String, pollCreationInfo: PollCreationInfo?, - pollResponseSummary: PollResponseData?, + pollResponseData: PollResponseData?, totalVotes: Int, - ): PollViewState { - val totalVotesText = if (pollResponseSummary?.hasEncryptedRelatedEvents.orFalse()) { + ): PollItemViewState { + val totalVotesText = if (pollResponseData?.hasEncryptedRelatedEvents.orFalse()) { stringProvider.getString(R.string.unable_to_decrypt_some_events_in_poll) } else { stringProvider.getQuantityString(R.plurals.poll_total_vote_count_after_ended, totalVotes, totalVotes) } - return PollViewState( + return PollItemViewState( question = question, votesStatus = totalVotesText, canVote = false, - optionViewStates = pollOptionViewStateFactory.createPollEndedOptions(pollCreationInfo, pollResponseSummary), + optionViewStates = pollOptionViewStateFactory.createPollEndedOptions(pollCreationInfo, pollResponseData), ) } private fun createUndisclosedPollViewState( question: String, pollCreationInfo: PollCreationInfo?, - pollResponseSummary: PollResponseData? - ): PollViewState { - return PollViewState( + pollResponseData: PollResponseData? + ): PollItemViewState { + return PollItemViewState( question = question, votesStatus = stringProvider.getString(R.string.poll_undisclosed_not_ended), canVote = true, - optionViewStates = pollOptionViewStateFactory.createPollUndisclosedOptions(pollCreationInfo, pollResponseSummary), + optionViewStates = pollOptionViewStateFactory.createPollUndisclosedOptions(pollCreationInfo, pollResponseData), ) } private fun createVotedPollViewState( question: String, pollCreationInfo: PollCreationInfo?, - pollResponseSummary: PollResponseData?, + pollResponseData: PollResponseData?, totalVotes: Int - ): PollViewState { - val totalVotesText = if (pollResponseSummary?.hasEncryptedRelatedEvents.orFalse()) { + ): PollItemViewState { + val totalVotesText = if (pollResponseData?.hasEncryptedRelatedEvents.orFalse()) { stringProvider.getString(R.string.unable_to_decrypt_some_events_in_poll) } else { stringProvider.getQuantityString(R.plurals.poll_total_vote_count_before_ended_and_voted, totalVotes, totalVotes) } - return PollViewState( + return PollItemViewState( question = question, votesStatus = totalVotesText, canVote = true, - optionViewStates = pollOptionViewStateFactory.createPollVotedOptions(pollCreationInfo, pollResponseSummary), + optionViewStates = pollOptionViewStateFactory.createPollVotedOptions(pollCreationInfo, pollResponseData), ) } @@ -125,13 +124,13 @@ class PollItemViewStateFactory @Inject constructor( question: String, pollCreationInfo: PollCreationInfo?, totalVotes: Int - ): PollViewState { + ): PollItemViewState { val totalVotesText = if (totalVotes == 0) { stringProvider.getString(R.string.poll_no_votes_cast) } else { stringProvider.getQuantityString(R.plurals.poll_total_vote_count_before_ended_and_not_voted, totalVotes, totalVotes) } - return PollViewState( + return PollItemViewState( question = question, votesStatus = totalVotesText, canVote = true, diff --git a/vector/src/main/java/im/vector/app/features/poll/PollViewState.kt b/vector/src/main/java/im/vector/app/features/poll/PollItemViewState.kt similarity index 96% rename from vector/src/main/java/im/vector/app/features/poll/PollViewState.kt rename to vector/src/main/java/im/vector/app/features/poll/PollItemViewState.kt index ecbee7438a..e5b4f71f1d 100644 --- a/vector/src/main/java/im/vector/app/features/poll/PollViewState.kt +++ b/vector/src/main/java/im/vector/app/features/poll/PollItemViewState.kt @@ -18,7 +18,7 @@ package im.vector.app.features.poll import im.vector.app.features.home.room.detail.timeline.item.PollOptionViewState -data class PollViewState( +data class PollItemViewState( val question: String, val votesStatus: String, val canVote: Boolean, diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/ui/RoomPollDetail.kt b/vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/ui/RoomPollDetail.kt index cbbd09f065..3389a5f473 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/ui/RoomPollDetail.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/ui/RoomPollDetail.kt @@ -16,14 +16,9 @@ package im.vector.app.features.roomprofile.polls.detail.ui -import im.vector.app.features.home.room.detail.timeline.item.PollOptionViewState +import im.vector.app.features.poll.PollItemViewState data class RoomPollDetail( - val eventId: String, - val question: String, - val canVote: Boolean, - val votesStatusSummary: String, - val optionViewStates: List, - val hasBeenEdited: Boolean, val isEnded: Boolean, + val pollItemViewState: PollItemViewState, ) 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 deleted file mode 100644 index efb20f9144..0000000000 --- a/vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/ui/RoomPollDetailAction.kt +++ /dev/null @@ -1,23 +0,0 @@ -/* - * 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.platform.VectorViewModelAction - -sealed interface RoomPollDetailAction : VectorViewModelAction { - -} 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 08db09a5b0..a4318151be 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 @@ -17,22 +17,29 @@ package im.vector.app.features.roomprofile.polls.detail.ui import com.airbnb.epoxy.TypedEpoxyController -import im.vector.app.features.home.room.detail.timeline.item.PollItem_ import javax.inject.Inject -class RoomPollDetailController @Inject constructor() - : TypedEpoxyController() { +class RoomPollDetailController @Inject constructor() : TypedEpoxyController() { + + interface Callback { + fun vote(pollId: String, optionId: String) + } + + var callback: Callback? = null override fun buildModels(viewState: RoomPollDetailViewState?) { - viewState ?: return + val pollDetail = viewState?.pollDetail ?: return + val pollItemViewState = pollDetail.pollItemViewState + val host = this - PollItem_() - /* - .eventId(pollSummary.id) - .pollQuestion(pollSummary.title.toEpoxyCharSequence()) - .canVote(viewState.canVoteSelectedPoll()) - .optionViewStates(pollSummary.optionViewStates) - .ended(viewState.canVoteSelectedPoll().not()) - */ + roomPollDetailItem { + id(viewState.pollId) + eventId(viewState.pollId) + question(pollItemViewState.question) + canVote(pollItemViewState.canVote) + votesStatus(pollItemViewState.votesStatus) + optionViewStates(pollItemViewState.optionViewStates.orEmpty()) + callback(host.callback) + } } } 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 1589954b35..7d27963023 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 @@ -59,6 +59,7 @@ class RoomPollDetailFragment : VectorBaseFragment roomPollDetailController, hasFixedSize = true, ) + // TODO setup callback in controller for vote action } override fun onDestroyView() { @@ -75,15 +76,13 @@ class RoomPollDetailFragment : VectorBaseFragment setupToolbar(views.roomPollDetailToolbar) .setTitle(title) - .allowBack() + .allowBack(useCross = true) } override fun invalidate() = withState(viewModel) { state -> state.pollDetail ?: return@withState - // TODO should we update the title when the poll status changes? setupToolbar(state.pollDetail.isEnded) - - // TODO update data of the controller + roomPollDetailController.setData(state) } } 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 new file mode 100644 index 0000000000..9a80c32640 --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/ui/RoomPollDetailItem.kt @@ -0,0 +1,85 @@ +/* + * 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(R.layout.item_timeline_event_poll) { + + @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 + + @EpoxyAttribute + var ended: Boolean = false + + override fun bind(holder: Holder) { + super.bind(holder) + + 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(pollId = relatedEventId, optionId = optionViewState.optionId) + } + } + + class Holder : VectorEpoxyHolder() { + val questionTextView by bind(R.id.questionTextView) + val optionsContainer by bind(R.id.optionsContainer) + val votesStatusTextView by bind(R.id.optionsVotesStatusTextView) + val endedPollTextView by bind(R.id.endedPollTextView) + } +} diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/ui/RoomPollDetailMapper.kt b/vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/ui/RoomPollDetailMapper.kt new file mode 100644 index 0000000000..931b38f243 --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/ui/RoomPollDetailMapper.kt @@ -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 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 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 + +// TODO add unit tests +class RoomPollDetailMapper @Inject constructor( + private val pollResponseDataFactory: PollResponseDataFactory, + private val pollItemViewStateFactory: PollItemViewStateFactory, +) { + + fun map(timelineEvent: TimelineEvent): RoomPollDetail? { + val eventId = timelineEvent.root.eventId.orEmpty() + val result = runCatching { + val content = timelineEvent.getVectorLastMessageContent() + val pollResponseData = pollResponseDataFactory.create(timelineEvent) + return if (eventId.isNotEmpty() && content is MessagePollContent) { + // we assume poll message has been sent here + val pollItemViewState = pollItemViewStateFactory.create( + pollContent = content, + pollResponseData = pollResponseData, + isSent = true, + ) + RoomPollDetail( + isEnded = pollResponseData?.isClosed == true, + pollItemViewState = pollItemViewState, + ) + } 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() + } +} 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 8227a55881..e3b7631cce 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 @@ -16,16 +16,33 @@ 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.EmptyAction import im.vector.app.core.platform.VectorViewModel +import im.vector.app.features.auth.ReAuthState +import im.vector.app.features.auth.ReAuthViewModel 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, -) : VectorViewModel(initialState) { + private val roomPollDetailMapper: RoomPollDetailMapper, +) : VectorViewModel(initialState) { + + @AssistedFactory + interface Factory : MavericksAssistedViewModelFactory { + override fun create(initialState: RoomPollDetailViewState): RoomPollDetailViewModel + } + + companion object : MavericksViewModelFactory by hiltMavericksViewModelFactory() init { observePollDetails( @@ -36,10 +53,12 @@ class RoomPollDetailViewModel @AssistedInject constructor( 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) { - // TODO handle go to timeline action + override fun handle(action: EmptyAction) { + // do nothing for now } } diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/ui/RoomPollDetailViewState.kt b/vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/ui/RoomPollDetailViewState.kt index a2906dc88f..a02db41de6 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/ui/RoomPollDetailViewState.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/ui/RoomPollDetailViewState.kt @@ -17,6 +17,7 @@ package im.vector.app.features.roomprofile.polls.detail.ui import com.airbnb.mvrx.MavericksState +import im.vector.app.features.poll.PollItemViewState data class RoomPollDetailViewState( val pollId: String, diff --git a/vector/src/main/res/layout/fragment_room_poll_detail.xml b/vector/src/main/res/layout/fragment_room_poll_detail.xml index 7ed8d1125e..4f65b2aa0f 100644 --- a/vector/src/main/res/layout/fragment_room_poll_detail.xml +++ b/vector/src/main/res/layout/fragment_room_poll_detail.xml @@ -24,12 +24,13 @@ diff --git a/vector/src/test/java/im/vector/app/features/home/room/detail/timeline/factory/PollItemViewStateFactoryTest.kt b/vector/src/test/java/im/vector/app/features/home/room/detail/timeline/factory/PollItemViewStateFactoryTest.kt index 512f7c8a17..bafe55f7a1 100644 --- a/vector/src/test/java/im/vector/app/features/home/room/detail/timeline/factory/PollItemViewStateFactoryTest.kt +++ b/vector/src/test/java/im/vector/app/features/home/room/detail/timeline/factory/PollItemViewStateFactoryTest.kt @@ -19,7 +19,7 @@ package im.vector.app.features.home.room.detail.timeline.factory 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.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.fixtures.PollFixture.A_MESSAGE_INFORMATION_DATA import im.vector.app.test.fixtures.PollFixture.A_POLL_CONTENT @@ -57,7 +57,7 @@ class PollItemViewStateFactoryTest { ) // Then - pollViewState shouldBeEqualTo PollViewState( + pollViewState shouldBeEqualTo PollItemViewState( question = A_POLL_CONTENT.getBestPollCreationInfo()?.question?.getBestQuestion() ?: "", votesStatus = fakeStringProvider.instance.getString(R.string.poll_no_votes_cast), canVote = false, @@ -90,7 +90,7 @@ class PollItemViewStateFactoryTest { ) // Then - pollViewState shouldBeEqualTo PollViewState( + pollViewState shouldBeEqualTo PollItemViewState( question = A_POLL_CONTENT.getBestPollCreationInfo()?.question?.getBestQuestion() ?: "", votesStatus = fakeStringProvider.instance.getQuantityString(R.plurals.poll_total_vote_count_after_ended, 0, 0), canVote = false, @@ -155,7 +155,7 @@ class PollItemViewStateFactoryTest { ) // Then - pollViewState shouldBeEqualTo PollViewState( + pollViewState shouldBeEqualTo PollItemViewState( question = A_POLL_CONTENT.getBestPollCreationInfo()?.question?.getBestQuestion() ?: "", votesStatus = fakeStringProvider.instance.getString(R.string.poll_undisclosed_not_ended), canVote = true, @@ -204,7 +204,7 @@ class PollItemViewStateFactoryTest { ) // Then - pollViewState shouldBeEqualTo PollViewState( + pollViewState shouldBeEqualTo PollItemViewState( question = A_POLL_CONTENT.getBestPollCreationInfo()?.question?.getBestQuestion() ?: "", votesStatus = fakeStringProvider.instance.getQuantityString(R.plurals.poll_total_vote_count_before_ended_and_voted, 1, 1), canVote = true, @@ -286,7 +286,7 @@ class PollItemViewStateFactoryTest { ) // Then - pollViewState shouldBeEqualTo PollViewState( + pollViewState shouldBeEqualTo PollItemViewState( question = A_POLL_CONTENT.getBestPollCreationInfo()?.question?.getBestQuestion() ?: "", votesStatus = fakeStringProvider.instance.getString(R.string.poll_no_votes_cast), canVote = true, From 922b8092aca60f224e07c37d760bb8a43cf56548 Mon Sep 17 00:00:00 2001 From: Maxime NATUREL <46314705+mnaturel@users.noreply.github.com> Date: Wed, 1 Feb 2023 18:03:02 +0100 Subject: [PATCH 14/33] Render only winner options in past poll list --- .../roomprofile/polls/list/ui/PollSummaryMapper.kt | 4 +++- .../polls/list/ui/PollSummaryMapperTest.kt | 14 +++++++++++--- 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/polls/list/ui/PollSummaryMapper.kt b/vector/src/main/java/im/vector/app/features/roomprofile/polls/list/ui/PollSummaryMapper.kt index ea22d7d5a3..748dfd0886 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/polls/list/ui/PollSummaryMapper.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/polls/list/ui/PollSummaryMapper.kt @@ -69,7 +69,9 @@ class PollSummaryMapper @Inject constructor( creationTimestamp = creationTimestamp, title = pollTitle, totalVotes = pollResponseData.totalVotes, - winnerOptions = pollOptionViewStateFactory.createPollEndedOptions(pollCreationInfo, pollResponseData), + winnerOptions = pollOptionViewStateFactory + .createPollEndedOptions(pollCreationInfo, pollResponseData) + .filter { it.isWinner }, ) } else { PollSummary.ActivePoll( diff --git a/vector/src/test/java/im/vector/app/features/roomprofile/polls/list/ui/PollSummaryMapperTest.kt b/vector/src/test/java/im/vector/app/features/roomprofile/polls/list/ui/PollSummaryMapperTest.kt index b523365970..2459e3c8da 100644 --- a/vector/src/test/java/im/vector/app/features/roomprofile/polls/list/ui/PollSummaryMapperTest.kt +++ b/vector/src/test/java/im/vector/app/features/roomprofile/polls/list/ui/PollSummaryMapperTest.kt @@ -84,10 +84,12 @@ internal class PollSummaryMapperTest { } @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 val totalVotes = 10 - val winnerOptions = listOf() + val option1 = givenAPollEndedOption(isWinner = false) + val option2 = givenAPollEndedOption(isWinner = true) + val winnerOptions = listOf(option1, option2) val endedPollEvent = givenAPollTimelineEvent( eventId = AN_EVENT_ID, creationTimestamp = AN_EVENT_TIMESTAMP, @@ -101,7 +103,7 @@ internal class PollSummaryMapperTest { creationTimestamp = AN_EVENT_TIMESTAMP, title = A_POLL_TITLE, totalVotes = totalVotes, - winnerOptions = winnerOptions, + winnerOptions = listOf(option2), ) // When @@ -198,4 +200,10 @@ internal class PollSummaryMapperTest { totalVotes = totalVotes, ) } + + private fun givenAPollEndedOption(isWinner: Boolean): PollOptionViewState.PollEnded { + return mockk().also { + every { it.isWinner } returns isWinner + } + } } From f855a3602209f0225c7fac382416ab0109bb3d26 Mon Sep 17 00:00:00 2001 From: Maxime NATUREL <46314705+mnaturel@users.noreply.github.com> Date: Thu, 2 Feb 2023 09:48:56 +0100 Subject: [PATCH 15/33] Adding vote action from details screen --- .../home/room/detail/TimelineViewModel.kt | 17 +++---- .../room/detail/poll/VoteToPollUseCase.kt | 51 +++++++++++++++++++ .../polls/detail/ui/RoomPollDetailAction.kt | 23 +++++++++ .../detail/ui/RoomPollDetailController.kt | 2 +- .../polls/detail/ui/RoomPollDetailFragment.kt | 25 ++++++--- .../polls/detail/ui/RoomPollDetailItem.kt | 2 +- .../detail/ui/RoomPollDetailViewModel.kt | 22 +++++--- 7 files changed, 117 insertions(+), 25 deletions(-) create mode 100644 vector/src/main/java/im/vector/app/features/home/room/detail/poll/VoteToPollUseCase.kt create mode 100644 vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/ui/RoomPollDetailAction.kt 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, + ) } } From eaa9cc740ea277bf89694f932f06ac239fd25985 Mon Sep 17 00:00:00 2001 From: Maxime NATUREL <46314705+mnaturel@users.noreply.github.com> Date: Thu, 2 Feb 2023 11:16:24 +0100 Subject: [PATCH 16/33] Make the title set at the creation of the screen --- .../polls/detail/ui/RoomPollDetailActivity.kt | 3 ++- .../polls/detail/ui/RoomPollDetailFragment.kt | 9 +++------ .../roomprofile/polls/list/ui/RoomPollsListFragment.kt | 1 + .../roomprofile/polls/list/ui/RoomPollsListNavigator.kt | 3 ++- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/ui/RoomPollDetailActivity.kt b/vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/ui/RoomPollDetailActivity.kt index ed89526349..cf29d5618a 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/ui/RoomPollDetailActivity.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/ui/RoomPollDetailActivity.kt @@ -47,11 +47,12 @@ class RoomPollDetailActivity : VectorBaseActivity() { } companion object { - fun newIntent(context: Context, pollId: String, roomId: String): Intent { + 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) } 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 8340c677ef..4e2c7461f7 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 @@ -37,6 +37,7 @@ import javax.inject.Inject data class RoomPollDetailArgs( val pollId: String, val roomId: String, + val isEnded: Boolean, ) : Parcelable @AndroidEntryPoint @@ -55,7 +56,7 @@ class RoomPollDetailFragment : override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) - setupToolbar() + setupToolbar(isEnded = roomPollDetailArgs.isEnded) setupDetailView() // TODO add link to go to timeline message + create a ViewNavigator } @@ -74,11 +75,10 @@ class RoomPollDetailFragment : ) } - private fun setupToolbar(isEnded: Boolean? = null) { + private fun setupToolbar(isEnded: Boolean) { val title = when (isEnded) { true -> getString(R.string.room_polls_ended) false -> getString(R.string.room_polls_active) - else -> "" } setupToolbar(views.roomPollDetailToolbar) @@ -87,9 +87,6 @@ class RoomPollDetailFragment : } override fun invalidate() = withState(viewModel) { state -> - state.pollDetail ?: return@withState - // TODO should we update the title when the poll status changes? - setupToolbar(state.pollDetail.isEnded) roomPollDetailController.setData(state) } diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/polls/list/ui/RoomPollsListFragment.kt b/vector/src/main/java/im/vector/app/features/roomprofile/polls/list/ui/RoomPollsListFragment.kt index bbf058d908..4324dcf124 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/polls/list/ui/RoomPollsListFragment.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/polls/list/ui/RoomPollsListFragment.kt @@ -132,6 +132,7 @@ abstract class RoomPollsListFragment : context = requireContext(), pollId = pollId, roomId = it.roomId, + isEnded = getRoomPollsType() == RoomPollsType.ENDED, ) } diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/polls/list/ui/RoomPollsListNavigator.kt b/vector/src/main/java/im/vector/app/features/roomprofile/polls/list/ui/RoomPollsListNavigator.kt index 3cba2fc355..c377d21490 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/polls/list/ui/RoomPollsListNavigator.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/polls/list/ui/RoomPollsListNavigator.kt @@ -23,12 +23,13 @@ import javax.inject.Inject // TODO add unit tests class RoomPollsListNavigator @Inject constructor() { - fun goToPollDetails(context: Context, pollId: String, roomId: String) { + fun goToPollDetails(context: Context, pollId: String, roomId: String, isEnded: Boolean) { context.startActivity( RoomPollDetailActivity.newIntent( context = context, pollId = pollId, roomId = roomId, + isEnded = isEnded, ) ) } From 384e7f674d9ac1fad4e7f08a627b9ea35ed5c09e Mon Sep 17 00:00:00 2001 From: Maxime NATUREL <46314705+mnaturel@users.noreply.github.com> Date: Thu, 2 Feb 2023 15:55:43 +0100 Subject: [PATCH 17/33] Adding go to timeline event button --- .../src/main/res/values/strings.xml | 1 + .../domain/GetEndedPollEventIdUseCase.kt | 38 ++++++++++++++ .../polls/detail/ui/RoomPollDetail.kt | 1 + .../detail/ui/RoomPollDetailController.kt | 14 ++++++ .../polls/detail/ui/RoomPollDetailFragment.kt | 11 +++- .../polls/detail/ui/RoomPollDetailMapper.kt | 50 +++++++++++++++---- .../detail/ui/RoomPollDetailNavigator.kt | 36 +++++++++++++ .../detail/ui/RoomPollGoToTimelineItem.kt | 42 ++++++++++++++++ .../res/layout/item_poll_go_to_timeline.xml | 20 ++++++++ 9 files changed, 203 insertions(+), 10 deletions(-) create mode 100644 vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/domain/GetEndedPollEventIdUseCase.kt create mode 100644 vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/ui/RoomPollDetailNavigator.kt create mode 100644 vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/ui/RoomPollGoToTimelineItem.kt create mode 100644 vector/src/main/res/layout/item_poll_go_to_timeline.xml diff --git a/library/ui-strings/src/main/res/values/strings.xml b/library/ui-strings/src/main/res/values/strings.xml index e690f06bbb..1e540b3360 100644 --- a/library/ui-strings/src/main/res/values/strings.xml +++ b/library/ui-strings/src/main/res/values/strings.xml @@ -3207,6 +3207,7 @@ Displaying polls Load more polls Error fetching polls. + View poll in timeline Share location diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/domain/GetEndedPollEventIdUseCase.kt b/vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/domain/GetEndedPollEventIdUseCase.kt new file mode 100644 index 0000000000..e32c1be963 --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/domain/GetEndedPollEventIdUseCase.kt @@ -0,0 +1,38 @@ +/* + * 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 javax.inject.Inject + +// TODO add unit tests +class GetEndedPollEventIdUseCase @Inject constructor( + private val activeSessionHolder: ActiveSessionHolder, +) { + + fun execute(roomId: String, startPollEventId: String): String? { + return activeSessionHolder.getActiveSession() + .roomService() + .getRoom(roomId) + ?.timelineService() + ?.getTimelineEventsRelatedTo(RelationType.REFERENCE, startPollEventId) + ?.find { it.root.isPollEnd() } + ?.eventId + } +} diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/ui/RoomPollDetail.kt b/vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/ui/RoomPollDetail.kt index 3389a5f473..91d52ca08d 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/ui/RoomPollDetail.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/ui/RoomPollDetail.kt @@ -20,5 +20,6 @@ import im.vector.app.features.poll.PollItemViewState data class RoomPollDetail( val isEnded: Boolean, + val endedPollEventId: String?, val pollItemViewState: PollItemViewState, ) 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 61ef41b14f..d2c5dfb8b1 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 @@ -17,12 +17,14 @@ package im.vector.app.features.roomprofile.polls.detail.ui import com.airbnb.epoxy.TypedEpoxyController +import java.util.UUID import javax.inject.Inject class RoomPollDetailController @Inject constructor() : TypedEpoxyController() { interface Callback { fun vote(pollEventId: String, optionId: String) + fun goToTimelineEvent(eventId: String) } var callback: Callback? = null @@ -41,5 +43,17 @@ class RoomPollDetailController @Inject constructor() : TypedEpoxyController(), RoomPollDetailController.Callback { + @Inject lateinit var viewNavigator: RoomPollDetailNavigator @Inject lateinit var roomPollDetailController: RoomPollDetailController private val viewModel: RoomPollDetailViewModel by fragmentViewModel() @@ -58,7 +60,6 @@ class RoomPollDetailFragment : super.onViewCreated(view, savedInstanceState) setupToolbar(isEnded = roomPollDetailArgs.isEnded) setupDetailView() - // TODO add link to go to timeline message + create a ViewNavigator } override fun onDestroyView() { @@ -93,4 +94,12 @@ class RoomPollDetailFragment : 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, + ) + } } diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/ui/RoomPollDetailMapper.kt b/vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/ui/RoomPollDetailMapper.kt index 931b38f243..7818703716 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/ui/RoomPollDetailMapper.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/ui/RoomPollDetailMapper.kt @@ -19,6 +19,9 @@ 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 @@ -28,6 +31,7 @@ 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? { @@ -36,16 +40,13 @@ class RoomPollDetailMapper @Inject constructor( val content = timelineEvent.getVectorLastMessageContent() val pollResponseData = pollResponseDataFactory.create(timelineEvent) return if (eventId.isNotEmpty() && content is MessagePollContent) { - // we assume poll message has been sent here - val pollItemViewState = pollItemViewStateFactory.create( - pollContent = content, - pollResponseData = pollResponseData, - isSent = true, - ) - RoomPollDetail( - isEnded = pollResponseData?.isClosed == true, - pollItemViewState = pollItemViewState, + val isPollEnded = pollResponseData?.isClosed.orFalse() + val endedPollEventId = getEndedPollEventId( + isPollEnded, + startPollEventId = eventId, + roomId = timelineEvent.roomId, ) + convertToRoomPollDetail(content, pollResponseData, isPollEnded, endedPollEventId) } else { Timber.w("missing mandatory info about poll event with id=$eventId") null @@ -57,4 +58,35 @@ class RoomPollDetailMapper @Inject constructor( } return result.getOrNull() } + + private fun convertToRoomPollDetail( + 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( + 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 + } + } } diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/ui/RoomPollDetailNavigator.kt b/vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/ui/RoomPollDetailNavigator.kt new file mode 100644 index 0000000000..402b9c4468 --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/ui/RoomPollDetailNavigator.kt @@ -0,0 +1,36 @@ +/* + * 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 + +// TODO add unit tests +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, + ) + } +} diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/ui/RoomPollGoToTimelineItem.kt b/vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/ui/RoomPollGoToTimelineItem.kt new file mode 100644 index 0000000000..59a5539a4f --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/ui/RoomPollGoToTimelineItem.kt @@ -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(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