Support open from upload media tab

This commit is contained in:
Valere 2020-07-09 15:22:34 +02:00
parent e38cb7c1a6
commit 195e2703b9
10 changed files with 455 additions and 187 deletions

View file

@ -0,0 +1,148 @@
/*
* Copyright (c) 2020 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.riotx.features.media
import android.content.Context
import android.graphics.drawable.Drawable
import android.view.View
import android.widget.ImageView
import com.bumptech.glide.request.target.CustomViewTarget
import com.bumptech.glide.request.transition.Transition
import im.vector.matrix.android.api.MatrixCallback
import im.vector.matrix.android.api.session.file.FileService
import im.vector.riotx.attachmentviewer.AttachmentInfo
import im.vector.riotx.attachmentviewer.AttachmentSourceProvider
import im.vector.riotx.attachmentviewer.ImageLoaderTarget
import im.vector.riotx.attachmentviewer.VideoLoaderTarget
import java.io.File
abstract class BaseAttachmentProvider(val imageContentRenderer: ImageContentRenderer, val fileService: FileService) : AttachmentSourceProvider {
interface InteractionListener {
fun onDismissTapped()
fun onShareTapped()
fun onPlayPause(play: Boolean)
fun videoSeekTo(percent: Int)
}
var interactionListener: InteractionListener? = null
protected var overlayView: AttachmentOverlayView? = null
override fun overlayViewAtPosition(context: Context, position: Int): View? {
if (position == -1) return null
if (overlayView == null) {
overlayView = AttachmentOverlayView(context)
overlayView?.onBack = {
interactionListener?.onDismissTapped()
}
overlayView?.onShareCallback = {
interactionListener?.onShareTapped()
}
overlayView?.onPlayPause = { play ->
interactionListener?.onPlayPause(play)
}
overlayView?.videoSeekTo = { percent ->
interactionListener?.videoSeekTo(percent)
}
}
return overlayView
}
override fun loadImage(target: ImageLoaderTarget, info: AttachmentInfo.Image) {
(info.data as? ImageContentRenderer.Data)?.let {
imageContentRenderer.render(it, target.contextView(), object : CustomViewTarget<ImageView, Drawable>(target.contextView()) {
override fun onLoadFailed(errorDrawable: Drawable?) {
target.onLoadFailed(info.uid, errorDrawable)
}
override fun onResourceCleared(placeholder: Drawable?) {
target.onResourceCleared(info.uid, placeholder)
}
override fun onResourceReady(resource: Drawable, transition: Transition<in Drawable>?) {
target.onResourceReady(info.uid, resource)
}
})
}
}
override fun loadImage(target: ImageLoaderTarget, info: AttachmentInfo.AnimatedImage) {
(info.data as? ImageContentRenderer.Data)?.let {
imageContentRenderer.render(it, target.contextView(), object : CustomViewTarget<ImageView, Drawable>(target.contextView()) {
override fun onLoadFailed(errorDrawable: Drawable?) {
target.onLoadFailed(info.uid, errorDrawable)
}
override fun onResourceCleared(placeholder: Drawable?) {
target.onResourceCleared(info.uid, placeholder)
}
override fun onResourceReady(resource: Drawable, transition: Transition<in Drawable>?) {
target.onResourceReady(info.uid, resource)
}
})
}
}
override fun loadVideo(target: VideoLoaderTarget, info: AttachmentInfo.Video) {
val data = info.data as? VideoContentRenderer.Data ?: return
// videoContentRenderer.render(data,
// holder.thumbnailImage,
// holder.loaderProgressBar,
// holder.videoView,
// holder.errorTextView)
imageContentRenderer.render(data.thumbnailMediaData, target.contextView(), object : CustomViewTarget<ImageView, Drawable>(target.contextView()) {
override fun onLoadFailed(errorDrawable: Drawable?) {
target.onThumbnailLoadFailed(info.uid, errorDrawable)
}
override fun onResourceCleared(placeholder: Drawable?) {
target.onThumbnailResourceCleared(info.uid, placeholder)
}
override fun onResourceReady(resource: Drawable, transition: Transition<in Drawable>?) {
target.onThumbnailResourceReady(info.uid, resource)
}
})
target.onVideoFileLoading(info.uid)
fileService.downloadFile(
downloadMode = FileService.DownloadMode.FOR_INTERNAL_USE,
id = data.eventId,
mimeType = data.mimeType,
elementToDecrypt = data.elementToDecrypt,
fileName = data.filename,
url = data.url,
callback = object : MatrixCallback<File> {
override fun onSuccess(data: File) {
target.onVideoFileReady(info.uid, data)
}
override fun onFailure(failure: Throwable) {
target.onVideoFileLoadFailed(info.uid)
}
}
)
}
override fun clear(id: String) {
// TODO("Not yet implemented")
}
abstract fun getFileForSharing(position: Int, callback: ((File?) -> Unit))
}

View file

@ -0,0 +1,112 @@
/*
* Copyright (c) 2020 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.riotx.features.media
import android.content.Context
import android.view.View
import androidx.core.view.isVisible
import im.vector.matrix.android.api.MatrixCallback
import im.vector.matrix.android.api.session.events.model.isVideoMessage
import im.vector.matrix.android.api.session.file.FileService
import im.vector.matrix.android.api.session.room.Room
import im.vector.riotx.attachmentviewer.AttachmentInfo
import im.vector.riotx.core.date.VectorDateFormatter
import im.vector.riotx.core.extensions.localDateTime
import java.io.File
class DataAttachmentRoomProvider(
private val attachments: List<AttachmentData>,
private val room: Room?,
private val initialIndex: Int,
imageContentRenderer: ImageContentRenderer,
private val dateFormatter: VectorDateFormatter,
fileService: FileService) : BaseAttachmentProvider(imageContentRenderer, fileService) {
override fun getItemCount(): Int = attachments.size
override fun getAttachmentInfoAt(position: Int): AttachmentInfo {
return attachments[position].let {
when (it) {
is ImageContentRenderer.Data -> {
if (it.mimeType == "image/gif") {
AttachmentInfo.AnimatedImage(
uid = it.eventId,
url = it.url ?: "",
data = it
)
} else {
AttachmentInfo.Image(
uid = it.eventId,
url = it.url ?: "",
data = it
)
}
}
is VideoContentRenderer.Data -> {
AttachmentInfo.Video(
uid = it.eventId,
url = it.url ?: "",
data = it,
thumbnail = AttachmentInfo.Image(
uid = it.eventId,
url = it.thumbnailMediaData.url ?: "",
data = it.thumbnailMediaData
)
)
}
else -> throw IllegalArgumentException()
}
}
}
override fun overlayViewAtPosition(context: Context, position: Int): View? {
super.overlayViewAtPosition(context, position)
val item = attachments[position]
val timeLineEvent = room?.getTimeLineEvent(item.eventId)
if (timeLineEvent != null) {
val dateString = timeLineEvent.root.localDateTime().let {
"${dateFormatter.formatMessageDay(it)} at ${dateFormatter.formatMessageHour(it)} "
}
overlayView?.updateWith("${position + 1} of ${attachments.size}", "${timeLineEvent.senderInfo.displayName} $dateString")
overlayView?.videoControlsGroup?.isVisible = timeLineEvent.root.isVideoMessage()
} else {
overlayView?.updateWith("", "")
}
return overlayView
}
override fun getFileForSharing(position: Int, callback: (File?) -> Unit) {
val item = attachments[position]
fileService.downloadFile(
downloadMode = FileService.DownloadMode.FOR_EXTERNAL_SHARE,
id = item.eventId,
fileName = item.filename,
mimeType = item.mimeType,
url = item.url ?: "",
elementToDecrypt = item.elementToDecrypt,
callback = object : MatrixCallback<File> {
override fun onSuccess(data: File) {
callback(data)
}
override fun onFailure(failure: Throwable) {
callback(null)
}
}
)
}
}

View file

@ -17,17 +17,14 @@
package im.vector.riotx.features.media package im.vector.riotx.features.media
import android.content.Context import android.content.Context
import android.graphics.drawable.Drawable
import android.view.View import android.view.View
import android.widget.ImageView
import androidx.core.view.isVisible import androidx.core.view.isVisible
import com.bumptech.glide.request.target.CustomViewTarget
import com.bumptech.glide.request.transition.Transition
import im.vector.matrix.android.api.MatrixCallback import im.vector.matrix.android.api.MatrixCallback
import im.vector.matrix.android.api.session.Session import im.vector.matrix.android.api.session.Session
import im.vector.matrix.android.api.session.events.model.isVideoMessage 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.events.model.toModel
import im.vector.matrix.android.api.session.file.FileService import im.vector.matrix.android.api.session.file.FileService
import im.vector.matrix.android.api.session.room.Room
import im.vector.matrix.android.api.session.room.model.message.MessageContent import im.vector.matrix.android.api.session.room.model.message.MessageContent
import im.vector.matrix.android.api.session.room.model.message.MessageImageContent import im.vector.matrix.android.api.session.room.model.message.MessageImageContent
import im.vector.matrix.android.api.session.room.model.message.MessageVideoContent import im.vector.matrix.android.api.session.room.model.message.MessageVideoContent
@ -36,33 +33,18 @@ import im.vector.matrix.android.api.session.room.model.message.getFileUrl
import im.vector.matrix.android.api.session.room.timeline.TimelineEvent import im.vector.matrix.android.api.session.room.timeline.TimelineEvent
import im.vector.matrix.android.internal.crypto.attachments.toElementToDecrypt import im.vector.matrix.android.internal.crypto.attachments.toElementToDecrypt
import im.vector.riotx.attachmentviewer.AttachmentInfo import im.vector.riotx.attachmentviewer.AttachmentInfo
import im.vector.riotx.attachmentviewer.AttachmentSourceProvider
import im.vector.riotx.attachmentviewer.ImageLoaderTarget
import im.vector.riotx.attachmentviewer.VideoLoaderTarget
import im.vector.riotx.core.date.VectorDateFormatter import im.vector.riotx.core.date.VectorDateFormatter
import im.vector.riotx.core.extensions.localDateTime import im.vector.riotx.core.extensions.localDateTime
import java.io.File import java.io.File
import javax.inject.Inject import javax.inject.Inject
class RoomAttachmentProvider( class RoomEventsAttachmentProvider(
private val attachments: List<TimelineEvent>, private val attachments: List<TimelineEvent>,
private val initialIndex: Int, private val initialIndex: Int,
private val imageContentRenderer: ImageContentRenderer, imageContentRenderer: ImageContentRenderer,
private val videoContentRenderer: VideoContentRenderer,
private val dateFormatter: VectorDateFormatter, private val dateFormatter: VectorDateFormatter,
private val fileService: FileService fileService: FileService
) : AttachmentSourceProvider { ) : BaseAttachmentProvider(imageContentRenderer, fileService) {
interface InteractionListener {
fun onDismissTapped()
fun onShareTapped()
fun onPlayPause(play: Boolean)
fun videoSeekTo(percent: Int)
}
var interactionListener: InteractionListener? = null
private var overlayView: AttachmentOverlayView? = null
override fun getItemCount(): Int { override fun getItemCount(): Int {
return attachments.size return attachments.size
@ -139,99 +121,8 @@ class RoomAttachmentProvider(
} }
} }
override fun loadImage(target: ImageLoaderTarget, info: AttachmentInfo.Image) {
(info.data as? ImageContentRenderer.Data)?.let {
imageContentRenderer.render(it, target.contextView(), object : CustomViewTarget<ImageView, Drawable>(target.contextView()) {
override fun onLoadFailed(errorDrawable: Drawable?) {
target.onLoadFailed(info.uid, errorDrawable)
}
override fun onResourceCleared(placeholder: Drawable?) {
target.onResourceCleared(info.uid, placeholder)
}
override fun onResourceReady(resource: Drawable, transition: Transition<in Drawable>?) {
target.onResourceReady(info.uid, resource)
}
})
}
}
override fun loadImage(target: ImageLoaderTarget, info: AttachmentInfo.AnimatedImage) {
(info.data as? ImageContentRenderer.Data)?.let {
imageContentRenderer.render(it, target.contextView(), object : CustomViewTarget<ImageView, Drawable>(target.contextView()) {
override fun onLoadFailed(errorDrawable: Drawable?) {
target.onLoadFailed(info.uid, errorDrawable)
}
override fun onResourceCleared(placeholder: Drawable?) {
target.onResourceCleared(info.uid, placeholder)
}
override fun onResourceReady(resource: Drawable, transition: Transition<in Drawable>?) {
target.onResourceReady(info.uid, resource)
}
})
}
}
override fun loadVideo(target: VideoLoaderTarget, info: AttachmentInfo.Video) {
val data = info.data as? VideoContentRenderer.Data ?: return
// videoContentRenderer.render(data,
// holder.thumbnailImage,
// holder.loaderProgressBar,
// holder.videoView,
// holder.errorTextView)
imageContentRenderer.render(data.thumbnailMediaData, target.contextView(), object : CustomViewTarget<ImageView, Drawable>(target.contextView()) {
override fun onLoadFailed(errorDrawable: Drawable?) {
target.onThumbnailLoadFailed(info.uid, errorDrawable)
}
override fun onResourceCleared(placeholder: Drawable?) {
target.onThumbnailResourceCleared(info.uid, placeholder)
}
override fun onResourceReady(resource: Drawable, transition: Transition<in Drawable>?) {
target.onThumbnailResourceReady(info.uid, resource)
}
})
target.onVideoFileLoading(info.uid)
fileService.downloadFile(
downloadMode = FileService.DownloadMode.FOR_INTERNAL_USE,
id = data.eventId,
mimeType = data.mimeType,
elementToDecrypt = data.elementToDecrypt,
fileName = data.filename,
url = data.url,
callback = object : MatrixCallback<File> {
override fun onSuccess(data: File) {
target.onVideoFileReady(info.uid, data)
}
override fun onFailure(failure: Throwable) {
target.onVideoFileLoadFailed(info.uid)
}
}
)
}
override fun overlayViewAtPosition(context: Context, position: Int): View? { override fun overlayViewAtPosition(context: Context, position: Int): View? {
if (overlayView == null) { super.overlayViewAtPosition(context, position)
overlayView = AttachmentOverlayView(context)
overlayView?.onBack = {
interactionListener?.onDismissTapped()
}
overlayView?.onShareCallback = {
interactionListener?.onShareTapped()
}
overlayView?.onPlayPause = { play ->
interactionListener?.onPlayPause(play)
}
overlayView?.videoSeekTo = { percent ->
interactionListener?.videoSeekTo(percent)
}
}
val item = attachments[position] val item = attachments[position]
val dateString = item.root.localDateTime().let { val dateString = item.root.localDateTime().let {
"${dateFormatter.formatMessageDay(it)} at ${dateFormatter.formatMessageHour(it)} " "${dateFormatter.formatMessageDay(it)} at ${dateFormatter.formatMessageHour(it)} "
@ -241,19 +132,44 @@ class RoomAttachmentProvider(
return overlayView return overlayView
} }
override fun clear(id: String) { override fun getFileForSharing(position: Int, callback: (File?) -> Unit) {
// TODO("Not yet implemented") attachments[position].let { timelineEvent ->
val messageContent = timelineEvent.root.getClearContent().toModel<MessageContent>()
as? MessageWithAttachmentContent
?: return@let
fileService.downloadFile(
downloadMode = FileService.DownloadMode.FOR_EXTERNAL_SHARE,
id = timelineEvent.eventId,
fileName = messageContent.body,
mimeType = messageContent.mimeType,
url = messageContent.getFileUrl(),
elementToDecrypt = messageContent.encryptedFileInfo?.toElementToDecrypt(),
callback = object : MatrixCallback<File> {
override fun onSuccess(data: File) {
callback(data)
}
override fun onFailure(failure: Throwable) {
callback(null)
}
}
)
}
} }
} }
class RoomAttachmentProviderFactory @Inject constructor( class AttachmentProviderFactory @Inject constructor(
private val imageContentRenderer: ImageContentRenderer, private val imageContentRenderer: ImageContentRenderer,
private val vectorDateFormatter: VectorDateFormatter, private val vectorDateFormatter: VectorDateFormatter,
private val videoContentRenderer: VideoContentRenderer,
private val session: Session private val session: Session
) { ) {
fun createProvider(attachments: List<TimelineEvent>, initialIndex: Int): RoomAttachmentProvider { fun createProvider(attachments: List<TimelineEvent>, initialIndex: Int): RoomEventsAttachmentProvider {
return RoomAttachmentProvider(attachments, initialIndex, imageContentRenderer, videoContentRenderer, vectorDateFormatter, session.fileService()) return RoomEventsAttachmentProvider(attachments, initialIndex, imageContentRenderer, vectorDateFormatter, session.fileService())
}
fun createProvider(attachments: List<AttachmentData>, room: Room?, initialIndex: Int): DataAttachmentRoomProvider {
return DataAttachmentRoomProvider(attachments, room, initialIndex, imageContentRenderer, vectorDateFormatter, session.fileService())
} }
} }

View file

@ -30,14 +30,6 @@ import androidx.core.view.isInvisible
import androidx.core.view.isVisible import androidx.core.view.isVisible
import androidx.lifecycle.Lifecycle import androidx.lifecycle.Lifecycle
import androidx.transition.Transition import androidx.transition.Transition
import im.vector.matrix.android.api.MatrixCallback
import im.vector.matrix.android.api.session.events.model.toModel
import im.vector.matrix.android.api.session.file.FileService
import im.vector.matrix.android.api.session.room.model.message.MessageContent
import im.vector.matrix.android.api.session.room.model.message.MessageWithAttachmentContent
import im.vector.matrix.android.api.session.room.model.message.getFileUrl
import im.vector.matrix.android.api.session.room.timeline.TimelineEvent
import im.vector.matrix.android.internal.crypto.attachments.toElementToDecrypt
import im.vector.riotx.R import im.vector.riotx.R
import im.vector.riotx.attachmentviewer.AttachmentCommands import im.vector.riotx.attachmentviewer.AttachmentCommands
import im.vector.riotx.attachmentviewer.AttachmentViewerActivity import im.vector.riotx.attachmentviewer.AttachmentViewerActivity
@ -52,11 +44,10 @@ import im.vector.riotx.features.themes.ActivityOtherThemes
import im.vector.riotx.features.themes.ThemeUtils import im.vector.riotx.features.themes.ThemeUtils
import kotlinx.android.parcel.Parcelize import kotlinx.android.parcel.Parcelize
import timber.log.Timber import timber.log.Timber
import java.io.File
import javax.inject.Inject import javax.inject.Inject
import kotlin.system.measureTimeMillis import kotlin.system.measureTimeMillis
class VectorAttachmentViewerActivity : AttachmentViewerActivity(), RoomAttachmentProvider.InteractionListener { class VectorAttachmentViewerActivity : AttachmentViewerActivity(), BaseAttachmentProvider.InteractionListener {
@Parcelize @Parcelize
data class Args( data class Args(
@ -69,7 +60,7 @@ class VectorAttachmentViewerActivity : AttachmentViewerActivity(), RoomAttachmen
lateinit var sessionHolder: ActiveSessionHolder lateinit var sessionHolder: ActiveSessionHolder
@Inject @Inject
lateinit var dataSourceFactory: RoomAttachmentProviderFactory lateinit var dataSourceFactory: AttachmentProviderFactory
@Inject @Inject
lateinit var imageContentRenderer: ImageContentRenderer lateinit var imageContentRenderer: ImageContentRenderer
@ -78,7 +69,8 @@ class VectorAttachmentViewerActivity : AttachmentViewerActivity(), RoomAttachmen
private var initialIndex = 0 private var initialIndex = 0
private var isAnimatingOut = false private var isAnimatingOut = false
private var eventList: List<TimelineEvent>? = null
var currentSourceProvider: BaseAttachmentProvider? = null
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
@ -92,13 +84,6 @@ class VectorAttachmentViewerActivity : AttachmentViewerActivity(), RoomAttachmen
ThemeUtils.setActivityTheme(this, getOtherThemes()) ThemeUtils.setActivityTheme(this, getOtherThemes())
val args = args() ?: throw IllegalArgumentException("Missing arguments") val args = args() ?: throw IllegalArgumentException("Missing arguments")
val session = sessionHolder.getSafeActiveSession() ?: return Unit.also { finish() }
val room = args.roomId?.let { session.getRoom(it) }
val events = room?.getAttachmentMessages() ?: emptyList()
eventList = events
val index = events.indexOfFirst { it.eventId == args.eventId }
initialIndex = index
if (savedInstanceState == null && addTransitionListener()) { if (savedInstanceState == null && addTransitionListener()) {
args.sharedTransitionName?.let { args.sharedTransitionName?.let {
@ -127,9 +112,18 @@ class VectorAttachmentViewerActivity : AttachmentViewerActivity(), RoomAttachmen
} }
} }
val sourceProvider = dataSourceFactory.createProvider(events, index) val session = sessionHolder.getSafeActiveSession() ?: return Unit.also { finish() }
val room = args.roomId?.let { session.getRoom(it) }
val inMemoryData = intent.getParcelableArrayListExtra<AttachmentData>(EXTRA_IN_MEMORY_DATA)
if (inMemoryData != null) {
val sourceProvider = dataSourceFactory.createProvider(inMemoryData, room, initialIndex)
val index = inMemoryData.indexOfFirst { it.eventId == args.eventId }
initialIndex = index
sourceProvider.interactionListener = this sourceProvider.interactionListener = this
setSourceProvider(sourceProvider) setSourceProvider(sourceProvider)
this.currentSourceProvider = sourceProvider
if (savedInstanceState == null) { if (savedInstanceState == null) {
pager2.setCurrentItem(index, false) pager2.setCurrentItem(index, false)
// The page change listener is not notified of the change... // The page change listener is not notified of the change...
@ -137,6 +131,24 @@ class VectorAttachmentViewerActivity : AttachmentViewerActivity(), RoomAttachmen
onSelectedPositionChanged(index) onSelectedPositionChanged(index)
} }
} }
} else {
val events = room?.getAttachmentMessages()
?: emptyList()
val index = events.indexOfFirst { it.eventId == args.eventId }
initialIndex = index
val sourceProvider = dataSourceFactory.createProvider(events, index)
sourceProvider.interactionListener = this
setSourceProvider(sourceProvider)
this.currentSourceProvider = sourceProvider
if (savedInstanceState == null) {
pager2.setCurrentItem(index, false)
// The page change listener is not notified of the change...
pager2.post {
onSelectedPositionChanged(index)
}
}
}
window.statusBarColor = ContextCompat.getColor(this, R.color.black_alpha) window.statusBarColor = ContextCompat.getColor(this, R.color.black_alpha)
window.navigationBarColor = ContextCompat.getColor(this, R.color.black_alpha) window.navigationBarColor = ContextCompat.getColor(this, R.color.black_alpha)
@ -228,14 +240,19 @@ class VectorAttachmentViewerActivity : AttachmentViewerActivity(), RoomAttachmen
const val EXTRA_ARGS = "EXTRA_ARGS" const val EXTRA_ARGS = "EXTRA_ARGS"
const val EXTRA_IMAGE_DATA = "EXTRA_IMAGE_DATA" const val EXTRA_IMAGE_DATA = "EXTRA_IMAGE_DATA"
const val EXTRA_IN_MEMORY_DATA = "EXTRA_IN_MEMORY_DATA"
fun newIntent(context: Context, fun newIntent(context: Context,
mediaData: AttachmentData, mediaData: AttachmentData,
roomId: String?, roomId: String?,
eventId: String, eventId: String,
inMemoryData: List<AttachmentData>?,
sharedTransitionName: String?) = Intent(context, VectorAttachmentViewerActivity::class.java).also { sharedTransitionName: String?) = Intent(context, VectorAttachmentViewerActivity::class.java).also {
it.putExtra(EXTRA_ARGS, Args(roomId, eventId, sharedTransitionName)) it.putExtra(EXTRA_ARGS, Args(roomId, eventId, sharedTransitionName))
it.putExtra(EXTRA_IMAGE_DATA, mediaData) it.putExtra(EXTRA_IMAGE_DATA, mediaData)
if (inMemoryData != null) {
it.putParcelableArrayListExtra(EXTRA_IN_MEMORY_DATA, ArrayList(inMemoryData))
}
} }
} }
@ -252,27 +269,10 @@ class VectorAttachmentViewerActivity : AttachmentViewerActivity(), RoomAttachmen
} }
override fun onShareTapped() { override fun onShareTapped() {
// Share this.currentSourceProvider?.getFileForSharing(currentPosition) { data ->
eventList?.get(currentPosition)?.let { timelineEvent -> if (data != null && lifecycle.currentState.isAtLeast(Lifecycle.State.RESUMED)) {
val messageContent = timelineEvent.root.getClearContent().toModel<MessageContent>()
as? MessageWithAttachmentContent
?: return@let
sessionHolder.getSafeActiveSession()?.fileService()?.downloadFile(
downloadMode = FileService.DownloadMode.FOR_EXTERNAL_SHARE,
id = timelineEvent.eventId,
fileName = messageContent.body,
mimeType = messageContent.mimeType,
url = messageContent.getFileUrl(),
elementToDecrypt = messageContent.encryptedFileInfo?.toElementToDecrypt(),
callback = object : MatrixCallback<File> {
override fun onSuccess(data: File) {
if (lifecycle.currentState.isAtLeast(Lifecycle.State.RESUMED)) {
shareMedia(this@VectorAttachmentViewerActivity, data, getMimeTypeFromUri(this@VectorAttachmentViewerActivity, data.toUri())) shareMedia(this@VectorAttachmentViewerActivity, data, getMimeTypeFromUri(this@VectorAttachmentViewerActivity, data.toUri()))
} }
} }
} }
)
}
}
} }

View file

@ -246,20 +246,25 @@ class DefaultNavigator @Inject constructor(
} }
override fun openImageViewer(activity: Activity, override fun openImageViewer(activity: Activity,
roomId: String?, roomId: String,
mediaData: AttachmentData, mediaData: AttachmentData,
view: View, view: View,
inMemory: List<AttachmentData>?,
options: ((MutableList<Pair<View, String>>) -> Unit)?) { options: ((MutableList<Pair<View, String>>) -> Unit)?) {
VectorAttachmentViewerActivity.newIntent(activity, mediaData, roomId, mediaData.eventId, ViewCompat.getTransitionName(view)).let { intent -> VectorAttachmentViewerActivity.newIntent(activity,
mediaData,
roomId,
mediaData.eventId,
inMemory,
ViewCompat.getTransitionName(view)).let { intent ->
val pairs = ArrayList<Pair<View, String>>() val pairs = ArrayList<Pair<View, String>>()
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
activity.window.decorView.findViewById<View>(android.R.id.statusBarBackground)?.let { activity.window.decorView.findViewById<View>(android.R.id.statusBarBackground)?.let {
pairs.add(Pair(it, Window.STATUS_BAR_BACKGROUND_TRANSITION_NAME)) pairs.add(Pair(it, Window.STATUS_BAR_BACKGROUND_TRANSITION_NAME))
} }
activity.window.decorView.findViewById<View>(android.R.id.navigationBarBackground)?.let { activity.window.decorView.findViewById<View>(android.R.id.navigationBarBackground)?.let {
pairs.add(Pair(it, Window.NAVIGATION_BAR_BACKGROUND_TRANSITION_NAME)) pairs.add(Pair(it, Window.NAVIGATION_BAR_BACKGROUND_TRANSITION_NAME))
} }
}
pairs.add(Pair(view, ViewCompat.getTransitionName(view) ?: "")) pairs.add(Pair(view, ViewCompat.getTransitionName(view) ?: ""))
options?.invoke(pairs) options?.invoke(pairs)
@ -284,12 +289,18 @@ class DefaultNavigator @Inject constructor(
} }
override fun openVideoViewer(activity: Activity, override fun openVideoViewer(activity: Activity,
roomId: String?, mediaData: VideoContentRenderer.Data, roomId: String, mediaData: VideoContentRenderer.Data,
view: View, view: View,
inMemory: List<AttachmentData>?,
options: ((MutableList<Pair<View, String>>) -> Unit)?) { options: ((MutableList<Pair<View, String>>) -> Unit)?) {
// val intent = VideoMediaViewerActivity.newIntent(activity, mediaData) // val intent = VideoMediaViewerActivity.newIntent(activity, mediaData)
// activity.startActivity(intent) // activity.startActivity(intent)
VectorAttachmentViewerActivity.newIntent(activity, mediaData, roomId, mediaData.eventId, ViewCompat.getTransitionName(view)).let { intent -> VectorAttachmentViewerActivity.newIntent(activity,
mediaData,
roomId,
mediaData.eventId,
inMemory,
ViewCompat.getTransitionName(view)).let { intent ->
val pairs = ArrayList<Pair<View, String>>() val pairs = ArrayList<Pair<View, String>>()
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
activity.window.decorView.findViewById<View>(android.R.id.statusBarBackground)?.let { activity.window.decorView.findViewById<View>(android.R.id.statusBarBackground)?.let {

View file

@ -92,13 +92,15 @@ interface Navigator {
fun openRoomWidget(context: Context, roomId: String, widget: Widget) fun openRoomWidget(context: Context, roomId: String, widget: Widget)
fun openImageViewer(activity: Activity, fun openImageViewer(activity: Activity,
roomId: String?, roomId: String,
mediaData: AttachmentData, mediaData: AttachmentData,
view: View, view: View,
inMemory: List<AttachmentData>? = null,
options: ((MutableList<Pair<View, String>>) -> Unit)?) options: ((MutableList<Pair<View, String>>) -> Unit)?)
fun openVideoViewer(activity: Activity, fun openVideoViewer(activity: Activity,
roomId: String?, mediaData: VideoContentRenderer.Data, roomId: String, mediaData: VideoContentRenderer.Data,
view: View, view: View,
inMemory: List<AttachmentData>? = null,
options: ((MutableList<Pair<View, String>>) -> Unit)?) options: ((MutableList<Pair<View, String>>) -> Unit)?)
} }

View file

@ -20,23 +20,34 @@ import android.os.Bundle
import android.util.DisplayMetrics import android.util.DisplayMetrics
import android.view.View import android.view.View
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import androidx.core.util.Pair
import androidx.core.view.ViewCompat
import androidx.recyclerview.widget.GridLayoutManager import androidx.recyclerview.widget.GridLayoutManager
import com.airbnb.mvrx.Fail import com.airbnb.mvrx.Fail
import com.airbnb.mvrx.Loading import com.airbnb.mvrx.Loading
import com.airbnb.mvrx.Success import com.airbnb.mvrx.Success
import com.airbnb.mvrx.parentFragmentViewModel import com.airbnb.mvrx.parentFragmentViewModel
import com.airbnb.mvrx.withState import com.airbnb.mvrx.withState
import com.google.android.material.appbar.AppBarLayout
import im.vector.matrix.android.api.session.room.model.message.MessageImageContent
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.R
import im.vector.riotx.core.extensions.cleanup import im.vector.riotx.core.extensions.cleanup
import im.vector.riotx.core.extensions.trackItemsVisibilityChange import im.vector.riotx.core.extensions.trackItemsVisibilityChange
import im.vector.riotx.core.platform.StateView import im.vector.riotx.core.platform.StateView
import im.vector.riotx.core.platform.VectorBaseFragment import im.vector.riotx.core.platform.VectorBaseFragment
import im.vector.riotx.core.utils.DimensionConverter import im.vector.riotx.core.utils.DimensionConverter
import im.vector.riotx.features.media.AttachmentData
import im.vector.riotx.features.media.ImageContentRenderer import im.vector.riotx.features.media.ImageContentRenderer
import im.vector.riotx.features.media.VideoContentRenderer import im.vector.riotx.features.media.VideoContentRenderer
import im.vector.riotx.features.roomprofile.uploads.RoomUploadsAction import im.vector.riotx.features.roomprofile.uploads.RoomUploadsAction
import im.vector.riotx.features.roomprofile.uploads.RoomUploadsFragment
import im.vector.riotx.features.roomprofile.uploads.RoomUploadsViewModel import im.vector.riotx.features.roomprofile.uploads.RoomUploadsViewModel
import im.vector.riotx.features.roomprofile.uploads.RoomUploadsViewState
import kotlinx.android.synthetic.main.fragment_generic_state_view_recycler.* import kotlinx.android.synthetic.main.fragment_generic_state_view_recycler.*
import kotlinx.android.synthetic.main.fragment_room_uploads.*
import javax.inject.Inject import javax.inject.Inject
class RoomUploadsMediaFragment @Inject constructor( class RoomUploadsMediaFragment @Inject constructor(
@ -76,13 +87,75 @@ class RoomUploadsMediaFragment @Inject constructor(
controller.listener = null controller.listener = null
} }
override fun onOpenImageClicked(view: View, mediaData: ImageContentRenderer.Data) { // It's very strange i can't just access
navigator.openImageViewer(requireActivity(), null, mediaData, view, null) // the app bar using find by id...
private fun trickFindAppBar() : AppBarLayout? {
return activity?.supportFragmentManager?.fragments
?.filterIsInstance<RoomUploadsFragment>()
?.firstOrNull()
?.roomUploadsAppBar
} }
override fun onOpenVideoClicked(view: View, mediaData: VideoContentRenderer.Data) { override fun onOpenImageClicked(view: View, mediaData: ImageContentRenderer.Data) = withState(uploadsViewModel) { state ->
// TODO
// navigator.openVideoViewer(requireActivity(), mediaData, null, ) val inMemory = getItemsArgs(state)
navigator.openImageViewer(requireActivity(), state.roomId, mediaData, view, inMemory) { pairs ->
trickFindAppBar()?.let {
pairs.add(Pair(it, ViewCompat.getTransitionName(it) ?: ""))
}
}
}
private fun getItemsArgs(state: RoomUploadsViewState): List<AttachmentData> {
return state.mediaEvents.mapNotNull {
when (val content = it.contentWithAttachmentContent) {
is MessageImageContent -> {
ImageContentRenderer.Data(
eventId = it.eventId,
filename = content.body,
mimeType = content.mimeType,
url = content.getFileUrl(),
elementToDecrypt = content.encryptedFileInfo?.toElementToDecrypt(),
maxHeight = -1,
maxWidth = -1,
width = null,
height = null
)
}
is MessageVideoContent -> {
val thumbnailData = ImageContentRenderer.Data(
eventId = it.eventId,
filename = content.body,
mimeType = content.mimeType,
url = content.videoInfo?.thumbnailFile?.url
?: content.videoInfo?.thumbnailUrl,
elementToDecrypt = content.videoInfo?.thumbnailFile?.toElementToDecrypt(),
height = content.videoInfo?.height,
maxHeight = -1,
width = content.videoInfo?.width,
maxWidth = -1
)
VideoContentRenderer.Data(
eventId = it.eventId,
filename = content.body,
mimeType = content.mimeType,
url = content.getFileUrl(),
elementToDecrypt = content.encryptedFileInfo?.toElementToDecrypt(),
thumbnailMediaData = thumbnailData
)
}
else -> null
}
}
}
override fun onOpenVideoClicked(view: View, mediaData: VideoContentRenderer.Data) = withState(uploadsViewModel) { state ->
val inMemory = getItemsArgs(state)
navigator.openVideoViewer(requireActivity(), state.roomId, mediaData, view, inMemory) { pairs ->
trickFindAppBar()?.let {
pairs.add(Pair(it, ViewCompat.getTransitionName(it) ?: ""))
}
}
} }
override fun loadMore() { override fun loadMore() {

View file

@ -18,6 +18,7 @@ package im.vector.riotx.features.roomprofile.uploads.media
import android.view.View import android.view.View
import android.widget.ImageView import android.widget.ImageView
import androidx.core.view.ViewCompat
import com.airbnb.epoxy.EpoxyAttribute import com.airbnb.epoxy.EpoxyAttribute
import com.airbnb.epoxy.EpoxyModelClass import com.airbnb.epoxy.EpoxyModelClass
import im.vector.riotx.R import im.vector.riotx.R
@ -37,6 +38,7 @@ abstract class UploadsImageItem : VectorEpoxyModel<UploadsImageItem.Holder>() {
super.bind(holder) super.bind(holder)
holder.view.setOnClickListener { listener?.onItemClicked(holder.imageView, data) } holder.view.setOnClickListener { listener?.onItemClicked(holder.imageView, data) }
imageContentRenderer.render(data, holder.imageView, IMAGE_SIZE_DP) imageContentRenderer.render(data, holder.imageView, IMAGE_SIZE_DP)
ViewCompat.setTransitionName(holder.imageView, "imagePreview_${id()}")
} }
class Holder : VectorEpoxyHolder() { class Holder : VectorEpoxyHolder() {

View file

@ -18,6 +18,7 @@ package im.vector.riotx.features.roomprofile.uploads.media
import android.view.View import android.view.View
import android.widget.ImageView import android.widget.ImageView
import androidx.core.view.ViewCompat
import com.airbnb.epoxy.EpoxyAttribute import com.airbnb.epoxy.EpoxyAttribute
import com.airbnb.epoxy.EpoxyModelClass import com.airbnb.epoxy.EpoxyModelClass
import im.vector.riotx.R import im.vector.riotx.R
@ -38,6 +39,7 @@ abstract class UploadsVideoItem : VectorEpoxyModel<UploadsVideoItem.Holder>() {
super.bind(holder) super.bind(holder)
holder.view.setOnClickListener { listener?.onItemClicked(holder.imageView, data) } holder.view.setOnClickListener { listener?.onItemClicked(holder.imageView, data) }
imageContentRenderer.render(data.thumbnailMediaData, holder.imageView, IMAGE_SIZE_DP) imageContentRenderer.render(data.thumbnailMediaData, holder.imageView, IMAGE_SIZE_DP)
ViewCompat.setTransitionName(holder.imageView, "videoPreview_${id()}")
} }
class Holder : VectorEpoxyHolder() { class Holder : VectorEpoxyHolder() {

View file

@ -8,6 +8,8 @@
<com.google.android.material.appbar.AppBarLayout <com.google.android.material.appbar.AppBarLayout
style="@style/VectorAppBarLayoutStyle" style="@style/VectorAppBarLayoutStyle"
android:id="@+id/roomUploadsAppBar"
android:transitionName="toolbar"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:elevation="4dp"> android:elevation="4dp">