Mutualizing list fragments and add ended polls tab

This commit is contained in:
Maxime NATUREL 2022-12-30 17:28:57 +01:00
parent 0b535910d6
commit cb45056c1a
12 changed files with 190 additions and 106 deletions

View File

@ -3193,6 +3193,8 @@
<string name="closed_poll_option_description">Results are only revealed when you end the poll</string>
<string name="room_polls_active">Active polls</string>
<string name="room_polls_active_no_item">There are no active polls in this room</string>
<string name="room_polls_ended">Past polls</string>
<string name="room_polls_ended_no_item">There are no past polls in this room</string>
<!-- Location -->
<string name="location_activity_title_static_sharing">Share location</string>

View File

@ -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<List<PollSummary>> {
fun execute(): Flow<List<PollSummary>> {
// 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<List<PollSummary.ActivePoll>> {

View File

@ -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
}

View File

@ -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

View File

@ -67,6 +67,7 @@ class RoomPollsFragment : VectorBaseFragment<FragmentRoomPollsBinding>() {
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() }
}

View File

@ -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 {

View File

@ -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<RoomPollsViewModel, RoomPollsViewState> 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
}
}

View File

@ -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<FragmentRoomPollsListBinding>(),
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<PollSummary.ActivePoll>) {
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
}
}

View File

@ -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
}
}

View File

@ -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<ActivePollItem.Holder>(R.layout.item_poll) {
abstract class RoomPollItem : VectorEpoxyModel<RoomPollItem.Holder>(R.layout.item_poll) {
@EpoxyAttribute
lateinit var formattedDate: String

View File

@ -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<List<PollSummary.ActivePoll>>() {
) : TypedEpoxyController<List<PollSummary>>() {
interface Listener {
fun onPollClicked(pollId: String)
@ -32,14 +32,22 @@ class RoomActivePollsController @Inject constructor(
var listener: Listener? = null
override fun buildModels(data: List<PollSummary.ActivePoll>?) {
override fun buildModels(data: List<PollSummary>?) {
if (data.isNullOrEmpty()) {
return
}
val host = this
for (poll in data) {
activePollItem {
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)
@ -48,5 +56,16 @@ class RoomActivePollsController @Inject constructor(
}
}
}
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)
}
}
}
}

View File

@ -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<FragmentRoomPollsListBinding>(),
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<PollSummary>) {
roomPollsController.setData(polls)
views.roomPollsEmptyTitle.isVisible = polls.isEmpty()
}
override fun onPollClicked(pollId: String) {
// TODO navigate to details
Timber.d("poll with id $pollId clicked")
}
}