Render specific empty list message when loading more is still possible

This commit is contained in:
Maxime NATUREL 2023-01-12 14:59:27 +01:00
parent b03b207c82
commit ec65564800
12 changed files with 163 additions and 30 deletions

View file

@ -3199,8 +3199,16 @@
<string name="unable_to_decrypt_some_events_in_poll">Due to decryption errors, some votes may not be counted</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>
<plurals name="room_polls_active_no_item_for_loaded_period">
<item quantity="one">"There are no active polls for the past day.\nLoad more polls to view polls for previous days."</item>
<item quantity="other">"There are no active polls for the past %1$d days.\nLoad more polls to view polls for previous days."</item>
</plurals>
<string name="room_polls_ended">Past polls</string>
<string name="room_polls_ended_no_item">There are no past polls in this room</string>
<plurals name="room_polls_ended_no_item_for_loaded_period">
<item quantity="one">"There are no past polls for the past day.\nLoad more polls to view polls for previous days."</item>
<item quantity="other">"There are no past polls for the past %1$d days.\nLoad more polls to view polls for previous days."</item>
</plurals>
<string name="room_polls_load_more">Load more polls</string>
<!-- Location -->

View file

@ -23,6 +23,7 @@ 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 im.vector.app.features.roomprofile.polls.list.domain.GetLoadedPollsStatusUseCase
import im.vector.app.features.roomprofile.polls.list.domain.GetPollsUseCase
import im.vector.app.features.roomprofile.polls.list.domain.LoadMorePollsUseCase
import kotlinx.coroutines.flow.launchIn
@ -32,6 +33,7 @@ import kotlinx.coroutines.launch
class RoomPollsViewModel @AssistedInject constructor(
@Assisted initialState: RoomPollsViewState,
private val getPollsUseCase: GetPollsUseCase,
private val getLoadedPollsStatusUseCase: GetLoadedPollsStatusUseCase,
private val loadMorePollsUseCase: LoadMorePollsUseCase,
) : VectorViewModel<RoomPollsViewState, RoomPollsAction, RoomPollsViewEvent>(initialState) {
@ -43,8 +45,20 @@ class RoomPollsViewModel @AssistedInject constructor(
companion object : MavericksViewModelFactory<RoomPollsViewModel, RoomPollsViewState> by hiltMavericksViewModelFactory()
init {
// TODO update canLoadMore in viewState
updateLoadedPollStatus(initialState.roomId)
observePolls()
// TODO
// call use case to sync polls until now = initial loading
}
private fun updateLoadedPollStatus(roomId: String) {
val loadedPollsStatus = getLoadedPollsStatusUseCase.execute(roomId)
setState {
copy(
canLoadMore = loadedPollsStatus.canLoadMore,
nbLoadedDays = loadedPollsStatus.nbLoadedDays
)
}
}
private fun observePolls() = withState { viewState ->
@ -64,7 +78,13 @@ class RoomPollsViewModel @AssistedInject constructor(
viewModelScope.launch {
setState { copy(isLoadingMore = true) }
val result = loadMorePollsUseCase.execute(viewState.roomId)
setState { copy(isLoadingMore = false, canLoadMore = result.canLoadMore) }
setState {
copy(
isLoadingMore = false,
canLoadMore = result.canLoadMore,
nbLoadedDays = result.nbLoadedDays,
)
}
}
}
}

View file

@ -26,6 +26,7 @@ data class RoomPollsViewState(
val polls: List<PollSummary> = emptyList(),
val isLoadingMore: Boolean = false,
val canLoadMore: Boolean = true,
val nbLoadedDays: Int = 0,
) : MavericksState {
constructor(roomProfileArgs: RoomProfileArgs) : this(roomId = roomProfileArgs.roomId)

View file

@ -24,8 +24,12 @@ import im.vector.app.features.roomprofile.polls.list.ui.RoomPollsListFragment
@AndroidEntryPoint
class RoomActivePollsFragment : RoomPollsListFragment() {
override fun getEmptyListTitle(): String {
return getString(R.string.room_polls_active_no_item)
override fun getEmptyListTitle(canLoadMore: Boolean, nbLoadedDays: Int): String {
return if (canLoadMore) {
stringProvider.getQuantityString(R.plurals.room_polls_active_no_item_for_loaded_period, nbLoadedDays, nbLoadedDays)
} else {
getString(R.string.room_polls_active_no_item)
}
}
override fun getRoomPollsType(): RoomPollsType {

View file

@ -24,8 +24,12 @@ import im.vector.app.features.roomprofile.polls.list.ui.RoomPollsListFragment
@AndroidEntryPoint
class RoomEndedPollsFragment : RoomPollsListFragment() {
override fun getEmptyListTitle(): String {
return getString(R.string.room_polls_ended_no_item)
override fun getEmptyListTitle(canLoadMore: Boolean, nbLoadedDays: Int): String {
return if (canLoadMore) {
stringProvider.getQuantityString(R.plurals.room_polls_ended_no_item_for_loaded_period, nbLoadedDays, nbLoadedDays)
} else {
getString(R.string.room_polls_ended_no_item)
}
}
override fun getRoomPollsType(): RoomPollsType {

View file

@ -16,6 +16,7 @@
package im.vector.app.features.roomprofile.polls.list.data
data class LoadMorePollsResult(
data class LoadedPollsStatus(
val canLoadMore: Boolean,
val nbLoadedDays: Int,
)

View file

@ -41,6 +41,32 @@ class RoomPollDataSource @Inject constructor() {
return pollsFlow.asSharedFlow()
}
fun getLoadedPollsStatus(roomId: String): LoadedPollsStatus {
Timber.d("roomId=$roomId")
return LoadedPollsStatus(
canLoadMore = canLoadMore(),
nbLoadedDays = fakeLoadCounter * 30,
)
}
private fun canLoadMore(): Boolean {
return fakeLoadCounter < 2
}
suspend fun loadMorePolls(roomId: String): LoadedPollsStatus {
// TODO
// unmock using SDK service + add unit tests
delay(3000)
fakeLoadCounter++
when (fakeLoadCounter) {
1 -> polls.addAll(getActivePollsPart1() + getEndedPollsPart1())
2 -> polls.addAll(getActivePollsPart2() + getEndedPollsPart2())
else -> Unit
}
pollsFlow.emit(polls)
return getLoadedPollsStatus(roomId)
}
private fun getActivePollsPart1(): List<PollSummary.ActivePoll> {
return listOf(
PollSummary.ActivePoll(
@ -132,19 +158,4 @@ class RoomPollDataSource @Inject constructor() {
),
)
}
suspend fun loadMorePolls(roomId: String): LoadMorePollsResult {
Timber.d("roomId=$roomId")
// TODO
// unmock using SDK service + add unit tests
delay(3000)
fakeLoadCounter++
when (fakeLoadCounter) {
1 -> polls.addAll(getActivePollsPart1() + getEndedPollsPart1())
2 -> polls.addAll(getActivePollsPart2() + getEndedPollsPart2())
else -> Unit
}
pollsFlow.emit(polls)
return LoadMorePollsResult(canLoadMore = fakeLoadCounter < 2)
}
}

View file

@ -30,7 +30,11 @@ class RoomPollRepository @Inject constructor(
return roomPollDataSource.getPolls(roomId)
}
suspend fun loadMorePolls(roomId: String): LoadMorePollsResult {
fun getLoadedPollsStatus(roomId: String): LoadedPollsStatus {
return roomPollDataSource.getLoadedPollsStatus(roomId)
}
suspend fun loadMorePolls(roomId: String): LoadedPollsStatus {
return roomPollDataSource.loadMorePolls(roomId)
}
}

View file

@ -0,0 +1,31 @@
/*
* 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.domain
import im.vector.app.features.roomprofile.polls.list.data.LoadedPollsStatus
import im.vector.app.features.roomprofile.polls.list.data.RoomPollRepository
import javax.inject.Inject
// TODO add unit tests
class GetLoadedPollsStatusUseCase @Inject constructor(
private val roomPollRepository: RoomPollRepository,
) {
fun execute(roomId: String): LoadedPollsStatus {
return roomPollRepository.getLoadedPollsStatus(roomId)
}
}

View file

@ -16,7 +16,7 @@
package im.vector.app.features.roomprofile.polls.list.domain
import im.vector.app.features.roomprofile.polls.list.data.LoadMorePollsResult
import im.vector.app.features.roomprofile.polls.list.data.LoadedPollsStatus
import im.vector.app.features.roomprofile.polls.list.data.RoomPollRepository
import javax.inject.Inject
@ -25,7 +25,7 @@ class LoadMorePollsUseCase @Inject constructor(
private val roomPollRepository: RoomPollRepository,
) {
suspend fun execute(roomId: String): LoadMorePollsResult {
suspend fun execute(roomId: String): LoadedPollsStatus {
return roomPollRepository.loadMorePolls(roomId)
}
}

View file

@ -23,9 +23,11 @@ import android.view.ViewGroup
import androidx.core.view.isVisible
import com.airbnb.mvrx.parentFragmentViewModel
import com.airbnb.mvrx.withState
import im.vector.app.core.epoxy.onClick
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.core.resources.StringProvider
import im.vector.app.databinding.FragmentRoomPollsListBinding
import im.vector.app.features.roomprofile.polls.RoomPollsAction
import im.vector.app.features.roomprofile.polls.RoomPollsType
@ -35,7 +37,6 @@ import timber.log.Timber
import javax.inject.Inject
// TODO add and render blocking loader view
// TODO add and render missing empty view when load more is possible
abstract class RoomPollsListFragment :
VectorBaseFragment<FragmentRoomPollsListBinding>(),
RoomPollsController.Listener {
@ -43,6 +44,9 @@ abstract class RoomPollsListFragment :
@Inject
lateinit var roomPollsController: RoomPollsController
@Inject
lateinit var stringProvider: StringProvider
private val viewModel: RoomPollsViewModel by parentFragmentViewModel(RoomPollsViewModel::class)
override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentRoomPollsListBinding {
@ -52,16 +56,26 @@ abstract class RoomPollsListFragment :
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
setupList()
setupLoadMoreButton()
}
abstract fun getEmptyListTitle(): String
abstract fun getEmptyListTitle(canLoadMore: Boolean, nbLoadedDays: Int): String
abstract fun getRoomPollsType(): RoomPollsType
private fun setupList() {
private fun setupList() = withState(viewModel) { viewState ->
roomPollsController.listener = this
views.roomPollsList.configureWith(roomPollsController)
views.roomPollsEmptyTitle.text = getEmptyListTitle()
views.roomPollsEmptyTitle.text = getEmptyListTitle(
canLoadMore = viewState.canLoadMore,
nbLoadedDays = viewState.nbLoadedDays,
)
}
private fun setupLoadMoreButton() {
views.roomPollsLoadMoreWhenEmpty.onClick {
onLoadMoreClicked()
}
}
override fun onDestroyView() {
@ -84,7 +98,14 @@ abstract class RoomPollsListFragment :
private fun renderList(viewState: RoomPollsViewState) {
roomPollsController.setData(viewState)
views.roomPollsEmptyTitle.text = getEmptyListTitle(
canLoadMore = viewState.canLoadMore,
nbLoadedDays = viewState.nbLoadedDays,
)
views.roomPollsEmptyTitle.isVisible = viewState.polls.isEmpty()
views.roomPollsLoadMoreWhenEmpty.isVisible = viewState.polls.isEmpty()
views.roomPollsLoadMoreWhenEmptyProgress.isVisible = viewState.polls.isEmpty() && viewState.isLoadingMore
views.roomPollsLoadMoreWhenEmptyProgress.isEnabled = !viewState.isLoadingMore
}
override fun onPollClicked(pollId: String) {

View file

@ -26,11 +26,39 @@
android:gravity="center"
android:textAppearance="@style/TextAppearance.Vector.Body"
android:textColor="?vctr_content_secondary"
android:textSize="17sp"
android:visibility="gone"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="@id/roomPollsEmptyGuideline"
tools:text="@string/room_polls_active_no_item" />
tools:text="@string/room_polls_active_no_item"
tools:visibility="visible" />
<Button
android:id="@+id/roomPollsLoadMoreWhenEmpty"
style="@style/Widget.Vector.Button.Text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginHorizontal="@dimen/layout_horizontal_margin"
android:layout_marginTop="8dp"
android:text="@string/room_polls_load_more"
android:visibility="gone"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/roomPollsEmptyTitle"
tools:visibility="visible" />
<ProgressBar
android:id="@+id/roomPollsLoadMoreWhenEmptyProgress"
style="?android:attr/progressBarStyle"
android:layout_width="16dp"
android:layout_height="16dp"
android:layout_marginStart="9dp"
android:visibility="gone"
app:layout_constraintBottom_toBottomOf="@id/roomPollsLoadMoreWhenEmpty"
app:layout_constraintStart_toEndOf="@id/roomPollsLoadMoreWhenEmpty"
app:layout_constraintTop_toTopOf="@id/roomPollsLoadMoreWhenEmpty"
tools:visibility="visible" />
<androidx.constraintlayout.widget.Guideline
android:id="@+id/roomPollsEmptyGuideline"