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()
}
}