Basic Video Support
This commit is contained in:
parent
cc5df1e1d5
commit
a1db8653ab
@ -62,6 +62,9 @@ dependencies {
|
|||||||
implementation 'com.github.chrisbanes:PhotoView:2.0.0'
|
implementation 'com.github.chrisbanes:PhotoView:2.0.0'
|
||||||
implementation "com.github.bumptech.glide:glide:4.10.0"
|
implementation "com.github.bumptech.glide:glide:4.10.0"
|
||||||
|
|
||||||
|
implementation 'io.reactivex.rxjava2:rxkotlin:2.3.0'
|
||||||
|
implementation 'io.reactivex.rxjava2:rxandroid:2.1.1'
|
||||||
|
|
||||||
implementation fileTree(dir: "libs", include: ["*.jar"])
|
implementation fileTree(dir: "libs", include: ["*.jar"])
|
||||||
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
|
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
|
||||||
implementation 'androidx.core:core-ktx:1.3.0'
|
implementation 'androidx.core:core-ktx:1.3.0'
|
||||||
|
@ -0,0 +1,26 @@
|
|||||||
|
/*
|
||||||
|
* 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.attachmentviewer
|
||||||
|
|
||||||
|
sealed class AttachmentEvents {
|
||||||
|
data class VideoEvent(val isPlaying: Boolean, val progress: Int, val duration: Int) : AttachmentEvents()
|
||||||
|
}
|
||||||
|
|
||||||
|
interface AttachmentEventListener {
|
||||||
|
|
||||||
|
fun onEvent(event: AttachmentEvents)
|
||||||
|
}
|
@ -22,7 +22,7 @@ import android.view.View
|
|||||||
sealed class AttachmentInfo {
|
sealed class AttachmentInfo {
|
||||||
data class Image(val url: String, val data: Any?) : AttachmentInfo()
|
data class Image(val url: String, val data: Any?) : AttachmentInfo()
|
||||||
data class AnimatedImage(val url: String, val data: Any?) : AttachmentInfo()
|
data class AnimatedImage(val url: String, val data: Any?) : AttachmentInfo()
|
||||||
data class Video(val url: String, val data: Any) : AttachmentInfo()
|
data class Video(val url: String, val data: Any, val thumbnail: Image?) : AttachmentInfo()
|
||||||
data class Audio(val url: String, val data: Any) : AttachmentInfo()
|
data class Audio(val url: String, val data: Any) : AttachmentInfo()
|
||||||
data class File(val url: String, val data: Any) : AttachmentInfo()
|
data class File(val url: String, val data: Any) : AttachmentInfo()
|
||||||
|
|
||||||
@ -40,5 +40,7 @@ interface AttachmentSourceProvider {
|
|||||||
|
|
||||||
fun loadImage(holder: AnimatedImageViewHolder, info: AttachmentInfo.AnimatedImage)
|
fun loadImage(holder: AnimatedImageViewHolder, info: AttachmentInfo.AnimatedImage)
|
||||||
|
|
||||||
|
fun loadVideo(holder: VideoViewHolder, info: AttachmentInfo.Video)
|
||||||
|
|
||||||
fun overlayViewAtPosition(context: Context, position: Int) : View?
|
fun overlayViewAtPosition(context: Context, position: Int) : View?
|
||||||
}
|
}
|
||||||
|
@ -34,15 +34,17 @@ import androidx.core.view.updatePadding
|
|||||||
import androidx.transition.TransitionManager
|
import androidx.transition.TransitionManager
|
||||||
import androidx.viewpager2.widget.ViewPager2
|
import androidx.viewpager2.widget.ViewPager2
|
||||||
import kotlinx.android.synthetic.main.activity_attachment_viewer.*
|
import kotlinx.android.synthetic.main.activity_attachment_viewer.*
|
||||||
|
import java.lang.ref.WeakReference
|
||||||
import kotlin.math.abs
|
import kotlin.math.abs
|
||||||
|
|
||||||
abstract class AttachmentViewerActivity : AppCompatActivity() {
|
abstract class AttachmentViewerActivity : AppCompatActivity(), AttachmentEventListener {
|
||||||
|
|
||||||
lateinit var pager2: ViewPager2
|
lateinit var pager2: ViewPager2
|
||||||
lateinit var imageTransitionView: ImageView
|
lateinit var imageTransitionView: ImageView
|
||||||
lateinit var transitionImageContainer: ViewGroup
|
lateinit var transitionImageContainer: ViewGroup
|
||||||
|
|
||||||
var topInset = 0
|
var topInset = 0
|
||||||
|
var bottomInset = 0
|
||||||
var systemUiVisibility = true
|
var systemUiVisibility = true
|
||||||
|
|
||||||
private var overlayView: View? = null
|
private var overlayView: View? = null
|
||||||
@ -50,7 +52,7 @@ abstract class AttachmentViewerActivity : AppCompatActivity() {
|
|||||||
if (value == overlayView) return
|
if (value == overlayView) return
|
||||||
overlayView?.let { rootContainer.removeView(it) }
|
overlayView?.let { rootContainer.removeView(it) }
|
||||||
rootContainer.addView(value)
|
rootContainer.addView(value)
|
||||||
value?.updatePadding(top = topInset)
|
value?.updatePadding(top = topInset, bottom = bottomInset)
|
||||||
field = value
|
field = value
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -109,8 +111,7 @@ abstract class AttachmentViewerActivity : AppCompatActivity() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun onPageSelected(position: Int) {
|
override fun onPageSelected(position: Int) {
|
||||||
currentPosition = position
|
onSelectedPositionChanged(position)
|
||||||
overlayView = attachmentsAdapter.attachmentSourceProvider?.overlayViewAtPosition(this@AttachmentViewerActivity, position)
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -121,12 +122,27 @@ abstract class AttachmentViewerActivity : AppCompatActivity() {
|
|||||||
scaleDetector = createScaleGestureDetector()
|
scaleDetector = createScaleGestureDetector()
|
||||||
|
|
||||||
ViewCompat.setOnApplyWindowInsetsListener(rootContainer) { _, insets ->
|
ViewCompat.setOnApplyWindowInsetsListener(rootContainer) { _, insets ->
|
||||||
overlayView?.updatePadding(top = insets.systemWindowInsetTop)
|
overlayView?.updatePadding(top = insets.systemWindowInsetTop, bottom = insets.systemWindowInsetBottom)
|
||||||
topInset = insets.systemWindowInsetTop
|
topInset = insets.systemWindowInsetTop
|
||||||
|
bottomInset = insets.systemWindowInsetBottom
|
||||||
insets
|
insets
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun onSelectedPositionChanged(position: Int) {
|
||||||
|
attachmentsAdapter.recyclerView?.findViewHolderForAdapterPosition(currentPosition)?.let {
|
||||||
|
(it as? BaseViewHolder)?.onSelected(false)
|
||||||
|
}
|
||||||
|
attachmentsAdapter.recyclerView?.findViewHolderForAdapterPosition(position)?.let {
|
||||||
|
(it as? BaseViewHolder)?.onSelected(true)
|
||||||
|
if (it is VideoViewHolder) {
|
||||||
|
it.eventListener = WeakReference(this)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
currentPosition = position
|
||||||
|
overlayView = attachmentsAdapter.attachmentSourceProvider?.overlayViewAtPosition(this@AttachmentViewerActivity, position)
|
||||||
|
}
|
||||||
|
|
||||||
override fun dispatchTouchEvent(ev: MotionEvent): Boolean {
|
override fun dispatchTouchEvent(ev: MotionEvent): Boolean {
|
||||||
// The zoomable view is configured to disallow interception when image is zoomed
|
// The zoomable view is configured to disallow interception when image is zoomed
|
||||||
|
|
||||||
@ -264,6 +280,12 @@ abstract class AttachmentViewerActivity : AppCompatActivity() {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
override fun onEvent(event: AttachmentEvents) {
|
||||||
|
if (overlayView is AttachmentEventListener) {
|
||||||
|
(overlayView as? AttachmentEventListener)?.onEvent(event)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
protected open fun shouldAnimateDismiss(): Boolean = true
|
protected open fun shouldAnimateDismiss(): Boolean = true
|
||||||
|
|
||||||
protected open fun animateClose() {
|
protected open fun animateClose() {
|
||||||
|
@ -24,7 +24,11 @@ import androidx.recyclerview.widget.RecyclerView
|
|||||||
abstract class BaseViewHolder constructor(itemView: View) :
|
abstract class BaseViewHolder constructor(itemView: View) :
|
||||||
RecyclerView.ViewHolder(itemView) {
|
RecyclerView.ViewHolder(itemView) {
|
||||||
|
|
||||||
abstract fun bind(attachmentInfo: AttachmentInfo)
|
open fun bind(attachmentInfo: AttachmentInfo) {}
|
||||||
|
open fun onRecycled() {}
|
||||||
|
open fun onAttached() {}
|
||||||
|
open fun onDetached() {}
|
||||||
|
open fun onSelected(selected: Boolean) {}
|
||||||
}
|
}
|
||||||
|
|
||||||
class AttachmentViewHolder constructor(itemView: View) :
|
class AttachmentViewHolder constructor(itemView: View) :
|
||||||
@ -59,6 +63,7 @@ class AttachmentsAdapter() : RecyclerView.Adapter<BaseViewHolder>() {
|
|||||||
return when (viewType) {
|
return when (viewType) {
|
||||||
R.layout.item_image_attachment -> ZoomableImageViewHolder(itemView)
|
R.layout.item_image_attachment -> ZoomableImageViewHolder(itemView)
|
||||||
R.layout.item_animated_image_attachment -> AnimatedImageViewHolder(itemView)
|
R.layout.item_animated_image_attachment -> AnimatedImageViewHolder(itemView)
|
||||||
|
R.layout.item_video_attachment -> VideoViewHolder(itemView)
|
||||||
else -> AttachmentViewHolder(itemView)
|
else -> AttachmentViewHolder(itemView)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -88,11 +93,26 @@ class AttachmentsAdapter() : RecyclerView.Adapter<BaseViewHolder>() {
|
|||||||
is AttachmentInfo.AnimatedImage -> {
|
is AttachmentInfo.AnimatedImage -> {
|
||||||
attachmentSourceProvider?.loadImage(holder as AnimatedImageViewHolder, it)
|
attachmentSourceProvider?.loadImage(holder as AnimatedImageViewHolder, it)
|
||||||
}
|
}
|
||||||
|
is AttachmentInfo.Video -> {
|
||||||
|
attachmentSourceProvider?.loadVideo(holder as VideoViewHolder, it)
|
||||||
|
}
|
||||||
else -> {}
|
else -> {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun onViewAttachedToWindow(holder: BaseViewHolder) {
|
||||||
|
holder.onAttached()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onViewRecycled(holder: BaseViewHolder) {
|
||||||
|
holder.onRecycled()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onViewDetachedFromWindow(holder: BaseViewHolder) {
|
||||||
|
holder.onDetached()
|
||||||
|
}
|
||||||
|
|
||||||
fun isScaled(position: Int): Boolean {
|
fun isScaled(position: Int): Boolean {
|
||||||
val holder = recyclerView?.findViewHolderForAdapterPosition(position)
|
val holder = recyclerView?.findViewHolderForAdapterPosition(position)
|
||||||
if (holder is ZoomableImageViewHolder) {
|
if (holder is ZoomableImageViewHolder) {
|
||||||
|
@ -0,0 +1,133 @@
|
|||||||
|
/*
|
||||||
|
* 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.attachmentviewer
|
||||||
|
|
||||||
|
import android.util.Log
|
||||||
|
import android.view.View
|
||||||
|
import android.widget.ImageView
|
||||||
|
import android.widget.ProgressBar
|
||||||
|
import android.widget.TextView
|
||||||
|
import android.widget.VideoView
|
||||||
|
import androidx.core.view.isVisible
|
||||||
|
import io.reactivex.Observable
|
||||||
|
import io.reactivex.android.schedulers.AndroidSchedulers
|
||||||
|
import io.reactivex.disposables.Disposable
|
||||||
|
import java.io.File
|
||||||
|
import java.lang.ref.WeakReference
|
||||||
|
import java.util.concurrent.TimeUnit
|
||||||
|
|
||||||
|
// TODO, it would be probably better to use a unique media player
|
||||||
|
// for better customization and control
|
||||||
|
// But for now VideoView is enough, it released player when detached, we use a timer to update progress
|
||||||
|
class VideoViewHolder constructor(itemView: View) :
|
||||||
|
BaseViewHolder(itemView) {
|
||||||
|
|
||||||
|
private var isSelected = false
|
||||||
|
private var mVideoPath: String? = null
|
||||||
|
private var progressDisposable: Disposable? = null
|
||||||
|
|
||||||
|
var eventListener: WeakReference<AttachmentEventListener>? = null
|
||||||
|
|
||||||
|
// interface Target {
|
||||||
|
// fun onResourceLoading(progress: Int, total: Int)
|
||||||
|
// fun onLoadFailed()
|
||||||
|
// fun onResourceReady(file: File)
|
||||||
|
// fun onThumbnailReady(thumbnail: Drawable?)
|
||||||
|
// }
|
||||||
|
|
||||||
|
init {
|
||||||
|
}
|
||||||
|
|
||||||
|
val thumbnailImage: ImageView = itemView.findViewById(R.id.videoThumbnailImage)
|
||||||
|
val videoView: VideoView = itemView.findViewById(R.id.videoView)
|
||||||
|
val loaderProgressBar: ProgressBar = itemView.findViewById(R.id.videoLoaderProgress)
|
||||||
|
val videoControlIcon: ImageView = itemView.findViewById(R.id.videoControlIcon)
|
||||||
|
val errorTextView: TextView = itemView.findViewById(R.id.videoMediaViewerErrorView)
|
||||||
|
|
||||||
|
// val videoTarget = object : Target {
|
||||||
|
// override fun onResourceLoading(progress: Int, total: Int) {
|
||||||
|
// videoView.isVisible = false
|
||||||
|
// loaderProgressBar.isVisible = true
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// override fun onLoadFailed() {
|
||||||
|
// loaderProgressBar.isVisible = false
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// override fun onResourceReady(file: File) {
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// override fun onThumbnailReady(thumbnail: Drawable?) {
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
override fun onRecycled() {
|
||||||
|
super.onRecycled()
|
||||||
|
progressDisposable?.dispose()
|
||||||
|
progressDisposable = null
|
||||||
|
mVideoPath = null
|
||||||
|
}
|
||||||
|
|
||||||
|
fun videoReady(file: File) {
|
||||||
|
mVideoPath = file.path
|
||||||
|
if (isSelected) {
|
||||||
|
startPlaying()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onSelected(selected: Boolean) {
|
||||||
|
if (!selected) {
|
||||||
|
if (videoView.isPlaying) {
|
||||||
|
videoView.stopPlayback()
|
||||||
|
progressDisposable?.dispose()
|
||||||
|
progressDisposable = null
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (mVideoPath != null) {
|
||||||
|
startPlaying()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
isSelected = true
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun startPlaying() {
|
||||||
|
thumbnailImage.isVisible = false
|
||||||
|
loaderProgressBar.isVisible = false
|
||||||
|
videoView.isVisible = true
|
||||||
|
|
||||||
|
videoView.setOnPreparedListener {
|
||||||
|
progressDisposable?.dispose()
|
||||||
|
progressDisposable = Observable.interval(100, TimeUnit.MILLISECONDS)
|
||||||
|
.timeInterval()
|
||||||
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
|
.subscribe {
|
||||||
|
val duration = videoView.duration
|
||||||
|
val progress = videoView.currentPosition
|
||||||
|
val isPlaying = videoView.isPlaying
|
||||||
|
Log.v("FOO", "isPlaying $isPlaying $progress/$duration")
|
||||||
|
eventListener?.get()?.onEvent(AttachmentEvents.VideoEvent(isPlaying, progress, duration))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
videoView.setVideoPath(mVideoPath)
|
||||||
|
videoView.start()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun bind(attachmentInfo: AttachmentInfo) {
|
||||||
|
Log.v("FOO", "")
|
||||||
|
}
|
||||||
|
}
|
@ -1,7 +1,8 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent">
|
android:layout_height="match_parent"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools">
|
||||||
|
|
||||||
<ImageView
|
<ImageView
|
||||||
android:id="@+id/videoThumbnailImage"
|
android:id="@+id/videoThumbnailImage"
|
||||||
@ -14,13 +15,35 @@
|
|||||||
android:id="@+id/videoView"
|
android:id="@+id/videoView"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:layout_gravity="center" />
|
android:layout_centerInParent="true" />
|
||||||
|
|
||||||
<ImageView
|
<ImageView
|
||||||
android:id="@+id/videoControlIcon"
|
android:id="@+id/videoControlIcon"
|
||||||
android:layout_centerInParent="true"
|
android:layout_centerInParent="true"
|
||||||
|
android:visibility="gone"
|
||||||
|
tools:visibility="visible"
|
||||||
android:layout_width="44dp"
|
android:layout_width="44dp"
|
||||||
android:layout_height="44dp"
|
android:layout_height="44dp"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<ProgressBar
|
||||||
|
android:layout_centerInParent="true"
|
||||||
|
android:id="@+id/videoLoaderProgress"
|
||||||
|
style="?android:attr/progressBarStyle"
|
||||||
|
android:layout_width="40dp"
|
||||||
|
android:layout_height="40dp"
|
||||||
|
android:visibility="invisible"
|
||||||
|
tools:visibility="visible" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/videoMediaViewerErrorView"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_centerInParent="true"
|
||||||
|
android:layout_margin="16dp"
|
||||||
|
android:textSize="16sp"
|
||||||
|
android:visibility="gone"
|
||||||
|
tools:text="Error"
|
||||||
|
tools:visibility="visible" />
|
||||||
|
|
||||||
</RelativeLayout>
|
</RelativeLayout>
|
||||||
|
@ -40,5 +40,5 @@ interface TimelineService {
|
|||||||
|
|
||||||
fun getTimeLineEventLive(eventId: String): LiveData<Optional<TimelineEvent>>
|
fun getTimeLineEventLive(eventId: String): LiveData<Optional<TimelineEvent>>
|
||||||
|
|
||||||
fun getAttachementMessages() : List<TimelineEvent>
|
fun getAttachmentMessages() : List<TimelineEvent>
|
||||||
}
|
}
|
||||||
|
@ -22,6 +22,7 @@ import com.squareup.inject.assisted.Assisted
|
|||||||
import com.squareup.inject.assisted.AssistedInject
|
import com.squareup.inject.assisted.AssistedInject
|
||||||
import com.zhuinden.monarchy.Monarchy
|
import com.zhuinden.monarchy.Monarchy
|
||||||
import im.vector.matrix.android.api.session.events.model.isImageMessage
|
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.room.timeline.Timeline
|
import im.vector.matrix.android.api.session.room.timeline.Timeline
|
||||||
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.api.session.room.timeline.TimelineService
|
import im.vector.matrix.android.api.session.room.timeline.TimelineService
|
||||||
@ -94,14 +95,14 @@ internal class DefaultTimelineService @AssistedInject constructor(@Assisted priv
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getAttachementMessages(): List<TimelineEvent> {
|
override fun getAttachmentMessages(): List<TimelineEvent> {
|
||||||
// TODO pretty bad query.. maybe we should denormalize clear type in base?
|
// TODO pretty bad query.. maybe we should denormalize clear type in base?
|
||||||
return doWithRealm(monarchy.realmConfiguration) { realm ->
|
return doWithRealm(monarchy.realmConfiguration) { realm ->
|
||||||
realm.where<TimelineEventEntity>()
|
realm.where<TimelineEventEntity>()
|
||||||
.equalTo(TimelineEventEntityFields.ROOM_ID, roomId)
|
.equalTo(TimelineEventEntityFields.ROOM_ID, roomId)
|
||||||
.sort(TimelineEventEntityFields.DISPLAY_INDEX, Sort.ASCENDING)
|
.sort(TimelineEventEntityFields.DISPLAY_INDEX, Sort.ASCENDING)
|
||||||
.findAll()
|
.findAll()
|
||||||
?.mapNotNull { timelineEventMapper.map(it).takeIf { it.root.isImageMessage() } }
|
?.mapNotNull { timelineEventMapper.map(it).takeIf { it.root.isImageMessage() || it.root.isVideoMessage() } }
|
||||||
?: emptyList()
|
?: emptyList()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1178,7 +1178,10 @@ class RoomDetailFragment @Inject constructor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun onVideoMessageClicked(messageVideoContent: MessageVideoContent, mediaData: VideoContentRenderer.Data, view: View) {
|
override fun onVideoMessageClicked(messageVideoContent: MessageVideoContent, mediaData: VideoContentRenderer.Data, view: View) {
|
||||||
navigator.openVideoViewer(requireActivity(), mediaData)
|
navigator.openVideoViewer(requireActivity(), roomDetailArgs.roomId, mediaData, view) { pairs ->
|
||||||
|
pairs.add(Pair(roomToolbar, ViewCompat.getTransitionName(roomToolbar) ?: ""))
|
||||||
|
pairs.add(Pair(composerLayout, ViewCompat.getTransitionName(composerLayout) ?: ""))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// override fun onFileMessageClicked(eventId: String, messageFileContent: MessageFileContent) {
|
// override fun onFileMessageClicked(eventId: String, messageFileContent: MessageFileContent) {
|
||||||
|
@ -337,7 +337,7 @@ class MessageItemFactory @Inject constructor(
|
|||||||
.playable(true)
|
.playable(true)
|
||||||
.highlighted(highlight)
|
.highlighted(highlight)
|
||||||
.mediaData(thumbnailData)
|
.mediaData(thumbnailData)
|
||||||
.clickListener { view -> callback?.onVideoMessageClicked(messageContent, videoData, view) }
|
.clickListener { view -> callback?.onVideoMessageClicked(messageContent, videoData, view.findViewById(R.id.messageThumbnailView)) }
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun buildItemForTextContent(messageContent: MessageTextContent,
|
private fun buildItemForTextContent(messageContent: MessageTextContent,
|
||||||
|
@ -21,20 +21,28 @@ import android.graphics.Color
|
|||||||
import android.util.AttributeSet
|
import android.util.AttributeSet
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.widget.ImageView
|
import android.widget.ImageView
|
||||||
|
import android.widget.SeekBar
|
||||||
import android.widget.TextView
|
import android.widget.TextView
|
||||||
import androidx.constraintlayout.widget.ConstraintLayout
|
import androidx.constraintlayout.widget.ConstraintLayout
|
||||||
|
import androidx.constraintlayout.widget.Group
|
||||||
import im.vector.riotx.R
|
import im.vector.riotx.R
|
||||||
|
import im.vector.riotx.attachmentviewer.AttachmentEventListener
|
||||||
|
import im.vector.riotx.attachmentviewer.AttachmentEvents
|
||||||
|
|
||||||
class AttachmentOverlayView @JvmOverloads constructor(
|
class AttachmentOverlayView @JvmOverloads constructor(
|
||||||
context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
|
context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
|
||||||
) : ConstraintLayout(context, attrs, defStyleAttr) {
|
) : ConstraintLayout(context, attrs, defStyleAttr), AttachmentEventListener {
|
||||||
|
|
||||||
var onShareCallback: (() -> Unit) ? = null
|
var onShareCallback: (() -> Unit)? = null
|
||||||
var onBack: (() -> Unit) ? = null
|
var onBack: (() -> Unit)? = null
|
||||||
|
|
||||||
private val counterTextView: TextView
|
private val counterTextView: TextView
|
||||||
private val infoTextView: TextView
|
private val infoTextView: TextView
|
||||||
private val shareImage: ImageView
|
private val shareImage: ImageView
|
||||||
|
private val overlayPlayPauseButton: ImageView
|
||||||
|
private val overlaySeekBar: SeekBar
|
||||||
|
|
||||||
|
val videoControlsGroup: Group
|
||||||
|
|
||||||
init {
|
init {
|
||||||
View.inflate(context, R.layout.merge_image_attachment_overlay, this)
|
View.inflate(context, R.layout.merge_image_attachment_overlay, this)
|
||||||
@ -42,14 +50,29 @@ class AttachmentOverlayView @JvmOverloads constructor(
|
|||||||
counterTextView = findViewById(R.id.overlayCounterText)
|
counterTextView = findViewById(R.id.overlayCounterText)
|
||||||
infoTextView = findViewById(R.id.overlayInfoText)
|
infoTextView = findViewById(R.id.overlayInfoText)
|
||||||
shareImage = findViewById(R.id.overlayShareButton)
|
shareImage = findViewById(R.id.overlayShareButton)
|
||||||
|
videoControlsGroup = findViewById(R.id.overlayVideoControlsGroup)
|
||||||
|
overlayPlayPauseButton = findViewById(R.id.overlayPlayPauseButton)
|
||||||
|
overlaySeekBar = findViewById(R.id.overlaySeekBar)
|
||||||
|
|
||||||
|
overlaySeekBar.isEnabled = false
|
||||||
findViewById<ImageView>(R.id.overlayBackButton).setOnClickListener {
|
findViewById<ImageView>(R.id.overlayBackButton).setOnClickListener {
|
||||||
onBack?.invoke()
|
onBack?.invoke()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun updateWith(counter: String, senderInfo : String) {
|
fun updateWith(counter: String, senderInfo: String) {
|
||||||
counterTextView.text = counter
|
counterTextView.text = counter
|
||||||
infoTextView.text = senderInfo
|
infoTextView.text = senderInfo
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun onEvent(event: AttachmentEvents) {
|
||||||
|
when (event) {
|
||||||
|
is AttachmentEvents.VideoEvent -> {
|
||||||
|
overlayPlayPauseButton.setImageResource(if (!event.isPlaying) R.drawable.ic_play_arrow else R.drawable.ic_pause)
|
||||||
|
val safeDuration = (if (event.duration == 0) 100 else event.duration).toFloat()
|
||||||
|
val percent = ((event.progress / safeDuration) * 100f).toInt().coerceAtMost(100)
|
||||||
|
overlaySeekBar.progress = percent
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -44,21 +44,29 @@ import java.io.File
|
|||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
import kotlin.math.min
|
import kotlin.math.min
|
||||||
|
|
||||||
|
interface AttachmentData : Parcelable {
|
||||||
|
val eventId: String
|
||||||
|
val filename: String
|
||||||
|
val mimeType: String?
|
||||||
|
val url: String?
|
||||||
|
val elementToDecrypt: ElementToDecrypt?
|
||||||
|
}
|
||||||
|
|
||||||
class ImageContentRenderer @Inject constructor(private val activeSessionHolder: ActiveSessionHolder,
|
class ImageContentRenderer @Inject constructor(private val activeSessionHolder: ActiveSessionHolder,
|
||||||
private val dimensionConverter: DimensionConverter) {
|
private val dimensionConverter: DimensionConverter) {
|
||||||
|
|
||||||
@Parcelize
|
@Parcelize
|
||||||
data class Data(
|
data class Data(
|
||||||
val eventId: String,
|
override val eventId: String,
|
||||||
val filename: String,
|
override val filename: String,
|
||||||
val mimeType: String?,
|
override val mimeType: String?,
|
||||||
val url: String?,
|
override val url: String?,
|
||||||
val elementToDecrypt: ElementToDecrypt?,
|
override val elementToDecrypt: ElementToDecrypt?,
|
||||||
val height: Int?,
|
val height: Int?,
|
||||||
val maxHeight: Int,
|
val maxHeight: Int,
|
||||||
val width: Int?,
|
val width: Int?,
|
||||||
val maxWidth: Int
|
val maxWidth: Int
|
||||||
) : Parcelable {
|
) : AttachmentData {
|
||||||
|
|
||||||
fun isLocalFile() = url.isLocalFile()
|
fun isLocalFile() = url.isLocalFile()
|
||||||
}
|
}
|
||||||
|
@ -19,9 +19,18 @@ package im.vector.riotx.features.media
|
|||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.graphics.drawable.Drawable
|
import android.graphics.drawable.Drawable
|
||||||
import android.view.View
|
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.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.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.MessageContent
|
||||||
|
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.MessageWithAttachmentContent
|
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.model.message.getFileUrl
|
||||||
import im.vector.matrix.android.api.session.room.timeline.TimelineEvent
|
import im.vector.matrix.android.api.session.room.timeline.TimelineEvent
|
||||||
@ -29,16 +38,20 @@ import im.vector.matrix.android.internal.crypto.attachments.toElementToDecrypt
|
|||||||
import im.vector.riotx.attachmentviewer.AnimatedImageViewHolder
|
import im.vector.riotx.attachmentviewer.AnimatedImageViewHolder
|
||||||
import im.vector.riotx.attachmentviewer.AttachmentInfo
|
import im.vector.riotx.attachmentviewer.AttachmentInfo
|
||||||
import im.vector.riotx.attachmentviewer.AttachmentSourceProvider
|
import im.vector.riotx.attachmentviewer.AttachmentSourceProvider
|
||||||
|
import im.vector.riotx.attachmentviewer.VideoViewHolder
|
||||||
import im.vector.riotx.attachmentviewer.ZoomableImageViewHolder
|
import im.vector.riotx.attachmentviewer.ZoomableImageViewHolder
|
||||||
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 javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
class RoomAttachmentProvider(
|
class RoomAttachmentProvider(
|
||||||
private val attachments: List<TimelineEvent>,
|
private val attachments: List<TimelineEvent>,
|
||||||
private val initialIndex: Int,
|
private val initialIndex: Int,
|
||||||
private val imageContentRenderer: ImageContentRenderer,
|
private val imageContentRenderer: ImageContentRenderer,
|
||||||
private val dateFormatter: VectorDateFormatter
|
private val videoContentRenderer: VideoContentRenderer,
|
||||||
|
private val dateFormatter: VectorDateFormatter,
|
||||||
|
private val fileService: FileService
|
||||||
) : AttachmentSourceProvider {
|
) : AttachmentSourceProvider {
|
||||||
|
|
||||||
interface InteractionListener {
|
interface InteractionListener {
|
||||||
@ -57,28 +70,66 @@ class RoomAttachmentProvider(
|
|||||||
override fun getAttachmentInfoAt(position: Int): AttachmentInfo {
|
override fun getAttachmentInfoAt(position: Int): AttachmentInfo {
|
||||||
return attachments[position].let {
|
return attachments[position].let {
|
||||||
val content = it.root.getClearContent().toModel<MessageContent>() as? MessageWithAttachmentContent
|
val content = it.root.getClearContent().toModel<MessageContent>() as? MessageWithAttachmentContent
|
||||||
|
if (content is MessageImageContent) {
|
||||||
val data = ImageContentRenderer.Data(
|
val data = ImageContentRenderer.Data(
|
||||||
eventId = it.eventId,
|
eventId = it.eventId,
|
||||||
filename = content?.body ?: "",
|
filename = content.body,
|
||||||
mimeType = content?.mimeType,
|
mimeType = content.mimeType,
|
||||||
url = content?.getFileUrl(),
|
url = content.getFileUrl(),
|
||||||
elementToDecrypt = content?.encryptedFileInfo?.toElementToDecrypt(),
|
elementToDecrypt = content.encryptedFileInfo?.toElementToDecrypt(),
|
||||||
maxHeight = -1,
|
maxHeight = -1,
|
||||||
maxWidth = -1,
|
maxWidth = -1,
|
||||||
width = null,
|
width = null,
|
||||||
height = null
|
height = null
|
||||||
)
|
)
|
||||||
if (content?.mimeType == "image/gif") {
|
if (content.mimeType == "image/gif") {
|
||||||
AttachmentInfo.AnimatedImage(
|
AttachmentInfo.AnimatedImage(
|
||||||
content.url ?: "",
|
content.url ?: "",
|
||||||
data
|
data
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
AttachmentInfo.Image(
|
AttachmentInfo.Image(
|
||||||
content?.url ?: "",
|
content.url ?: "",
|
||||||
data
|
data
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
} else if (content 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
|
||||||
|
)
|
||||||
|
val data = VideoContentRenderer.Data(
|
||||||
|
eventId = it.eventId,
|
||||||
|
filename = content.body,
|
||||||
|
mimeType = content.mimeType,
|
||||||
|
url = content.getFileUrl(),
|
||||||
|
elementToDecrypt = content.encryptedFileInfo?.toElementToDecrypt(),
|
||||||
|
thumbnailMediaData = thumbnailData
|
||||||
|
)
|
||||||
|
AttachmentInfo.Video(
|
||||||
|
content.getFileUrl() ?: "",
|
||||||
|
data,
|
||||||
|
AttachmentInfo.Image(
|
||||||
|
url = content.videoInfo?.thumbnailFile?.url
|
||||||
|
?: content.videoInfo?.thumbnailUrl ?: "",
|
||||||
|
data = thumbnailData
|
||||||
|
|
||||||
|
)
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
AttachmentInfo.Image(
|
||||||
|
"",
|
||||||
|
null
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -94,6 +145,49 @@ class RoomAttachmentProvider(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun loadVideo(holder: VideoViewHolder, 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, holder.thumbnailImage, object : CustomViewTarget<ImageView, Drawable>(holder.thumbnailImage) {
|
||||||
|
override fun onLoadFailed(errorDrawable: Drawable?) {
|
||||||
|
holder.thumbnailImage.setImageDrawable(errorDrawable)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onResourceCleared(placeholder: Drawable?) {
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onResourceReady(resource: Drawable, transition: Transition<in Drawable>?) {
|
||||||
|
holder.thumbnailImage.setImageDrawable(resource)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
holder.thumbnailImage.isVisible = false
|
||||||
|
holder.loaderProgressBar.isVisible = false
|
||||||
|
holder.videoView.isVisible = false
|
||||||
|
|
||||||
|
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) {
|
||||||
|
holder.videoReady(data)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onFailure(failure: Throwable) {
|
||||||
|
holder.videoView.isVisible = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
override fun overlayViewAtPosition(context: Context, position: Int): View? {
|
override fun overlayViewAtPosition(context: Context, position: Int): View? {
|
||||||
if (overlayView == null) {
|
if (overlayView == null) {
|
||||||
overlayView = AttachmentOverlayView(context)
|
overlayView = AttachmentOverlayView(context)
|
||||||
@ -109,22 +203,19 @@ class RoomAttachmentProvider(
|
|||||||
"${dateFormatter.formatMessageDay(it)} at ${dateFormatter.formatMessageHour(it)} "
|
"${dateFormatter.formatMessageDay(it)} at ${dateFormatter.formatMessageHour(it)} "
|
||||||
}
|
}
|
||||||
overlayView?.updateWith("${position + 1} of ${attachments.size}", "${item.senderInfo.displayName} $dateString")
|
overlayView?.updateWith("${position + 1} of ${attachments.size}", "${item.senderInfo.displayName} $dateString")
|
||||||
|
overlayView?.videoControlsGroup?.isVisible = item.root.isVideoMessage()
|
||||||
return overlayView
|
return overlayView
|
||||||
}
|
}
|
||||||
|
|
||||||
// override fun loadImage(holder: ImageViewHolder, info: AttachmentInfo.Image) {
|
|
||||||
// (info.data as? ImageContentRenderer.Data)?.let {
|
|
||||||
// imageContentRenderer.render(it, ImageContentRenderer.Mode.FULL_SIZE, holder.touchImageView)
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class RoomAttachmentProviderFactory @Inject constructor(
|
class RoomAttachmentProviderFactory @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
|
||||||
) {
|
) {
|
||||||
|
|
||||||
fun createProvider(attachments: List<TimelineEvent>, initialIndex: Int): RoomAttachmentProvider {
|
fun createProvider(attachments: List<TimelineEvent>, initialIndex: Int): RoomAttachmentProvider {
|
||||||
return RoomAttachmentProvider(attachments, initialIndex, imageContentRenderer, vectorDateFormatter)
|
return RoomAttachmentProvider(attachments, initialIndex, imageContentRenderer, videoContentRenderer, vectorDateFormatter, session.fileService())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -80,7 +80,7 @@ class VectorAttachmentViewerActivity : AttachmentViewerActivity(), RoomAttachmen
|
|||||||
val session = sessionHolder.getSafeActiveSession() ?: return Unit.also { finish() }
|
val session = sessionHolder.getSafeActiveSession() ?: return Unit.also { finish() }
|
||||||
|
|
||||||
val room = args.roomId?.let { session.getRoom(it) }
|
val room = args.roomId?.let { session.getRoom(it) }
|
||||||
val events = room?.getAttachementMessages() ?: emptyList()
|
val events = room?.getAttachmentMessages() ?: emptyList()
|
||||||
val index = events.indexOfFirst { it.eventId == args.eventId }
|
val index = events.indexOfFirst { it.eventId == args.eventId }
|
||||||
initialIndex = index
|
initialIndex = index
|
||||||
|
|
||||||
@ -90,8 +90,8 @@ class VectorAttachmentViewerActivity : AttachmentViewerActivity(), RoomAttachmen
|
|||||||
transitionImageContainer.isVisible = true
|
transitionImageContainer.isVisible = true
|
||||||
|
|
||||||
// Postpone transaction a bit until thumbnail is loaded
|
// Postpone transaction a bit until thumbnail is loaded
|
||||||
val mediaData: ImageContentRenderer.Data? = intent.getParcelableExtra(EXTRA_IMAGE_DATA)
|
val mediaData: Parcelable? = intent.getParcelableExtra(EXTRA_IMAGE_DATA)
|
||||||
if (mediaData != null) {
|
if (mediaData is ImageContentRenderer.Data) {
|
||||||
// will be shown at end of transition
|
// will be shown at end of transition
|
||||||
pager2.isInvisible = true
|
pager2.isInvisible = true
|
||||||
supportPostponeEnterTransition()
|
supportPostponeEnterTransition()
|
||||||
@ -99,6 +99,14 @@ class VectorAttachmentViewerActivity : AttachmentViewerActivity(), RoomAttachmen
|
|||||||
// Proceed with transaction
|
// Proceed with transaction
|
||||||
scheduleStartPostponedTransition(imageTransitionView)
|
scheduleStartPostponedTransition(imageTransitionView)
|
||||||
}
|
}
|
||||||
|
} else if (mediaData is VideoContentRenderer.Data) {
|
||||||
|
// will be shown at end of transition
|
||||||
|
pager2.isInvisible = true
|
||||||
|
supportPostponeEnterTransition()
|
||||||
|
imageContentRenderer.renderThumbnailDontTransform(mediaData.thumbnailMediaData, imageTransitionView) {
|
||||||
|
// Proceed with transaction
|
||||||
|
scheduleStartPostponedTransition(imageTransitionView)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -108,6 +116,10 @@ class VectorAttachmentViewerActivity : AttachmentViewerActivity(), RoomAttachmen
|
|||||||
setSourceProvider(sourceProvider)
|
setSourceProvider(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...
|
||||||
|
pager2.post {
|
||||||
|
onSelectedPositionChanged(index)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
window.statusBarColor = ContextCompat.getColor(this, R.color.black_alpha)
|
window.statusBarColor = ContextCompat.getColor(this, R.color.black_alpha)
|
||||||
@ -202,7 +214,7 @@ class VectorAttachmentViewerActivity : AttachmentViewerActivity(), RoomAttachmen
|
|||||||
const val EXTRA_IMAGE_DATA = "EXTRA_IMAGE_DATA"
|
const val EXTRA_IMAGE_DATA = "EXTRA_IMAGE_DATA"
|
||||||
|
|
||||||
fun newIntent(context: Context,
|
fun newIntent(context: Context,
|
||||||
mediaData: ImageContentRenderer.Data,
|
mediaData: AttachmentData,
|
||||||
roomId: String?,
|
roomId: String?,
|
||||||
eventId: String,
|
eventId: String,
|
||||||
sharedTransitionName: String?) = Intent(context, VectorAttachmentViewerActivity::class.java).also {
|
sharedTransitionName: String?) = Intent(context, VectorAttachmentViewerActivity::class.java).also {
|
||||||
|
@ -16,7 +16,6 @@
|
|||||||
|
|
||||||
package im.vector.riotx.features.media
|
package im.vector.riotx.features.media
|
||||||
|
|
||||||
import android.os.Parcelable
|
|
||||||
import android.widget.ImageView
|
import android.widget.ImageView
|
||||||
import android.widget.ProgressBar
|
import android.widget.ProgressBar
|
||||||
import android.widget.TextView
|
import android.widget.TextView
|
||||||
@ -38,13 +37,13 @@ class VideoContentRenderer @Inject constructor(private val activeSessionHolder:
|
|||||||
|
|
||||||
@Parcelize
|
@Parcelize
|
||||||
data class Data(
|
data class Data(
|
||||||
val eventId: String,
|
override val eventId: String,
|
||||||
val filename: String,
|
override val filename: String,
|
||||||
val mimeType: String?,
|
override val mimeType: String?,
|
||||||
val url: String?,
|
override val url: String?,
|
||||||
val elementToDecrypt: ElementToDecrypt?,
|
override val elementToDecrypt: ElementToDecrypt?,
|
||||||
val thumbnailMediaData: ImageContentRenderer.Data
|
val thumbnailMediaData: ImageContentRenderer.Data
|
||||||
) : Parcelable
|
) : AttachmentData
|
||||||
|
|
||||||
fun render(data: Data,
|
fun render(data: Data,
|
||||||
thumbnailView: ImageView,
|
thumbnailView: ImageView,
|
||||||
|
@ -49,11 +49,10 @@ import im.vector.riotx.features.home.room.detail.RoomDetailArgs
|
|||||||
import im.vector.riotx.features.home.room.detail.widget.WidgetRequestCodes
|
import im.vector.riotx.features.home.room.detail.widget.WidgetRequestCodes
|
||||||
import im.vector.riotx.features.home.room.filtered.FilteredRoomsActivity
|
import im.vector.riotx.features.home.room.filtered.FilteredRoomsActivity
|
||||||
import im.vector.riotx.features.invite.InviteUsersToRoomActivity
|
import im.vector.riotx.features.invite.InviteUsersToRoomActivity
|
||||||
|
import im.vector.riotx.features.media.AttachmentData
|
||||||
import im.vector.riotx.features.media.BigImageViewerActivity
|
import im.vector.riotx.features.media.BigImageViewerActivity
|
||||||
import im.vector.riotx.features.media.ImageContentRenderer
|
|
||||||
import im.vector.riotx.features.media.VectorAttachmentViewerActivity
|
import im.vector.riotx.features.media.VectorAttachmentViewerActivity
|
||||||
import im.vector.riotx.features.media.VideoContentRenderer
|
import im.vector.riotx.features.media.VideoContentRenderer
|
||||||
import im.vector.riotx.features.media.VideoMediaViewerActivity
|
|
||||||
import im.vector.riotx.features.roomdirectory.RoomDirectoryActivity
|
import im.vector.riotx.features.roomdirectory.RoomDirectoryActivity
|
||||||
import im.vector.riotx.features.roomdirectory.createroom.CreateRoomActivity
|
import im.vector.riotx.features.roomdirectory.createroom.CreateRoomActivity
|
||||||
import im.vector.riotx.features.roomdirectory.roompreview.RoomPreviewActivity
|
import im.vector.riotx.features.roomdirectory.roompreview.RoomPreviewActivity
|
||||||
@ -248,7 +247,7 @@ class DefaultNavigator @Inject constructor(
|
|||||||
|
|
||||||
override fun openImageViewer(activity: Activity,
|
override fun openImageViewer(activity: Activity,
|
||||||
roomId: String?,
|
roomId: String?,
|
||||||
mediaData: ImageContentRenderer.Data,
|
mediaData: AttachmentData,
|
||||||
view: View,
|
view: View,
|
||||||
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, ViewCompat.getTransitionName(view)).let { intent ->
|
||||||
@ -284,9 +283,28 @@ class DefaultNavigator @Inject constructor(
|
|||||||
// activity.startActivity(intent, bundle)
|
// activity.startActivity(intent, bundle)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun openVideoViewer(activity: Activity, mediaData: VideoContentRenderer.Data) {
|
override fun openVideoViewer(activity: Activity,
|
||||||
val intent = VideoMediaViewerActivity.newIntent(activity, mediaData)
|
roomId: String?, mediaData: VideoContentRenderer.Data,
|
||||||
activity.startActivity(intent)
|
view: View,
|
||||||
|
options: ((MutableList<Pair<View, String>>) -> Unit)?) {
|
||||||
|
// val intent = VideoMediaViewerActivity.newIntent(activity, mediaData)
|
||||||
|
// activity.startActivity(intent)
|
||||||
|
VectorAttachmentViewerActivity.newIntent(activity, mediaData, roomId, mediaData.eventId, ViewCompat.getTransitionName(view)).let { intent ->
|
||||||
|
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 {
|
||||||
|
pairs.add(Pair(it, Window.STATUS_BAR_BACKGROUND_TRANSITION_NAME))
|
||||||
|
}
|
||||||
|
activity.window.decorView.findViewById<View>(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)
|
||||||
|
|
||||||
|
val bundle = ActivityOptionsCompat.makeSceneTransitionAnimation(activity, *pairs.toTypedArray()).toBundle()
|
||||||
|
activity.startActivity(intent, bundle)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun startActivity(context: Context, intent: Intent, buildTask: Boolean) {
|
private fun startActivity(context: Context, intent: Intent, buildTask: Boolean) {
|
||||||
|
@ -26,7 +26,7 @@ import im.vector.matrix.android.api.session.terms.TermsService
|
|||||||
import im.vector.matrix.android.api.util.MatrixItem
|
import im.vector.matrix.android.api.util.MatrixItem
|
||||||
import im.vector.matrix.android.api.session.widgets.model.Widget
|
import im.vector.matrix.android.api.session.widgets.model.Widget
|
||||||
import im.vector.riotx.features.home.room.detail.widget.WidgetRequestCodes
|
import im.vector.riotx.features.home.room.detail.widget.WidgetRequestCodes
|
||||||
import im.vector.riotx.features.media.ImageContentRenderer
|
import im.vector.riotx.features.media.AttachmentData
|
||||||
import im.vector.riotx.features.media.VideoContentRenderer
|
import im.vector.riotx.features.media.VideoContentRenderer
|
||||||
import im.vector.riotx.features.settings.VectorSettingsActivity
|
import im.vector.riotx.features.settings.VectorSettingsActivity
|
||||||
import im.vector.riotx.features.share.SharedData
|
import im.vector.riotx.features.share.SharedData
|
||||||
@ -93,9 +93,12 @@ interface Navigator {
|
|||||||
|
|
||||||
fun openImageViewer(activity: Activity,
|
fun openImageViewer(activity: Activity,
|
||||||
roomId: String?,
|
roomId: String?,
|
||||||
mediaData: ImageContentRenderer.Data,
|
mediaData: AttachmentData,
|
||||||
view: View,
|
view: View,
|
||||||
options: ((MutableList<Pair<View, String>>) -> Unit)?)
|
options: ((MutableList<Pair<View, String>>) -> Unit)?)
|
||||||
|
|
||||||
fun openVideoViewer(activity: Activity, mediaData: VideoContentRenderer.Data)
|
fun openVideoViewer(activity: Activity,
|
||||||
|
roomId: String?, mediaData: VideoContentRenderer.Data,
|
||||||
|
view: View,
|
||||||
|
options: ((MutableList<Pair<View, String>>) -> Unit)?)
|
||||||
}
|
}
|
||||||
|
@ -81,7 +81,8 @@ class RoomUploadsMediaFragment @Inject constructor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun onOpenVideoClicked(view: View, mediaData: VideoContentRenderer.Data) {
|
override fun onOpenVideoClicked(view: View, mediaData: VideoContentRenderer.Data) {
|
||||||
navigator.openVideoViewer(requireActivity(), mediaData)
|
// TODO
|
||||||
|
// navigator.openVideoViewer(requireActivity(), mediaData, null, )
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun loadMore() {
|
override fun loadMore() {
|
||||||
|
10
vector/src/main/res/drawable/ic_pause.xml
Normal file
10
vector/src/main/res/drawable/ic_pause.xml
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="24dp"
|
||||||
|
android:height="24dp"
|
||||||
|
android:viewportWidth="24"
|
||||||
|
android:viewportHeight="24"
|
||||||
|
android:tint="?attr/colorControlNormal">
|
||||||
|
<path
|
||||||
|
android:fillColor="@android:color/white"
|
||||||
|
android:pathData="M6,19h4L10,5L6,5v14zM14,5v14h4L18,5h-4z"/>
|
||||||
|
</vector>
|
10
vector/src/main/res/drawable/ic_play_arrow.xml
Normal file
10
vector/src/main/res/drawable/ic_play_arrow.xml
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="24dp"
|
||||||
|
android:height="24dp"
|
||||||
|
android:viewportWidth="24"
|
||||||
|
android:viewportHeight="24"
|
||||||
|
android:tint="?attr/colorControlNormal">
|
||||||
|
<path
|
||||||
|
android:fillColor="@android:color/white"
|
||||||
|
android:pathData="M8,5v14l11,-7z"/>
|
||||||
|
</vector>
|
@ -10,7 +10,8 @@
|
|||||||
android:id="@+id/overlayTopBackground"
|
android:id="@+id/overlayTopBackground"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="60dp"
|
android:layout_height="60dp"
|
||||||
android:background="@color/black_alpha">
|
android:background="@color/black_alpha"
|
||||||
|
app:layout_constraintTop_toTopOf="parent">
|
||||||
|
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
@ -21,12 +22,12 @@
|
|||||||
android:layout_height="44dp"
|
android:layout_height="44dp"
|
||||||
android:layout_marginStart="8dp"
|
android:layout_marginStart="8dp"
|
||||||
android:layout_marginEnd="16dp"
|
android:layout_marginEnd="16dp"
|
||||||
android:scaleType="centerInside"
|
|
||||||
android:clickable="true"
|
|
||||||
android:focusable="true"
|
|
||||||
android:background="?attr/selectableItemBackgroundBorderless"
|
android:background="?attr/selectableItemBackgroundBorderless"
|
||||||
|
android:clickable="true"
|
||||||
android:contentDescription="@string/share"
|
android:contentDescription="@string/share"
|
||||||
|
android:focusable="true"
|
||||||
android:padding="6dp"
|
android:padding="6dp"
|
||||||
|
android:scaleType="centerInside"
|
||||||
android:tint="@color/white"
|
android:tint="@color/white"
|
||||||
app:layout_constraintBottom_toBottomOf="@id/overlayTopBackground"
|
app:layout_constraintBottom_toBottomOf="@id/overlayTopBackground"
|
||||||
app:layout_constraintStart_toStartOf="@id/overlayTopBackground"
|
app:layout_constraintStart_toStartOf="@id/overlayTopBackground"
|
||||||
@ -72,6 +73,8 @@
|
|||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginEnd="16dp"
|
android:layout_marginEnd="16dp"
|
||||||
android:background="?attr/selectableItemBackgroundBorderless"
|
android:background="?attr/selectableItemBackgroundBorderless"
|
||||||
|
android:clickable="true"
|
||||||
|
android:focusable="true"
|
||||||
android:contentDescription="@string/share"
|
android:contentDescription="@string/share"
|
||||||
android:padding="6dp"
|
android:padding="6dp"
|
||||||
android:tint="@color/white"
|
android:tint="@color/white"
|
||||||
@ -80,4 +83,51 @@
|
|||||||
app:layout_constraintTop_toTopOf="@id/overlayTopBackground"
|
app:layout_constraintTop_toTopOf="@id/overlayTopBackground"
|
||||||
app:srcCompat="@drawable/ic_share" />
|
app:srcCompat="@drawable/ic_share" />
|
||||||
|
|
||||||
|
|
||||||
|
<androidx.constraintlayout.widget.Group
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:id="@+id/overlayVideoControlsGroup"
|
||||||
|
android:visibility="gone"
|
||||||
|
tools:visibility="visible"
|
||||||
|
app:constraint_referenced_ids="overlayBottomBackground,overlayBackButton,overlayPlayPauseButton,overlaySeekBar" />
|
||||||
|
|
||||||
|
<View
|
||||||
|
android:id="@+id/overlayBottomBackground"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="60dp"
|
||||||
|
android:background="@color/black_alpha"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent" />
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/overlayPlayPauseButton"
|
||||||
|
android:layout_width="44dp"
|
||||||
|
android:layout_height="44dp"
|
||||||
|
android:layout_marginStart="8dp"
|
||||||
|
android:layout_marginEnd="16dp"
|
||||||
|
android:background="?attr/selectableItemBackgroundBorderless"
|
||||||
|
android:clickable="true"
|
||||||
|
android:contentDescription="@string/play_video"
|
||||||
|
android:focusable="true"
|
||||||
|
android:padding="6dp"
|
||||||
|
android:scaleType="centerInside"
|
||||||
|
android:tint="@color/white"
|
||||||
|
app:layout_constraintBottom_toBottomOf="@id/overlayBottomBackground"
|
||||||
|
app:layout_constraintStart_toStartOf="@id/overlayBottomBackground"
|
||||||
|
app:layout_constraintTop_toTopOf="@id/overlayBottomBackground"
|
||||||
|
app:srcCompat="@drawable/ic_play_arrow" />
|
||||||
|
|
||||||
|
<SeekBar
|
||||||
|
android:id="@+id/overlaySeekBar"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:backgroundTint="@color/white"
|
||||||
|
android:progressBackgroundTint="@color/white"
|
||||||
|
android:thumbTint="@color/white"
|
||||||
|
android:layout_marginStart="8dp"
|
||||||
|
android:layout_marginEnd="8dp"
|
||||||
|
app:layout_constraintBottom_toBottomOf="@id/overlayBottomBackground"
|
||||||
|
app:layout_constraintEnd_toEndOf="@id/overlayBottomBackground"
|
||||||
|
app:layout_constraintStart_toEndOf="@id/overlayPlayPauseButton"
|
||||||
|
app:layout_constraintTop_toTopOf="@id/overlayBottomBackground" />
|
||||||
</merge>
|
</merge>
|
||||||
|
@ -76,6 +76,10 @@
|
|||||||
<string name="disconnect">Disconnect</string>
|
<string name="disconnect">Disconnect</string>
|
||||||
<string name="report_content">Report content</string>
|
<string name="report_content">Report content</string>
|
||||||
<string name="active_call">Active call</string>
|
<string name="active_call">Active call</string>
|
||||||
|
<string name="play_video">Play</string>
|
||||||
|
<string name="pause_video">Pause</string>
|
||||||
|
|
||||||
|
|
||||||
<!-- First param will be replace by the value of ongoing_conference_call_voice, and second one by the value of ongoing_conference_call_video -->
|
<!-- First param will be replace by the value of ongoing_conference_call_voice, and second one by the value of ongoing_conference_call_video -->
|
||||||
<string name="ongoing_conference_call">Ongoing conference call.\nJoin as %1$s or %2$s</string>
|
<string name="ongoing_conference_call">Ongoing conference call.\nJoin as %1$s or %2$s</string>
|
||||||
<string name="ongoing_conference_call_voice">Voice</string>
|
<string name="ongoing_conference_call_voice">Voice</string>
|
||||||
|
Loading…
x
Reference in New Issue
Block a user