From cb45056c1a311ec652d72a198c089abb66f78669 Mon Sep 17 00:00:00 2001 From: Maxime NATUREL <46314705+mnaturel@users.noreply.github.com> Date: Fri, 30 Dec 2022 17:28:57 +0100 Subject: [PATCH] Mutualizing list fragments and add ended polls tab --- .../src/main/res/values/strings.xml | 2 + .../roomprofile/polls/GetPollsUseCase.kt | 9 +- .../features/roomprofile/polls/PollSummary.kt | 6 ++ .../roomprofile/polls/RoomPollsAction.kt | 4 +- .../roomprofile/polls/RoomPollsFragment.kt | 1 + .../polls/RoomPollsPagerAdapter.kt | 8 +- .../roomprofile/polls/RoomPollsViewModel.kt | 25 ++---- .../polls/active/RoomActivePollsFragment.kt | 70 ++------------- .../polls/ended/RoomEndedPollsFragment.kt | 34 +++++++ .../RoomPollItem.kt} | 4 +- .../RoomPollsController.kt} | 43 ++++++--- .../polls/list/RoomPollsListFragment.kt | 90 +++++++++++++++++++ 12 files changed, 190 insertions(+), 106 deletions(-) create mode 100644 vector/src/main/java/im/vector/app/features/roomprofile/polls/ended/RoomEndedPollsFragment.kt rename vector/src/main/java/im/vector/app/features/roomprofile/polls/{active/ActivePollItem.kt => list/RoomPollItem.kt} (90%) rename vector/src/main/java/im/vector/app/features/roomprofile/polls/{active/RoomActivePollsController.kt => list/RoomPollsController.kt} (50%) create mode 100644 vector/src/main/java/im/vector/app/features/roomprofile/polls/list/RoomPollsListFragment.kt diff --git a/library/ui-strings/src/main/res/values/strings.xml b/library/ui-strings/src/main/res/values/strings.xml index 43507e60ce..6120398566 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..06915c7b6a 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 @@ -17,19 +17,16 @@ package im.vector.app.features.roomprofile.polls 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 getActivePolls() + .map { it.sortedByDescending { poll -> poll.creationTimestamp } } } private fun getActivePolls(): Flow> { 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..939ee3ffa0 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 @@ -22,4 +22,10 @@ sealed interface PollSummary { val creationTimestamp: Long, val title: String, ) : PollSummary + + data class EndedPoll( + val id: String, + val creationTimestamp: Long, + val title: String, + ) : 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..95aa5d0d95 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 @@ -67,6 +67,7 @@ class RoomPollsFragment : VectorBaseFragment() { tabLayoutMediator = TabLayoutMediator(views.roomPollsTabs, views.roomPollsViewPager) { tab, position -> when (position) { 0 -> tab.text = getString(R.string.room_polls_active) + 1 -> 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..2dc6fd4369 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,19 @@ 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() = 2 override fun createFragment(position: Int): Fragment { - return instantiateFragment(RoomActivePollsFragment::class.java.name) + return when (position) { + 0 -> instantiateFragment(RoomActivePollsFragment::class.java.name) + else -> instantiateFragment(RoomEndedPollsFragment::class.java.name) + } } private fun instantiateFragment(fragmentName: String): Fragment { 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/RoomActivePollsFragment.kt b/vector/src/main/java/im/vector/app/features/roomprofile/polls/active/RoomActivePollsFragment.kt index 61c7e961bd..dc735c79be 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.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 getRoomPollsFilter(): RoomPollsFilterType { + return RoomPollsFilterType.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..bad1a4e2da --- /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.RoomPollsFilter +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 getRoomPollsFilter(): RoomPollsFilter { + return RoomPollsFilter.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 90% 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..ac2b9cf3c0 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,7 +14,7 @@ * limitations under the License. */ -package im.vector.app.features.roomprofile.polls.active +package im.vector.app.features.roomprofile.polls.list import android.widget.TextView import com.airbnb.epoxy.EpoxyAttribute @@ -26,7 +26,7 @@ import im.vector.app.core.epoxy.VectorEpoxyModel import im.vector.app.core.epoxy.onClick @EpoxyModelClass -abstract class ActivePollItem : VectorEpoxyModel(R.layout.item_poll) { +abstract class RoomPollItem : VectorEpoxyModel(R.layout.item_poll) { @EpoxyAttribute lateinit var formattedDate: String 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/list/RoomPollsController.kt similarity index 50% rename from vector/src/main/java/im/vector/app/features/roomprofile/polls/active/RoomActivePollsController.kt rename to vector/src/main/java/im/vector/app/features/roomprofile/polls/list/RoomPollsController.kt index 7a7c818693..e24241f0af 100644 --- 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/list/RoomPollsController.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package im.vector.app.features.roomprofile.polls.active +package im.vector.app.features.roomprofile.polls.list import com.airbnb.epoxy.TypedEpoxyController import im.vector.app.core.date.DateFormatKind @@ -22,9 +22,9 @@ import im.vector.app.core.date.VectorDateFormatter import im.vector.app.features.roomprofile.polls.PollSummary import javax.inject.Inject -class RoomActivePollsController @Inject constructor( +class RoomPollsController @Inject constructor( val dateFormatter: VectorDateFormatter, -) : TypedEpoxyController>() { +) : TypedEpoxyController>() { interface Listener { fun onPollClicked(pollId: String) @@ -32,20 +32,39 @@ class RoomActivePollsController @Inject constructor( var listener: Listener? = null - override fun buildModels(data: List?) { + 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) - } + 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) + 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..f408f1c781 --- /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.RoomPollsFilter +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 getRoomPollsFilter(): RoomPollsFilter + + 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 (getRoomPollsFilter()) { + RoomPollsFilter.ACTIVE -> renderList(viewState.polls.filterIsInstance(PollSummary.ActivePoll::class.java)) + RoomPollsFilter.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") + } +}