Uploads: add screen - WIP

This commit is contained in:
Benoit Marty 2020-05-19 18:43:57 +02:00
parent e9ca876444
commit 88cba74cac
11 changed files with 88 additions and 22 deletions

View file

@ -21,6 +21,8 @@ import im.vector.matrix.android.api.session.events.model.Event
data class GetUploadsResult(
// List of fetched Events, most recent first
val events: List<Event>,
// token to get more events, or null if there is no more event to fetch
val nextToken: String?
// token to get more events
val nextToken: String,
// True if there are more event to load
val hasMore: Boolean
)

View file

@ -23,4 +23,6 @@ internal interface TokenChunkEvent {
val end: String?
val events: List<Event>
val stateEvents: List<Event>
fun hasMore() = start != end
}

View file

@ -149,7 +149,7 @@ internal class TokenChunkEventPersistor @Inject constructor(private val monarchy
}
?: ChunkEntity.create(realm, prevToken, nextToken)
if (receivedChunk.events.isEmpty() && receivedChunk.end == receivedChunk.start) {
if (receivedChunk.events.isEmpty() && !receivedChunk.hasMore()) {
handleReachEnd(realm, roomId, direction, currentChunk)
} else {
handlePagination(realm, roomId, direction, receivedChunk, currentChunk)

View file

@ -52,7 +52,8 @@ internal class DefaultGetUploadsTask @Inject constructor(
return GetUploadsResult(
events = chunk.events,
nextToken = chunk.end?.takeIf { it != chunk.start }
nextToken = chunk.end ?: "",
hasMore = chunk.hasMore()
)
}
}

View file

@ -24,4 +24,5 @@ sealed class RoomUploadsAction : VectorViewModelAction {
data class Share(val event: Event) : RoomUploadsAction()
object Retry : RoomUploadsAction()
object LoadMore : RoomUploadsAction()
}

View file

@ -22,7 +22,7 @@ import com.airbnb.mvrx.Fail
import com.airbnb.mvrx.FragmentViewModelContext
import com.airbnb.mvrx.Loading
import com.airbnb.mvrx.MvRxViewModelFactory
import com.airbnb.mvrx.Uninitialized
import com.airbnb.mvrx.Success
import com.airbnb.mvrx.ViewModelContext
import com.squareup.inject.assisted.Assisted
import com.squareup.inject.assisted.AssistedInject
@ -35,6 +35,7 @@ import im.vector.matrix.android.api.session.room.uploads.GetUploadsResult
import im.vector.matrix.android.internal.util.awaitCallback
import im.vector.matrix.rx.rx
import im.vector.matrix.rx.unwrap
import im.vector.riotx.core.extensions.exhaustive
import im.vector.riotx.core.platform.EmptyViewEvents
import im.vector.riotx.core.platform.VectorViewModel
import kotlinx.coroutines.launch
@ -79,6 +80,7 @@ class RoomUploadsViewModel @AssistedInject constructor(
private fun handleLoadMore() = withState { state ->
if (state.asyncEventsRequest is Loading) return@withState
if (!state.hasMore) return@withState
setState {
copy(
@ -100,10 +102,10 @@ class RoomUploadsViewModel @AssistedInject constructor(
setState {
copy(
asyncEventsRequest = Uninitialized,
asyncEventsRequest = Success(Unit),
mediaEvents = this.mediaEvents + groupedEvents[true].orEmpty(),
fileEvents = this.fileEvents + groupedEvents[false].orEmpty(),
hasMore = result.nextToken != null
hasMore = result.hasMore
)
}
} catch (failure: Throwable) {
@ -120,8 +122,11 @@ class RoomUploadsViewModel @AssistedInject constructor(
private var token: String? = null
override fun handle(action: RoomUploadsAction) {
// when (action) {
//
// }.exhaustive
when (action) {
is RoomUploadsAction.Download -> TODO()
is RoomUploadsAction.Share -> TODO()
RoomUploadsAction.Retry -> handleLoadMore()
RoomUploadsAction.LoadMore -> handleLoadMore()
}.exhaustive
}
}

View file

@ -30,9 +30,9 @@ data class RoomUploadsViewState(
val mediaEvents: List<Event> = emptyList(),
val fileEvents: List<Event> = emptyList(),
// Current pagination request
val asyncEventsRequest: Async<List<Event>> = Uninitialized,
val asyncEventsRequest: Async<Unit> = Uninitialized,
// True if more result are available server side
val hasMore: Boolean = false
val hasMore: Boolean = true
) : MvRxState {
constructor(args: RoomProfileArgs) : this(roomId = args.roomId)

View file

@ -18,6 +18,7 @@ package im.vector.riotx.features.roomprofile.uploads.files
import android.os.Bundle
import android.view.View
import com.airbnb.epoxy.EpoxyVisibilityTracker
import com.airbnb.mvrx.parentFragmentViewModel
import com.airbnb.mvrx.withState
import im.vector.matrix.android.api.session.events.model.Event
@ -41,6 +42,8 @@ class RoomUploadsFilesFragment @Inject constructor(
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
val epoxyVisibilityTracker = EpoxyVisibilityTracker()
epoxyVisibilityTracker.attach(recyclerView)
recyclerView.configureWith(controller, showDivider = true)
controller.listener = this
}
@ -59,6 +62,10 @@ class RoomUploadsFilesFragment @Inject constructor(
uploadsViewModel.handle(RoomUploadsAction.Retry)
}
override fun loadMore() {
uploadsViewModel.handle(RoomUploadsAction.LoadMore)
}
override fun onDownloadClicked(event: Event) {
uploadsViewModel.handle(RoomUploadsAction.Download(event))
}

View file

@ -17,31 +17,33 @@
package im.vector.riotx.features.roomprofile.uploads.files
import com.airbnb.epoxy.TypedEpoxyController
import com.airbnb.epoxy.VisibilityState
import com.airbnb.mvrx.Fail
import com.airbnb.mvrx.Loading
import com.airbnb.mvrx.Success
import im.vector.matrix.android.api.session.events.model.Event
import im.vector.riotx.R
import im.vector.riotx.core.epoxy.errorWithRetryItem
import im.vector.riotx.core.epoxy.loadingItem
import im.vector.riotx.core.epoxy.noResultItem
import im.vector.riotx.core.error.ErrorFormatter
import im.vector.riotx.core.resources.ColorProvider
import im.vector.riotx.core.resources.StringProvider
import im.vector.riotx.features.roomprofile.uploads.RoomUploadsViewState
import javax.inject.Inject
class UploadsFileController @Inject constructor(
private val errorFormatter: ErrorFormatter,
colorProvider: ColorProvider
private val stringProvider: StringProvider
) : TypedEpoxyController<RoomUploadsViewState>() {
interface Listener {
fun onRetry()
fun loadMore()
fun onOpenClicked(event: Event)
fun onDownloadClicked(event: Event)
fun onShareClicked(event: Event)
}
private val dividerColor = colorProvider.getColorFromAttribute(R.attr.vctr_list_divider_color)
var listener: Listener? = null
init {
@ -65,6 +67,20 @@ class UploadsFileController @Inject constructor(
listener { listener?.onRetry() }
}
}
is Success -> {
if (data.hasMore) {
// We need to load more items
listener?.loadMore()
loadingItem {
id("loading")
}
} else {
noResultItem {
id("noResult")
text(stringProvider.getString(R.string.uploads_files_no_result))
}
}
}
}
} else {
buildFileItems(data.fileEvents)
@ -72,6 +88,11 @@ class UploadsFileController @Inject constructor(
if (data.hasMore) {
loadingItem {
id("loadMore")
onVisibilityStateChanged { _, _, visibilityState ->
if (visibilityState == VisibilityState.VISIBLE) {
listener?.loadMore()
}
}
}
}
}

View file

@ -20,6 +20,7 @@ import android.os.Bundle
import android.util.DisplayMetrics
import android.view.View
import androidx.recyclerview.widget.GridLayoutManager
import com.airbnb.epoxy.EpoxyVisibilityTracker
import com.airbnb.mvrx.parentFragmentViewModel
import com.airbnb.mvrx.withState
import im.vector.riotx.R
@ -45,6 +46,8 @@ class RoomUploadsMediaFragment @Inject constructor(
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
val epoxyVisibilityTracker = EpoxyVisibilityTracker()
epoxyVisibilityTracker.attach(recyclerView)
recyclerView.layoutManager = GridLayoutManager(context, getNumberOfColumns())
recyclerView.adapter = controller.adapter
recyclerView.setHasFixedSize(true)
@ -72,6 +75,10 @@ class RoomUploadsMediaFragment @Inject constructor(
navigator.openVideoViewer(requireActivity(), mediaData)
}
override fun loadMore() {
uploadsViewModel.handle(RoomUploadsAction.LoadMore)
}
override fun onRetry() {
uploadsViewModel.handle(RoomUploadsAction.Retry)
}

View file

@ -18,22 +18,24 @@ package im.vector.riotx.features.roomprofile.uploads.media
import android.view.View
import com.airbnb.epoxy.TypedEpoxyController
import com.airbnb.epoxy.VisibilityState
import com.airbnb.mvrx.Fail
import com.airbnb.mvrx.Loading
import com.airbnb.mvrx.Success
import im.vector.matrix.android.api.session.events.model.Event
import im.vector.matrix.android.api.session.events.model.isImageMessage
import im.vector.matrix.android.api.session.events.model.isVideoMessage
import im.vector.matrix.android.api.session.events.model.toModel
import im.vector.matrix.android.api.session.room.model.message.MessageImageContent
import im.vector.matrix.android.api.session.room.model.message.MessageImageInfoContent
import im.vector.matrix.android.api.session.room.model.message.MessageVideoContent
import im.vector.matrix.android.api.session.room.model.message.getFileUrl
import im.vector.matrix.android.internal.crypto.attachments.toElementToDecrypt
import im.vector.riotx.R
import im.vector.riotx.core.epoxy.errorWithRetryItem
import im.vector.riotx.core.epoxy.noResultItem
import im.vector.riotx.core.epoxy.squareLoadingItem
import im.vector.riotx.core.error.ErrorFormatter
import im.vector.riotx.core.resources.ColorProvider
import im.vector.riotx.core.resources.StringProvider
import im.vector.riotx.core.utils.DimensionConverter
import im.vector.riotx.features.media.ImageContentRenderer
import im.vector.riotx.features.media.VideoContentRenderer
@ -43,18 +45,17 @@ import javax.inject.Inject
class UploadsMediaController @Inject constructor(
private val errorFormatter: ErrorFormatter,
private val imageContentRenderer: ImageContentRenderer,
private val dimensionConverter: DimensionConverter,
colorProvider: ColorProvider
private val stringProvider: StringProvider,
dimensionConverter: DimensionConverter
) : TypedEpoxyController<RoomUploadsViewState>() {
interface Listener {
fun onRetry()
fun onOpenImageClicked(view: View, mediaData: ImageContentRenderer.Data)
fun onOpenVideoClicked(view: View, mediaData: VideoContentRenderer.Data)
fun loadMore()
}
private val dividerColor = colorProvider.getColorFromAttribute(R.attr.vctr_list_divider_color)
var listener: Listener? = null
private val itemSize = dimensionConverter.dpToPx(64)
@ -80,6 +81,20 @@ class UploadsMediaController @Inject constructor(
listener { listener?.onRetry() }
}
}
is Success -> {
if (data.hasMore) {
// We need to load more items
listener?.loadMore()
squareLoadingItem {
id("loading")
}
} else {
noResultItem {
id("noResult")
text(stringProvider.getString(R.string.uploads_media_no_result))
}
}
}
}
} else {
buildMediaItems(data.mediaEvents)
@ -87,6 +102,11 @@ class UploadsMediaController @Inject constructor(
if (data.hasMore) {
squareLoadingItem {
id("loadMore")
onVisibilityStateChanged { _, _, visibilityState ->
if (visibilityState == VisibilityState.VISIBLE) {
listener?.loadMore()
}
}
}
}
}