diff --git a/vector/src/main/java/im/vector/riotx/features/media/BaseAttachmentProvider.kt b/vector/src/main/java/im/vector/riotx/features/media/BaseAttachmentProvider.kt new file mode 100644 index 0000000000..d4c41c7cb3 --- /dev/null +++ b/vector/src/main/java/im/vector/riotx/features/media/BaseAttachmentProvider.kt @@ -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(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?) { + 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(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?) { + 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(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?) { + 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 { + 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)) +} diff --git a/vector/src/main/java/im/vector/riotx/features/media/DataAttachmentRoomProvider.kt b/vector/src/main/java/im/vector/riotx/features/media/DataAttachmentRoomProvider.kt new file mode 100644 index 0000000000..cb0039fc7e --- /dev/null +++ b/vector/src/main/java/im/vector/riotx/features/media/DataAttachmentRoomProvider.kt @@ -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, + 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 { + override fun onSuccess(data: File) { + callback(data) + } + + override fun onFailure(failure: Throwable) { + callback(null) + } + } + ) + } +} diff --git a/vector/src/main/java/im/vector/riotx/features/media/RoomAttachmentProvider.kt b/vector/src/main/java/im/vector/riotx/features/media/RoomEventsAttachmentProvider.kt similarity index 53% rename from vector/src/main/java/im/vector/riotx/features/media/RoomAttachmentProvider.kt rename to vector/src/main/java/im/vector/riotx/features/media/RoomEventsAttachmentProvider.kt index f7299bf714..7a7fea6dc4 100644 --- a/vector/src/main/java/im/vector/riotx/features/media/RoomAttachmentProvider.kt +++ b/vector/src/main/java/im/vector/riotx/features/media/RoomEventsAttachmentProvider.kt @@ -17,17 +17,14 @@ package im.vector.riotx.features.media import android.content.Context -import android.graphics.drawable.Drawable import android.view.View -import android.widget.ImageView 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.session.Session 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.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.MessageImageContent 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.internal.crypto.attachments.toElementToDecrypt 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.extensions.localDateTime import java.io.File import javax.inject.Inject -class RoomAttachmentProvider( +class RoomEventsAttachmentProvider( private val attachments: List, private val initialIndex: Int, - private val imageContentRenderer: ImageContentRenderer, - private val videoContentRenderer: VideoContentRenderer, + imageContentRenderer: ImageContentRenderer, private val dateFormatter: VectorDateFormatter, - private val fileService: FileService -) : AttachmentSourceProvider { - - interface InteractionListener { - fun onDismissTapped() - fun onShareTapped() - fun onPlayPause(play: Boolean) - fun videoSeekTo(percent: Int) - } - - var interactionListener: InteractionListener? = null - - private var overlayView: AttachmentOverlayView? = null + fileService: FileService +) : BaseAttachmentProvider(imageContentRenderer, fileService) { override fun getItemCount(): Int { 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(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?) { - 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(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?) { - 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(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?) { - 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 { - 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? { - 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) - } - } + super.overlayViewAtPosition(context, position) val item = attachments[position] val dateString = item.root.localDateTime().let { "${dateFormatter.formatMessageDay(it)} at ${dateFormatter.formatMessageHour(it)} " @@ -241,19 +132,44 @@ class RoomAttachmentProvider( return overlayView } - override fun clear(id: String) { - // TODO("Not yet implemented") + override fun getFileForSharing(position: Int, callback: (File?) -> Unit) { + attachments[position].let { timelineEvent -> + + val messageContent = timelineEvent.root.getClearContent().toModel() + 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 { + 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 vectorDateFormatter: VectorDateFormatter, - private val videoContentRenderer: VideoContentRenderer, private val session: Session ) { - fun createProvider(attachments: List, initialIndex: Int): RoomAttachmentProvider { - return RoomAttachmentProvider(attachments, initialIndex, imageContentRenderer, videoContentRenderer, vectorDateFormatter, session.fileService()) + fun createProvider(attachments: List, initialIndex: Int): RoomEventsAttachmentProvider { + return RoomEventsAttachmentProvider(attachments, initialIndex, imageContentRenderer, vectorDateFormatter, session.fileService()) + } + + fun createProvider(attachments: List, room: Room?, initialIndex: Int): DataAttachmentRoomProvider { + return DataAttachmentRoomProvider(attachments, room, initialIndex, imageContentRenderer, vectorDateFormatter, session.fileService()) } } diff --git a/vector/src/main/java/im/vector/riotx/features/media/VectorAttachmentViewerActivity.kt b/vector/src/main/java/im/vector/riotx/features/media/VectorAttachmentViewerActivity.kt index 10483f3fa9..c0b822c13a 100644 --- a/vector/src/main/java/im/vector/riotx/features/media/VectorAttachmentViewerActivity.kt +++ b/vector/src/main/java/im/vector/riotx/features/media/VectorAttachmentViewerActivity.kt @@ -30,14 +30,6 @@ import androidx.core.view.isInvisible import androidx.core.view.isVisible import androidx.lifecycle.Lifecycle 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.attachmentviewer.AttachmentCommands 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 kotlinx.android.parcel.Parcelize import timber.log.Timber -import java.io.File import javax.inject.Inject import kotlin.system.measureTimeMillis -class VectorAttachmentViewerActivity : AttachmentViewerActivity(), RoomAttachmentProvider.InteractionListener { +class VectorAttachmentViewerActivity : AttachmentViewerActivity(), BaseAttachmentProvider.InteractionListener { @Parcelize data class Args( @@ -69,7 +60,7 @@ class VectorAttachmentViewerActivity : AttachmentViewerActivity(), RoomAttachmen lateinit var sessionHolder: ActiveSessionHolder @Inject - lateinit var dataSourceFactory: RoomAttachmentProviderFactory + lateinit var dataSourceFactory: AttachmentProviderFactory @Inject lateinit var imageContentRenderer: ImageContentRenderer @@ -78,7 +69,8 @@ class VectorAttachmentViewerActivity : AttachmentViewerActivity(), RoomAttachmen private var initialIndex = 0 private var isAnimatingOut = false - private var eventList: List? = null + + var currentSourceProvider: BaseAttachmentProvider? = null override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) @@ -92,13 +84,6 @@ class VectorAttachmentViewerActivity : AttachmentViewerActivity(), RoomAttachmen ThemeUtils.setActivityTheme(this, getOtherThemes()) 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()) { args.sharedTransitionName?.let { @@ -127,14 +112,41 @@ class VectorAttachmentViewerActivity : AttachmentViewerActivity(), RoomAttachmen } } - val sourceProvider = dataSourceFactory.createProvider(events, index) - sourceProvider.interactionListener = this - setSourceProvider(sourceProvider) - if (savedInstanceState == null) { - pager2.setCurrentItem(index, false) - // The page change listener is not notified of the change... - pager2.post { - onSelectedPositionChanged(index) + val session = sessionHolder.getSafeActiveSession() ?: return Unit.also { finish() } + + val room = args.roomId?.let { session.getRoom(it) } + + val inMemoryData = intent.getParcelableArrayListExtra(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 + 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) + } + } + } 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) + } } } @@ -228,14 +240,19 @@ class VectorAttachmentViewerActivity : AttachmentViewerActivity(), RoomAttachmen const val EXTRA_ARGS = "EXTRA_ARGS" const val EXTRA_IMAGE_DATA = "EXTRA_IMAGE_DATA" + const val EXTRA_IN_MEMORY_DATA = "EXTRA_IN_MEMORY_DATA" fun newIntent(context: Context, mediaData: AttachmentData, roomId: String?, eventId: String, + inMemoryData: List?, sharedTransitionName: String?) = Intent(context, VectorAttachmentViewerActivity::class.java).also { it.putExtra(EXTRA_ARGS, Args(roomId, eventId, sharedTransitionName)) 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() { - // Share - eventList?.get(currentPosition)?.let { timelineEvent -> - - val messageContent = timelineEvent.root.getClearContent().toModel() - 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 { - override fun onSuccess(data: File) { - if (lifecycle.currentState.isAtLeast(Lifecycle.State.RESUMED)) { - shareMedia(this@VectorAttachmentViewerActivity, data, getMimeTypeFromUri(this@VectorAttachmentViewerActivity, data.toUri())) - } - } - } - ) + this.currentSourceProvider?.getFileForSharing(currentPosition) { data -> + if (data != null && lifecycle.currentState.isAtLeast(Lifecycle.State.RESUMED)) { + shareMedia(this@VectorAttachmentViewerActivity, data, getMimeTypeFromUri(this@VectorAttachmentViewerActivity, data.toUri())) + } } } } diff --git a/vector/src/main/java/im/vector/riotx/features/navigation/DefaultNavigator.kt b/vector/src/main/java/im/vector/riotx/features/navigation/DefaultNavigator.kt index 2b0b6175f5..8940ac6791 100644 --- a/vector/src/main/java/im/vector/riotx/features/navigation/DefaultNavigator.kt +++ b/vector/src/main/java/im/vector/riotx/features/navigation/DefaultNavigator.kt @@ -246,20 +246,25 @@ class DefaultNavigator @Inject constructor( } override fun openImageViewer(activity: Activity, - roomId: String?, + roomId: String, mediaData: AttachmentData, view: View, + inMemory: List?, options: ((MutableList>) -> 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>() - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { - activity.window.decorView.findViewById(android.R.id.statusBarBackground)?.let { - pairs.add(Pair(it, Window.STATUS_BAR_BACKGROUND_TRANSITION_NAME)) - } - activity.window.decorView.findViewById(android.R.id.navigationBarBackground)?.let { - pairs.add(Pair(it, Window.NAVIGATION_BAR_BACKGROUND_TRANSITION_NAME)) - } + activity.window.decorView.findViewById(android.R.id.statusBarBackground)?.let { + pairs.add(Pair(it, Window.STATUS_BAR_BACKGROUND_TRANSITION_NAME)) } + activity.window.decorView.findViewById(android.R.id.navigationBarBackground)?.let { + pairs.add(Pair(it, Window.NAVIGATION_BAR_BACKGROUND_TRANSITION_NAME)) + } + pairs.add(Pair(view, ViewCompat.getTransitionName(view) ?: "")) options?.invoke(pairs) @@ -284,12 +289,18 @@ class DefaultNavigator @Inject constructor( } override fun openVideoViewer(activity: Activity, - roomId: String?, mediaData: VideoContentRenderer.Data, + roomId: String, mediaData: VideoContentRenderer.Data, view: View, + inMemory: List?, options: ((MutableList>) -> Unit)?) { // val intent = VideoMediaViewerActivity.newIntent(activity, mediaData) // 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>() if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { activity.window.decorView.findViewById(android.R.id.statusBarBackground)?.let { diff --git a/vector/src/main/java/im/vector/riotx/features/navigation/Navigator.kt b/vector/src/main/java/im/vector/riotx/features/navigation/Navigator.kt index f1be6e072b..f925344570 100644 --- a/vector/src/main/java/im/vector/riotx/features/navigation/Navigator.kt +++ b/vector/src/main/java/im/vector/riotx/features/navigation/Navigator.kt @@ -92,13 +92,15 @@ interface Navigator { fun openRoomWidget(context: Context, roomId: String, widget: Widget) fun openImageViewer(activity: Activity, - roomId: String?, + roomId: String, mediaData: AttachmentData, view: View, + inMemory: List? = null, options: ((MutableList>) -> Unit)?) fun openVideoViewer(activity: Activity, - roomId: String?, mediaData: VideoContentRenderer.Data, + roomId: String, mediaData: VideoContentRenderer.Data, view: View, + inMemory: List? = null, options: ((MutableList>) -> Unit)?) } diff --git a/vector/src/main/java/im/vector/riotx/features/roomprofile/uploads/media/RoomUploadsMediaFragment.kt b/vector/src/main/java/im/vector/riotx/features/roomprofile/uploads/media/RoomUploadsMediaFragment.kt index a5f126875a..e0758c7d72 100644 --- a/vector/src/main/java/im/vector/riotx/features/roomprofile/uploads/media/RoomUploadsMediaFragment.kt +++ b/vector/src/main/java/im/vector/riotx/features/roomprofile/uploads/media/RoomUploadsMediaFragment.kt @@ -20,23 +20,34 @@ import android.os.Bundle import android.util.DisplayMetrics import android.view.View import androidx.core.content.ContextCompat +import androidx.core.util.Pair +import androidx.core.view.ViewCompat import androidx.recyclerview.widget.GridLayoutManager import com.airbnb.mvrx.Fail import com.airbnb.mvrx.Loading import com.airbnb.mvrx.Success import com.airbnb.mvrx.parentFragmentViewModel 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.core.extensions.cleanup import im.vector.riotx.core.extensions.trackItemsVisibilityChange import im.vector.riotx.core.platform.StateView import im.vector.riotx.core.platform.VectorBaseFragment 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.VideoContentRenderer 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.RoomUploadsViewState import kotlinx.android.synthetic.main.fragment_generic_state_view_recycler.* +import kotlinx.android.synthetic.main.fragment_room_uploads.* import javax.inject.Inject class RoomUploadsMediaFragment @Inject constructor( @@ -76,13 +87,75 @@ class RoomUploadsMediaFragment @Inject constructor( controller.listener = null } - override fun onOpenImageClicked(view: View, mediaData: ImageContentRenderer.Data) { - navigator.openImageViewer(requireActivity(), null, mediaData, view, null) + // It's very strange i can't just access + // the app bar using find by id... + private fun trickFindAppBar() : AppBarLayout? { + return activity?.supportFragmentManager?.fragments + ?.filterIsInstance() + ?.firstOrNull() + ?.roomUploadsAppBar } - override fun onOpenVideoClicked(view: View, mediaData: VideoContentRenderer.Data) { - // TODO - // navigator.openVideoViewer(requireActivity(), mediaData, null, ) + override fun onOpenImageClicked(view: View, mediaData: ImageContentRenderer.Data) = withState(uploadsViewModel) { state -> + + 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 { + 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() { diff --git a/vector/src/main/java/im/vector/riotx/features/roomprofile/uploads/media/UploadsImageItem.kt b/vector/src/main/java/im/vector/riotx/features/roomprofile/uploads/media/UploadsImageItem.kt index 98026901cc..f994ad0110 100644 --- a/vector/src/main/java/im/vector/riotx/features/roomprofile/uploads/media/UploadsImageItem.kt +++ b/vector/src/main/java/im/vector/riotx/features/roomprofile/uploads/media/UploadsImageItem.kt @@ -18,6 +18,7 @@ package im.vector.riotx.features.roomprofile.uploads.media import android.view.View import android.widget.ImageView +import androidx.core.view.ViewCompat import com.airbnb.epoxy.EpoxyAttribute import com.airbnb.epoxy.EpoxyModelClass import im.vector.riotx.R @@ -37,6 +38,7 @@ abstract class UploadsImageItem : VectorEpoxyModel() { super.bind(holder) holder.view.setOnClickListener { listener?.onItemClicked(holder.imageView, data) } imageContentRenderer.render(data, holder.imageView, IMAGE_SIZE_DP) + ViewCompat.setTransitionName(holder.imageView, "imagePreview_${id()}") } class Holder : VectorEpoxyHolder() { diff --git a/vector/src/main/java/im/vector/riotx/features/roomprofile/uploads/media/UploadsVideoItem.kt b/vector/src/main/java/im/vector/riotx/features/roomprofile/uploads/media/UploadsVideoItem.kt index 82e33b76da..1c9ab4ae74 100644 --- a/vector/src/main/java/im/vector/riotx/features/roomprofile/uploads/media/UploadsVideoItem.kt +++ b/vector/src/main/java/im/vector/riotx/features/roomprofile/uploads/media/UploadsVideoItem.kt @@ -18,6 +18,7 @@ package im.vector.riotx.features.roomprofile.uploads.media import android.view.View import android.widget.ImageView +import androidx.core.view.ViewCompat import com.airbnb.epoxy.EpoxyAttribute import com.airbnb.epoxy.EpoxyModelClass import im.vector.riotx.R @@ -38,6 +39,7 @@ abstract class UploadsVideoItem : VectorEpoxyModel() { super.bind(holder) holder.view.setOnClickListener { listener?.onItemClicked(holder.imageView, data) } imageContentRenderer.render(data.thumbnailMediaData, holder.imageView, IMAGE_SIZE_DP) + ViewCompat.setTransitionName(holder.imageView, "videoPreview_${id()}") } class Holder : VectorEpoxyHolder() { diff --git a/vector/src/main/res/layout/fragment_room_uploads.xml b/vector/src/main/res/layout/fragment_room_uploads.xml index 5e289d4724..f5d3658ee5 100644 --- a/vector/src/main/res/layout/fragment_room_uploads.xml +++ b/vector/src/main/res/layout/fragment_room_uploads.xml @@ -8,6 +8,8 @@