diff --git a/changelog.d/7864.wip b/changelog.d/7864.wip index 4dc55708be..e1187ee1e7 100644 --- a/changelog.d/7864.wip +++ b/changelog.d/7864.wip @@ -1 +1,2 @@ [Poll] Render active polls list of a room +[Poll] Render past polls list of a room diff --git a/library/ui-strings/src/main/res/values/strings.xml b/library/ui-strings/src/main/res/values/strings.xml index 8064ee5b67..7c75f36a3b 100644 --- a/library/ui-strings/src/main/res/values/strings.xml +++ b/library/ui-strings/src/main/res/values/strings.xml @@ -3193,6 +3193,8 @@ Results are only revealed when you end the poll Active polls There are no active polls in this room + Past polls + There are no past polls in this room Share location diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/polls/GetPollsUseCase.kt b/vector/src/main/java/im/vector/app/features/roomprofile/polls/GetPollsUseCase.kt index d35d192e04..6f2a757ed7 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/polls/GetPollsUseCase.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/polls/GetPollsUseCase.kt @@ -16,50 +16,99 @@ package im.vector.app.features.roomprofile.polls +import im.vector.app.features.home.room.detail.timeline.item.PollOptionViewState import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.emptyFlow import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.flow.map import javax.inject.Inject class GetPollsUseCase @Inject constructor() { - fun execute(filter: RoomPollsFilterType): Flow> { + fun execute(): Flow> { // TODO unmock and add unit tests - return when (filter) { - RoomPollsFilterType.ACTIVE -> getActivePolls() - RoomPollsFilterType.ENDED -> emptyFlow() - }.map { it.sortedByDescending { poll -> poll.creationTimestamp } } + return flowOf(getActivePolls() + getEndedPolls()) + .map { it.sortedByDescending { poll -> poll.creationTimestamp } } } - private fun getActivePolls(): Flow> { - return flowOf( - listOf( - PollSummary.ActivePoll( - id = "id1", - // 2022/06/28 UTC+1 - creationTimestamp = 1656367200000, - title = "Which charity would you like to support?" + private fun getActivePolls(): List { + return listOf( + PollSummary.ActivePoll( + id = "id1", + // 2022/06/28 UTC+1 + creationTimestamp = 1656367200000, + title = "Which charity would you like to support?" + ), + PollSummary.ActivePoll( + id = "id2", + // 2022/06/26 UTC+1 + creationTimestamp = 1656194400000, + title = "Which sport should the pupils do this year?" + ), + PollSummary.ActivePoll( + id = "id3", + // 2022/06/24 UTC+1 + creationTimestamp = 1656021600000, + title = "What type of food should we have at the party?" + ), + PollSummary.ActivePoll( + id = "id4", + // 2022/06/22 UTC+1 + creationTimestamp = 1655848800000, + title = "What film should we show at the end of the year party?" + ), + ) + } + + private fun getEndedPolls(): List { + return listOf( + PollSummary.EndedPoll( + id = "id1-ended", + // 2022/06/28 UTC+1 + creationTimestamp = 1656367200000, + title = "Which charity would you like to support?", + totalVotes = 22, + winnerOptions = listOf( + PollOptionViewState.PollEnded( + optionId = "id1", + optionAnswer = "Cancer research", + voteCount = 13, + votePercentage = 13 / 22.0, + isWinner = true, + ) ), - PollSummary.ActivePoll( - id = "id2", - // 2022/06/26 UTC+1 - creationTimestamp = 1656194400000, - title = "Which sport should the pupils do this year?" + ), + PollSummary.EndedPoll( + id = "id2-ended", + // 2022/06/26 UTC+1 + creationTimestamp = 1656194400000, + title = "Where should we do the offsite?", + totalVotes = 92, + winnerOptions = listOf( + PollOptionViewState.PollEnded( + optionId = "id1", + optionAnswer = "Hawaii", + voteCount = 43, + votePercentage = 43 / 92.0, + isWinner = true, + ) ), - PollSummary.ActivePoll( - id = "id3", - // 2022/06/24 UTC+1 - creationTimestamp = 1656021600000, - title = "What type of food should we have at the party?" + ), + PollSummary.EndedPoll( + id = "id3-ended", + // 2022/06/24 UTC+1 + creationTimestamp = 1656021600000, + title = "What type of food should we have at the party?", + totalVotes = 22, + winnerOptions = listOf( + PollOptionViewState.PollEnded( + optionId = "id1", + optionAnswer = "Brazilian", + voteCount = 13, + votePercentage = 13 / 22.0, + isWinner = true, + ) ), - PollSummary.ActivePoll( - id = "id4", - // 2022/06/22 UTC+1 - creationTimestamp = 1655848800000, - title = "What film should we show at the end of the year party?" - ), - ) + ), ) } } diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/polls/PollSummary.kt b/vector/src/main/java/im/vector/app/features/roomprofile/polls/PollSummary.kt index 3eb45c6144..f24ac8b8a6 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/polls/PollSummary.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/polls/PollSummary.kt @@ -16,10 +16,24 @@ package im.vector.app.features.roomprofile.polls +import im.vector.app.features.home.room.detail.timeline.item.PollOptionViewState + sealed interface PollSummary { + val id: String + val creationTimestamp: Long + val title: String + data class ActivePoll( - val id: String, - val creationTimestamp: Long, - val title: String, + override val id: String, + override val creationTimestamp: Long, + override val title: String, + ) : PollSummary + + data class EndedPoll( + override val id: String, + override val creationTimestamp: Long, + override val title: String, + val totalVotes: Int, + val winnerOptions: List, ) : PollSummary } 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 5f074bdd6f..c18142a306 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 @@ -18,6 +18,4 @@ package im.vector.app.features.roomprofile.polls import im.vector.app.core.platform.VectorViewModelAction -sealed interface RoomPollsAction : VectorViewModelAction { - data class SetFilter(val filter: RoomPollsFilterType) : RoomPollsAction -} +sealed interface RoomPollsAction : VectorViewModelAction 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 5c150f4391..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 @@ -66,7 +66,8 @@ class RoomPollsFragment : VectorBaseFragment() { tabLayoutMediator = TabLayoutMediator(views.roomPollsTabs, views.roomPollsViewPager) { tab, position -> when (position) { - 0 -> tab.text = getString(R.string.room_polls_active) + 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() } } diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/polls/RoomPollsPagerAdapter.kt b/vector/src/main/java/im/vector/app/features/roomprofile/polls/RoomPollsPagerAdapter.kt index 5472782079..c60fc5de27 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/polls/RoomPollsPagerAdapter.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/polls/RoomPollsPagerAdapter.kt @@ -19,15 +19,20 @@ package im.vector.app.features.roomprofile.polls import androidx.fragment.app.Fragment import androidx.viewpager2.adapter.FragmentStateAdapter import im.vector.app.features.roomprofile.polls.active.RoomActivePollsFragment +import im.vector.app.features.roomprofile.polls.ended.RoomEndedPollsFragment class RoomPollsPagerAdapter( private val fragment: Fragment ) : FragmentStateAdapter(fragment) { - override fun getItemCount() = 1 + override fun getItemCount() = RoomPollsType.values().size override fun createFragment(position: Int): Fragment { - return instantiateFragment(RoomActivePollsFragment::class.java.name) + return when (position) { + RoomPollsType.ACTIVE.ordinal -> instantiateFragment(RoomActivePollsFragment::class.java.name) + RoomPollsType.ENDED.ordinal -> instantiateFragment(RoomEndedPollsFragment::class.java.name) + else -> throw IllegalArgumentException("position should be between 0 and ${itemCount - 1}, while it was $position") + } } private fun instantiateFragment(fragmentName: String): Fragment { diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/polls/RoomPollsFilterType.kt b/vector/src/main/java/im/vector/app/features/roomprofile/polls/RoomPollsType.kt similarity index 95% rename from vector/src/main/java/im/vector/app/features/roomprofile/polls/RoomPollsFilterType.kt rename to vector/src/main/java/im/vector/app/features/roomprofile/polls/RoomPollsType.kt index 39f1163536..134ef9a195 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/polls/RoomPollsFilterType.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/polls/RoomPollsType.kt @@ -16,7 +16,7 @@ package im.vector.app.features.roomprofile.polls -enum class RoomPollsFilterType { +enum class RoomPollsType { ACTIVE, ENDED, } 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 7bc06894fa..95cb4717ca 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 @@ -16,7 +16,6 @@ package im.vector.app.features.roomprofile.polls -import androidx.annotation.VisibleForTesting import com.airbnb.mvrx.MavericksViewModelFactory import dagger.assisted.Assisted import dagger.assisted.AssistedFactory @@ -24,7 +23,6 @@ import dagger.assisted.AssistedInject import im.vector.app.core.di.MavericksAssistedViewModelFactory import im.vector.app.core.di.hiltMavericksViewModelFactory import im.vector.app.core.platform.VectorViewModel -import kotlinx.coroutines.Job import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach @@ -40,24 +38,17 @@ class RoomPollsViewModel @AssistedInject constructor( companion object : MavericksViewModelFactory by hiltMavericksViewModelFactory() - @VisibleForTesting - var pollsCollectionJob: Job? = null - - override fun handle(action: RoomPollsAction) { - when (action) { - is RoomPollsAction.SetFilter -> handleSetFilter(action.filter) - } + init { + observePolls() } - override fun onCleared() { - pollsCollectionJob = null - super.onCleared() - } - - private fun handleSetFilter(filter: RoomPollsFilterType) { - pollsCollectionJob?.cancel() - pollsCollectionJob = getPollsUseCase.execute(filter) + private fun observePolls() { + getPollsUseCase.execute() .onEach { setState { copy(polls = it) } } .launchIn(viewModelScope) } + + override fun handle(action: RoomPollsAction) { + // do nothing for now + } } diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/polls/active/RoomActivePollsController.kt b/vector/src/main/java/im/vector/app/features/roomprofile/polls/active/RoomActivePollsController.kt deleted file mode 100644 index 7a7c818693..0000000000 --- a/vector/src/main/java/im/vector/app/features/roomprofile/polls/active/RoomActivePollsController.kt +++ /dev/null @@ -1,52 +0,0 @@ -/* - * 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.active - -import com.airbnb.epoxy.TypedEpoxyController -import im.vector.app.core.date.DateFormatKind -import im.vector.app.core.date.VectorDateFormatter -import im.vector.app.features.roomprofile.polls.PollSummary -import javax.inject.Inject - -class RoomActivePollsController @Inject constructor( - val dateFormatter: VectorDateFormatter, -) : TypedEpoxyController>() { - - interface Listener { - fun onPollClicked(pollId: String) - } - - var listener: Listener? = null - - override fun buildModels(data: List?) { - if (data.isNullOrEmpty()) { - return - } - - val host = this - for (poll in data) { - activePollItem { - id(poll.id) - formattedDate(host.dateFormatter.format(poll.creationTimestamp, DateFormatKind.TIMELINE_DAY_DIVIDER)) - title(poll.title) - clickListener { - host.listener?.onPollClicked(poll.id) - } - } - } - } -} diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/polls/active/RoomActivePollsFragment.kt b/vector/src/main/java/im/vector/app/features/roomprofile/polls/active/RoomActivePollsFragment.kt index 61c7e961bd..1c6a03c480 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/polls/active/RoomActivePollsFragment.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/polls/active/RoomActivePollsFragment.kt @@ -16,77 +16,19 @@ package im.vector.app.features.roomprofile.polls.active -import android.os.Bundle -import android.view.LayoutInflater -import android.view.View -import android.view.ViewGroup -import androidx.core.view.isVisible -import com.airbnb.mvrx.parentFragmentViewModel -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.FragmentRoomPollsListBinding -import im.vector.app.features.roomprofile.polls.PollSummary -import im.vector.app.features.roomprofile.polls.RoomPollsAction -import im.vector.app.features.roomprofile.polls.RoomPollsFilterType -import im.vector.app.features.roomprofile.polls.RoomPollsViewModel -import timber.log.Timber -import javax.inject.Inject +import im.vector.app.features.roomprofile.polls.RoomPollsType +import im.vector.app.features.roomprofile.polls.list.RoomPollsListFragment @AndroidEntryPoint -class RoomActivePollsFragment : - VectorBaseFragment(), - RoomActivePollsController.Listener { +class RoomActivePollsFragment : RoomPollsListFragment() { - @Inject - lateinit var roomActivePollsController: RoomActivePollsController - - private val viewModel: RoomPollsViewModel by parentFragmentViewModel(RoomPollsViewModel::class) - - override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentRoomPollsListBinding { - return FragmentRoomPollsListBinding.inflate(inflater, container, false) + override fun getEmptyListTitle(): String { + return getString(R.string.room_polls_active_no_item) } - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) - setupList() - } - - private fun setupList() { - roomActivePollsController.listener = this - views.roomPollsList.configureWith(roomActivePollsController) - views.roomPollsEmptyTitle.text = getString(R.string.room_polls_active_no_item) - } - - override fun onDestroyView() { - cleanUpList() - super.onDestroyView() - } - - private fun cleanUpList() { - views.roomPollsList.cleanup() - roomActivePollsController.listener = null - } - - override fun onResume() { - super.onResume() - viewModel.handle(RoomPollsAction.SetFilter(RoomPollsFilterType.ACTIVE)) - } - - override fun invalidate() = withState(viewModel) { viewState -> - renderList(viewState.polls.filterIsInstance(PollSummary.ActivePoll::class.java)) - } - - private fun renderList(polls: List) { - roomActivePollsController.setData(polls) - views.roomPollsEmptyTitle.isVisible = polls.isEmpty() - } - - override fun onPollClicked(pollId: String) { - // TODO navigate to details - Timber.d("poll with id $pollId clicked") + override fun getRoomPollsType(): RoomPollsType { + return RoomPollsType.ACTIVE } } diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/polls/ended/RoomEndedPollsFragment.kt b/vector/src/main/java/im/vector/app/features/roomprofile/polls/ended/RoomEndedPollsFragment.kt new file mode 100644 index 0000000000..8dd0cadadf --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/roomprofile/polls/ended/RoomEndedPollsFragment.kt @@ -0,0 +1,34 @@ +/* + * 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.ended + +import dagger.hilt.android.AndroidEntryPoint +import im.vector.app.R +import im.vector.app.features.roomprofile.polls.RoomPollsType +import im.vector.app.features.roomprofile.polls.list.RoomPollsListFragment + +@AndroidEntryPoint +class RoomEndedPollsFragment : RoomPollsListFragment() { + + override fun getEmptyListTitle(): String { + return getString(R.string.room_polls_ended_no_item) + } + + override fun getRoomPollsType(): RoomPollsType { + return RoomPollsType.ENDED + } +} diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/polls/active/ActivePollItem.kt b/vector/src/main/java/im/vector/app/features/roomprofile/polls/list/RoomPollItem.kt similarity index 53% rename from vector/src/main/java/im/vector/app/features/roomprofile/polls/active/ActivePollItem.kt rename to vector/src/main/java/im/vector/app/features/roomprofile/polls/list/RoomPollItem.kt index 35b1ecd6e1..da00fedddb 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/polls/active/ActivePollItem.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/polls/list/RoomPollItem.kt @@ -14,9 +14,11 @@ * limitations under the License. */ -package im.vector.app.features.roomprofile.polls.active +package im.vector.app.features.roomprofile.polls.list +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 @@ -24,9 +26,12 @@ 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 +import im.vector.app.core.extensions.setTextOrHide +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 ActivePollItem : VectorEpoxyModel(R.layout.item_poll) { +abstract class RoomPollItem : VectorEpoxyModel(R.layout.item_poll) { @EpoxyAttribute lateinit var formattedDate: String @@ -34,6 +39,12 @@ abstract class ActivePollItem : VectorEpoxyModel(R.layout @EpoxyAttribute lateinit var title: String + @EpoxyAttribute + var winnerOptions: List = emptyList() + + @EpoxyAttribute + var totalVotesStatus: String? = null + @EpoxyAttribute(EpoxyAttribute.Option.DoNotHash) var clickListener: ClickListener? = null @@ -42,10 +53,20 @@ abstract class ActivePollItem : VectorEpoxyModel(R.layout holder.view.onClick(clickListener) holder.date.text = formattedDate holder.title.text = title + holder.winnerOptions.removeAllViews() + holder.winnerOptions.isVisible = winnerOptions.isNotEmpty() + for (winnerOption in winnerOptions) { + val optionView = PollOptionView(holder.view.context) + holder.winnerOptions.addView(optionView) + optionView.render(winnerOption) + } + holder.totalVotes.setTextOrHide(totalVotesStatus) } class Holder : VectorEpoxyHolder() { - val date by bind(R.id.pollActiveDate) - val title by bind(R.id.pollActiveTitle) + val date by bind(R.id.pollDate) + val title by bind(R.id.pollTitle) + val winnerOptions by bind(R.id.pollWinnerOptionsContainer) + val totalVotes by bind(R.id.pollTotalVotes) } } diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/polls/list/RoomPollsController.kt b/vector/src/main/java/im/vector/app/features/roomprofile/polls/list/RoomPollsController.kt new file mode 100644 index 0000000000..f0e3b6b9a4 --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/roomprofile/polls/list/RoomPollsController.kt @@ -0,0 +1,76 @@ +/* + * 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.list + +import com.airbnb.epoxy.TypedEpoxyController +import im.vector.app.R +import im.vector.app.core.date.DateFormatKind +import im.vector.app.core.date.VectorDateFormatter +import im.vector.app.core.resources.StringProvider +import im.vector.app.features.roomprofile.polls.PollSummary +import javax.inject.Inject + +class RoomPollsController @Inject constructor( + val dateFormatter: VectorDateFormatter, + val stringProvider: StringProvider, +) : TypedEpoxyController>() { + + interface Listener { + fun onPollClicked(pollId: String) + } + + var listener: Listener? = null + + override fun buildModels(data: List?) { + if (data.isNullOrEmpty()) { + return + } + + for (poll in data) { + when (poll) { + is PollSummary.ActivePoll -> buildActivePollItem(poll) + is PollSummary.EndedPoll -> buildEndedPollItem(poll) + } + } + } + + private fun buildActivePollItem(poll: PollSummary.ActivePoll) { + val host = this + roomPollItem { + id(poll.id) + formattedDate(host.dateFormatter.format(poll.creationTimestamp, DateFormatKind.TIMELINE_DAY_DIVIDER)) + title(poll.title) + clickListener { + host.listener?.onPollClicked(poll.id) + } + } + } + + private fun buildEndedPollItem(poll: PollSummary.EndedPoll) { + val host = this + roomPollItem { + id(poll.id) + formattedDate(host.dateFormatter.format(poll.creationTimestamp, DateFormatKind.TIMELINE_DAY_DIVIDER)) + title(poll.title) + winnerOptions(poll.winnerOptions) + totalVotesStatus(host.stringProvider.getQuantityString(R.plurals.poll_total_vote_count_after_ended, poll.totalVotes, poll.totalVotes)) + clickListener { + host.listener?.onPollClicked(poll.id) + } + } + } +} diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/polls/list/RoomPollsListFragment.kt b/vector/src/main/java/im/vector/app/features/roomprofile/polls/list/RoomPollsListFragment.kt new file mode 100644 index 0000000000..0d97bd8dcb --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/roomprofile/polls/list/RoomPollsListFragment.kt @@ -0,0 +1,90 @@ +/* + * 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.list + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.core.view.isVisible +import com.airbnb.mvrx.parentFragmentViewModel +import com.airbnb.mvrx.withState +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.FragmentRoomPollsListBinding +import im.vector.app.features.roomprofile.polls.PollSummary +import im.vector.app.features.roomprofile.polls.RoomPollsType +import im.vector.app.features.roomprofile.polls.RoomPollsViewModel +import timber.log.Timber +import javax.inject.Inject + +abstract class RoomPollsListFragment : + VectorBaseFragment(), + RoomPollsController.Listener { + + @Inject + lateinit var roomPollsController: RoomPollsController + + private val viewModel: RoomPollsViewModel by parentFragmentViewModel(RoomPollsViewModel::class) + + override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentRoomPollsListBinding { + return FragmentRoomPollsListBinding.inflate(inflater, container, false) + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + setupList() + } + + abstract fun getEmptyListTitle(): String + + abstract fun getRoomPollsType(): RoomPollsType + + private fun setupList() { + roomPollsController.listener = this + views.roomPollsList.configureWith(roomPollsController) + views.roomPollsEmptyTitle.text = getEmptyListTitle() + } + + override fun onDestroyView() { + cleanUpList() + super.onDestroyView() + } + + private fun cleanUpList() { + views.roomPollsList.cleanup() + roomPollsController.listener = null + } + + override fun invalidate() = withState(viewModel) { viewState -> + when (getRoomPollsType()) { + RoomPollsType.ACTIVE -> renderList(viewState.polls.filterIsInstance(PollSummary.ActivePoll::class.java)) + RoomPollsType.ENDED -> renderList(viewState.polls.filterIsInstance(PollSummary.EndedPoll::class.java)) + } + } + + private fun renderList(polls: List) { + roomPollsController.setData(polls) + views.roomPollsEmptyTitle.isVisible = polls.isEmpty() + } + + override fun onPollClicked(pollId: String) { + // TODO navigate to details + Timber.d("poll with id $pollId clicked") + } +} diff --git a/vector/src/main/res/layout/item_poll.xml b/vector/src/main/res/layout/item_poll.xml index 956ecf9b3c..17f3b5abf5 100644 --- a/vector/src/main/res/layout/item_poll.xml +++ b/vector/src/main/res/layout/item_poll.xml @@ -7,7 +7,7 @@ android:foreground="?selectableItemBackground"> + + + + diff --git a/vector/src/test/java/im/vector/app/features/roomprofile/polls/RoomPollsViewModelTest.kt b/vector/src/test/java/im/vector/app/features/roomprofile/polls/RoomPollsViewModelTest.kt index 0dce2dd6e0..9cca32c5e6 100644 --- a/vector/src/test/java/im/vector/app/features/roomprofile/polls/RoomPollsViewModelTest.kt +++ b/vector/src/test/java/im/vector/app/features/roomprofile/polls/RoomPollsViewModelTest.kt @@ -23,7 +23,6 @@ import io.mockk.every import io.mockk.mockk import io.mockk.verify import kotlinx.coroutines.flow.flowOf -import org.amshove.kluent.shouldNotBeNull import org.junit.Rule import org.junit.Test @@ -45,28 +44,22 @@ class RoomPollsViewModelTest { } @Test - fun `given SetFilter action when handle then useCase is called with given filter and viewState is updated`() { + fun `given viewModel when created then polls list is observed and viewState is updated`() { // Given - val filter = RoomPollsFilterType.ACTIVE - val action = RoomPollsAction.SetFilter(filter = filter) val polls = listOf(givenAPollSummary()) - every { fakeGetPollsUseCase.execute(any()) } returns flowOf(polls) - val viewModel = createViewModel() + every { fakeGetPollsUseCase.execute() } returns flowOf(polls) val expectedViewState = initialState.copy(polls = polls) // When + val viewModel = createViewModel() val viewModelTest = viewModel.test() - viewModel.pollsCollectionJob = null - viewModel.handle(action) // Then viewModelTest .assertLatestState(expectedViewState) .finish() - viewModel.pollsCollectionJob.shouldNotBeNull() verify { - viewModel.pollsCollectionJob?.cancel() - fakeGetPollsUseCase.execute(filter) + fakeGetPollsUseCase.execute() } }