From 4a2a6d34aebbb2383a71eaa0ad99c24dac81cabd Mon Sep 17 00:00:00 2001 From: Valere Date: Sun, 5 Jul 2020 21:47:38 +0200 Subject: [PATCH 01/25] Initial commit --- attachment-viewer/.gitignore | 1 + attachment-viewer/build.gradle | 81 +++++++ attachment-viewer/consumer-rules.pro | 0 attachment-viewer/proguard-rules.pro | 21 ++ .../src/main/AndroidManifest.xml | 11 + .../AttachmentSourceProvider.kt | 36 +++ .../AttachmentViewerActivity.kt | 210 ++++++++++++++++++ .../attachment_viewer/AttachmentsAdapter.kt | 133 +++++++++++ .../attachment_viewer/ImageViewHolder.kt | 75 +++++++ .../riotx/attachment_viewer/SwipeDirection.kt | 38 ++++ .../SwipeDirectionDetector.kt | 90 ++++++++ .../SwipeToDismissHandler.kt | 126 +++++++++++ .../res/layout/activity_attachment_viewer.xml | 46 ++++ .../main/res/layout/item_image_attachment.xml | 22 ++ .../main/res/layout/item_video_attachment.xml | 26 +++ .../main/res/layout/view_image_attachment.xml | 17 ++ .../src/main/res/values/dimens.xml | 3 + .../src/main/res/values/strings.xml | 11 + .../src/main/res/values/styles.xml | 12 + build.gradle | 2 + .../session/room/timeline/TimelineService.kt | 2 + .../room/timeline/DefaultTimelineService.kt | 25 ++- settings.gradle | 4 +- vector/build.gradle | 5 + vector/src/main/AndroidManifest.xml | 5 + .../vector/riotx/core/di/ScreenComponent.kt | 2 + .../home/room/detail/RoomDetailFragment.kt | 2 +- .../features/media/ImageContentRenderer.kt | 63 ++++++ .../media/ImageMediaViewerActivity.kt | 2 + .../features/media/RoomAttachmentProvider.kt | 82 +++++++ .../media/VectorAttachmentViewerActivity.kt | 207 +++++++++++++++++ .../features/navigation/DefaultNavigator.kt | 56 +++-- .../riotx/features/navigation/Navigator.kt | 2 +- .../riotx/features/popup/PopupAlertManager.kt | 3 +- .../uploads/media/RoomUploadsMediaFragment.kt | 2 +- .../features/themes/ActivityOtherThemes.kt | 6 + vector/src/main/res/values/theme_common.xml | 11 + 37 files changed, 1409 insertions(+), 31 deletions(-) create mode 100644 attachment-viewer/.gitignore create mode 100644 attachment-viewer/build.gradle create mode 100644 attachment-viewer/consumer-rules.pro create mode 100644 attachment-viewer/proguard-rules.pro create mode 100644 attachment-viewer/src/main/AndroidManifest.xml create mode 100644 attachment-viewer/src/main/java/im/vector/riotx/attachment_viewer/AttachmentSourceProvider.kt create mode 100644 attachment-viewer/src/main/java/im/vector/riotx/attachment_viewer/AttachmentViewerActivity.kt create mode 100644 attachment-viewer/src/main/java/im/vector/riotx/attachment_viewer/AttachmentsAdapter.kt create mode 100644 attachment-viewer/src/main/java/im/vector/riotx/attachment_viewer/ImageViewHolder.kt create mode 100644 attachment-viewer/src/main/java/im/vector/riotx/attachment_viewer/SwipeDirection.kt create mode 100644 attachment-viewer/src/main/java/im/vector/riotx/attachment_viewer/SwipeDirectionDetector.kt create mode 100644 attachment-viewer/src/main/java/im/vector/riotx/attachment_viewer/SwipeToDismissHandler.kt create mode 100644 attachment-viewer/src/main/res/layout/activity_attachment_viewer.xml create mode 100644 attachment-viewer/src/main/res/layout/item_image_attachment.xml create mode 100644 attachment-viewer/src/main/res/layout/item_video_attachment.xml create mode 100644 attachment-viewer/src/main/res/layout/view_image_attachment.xml create mode 100644 attachment-viewer/src/main/res/values/dimens.xml create mode 100644 attachment-viewer/src/main/res/values/strings.xml create mode 100644 attachment-viewer/src/main/res/values/styles.xml create mode 100644 vector/src/main/java/im/vector/riotx/features/media/RoomAttachmentProvider.kt create mode 100644 vector/src/main/java/im/vector/riotx/features/media/VectorAttachmentViewerActivity.kt diff --git a/attachment-viewer/.gitignore b/attachment-viewer/.gitignore new file mode 100644 index 0000000000..42afabfd2a --- /dev/null +++ b/attachment-viewer/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/attachment-viewer/build.gradle b/attachment-viewer/build.gradle new file mode 100644 index 0000000000..7fcda7a742 --- /dev/null +++ b/attachment-viewer/build.gradle @@ -0,0 +1,81 @@ +/* + * 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. + */ + +apply plugin: 'com.android.library' +apply plugin: 'kotlin-android' +apply plugin: 'kotlin-android-extensions' + +buildscript { + repositories { + maven { + url 'https://jitpack.io' + content { + // PhotoView + includeGroupByRegex 'com\\.github\\.chrisbanes' + } + } + jcenter() + } + +} + +android { + compileSdkVersion 29 + buildToolsVersion "29.0.3" + + defaultConfig { + minSdkVersion 21 + targetSdkVersion 29 + versionCode 1 + versionName "1.0" + + testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" + consumerProguardFiles "consumer-rules.pro" + } + + buildTypes { + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' + } + } + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } + kotlinOptions { + jvmTarget = '1.8' + } +} + +dependencies { +// implementation 'com.github.MikeOrtiz:TouchImageView:3.0.2' + implementation 'com.github.chrisbanes:PhotoView:2.0.0' + implementation "com.github.bumptech.glide:glide:4.10.0" + + implementation fileTree(dir: "libs", include: ["*.jar"]) + implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version" + implementation 'androidx.core:core-ktx:1.3.0' + implementation 'androidx.appcompat:appcompat:1.1.0' + implementation 'com.google.android.material:material:1.1.0' + implementation 'androidx.constraintlayout:constraintlayout:1.1.3' + implementation 'androidx.navigation:navigation-fragment-ktx:2.1.0' + implementation 'androidx.navigation:navigation-ui-ktx:2.1.0' + testImplementation 'junit:junit:4.12' + androidTestImplementation 'androidx.test.ext:junit:1.1.1' + androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0' + +} \ No newline at end of file diff --git a/attachment-viewer/consumer-rules.pro b/attachment-viewer/consumer-rules.pro new file mode 100644 index 0000000000..e69de29bb2 diff --git a/attachment-viewer/proguard-rules.pro b/attachment-viewer/proguard-rules.pro new file mode 100644 index 0000000000..481bb43481 --- /dev/null +++ b/attachment-viewer/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile \ No newline at end of file diff --git a/attachment-viewer/src/main/AndroidManifest.xml b/attachment-viewer/src/main/AndroidManifest.xml new file mode 100644 index 0000000000..4a632774f7 --- /dev/null +++ b/attachment-viewer/src/main/AndroidManifest.xml @@ -0,0 +1,11 @@ + + + + + + + + \ No newline at end of file diff --git a/attachment-viewer/src/main/java/im/vector/riotx/attachment_viewer/AttachmentSourceProvider.kt b/attachment-viewer/src/main/java/im/vector/riotx/attachment_viewer/AttachmentSourceProvider.kt new file mode 100644 index 0000000000..9fd2902970 --- /dev/null +++ b/attachment-viewer/src/main/java/im/vector/riotx/attachment_viewer/AttachmentSourceProvider.kt @@ -0,0 +1,36 @@ +/* + * 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.attachment_viewer + +sealed class AttachmentInfo { + data class Image(val url: String, val data: Any?) : AttachmentInfo() + data class Video(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() + + fun bind() { + } +} + +interface AttachmentSourceProvider { + + fun getItemCount(): Int + + fun getAttachmentInfoAt(position: Int): AttachmentInfo + + fun loadImage(holder: ImageViewHolder, info: AttachmentInfo.Image) +} diff --git a/attachment-viewer/src/main/java/im/vector/riotx/attachment_viewer/AttachmentViewerActivity.kt b/attachment-viewer/src/main/java/im/vector/riotx/attachment_viewer/AttachmentViewerActivity.kt new file mode 100644 index 0000000000..2d4cbff00d --- /dev/null +++ b/attachment-viewer/src/main/java/im/vector/riotx/attachment_viewer/AttachmentViewerActivity.kt @@ -0,0 +1,210 @@ +/* + * 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.attachment_viewer + +import android.graphics.Color +import android.os.Bundle +import android.util.Log +import android.view.MotionEvent +import android.view.ScaleGestureDetector +import android.view.View +import android.view.ViewGroup +import android.widget.ImageView +import androidx.appcompat.app.AppCompatActivity +import androidx.core.view.isVisible +import androidx.viewpager2.widget.ViewPager2 +import kotlinx.android.synthetic.main.activity_attachment_viewer.* +import kotlin.math.abs + +abstract class AttachmentViewerActivity : AppCompatActivity() { + + lateinit var pager2: ViewPager2 + lateinit var imageTransitionView: ImageView + lateinit var transitionImageContainer: ViewGroup + + // TODO + private var overlayView: View? = null + + private lateinit var swipeDismissHandler: SwipeToDismissHandler + private lateinit var directionDetector: SwipeDirectionDetector + private lateinit var scaleDetector: ScaleGestureDetector + + + var currentPosition = 0 + + private var swipeDirection: SwipeDirection? = null + + private fun isScaled() = attachmentsAdapter.isScaled(currentPosition) + + private var wasScaled: Boolean = false + private var isSwipeToDismissAllowed: Boolean = true + private lateinit var attachmentsAdapter: AttachmentsAdapter + +// private val shouldDismissToBottom: Boolean +// get() = e == null +// || !externalTransitionImageView.isRectVisible +// || !isAtStartPosition + + private var isImagePagerIdle = true + + fun setSourceProvider(sourceProvider: AttachmentSourceProvider) { + attachmentsAdapter.attachmentSourceProvider = sourceProvider + } + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(R.layout.activity_attachment_viewer) + attachmentPager.orientation = ViewPager2.ORIENTATION_HORIZONTAL + attachmentsAdapter = AttachmentsAdapter() + attachmentPager.adapter = attachmentsAdapter + imageTransitionView = transitionImageView + transitionImageContainer = findViewById(R.id.transitionImageContainer) + pager2 = attachmentPager + directionDetector = createSwipeDirectionDetector() + + attachmentPager.registerOnPageChangeCallback(object : ViewPager2.OnPageChangeCallback() { + override fun onPageScrollStateChanged(state: Int) { + isImagePagerIdle = state == ViewPager2.SCROLL_STATE_IDLE + } + + override fun onPageSelected(position: Int) { + currentPosition = position + } + }) + + swipeDismissHandler = createSwipeToDismissHandler() + rootContainer.setOnTouchListener(swipeDismissHandler) + rootContainer.viewTreeObserver.addOnGlobalLayoutListener { swipeDismissHandler.translationLimit = dismissContainer.height / 4 } + + scaleDetector = createScaleGestureDetector() + + } + + override fun dispatchTouchEvent(ev: MotionEvent): Boolean { + + // The zoomable view is configured to disallow interception when image is zoomed + + // Check if the overlay is visible, and wants to handle the click +// if (overlayView.isVisible && overlayView?.dispatchTouchEvent(event) == true) { +// return true +// } + + + Log.v("ATTACHEMENTS", "================\ndispatchTouchEvent $ev") + handleUpDownEvent(ev) + + Log.v("ATTACHEMENTS", "scaleDetector is in progress ${scaleDetector.isInProgress}") + Log.v("ATTACHEMENTS", "pointerCount ${ev.pointerCount}") + Log.v("ATTACHEMENTS", "wasScaled ${wasScaled}") + if (swipeDirection == null && (scaleDetector.isInProgress || ev.pointerCount > 1 || wasScaled)) { + wasScaled = true + Log.v("ATTACHEMENTS", "dispatch to pager") + return attachmentPager.dispatchTouchEvent(ev) + } + + + Log.v("ATTACHEMENTS", "is current item scaled ${isScaled()}") + return (if (isScaled()) super.dispatchTouchEvent(ev) else handleTouchIfNotScaled(ev)).also { + Log.v("ATTACHEMENTS", "\n================") + } + } + + private fun handleUpDownEvent(event: MotionEvent) { + Log.v("ATTACHEMENTS", "handleUpDownEvent $event") + if (event.action == MotionEvent.ACTION_UP) { + handleEventActionUp(event) + } + + if (event.action == MotionEvent.ACTION_DOWN) { + handleEventActionDown(event) + } + + scaleDetector.onTouchEvent(event) +// gestureDetector.onTouchEvent(event) + } + + private fun handleEventActionDown(event: MotionEvent) { + swipeDirection = null + wasScaled = false + attachmentPager.dispatchTouchEvent(event) + + swipeDismissHandler.onTouch(rootContainer, event) +// isOverlayWasClicked = dispatchOverlayTouch(event) + } + + private fun handleEventActionUp(event: MotionEvent) { +// wasDoubleTapped = false + swipeDismissHandler.onTouch(rootContainer, event) + attachmentPager.dispatchTouchEvent(event) +// isOverlayWasClicked = dispatchOverlayTouch(event) + } + + private fun handleTouchIfNotScaled(event: MotionEvent): Boolean { + + Log.v("ATTACHEMENTS", "handleTouchIfNotScaled ${event}") + directionDetector.handleTouchEvent(event) + + return when (swipeDirection) { + SwipeDirection.Up, SwipeDirection.Down -> { + if (isSwipeToDismissAllowed && !wasScaled && isImagePagerIdle) { + swipeDismissHandler.onTouch(rootContainer, event) + } else true + } + SwipeDirection.Left, SwipeDirection.Right -> { + attachmentPager.dispatchTouchEvent(event) + } + else -> true + } + } + + + private fun handleSwipeViewMove(translationY: Float, translationLimit: Int) { + val alpha = calculateTranslationAlpha(translationY, translationLimit) + backgroundView.alpha = alpha + dismissContainer.alpha = alpha + overlayView?.alpha = alpha + } + + private fun dispatchOverlayTouch(event: MotionEvent): Boolean = + overlayView + ?.let { it.isVisible && it.dispatchTouchEvent(event) } + ?: false + + private fun calculateTranslationAlpha(translationY: Float, translationLimit: Int): Float = + 1.0f - 1.0f / translationLimit.toFloat() / 4f * abs(translationY) + + private fun createSwipeToDismissHandler() + : SwipeToDismissHandler = SwipeToDismissHandler( + swipeView = dismissContainer, + shouldAnimateDismiss = { shouldAnimateDismiss() }, + onDismiss = { animateClose() }, + onSwipeViewMove = ::handleSwipeViewMove) + + private fun createSwipeDirectionDetector() = + SwipeDirectionDetector(this) { swipeDirection = it } + + private fun createScaleGestureDetector() = + ScaleGestureDetector(this, ScaleGestureDetector.SimpleOnScaleGestureListener()) + + + protected open fun shouldAnimateDismiss(): Boolean = true + + protected open fun animateClose() { + window.statusBarColor = Color.TRANSPARENT + finish() + } +} diff --git a/attachment-viewer/src/main/java/im/vector/riotx/attachment_viewer/AttachmentsAdapter.kt b/attachment-viewer/src/main/java/im/vector/riotx/attachment_viewer/AttachmentsAdapter.kt new file mode 100644 index 0000000000..b9914e4dda --- /dev/null +++ b/attachment-viewer/src/main/java/im/vector/riotx/attachment_viewer/AttachmentsAdapter.kt @@ -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.attachment_viewer + +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.recyclerview.widget.RecyclerView + + +abstract class BaseViewHolder constructor(itemView: View) : + RecyclerView.ViewHolder(itemView) { + + abstract fun bind(attachmentInfo: AttachmentInfo) +} + + +class AttachmentViewHolder constructor(itemView: View) : + BaseViewHolder(itemView) { + + override fun bind(attachmentInfo: AttachmentInfo) { + + } +} + +//class AttachmentsAdapter(fragmentManager: FragmentManager, lifecycle: Lifecycle) : FragmentStateAdapter(fragmentManager, lifecycle) { +class AttachmentsAdapter() : RecyclerView.Adapter() { + + var attachmentSourceProvider: AttachmentSourceProvider? = null + set(value) { + field = value + notifyDataSetChanged() + } + + var recyclerView: RecyclerView? = null + + override fun onAttachedToRecyclerView(recyclerView: RecyclerView) { + this.recyclerView = recyclerView + } + + override fun onDetachedFromRecyclerView(recyclerView: RecyclerView) { + this.recyclerView = null + } + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BaseViewHolder { + val inflater = LayoutInflater.from(parent.context) + val itemView = inflater.inflate(viewType, parent, false) + return when (viewType) { + R.layout.item_image_attachment -> ImageViewHolder(itemView) + else -> AttachmentViewHolder(itemView) + } + } + + override fun getItemViewType(position: Int): Int { + val info = attachmentSourceProvider!!.getAttachmentInfoAt(position) + return when (info) { + is AttachmentInfo.Image -> R.layout.item_image_attachment + is AttachmentInfo.Video -> R.layout.item_video_attachment + is AttachmentInfo.Audio -> TODO() + is AttachmentInfo.File -> TODO() + } + + } + + override fun getItemCount(): Int { + return attachmentSourceProvider?.getItemCount() ?: 0 + } + + override fun onBindViewHolder(holder: BaseViewHolder, position: Int) { + attachmentSourceProvider?.getAttachmentInfoAt(position)?.let { + holder.bind(it) + if (it is AttachmentInfo.Image) { + attachmentSourceProvider?.loadImage(holder as ImageViewHolder, it) + } + } + } + + fun isScaled(position: Int): Boolean { + val holder = recyclerView?.findViewHolderForAdapterPosition(position) + if (holder is ImageViewHolder) { + return holder.touchImageView.attacher.scale > 1f + } + return false + } + +// override fun getItemCount(): Int { +// return 8 +// } +// +// override fun createFragment(position: Int): Fragment { +// // Return a NEW fragment instance in createFragment(int) +// val fragment = DemoObjectFragment() +// fragment.arguments = Bundle().apply { +// // Our object is just an integer :-P +// putInt(ARG_OBJECT, position + 1) +// } +// return fragment +// } + +} + + +//private const val ARG_OBJECT = "object" +// +//// Instances of this class are fragments representing a single +//// object in our collection. +//class DemoObjectFragment : Fragment() { +// +// override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { +// return inflater.inflate(R.layout.view_image_attachment, container, false) +// } +// +// override fun onViewCreated(view: View, savedInstanceState: Bundle?) { +// arguments?.takeIf { it.containsKey(ARG_OBJECT) }?.apply { +// val textView: TextView = view.findViewById(R.id.testPage) +// textView.text = getInt(ARG_OBJECT).toString() +// } +// } +//} diff --git a/attachment-viewer/src/main/java/im/vector/riotx/attachment_viewer/ImageViewHolder.kt b/attachment-viewer/src/main/java/im/vector/riotx/attachment_viewer/ImageViewHolder.kt new file mode 100644 index 0000000000..cac6a4fd9e --- /dev/null +++ b/attachment-viewer/src/main/java/im/vector/riotx/attachment_viewer/ImageViewHolder.kt @@ -0,0 +1,75 @@ +/* + * 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.attachment_viewer + +import android.graphics.drawable.Drawable +import android.util.Log +import android.view.View +import android.widget.LinearLayout +import android.widget.ProgressBar +import androidx.core.view.isVisible +import androidx.core.view.updateLayoutParams +import com.bumptech.glide.request.target.CustomViewTarget +import com.bumptech.glide.request.transition.Transition +import com.github.chrisbanes.photoview.PhotoView + +class ImageViewHolder constructor(itemView: View) : + BaseViewHolder(itemView) { + + val touchImageView: PhotoView = itemView.findViewById(R.id.touchImageView) + val imageLoaderProgress: ProgressBar = itemView.findViewById(R.id.imageLoaderProgress) + + init { + touchImageView.setAllowParentInterceptOnEdge(false) + touchImageView.setOnScaleChangeListener { scaleFactor, _, _ -> + Log.v("ATTACHEMENTS", "scaleFactor $scaleFactor") + // It's a bit annoying but when you pitch down the scaling + // is not exactly one :/ + touchImageView.setAllowParentInterceptOnEdge(scaleFactor <= 1.0008f) + } + touchImageView.setScale(1.0f, true) + touchImageView.setAllowParentInterceptOnEdge(true) + } + + val customTargetView = object : CustomViewTarget(touchImageView) { + + override fun onResourceLoading(placeholder: Drawable?) { + imageLoaderProgress.isVisible = true + } + + override fun onLoadFailed(errorDrawable: Drawable?) { + imageLoaderProgress.isVisible = false + } + + override fun onResourceCleared(placeholder: Drawable?) { + touchImageView.setImageDrawable(placeholder) + } + + override fun onResourceReady(resource: Drawable, transition: Transition?) { + imageLoaderProgress.isVisible = false + // Glide mess up the view size :/ + touchImageView.updateLayoutParams { + width = LinearLayout.LayoutParams.MATCH_PARENT + height = LinearLayout.LayoutParams.MATCH_PARENT + } + touchImageView.setImageDrawable(resource) + } + } + + override fun bind(attachmentInfo: AttachmentInfo) { + } +} diff --git a/attachment-viewer/src/main/java/im/vector/riotx/attachment_viewer/SwipeDirection.kt b/attachment-viewer/src/main/java/im/vector/riotx/attachment_viewer/SwipeDirection.kt new file mode 100644 index 0000000000..fc54d292c2 --- /dev/null +++ b/attachment-viewer/src/main/java/im/vector/riotx/attachment_viewer/SwipeDirection.kt @@ -0,0 +1,38 @@ +/* + * 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.attachment_viewer + +sealed class SwipeDirection { + object NotDetected : SwipeDirection() + object Up : SwipeDirection() + object Down : SwipeDirection() + object Left : SwipeDirection() + object Right : SwipeDirection() + + companion object { + fun fromAngle(angle: Double): SwipeDirection { + return when (angle) { + in 0.0..45.0 -> Right + in 45.0..135.0 -> Up + in 135.0..225.0 -> Left + in 225.0..315.0 -> Down + in 315.0..360.0 -> Right + else -> NotDetected + } + } + } +} diff --git a/attachment-viewer/src/main/java/im/vector/riotx/attachment_viewer/SwipeDirectionDetector.kt b/attachment-viewer/src/main/java/im/vector/riotx/attachment_viewer/SwipeDirectionDetector.kt new file mode 100644 index 0000000000..cce37a6d05 --- /dev/null +++ b/attachment-viewer/src/main/java/im/vector/riotx/attachment_viewer/SwipeDirectionDetector.kt @@ -0,0 +1,90 @@ +/* + * 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.attachment_viewer + +import android.content.Context +import android.view.MotionEvent +import kotlin.math.sqrt + +class SwipeDirectionDetector( + context: Context, + private val onDirectionDetected: (SwipeDirection) -> Unit +) { + + private val touchSlop: Int = android.view.ViewConfiguration.get(context).scaledTouchSlop + private var startX: Float = 0f + private var startY: Float = 0f + private var isDetected: Boolean = false + + fun handleTouchEvent(event: MotionEvent) { + when (event.action) { + MotionEvent.ACTION_DOWN -> { + startX = event.x + startY = event.y + } + MotionEvent.ACTION_CANCEL, MotionEvent.ACTION_UP -> { + if (!isDetected) { + onDirectionDetected(SwipeDirection.NotDetected) + } + startY = 0.0f + startX = startY + isDetected = false + } + MotionEvent.ACTION_MOVE -> if (!isDetected && getEventDistance(event) > touchSlop) { + isDetected = true + onDirectionDetected(getDirection(startX, startY, event.x, event.y)) + } + } + } + + /** + * Given two points in the plane p1=(x1, x2) and p2=(y1, y1), this method + * returns the direction that an arrow pointing from p1 to p2 would have. + * + * @param x1 the x position of the first point + * @param y1 the y position of the first point + * @param x2 the x position of the second point + * @param y2 the y position of the second point + * @return the direction + */ + private fun getDirection(x1: Float, y1: Float, x2: Float, y2: Float): SwipeDirection { + val angle = getAngle(x1, y1, x2, y2) + return SwipeDirection.fromAngle(angle) + } + + /** + * Finds the angle between two points in the plane (x1,y1) and (x2, y2) + * The angle is measured with 0/360 being the X-axis to the right, angles + * increase counter clockwise. + * + * @param x1 the x position of the first point + * @param y1 the y position of the first point + * @param x2 the x position of the second point + * @param y2 the y position of the second point + * @return the angle between two points + */ + private fun getAngle(x1: Float, y1: Float, x2: Float, y2: Float): Double { + val rad = Math.atan2((y1 - y2).toDouble(), (x2 - x1).toDouble()) + Math.PI + return (rad * 180 / Math.PI + 180) % 360 + } + + private fun getEventDistance(ev: MotionEvent): Float { + val dx = ev.getX(0) - startX + val dy = ev.getY(0) - startY + return sqrt((dx * dx + dy * dy).toDouble()).toFloat() + } +} diff --git a/attachment-viewer/src/main/java/im/vector/riotx/attachment_viewer/SwipeToDismissHandler.kt b/attachment-viewer/src/main/java/im/vector/riotx/attachment_viewer/SwipeToDismissHandler.kt new file mode 100644 index 0000000000..3a317d94e2 --- /dev/null +++ b/attachment-viewer/src/main/java/im/vector/riotx/attachment_viewer/SwipeToDismissHandler.kt @@ -0,0 +1,126 @@ +/* + * 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.attachment_viewer + +import android.animation.Animator +import android.animation.AnimatorListenerAdapter +import android.annotation.SuppressLint +import android.graphics.Rect +import android.view.MotionEvent +import android.view.View +import android.view.ViewPropertyAnimator +import android.view.animation.AccelerateInterpolator + +class SwipeToDismissHandler( + private val swipeView: View, + private val onDismiss: () -> Unit, + private val onSwipeViewMove: (translationY: Float, translationLimit: Int) -> Unit, + private val shouldAnimateDismiss: () -> Boolean +) : View.OnTouchListener { + + companion object { + private const val ANIMATION_DURATION = 200L + } + + var translationLimit: Int = swipeView.height / 4 + private var isTracking = false + private var startY: Float = 0f + + @SuppressLint("ClickableViewAccessibility") + override fun onTouch(v: View, event: MotionEvent): Boolean { + when (event.action) { + MotionEvent.ACTION_DOWN -> { + if (swipeView.hitRect.contains(event.x.toInt(), event.y.toInt())) { + isTracking = true + } + startY = event.y + return true + } + MotionEvent.ACTION_UP, MotionEvent.ACTION_CANCEL -> { + if (isTracking) { + isTracking = false + onTrackingEnd(v.height) + } + return true + } + MotionEvent.ACTION_MOVE -> { + if (isTracking) { + val translationY = event.y - startY + swipeView.translationY = translationY + onSwipeViewMove(translationY, translationLimit) + } + return true + } + else -> { + return false + } + } + } + + internal fun initiateDismissToBottom() { + animateTranslation(swipeView.height.toFloat()) + } + + private fun onTrackingEnd(parentHeight: Int) { + val animateTo = when { + swipeView.translationY < -translationLimit -> -parentHeight.toFloat() + swipeView.translationY > translationLimit -> parentHeight.toFloat() + else -> 0f + } + + if (animateTo != 0f && !shouldAnimateDismiss()) { + onDismiss() + } else { + animateTranslation(animateTo) + } + } + + private fun animateTranslation(translationTo: Float) { + swipeView.animate() + .translationY(translationTo) + .setDuration(ANIMATION_DURATION) + .setInterpolator(AccelerateInterpolator()) + .setUpdateListener { onSwipeViewMove(swipeView.translationY, translationLimit) } + .setAnimatorListener(onAnimationEnd = { + if (translationTo != 0f) { + onDismiss() + } + + //remove the update listener, otherwise it will be saved on the next animation execution: + swipeView.animate().setUpdateListener(null) + }) + .start() + } +} + +internal fun ViewPropertyAnimator.setAnimatorListener( + onAnimationEnd: ((Animator?) -> Unit)? = null, + onAnimationStart: ((Animator?) -> Unit)? = null +) = this.setListener( + object : AnimatorListenerAdapter() { + + override fun onAnimationEnd(animation: Animator?) { + onAnimationEnd?.invoke(animation) + } + + override fun onAnimationStart(animation: Animator?) { + onAnimationStart?.invoke(animation) + } + }) + +internal val View?.hitRect: Rect + get() = Rect().also { this?.getHitRect(it) } diff --git a/attachment-viewer/src/main/res/layout/activity_attachment_viewer.xml b/attachment-viewer/src/main/res/layout/activity_attachment_viewer.xml new file mode 100644 index 0000000000..a8a68db1a5 --- /dev/null +++ b/attachment-viewer/src/main/res/layout/activity_attachment_viewer.xml @@ -0,0 +1,46 @@ + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/attachment-viewer/src/main/res/layout/item_image_attachment.xml b/attachment-viewer/src/main/res/layout/item_image_attachment.xml new file mode 100644 index 0000000000..91a009df2a --- /dev/null +++ b/attachment-viewer/src/main/res/layout/item_image_attachment.xml @@ -0,0 +1,22 @@ + + + + + + + + \ No newline at end of file diff --git a/attachment-viewer/src/main/res/layout/item_video_attachment.xml b/attachment-viewer/src/main/res/layout/item_video_attachment.xml new file mode 100644 index 0000000000..9449ec2e9f --- /dev/null +++ b/attachment-viewer/src/main/res/layout/item_video_attachment.xml @@ -0,0 +1,26 @@ + + + + + + + + + + diff --git a/attachment-viewer/src/main/res/layout/view_image_attachment.xml b/attachment-viewer/src/main/res/layout/view_image_attachment.xml new file mode 100644 index 0000000000..3518a4472d --- /dev/null +++ b/attachment-viewer/src/main/res/layout/view_image_attachment.xml @@ -0,0 +1,17 @@ + + + + + + + \ No newline at end of file diff --git a/attachment-viewer/src/main/res/values/dimens.xml b/attachment-viewer/src/main/res/values/dimens.xml new file mode 100644 index 0000000000..125df87119 --- /dev/null +++ b/attachment-viewer/src/main/res/values/dimens.xml @@ -0,0 +1,3 @@ + + 16dp + \ No newline at end of file diff --git a/attachment-viewer/src/main/res/values/strings.xml b/attachment-viewer/src/main/res/values/strings.xml new file mode 100644 index 0000000000..6dcb56555a --- /dev/null +++ b/attachment-viewer/src/main/res/values/strings.xml @@ -0,0 +1,11 @@ + + AttachementViewerActivity + + First Fragment + Second Fragment + Next + Previous + + Hello first fragment + Hello second fragment. Arg: %1$s + \ No newline at end of file diff --git a/attachment-viewer/src/main/res/values/styles.xml b/attachment-viewer/src/main/res/values/styles.xml new file mode 100644 index 0000000000..a81174782e --- /dev/null +++ b/attachment-viewer/src/main/res/values/styles.xml @@ -0,0 +1,12 @@ + + + + + \ No newline at end of file diff --git a/build.gradle b/build.gradle index af3952b2d3..47b3ab240d 100644 --- a/build.gradle +++ b/build.gradle @@ -39,6 +39,8 @@ allprojects { includeGroupByRegex "com\\.github\\.yalantis" // JsonViewer includeGroupByRegex 'com\\.github\\.BillCarsonFr' + // PhotoView + includeGroupByRegex 'com\\.github\\.chrisbanes' } } maven { diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/timeline/TimelineService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/timeline/TimelineService.kt index a69127532e..bdbbbf11bd 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/timeline/TimelineService.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/timeline/TimelineService.kt @@ -39,4 +39,6 @@ interface TimelineService { fun getTimeLineEvent(eventId: String): TimelineEvent? fun getTimeLineEventLive(eventId: String): LiveData> + + fun getAttachementMessages() : List } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/DefaultTimelineService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/DefaultTimelineService.kt index 5723568197..ebdb8dd24d 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/DefaultTimelineService.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/DefaultTimelineService.kt @@ -21,19 +21,24 @@ import androidx.lifecycle.Transformations import com.squareup.inject.assisted.Assisted import com.squareup.inject.assisted.AssistedInject import com.zhuinden.monarchy.Monarchy +import im.vector.matrix.android.api.session.events.model.isImageMessage 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.TimelineService import im.vector.matrix.android.api.session.room.timeline.TimelineSettings import im.vector.matrix.android.api.util.Optional import im.vector.matrix.android.api.util.toOptional +import im.vector.matrix.android.internal.crypto.store.db.doWithRealm import im.vector.matrix.android.internal.database.mapper.ReadReceiptsSummaryMapper import im.vector.matrix.android.internal.database.mapper.TimelineEventMapper import im.vector.matrix.android.internal.database.model.TimelineEventEntity +import im.vector.matrix.android.internal.database.model.TimelineEventEntityFields import im.vector.matrix.android.internal.database.query.where import im.vector.matrix.android.internal.di.SessionDatabase import im.vector.matrix.android.internal.task.TaskExecutor import im.vector.matrix.android.internal.util.fetchCopyMap +import io.realm.Sort +import io.realm.kotlin.where import org.greenrobot.eventbus.EventBus internal class DefaultTimelineService @AssistedInject constructor(@Assisted private val roomId: String, @@ -73,10 +78,10 @@ internal class DefaultTimelineService @AssistedInject constructor(@Assisted priv override fun getTimeLineEvent(eventId: String): TimelineEvent? { return monarchy .fetchCopyMap({ - TimelineEventEntity.where(it, roomId = roomId, eventId = eventId).findFirst() - }, { entity, _ -> - timelineEventMapper.map(entity) - }) + TimelineEventEntity.where(it, roomId = roomId, eventId = eventId).findFirst() + }, { entity, _ -> + timelineEventMapper.map(entity) + }) } override fun getTimeLineEventLive(eventId: String): LiveData> { @@ -88,4 +93,16 @@ internal class DefaultTimelineService @AssistedInject constructor(@Assisted priv events.firstOrNull().toOptional() } } + + override fun getAttachementMessages(): List { + // TODO pretty bad query.. maybe we should denormalize clear type in base? + return doWithRealm(monarchy.realmConfiguration) { realm -> + realm.where() + .equalTo(TimelineEventEntityFields.ROOM_ID, roomId) + .sort(TimelineEventEntityFields.DISPLAY_INDEX, Sort.ASCENDING) + .findAll() + ?.mapNotNull { timelineEventMapper.map(it).takeIf { it.root.isImageMessage() } } + ?: emptyList() + } + } } diff --git a/settings.gradle b/settings.gradle index 04307e89d9..3a7aa9ac1c 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1,2 +1,2 @@ -include ':vector', ':matrix-sdk-android', ':matrix-sdk-android-rx', ':diff-match-patch' -include ':multipicker' +include ':vector', ':matrix-sdk-android', ':matrix-sdk-android-rx', ':diff-match-patch', ':attachment-viewer' +include ':multipicker' \ No newline at end of file diff --git a/vector/build.gradle b/vector/build.gradle index 59ae3d35de..b409a7d8b8 100644 --- a/vector/build.gradle +++ b/vector/build.gradle @@ -279,6 +279,7 @@ dependencies { implementation project(":matrix-sdk-android-rx") implementation project(":diff-match-patch") implementation project(":multipicker") + implementation project(":attachment-viewer") implementation 'com.android.support:multidex:1.0.3' implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" @@ -368,6 +369,10 @@ dependencies { implementation "com.github.piasy:GlideImageLoader:$big_image_viewer_version" implementation "com.github.piasy:ProgressPieIndicator:$big_image_viewer_version" implementation "com.github.piasy:GlideImageViewFactory:$big_image_viewer_version" + + // implementation 'com.github.MikeOrtiz:TouchImageView:3.0.2' + implementation 'com.github.chrisbanes:PhotoView:2.0.0' + implementation "com.github.bumptech.glide:glide:$glide_version" kapt "com.github.bumptech.glide:compiler:$glide_version" implementation 'com.danikula:videocache:2.7.1' diff --git a/vector/src/main/AndroidManifest.xml b/vector/src/main/AndroidManifest.xml index f9b78db17c..155c3bcd64 100644 --- a/vector/src/main/AndroidManifest.xml +++ b/vector/src/main/AndroidManifest.xml @@ -85,6 +85,11 @@ + + + + navigator.openImageViewer(requireActivity(), roomDetailArgs.roomId, mediaData, view) { pairs -> pairs.add(Pair(roomToolbar, ViewCompat.getTransitionName(roomToolbar) ?: "")) pairs.add(Pair(composerLayout, ViewCompat.getTransitionName(composerLayout) ?: "")) } diff --git a/vector/src/main/java/im/vector/riotx/features/media/ImageContentRenderer.kt b/vector/src/main/java/im/vector/riotx/features/media/ImageContentRenderer.kt index eeeb55ed15..7cd7ba56e5 100644 --- a/vector/src/main/java/im/vector/riotx/features/media/ImageContentRenderer.kt +++ b/vector/src/main/java/im/vector/riotx/features/media/ImageContentRenderer.kt @@ -19,11 +19,13 @@ package im.vector.riotx.features.media import android.graphics.drawable.Drawable import android.net.Uri import android.os.Parcelable +import android.view.View import android.widget.ImageView import com.bumptech.glide.load.DataSource import com.bumptech.glide.load.engine.GlideException import com.bumptech.glide.load.resource.bitmap.RoundedCorners import com.bumptech.glide.request.RequestListener +import com.bumptech.glide.request.target.CustomViewTarget import com.bumptech.glide.request.target.Target import com.davemorrissey.labs.subscaleview.SubsamplingScaleImageView.ORIENTATION_USE_EXIF import com.github.piasy.biv.view.BigImageView @@ -93,6 +95,25 @@ class ImageContentRenderer @Inject constructor(private val activeSessionHolder: .into(imageView) } + fun render(data: Data, contextView: View, target: CustomViewTarget<*, Drawable>) { + val req = if (data.elementToDecrypt != null) { + // Encrypted image + GlideApp + .with(contextView) + .load(data) + } else { + // Clear image + val resolvedUrl = activeSessionHolder.getActiveSession().contentUrlResolver().resolveFullSize(data.url) + GlideApp + .with(contextView) + .load(resolvedUrl) + } + + req.override(Target.SIZE_ORIGINAL, Target.SIZE_ORIGINAL) + .fitCenter() + .into(target) + } + fun renderFitTarget(data: Data, mode: Mode, imageView: ImageView, callback: ((Boolean) -> Unit)? = null) { val size = processSize(data, mode) @@ -122,6 +143,48 @@ class ImageContentRenderer @Inject constructor(private val activeSessionHolder: .into(imageView) } + fun renderThumbnailDontTransform(data: Data, imageView: ImageView, callback: ((Boolean) -> Unit)? = null) { + + // a11y + imageView.contentDescription = data.filename + + val req = if (data.elementToDecrypt != null) { + // Encrypted image + GlideApp + .with(imageView) + .load(data) + } else { + // Clear image + val resolvedUrl = activeSessionHolder.getActiveSession().contentUrlResolver().resolveFullSize(data.url) + GlideApp + .with(imageView) + .load(resolvedUrl) + } + + req.listener(object : RequestListener { + override fun onLoadFailed(e: GlideException?, + model: Any?, + target: Target?, + isFirstResource: Boolean): Boolean { + callback?.invoke(false) + return false + } + + override fun onResourceReady(resource: Drawable?, + model: Any?, + target: Target?, + dataSource: DataSource?, + isFirstResource: Boolean): Boolean { + callback?.invoke(true) + return false + } + }) + .dontTransform() + .into(imageView) + + + } + private fun createGlideRequest(data: Data, mode: Mode, imageView: ImageView, size: Size): GlideRequest { return if (data.elementToDecrypt != null) { // Encrypted image diff --git a/vector/src/main/java/im/vector/riotx/features/media/ImageMediaViewerActivity.kt b/vector/src/main/java/im/vector/riotx/features/media/ImageMediaViewerActivity.kt index 092199759f..8a6c2f7545 100644 --- a/vector/src/main/java/im/vector/riotx/features/media/ImageMediaViewerActivity.kt +++ b/vector/src/main/java/im/vector/riotx/features/media/ImageMediaViewerActivity.kt @@ -91,6 +91,8 @@ class ImageMediaViewerActivity : VectorBaseActivity() { encryptedImageView.isVisible = false // Postpone transaction a bit until thumbnail is loaded supportPostponeEnterTransition() + + // We are not passing the exact same image that in the imageContentRenderer.renderFitTarget(mediaData, ImageContentRenderer.Mode.THUMBNAIL, imageTransitionView) { // Proceed with transaction scheduleStartPostponedTransition(imageTransitionView) diff --git a/vector/src/main/java/im/vector/riotx/features/media/RoomAttachmentProvider.kt b/vector/src/main/java/im/vector/riotx/features/media/RoomAttachmentProvider.kt new file mode 100644 index 0000000000..991ecaafde --- /dev/null +++ b/vector/src/main/java/im/vector/riotx/features/media/RoomAttachmentProvider.kt @@ -0,0 +1,82 @@ +/* + * 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.graphics.drawable.Drawable +import com.bumptech.glide.request.target.CustomViewTarget +import im.vector.matrix.android.api.session.events.model.toModel +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.attachment_viewer.AttachmentInfo +import im.vector.riotx.attachment_viewer.AttachmentSourceProvider +import im.vector.riotx.attachment_viewer.ImageViewHolder +import javax.inject.Inject + +class RoomAttachmentProvider( + private val attachments: List, + private val initialIndex: Int, + private val imageContentRenderer: ImageContentRenderer +) : AttachmentSourceProvider { + + override fun getItemCount(): Int { + return attachments.size + } + + override fun getAttachmentInfoAt(position: Int): AttachmentInfo { + return attachments[position].let { + val content = it.root.getClearContent().toModel() as? MessageWithAttachmentContent + val data = 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 + ) + AttachmentInfo.Image( + content?.url ?: "", + data + ) + } + } + + override fun loadImage(holder: ImageViewHolder, info: AttachmentInfo.Image) { + (info.data as? ImageContentRenderer.Data)?.let { + imageContentRenderer.render(it, holder.touchImageView, holder.customTargetView as CustomViewTarget<*, Drawable>) + } + } +// 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( + private val imageContentRenderer: ImageContentRenderer +) { + + fun createProvider(attachments: List, initialIndex: Int): RoomAttachmentProvider { + return RoomAttachmentProvider(attachments, initialIndex, imageContentRenderer) + } +} 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 new file mode 100644 index 0000000000..2df8bfd0f6 --- /dev/null +++ b/vector/src/main/java/im/vector/riotx/features/media/VectorAttachmentViewerActivity.kt @@ -0,0 +1,207 @@ +/* + * 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.content.Intent +import android.os.Bundle +import android.os.Parcelable +import android.view.View +import android.view.ViewTreeObserver +import androidx.core.app.ActivityCompat +import androidx.core.transition.addListener +import androidx.core.view.ViewCompat +import androidx.core.view.isInvisible +import androidx.core.view.isVisible +import androidx.transition.Transition +import im.vector.riotx.attachment_viewer.AttachmentViewerActivity +import im.vector.riotx.core.di.ActiveSessionHolder +import im.vector.riotx.core.di.DaggerScreenComponent +import im.vector.riotx.core.di.HasVectorInjector +import im.vector.riotx.core.di.ScreenComponent +import im.vector.riotx.core.di.VectorComponent +import im.vector.riotx.features.themes.ActivityOtherThemes +import im.vector.riotx.features.themes.ThemeUtils +import kotlinx.android.parcel.Parcelize +import timber.log.Timber +import javax.inject.Inject +import kotlin.system.measureTimeMillis + +class VectorAttachmentViewerActivity : AttachmentViewerActivity() { + + @Parcelize + data class Args( + val roomId: String?, + val eventId: String, + val sharedTransitionName: String? + ) : Parcelable + + @Inject + lateinit var sessionHolder: ActiveSessionHolder + + @Inject + lateinit var dataSourceFactory: RoomAttachmentProviderFactory + + @Inject + lateinit var imageContentRenderer: ImageContentRenderer + + private lateinit var screenComponent: ScreenComponent + + private var initialIndex = 0 + private var isAnimatingOut = false + + override fun onCreate(savedInstanceState: Bundle?) { + + super.onCreate(savedInstanceState) + Timber.i("onCreate Activity ${this.javaClass.simpleName}") + val vectorComponent = getVectorComponent() + screenComponent = DaggerScreenComponent.factory().create(vectorComponent, this) + val timeForInjection = measureTimeMillis { + screenComponent.inject(this) + } + Timber.v("Injecting dependencies into ${javaClass.simpleName} took $timeForInjection ms") + 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?.getAttachementMessages() ?: emptyList() + val index = events.indexOfFirst { it.eventId == args.eventId } + initialIndex = index + + + if (savedInstanceState == null && addTransitionListener()) { + args.sharedTransitionName?.let { + ViewCompat.setTransitionName(imageTransitionView, it) + transitionImageContainer.isVisible = true + + // Postpone transaction a bit until thumbnail is loaded + val mediaData: ImageContentRenderer.Data? = intent.getParcelableExtra(EXTRA_IMAGE_DATA) + if (mediaData != null) { + // will be shown at end of transition + pager2.isInvisible = true + supportPostponeEnterTransition() + imageContentRenderer.renderThumbnailDontTransform(mediaData, imageTransitionView) { + // Proceed with transaction + scheduleStartPostponedTransition(imageTransitionView) + } + } + } + } + + setSourceProvider(dataSourceFactory.createProvider(events, index)) + if (savedInstanceState == null) { + pager2.setCurrentItem(index, false) + } + + } + + private fun getOtherThemes() = ActivityOtherThemes.VectorAttachmentsPreview + + + override fun shouldAnimateDismiss(): Boolean { + return currentPosition != initialIndex + } + + override fun onBackPressed() { + if (currentPosition == initialIndex) { + // show back the transition view + // TODO, we should track and update the mapping + transitionImageContainer.isVisible = true + } + isAnimatingOut = true + super.onBackPressed() + } + + override fun animateClose() { + if (currentPosition == initialIndex) { + // show back the transition view + // TODO, we should track and update the mapping + transitionImageContainer.isVisible = true + } + isAnimatingOut = true + ActivityCompat.finishAfterTransition(this); + } + + /* ========================================================================================== + * PRIVATE METHODS + * ========================================================================================== */ + + /** + * Try and add a [Transition.TransitionListener] to the entering shared element + * [Transition]. We do this so that we can load the full-size image after the transition + * has completed. + * + * @return true if we were successful in adding a listener to the enter transition + */ + private fun addTransitionListener(): Boolean { + val transition = window.sharedElementEnterTransition + + if (transition != null) { + // There is an entering shared element transition so add a listener to it + transition.addListener( + onEnd = { + if (!isAnimatingOut) { + // The listener is also called when we are exiting + transitionImageContainer.isVisible = false + pager2.isInvisible = false + } + }, + onCancel = { + if (!isAnimatingOut) { + transitionImageContainer.isVisible = false + pager2.isInvisible = false + } + } + ) + return true + } + + // If we reach here then we have not added a listener + return false + } + + private fun args() = intent.getParcelableExtra(EXTRA_ARGS) + + + private fun getVectorComponent(): VectorComponent { + return (application as HasVectorInjector).injector() + } + + private fun scheduleStartPostponedTransition(sharedElement: View) { + sharedElement.viewTreeObserver.addOnPreDrawListener( + object : ViewTreeObserver.OnPreDrawListener { + override fun onPreDraw(): Boolean { + sharedElement.viewTreeObserver.removeOnPreDrawListener(this) + supportStartPostponedEnterTransition() + return true + } + }) + } + + companion object { + + const val EXTRA_ARGS = "EXTRA_ARGS" + const val EXTRA_IMAGE_DATA = "EXTRA_IMAGE_DATA" + + fun newIntent(context: Context, mediaData: ImageContentRenderer.Data, roomId: String?, eventId: String, sharedTransitionName: String?) = Intent(context, VectorAttachmentViewerActivity::class.java).also { + it.putExtra(EXTRA_ARGS, Args(roomId, eventId, sharedTransitionName)) + it.putExtra(EXTRA_IMAGE_DATA, mediaData) + } + + } +} 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 0b89ab8ec4..debd58e6d2 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 @@ -49,11 +49,7 @@ 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.filtered.FilteredRoomsActivity import im.vector.riotx.features.invite.InviteUsersToRoomActivity -import im.vector.riotx.features.media.BigImageViewerActivity -import im.vector.riotx.features.media.ImageContentRenderer -import im.vector.riotx.features.media.ImageMediaViewerActivity -import im.vector.riotx.features.media.VideoContentRenderer -import im.vector.riotx.features.media.VideoMediaViewerActivity +import im.vector.riotx.features.media.* import im.vector.riotx.features.roomdirectory.RoomDirectoryActivity import im.vector.riotx.features.roomdirectory.createroom.CreateRoomActivity import im.vector.riotx.features.roomdirectory.roompreview.RoomPreviewActivity @@ -89,7 +85,8 @@ class DefaultNavigator @Inject constructor( override fun performDeviceVerification(context: Context, otherUserId: String, sasTransactionId: String) { val session = sessionHolder.getSafeActiveSession() ?: return - val tx = session.cryptoService().verificationService().getExistingTransaction(otherUserId, sasTransactionId) ?: return + val tx = session.cryptoService().verificationService().getExistingTransaction(otherUserId, sasTransactionId) + ?: return (tx as? IncomingSasVerificationTransaction)?.performAccept() if (context is VectorBaseActivity) { VerificationBottomSheet.withArgs( @@ -216,7 +213,8 @@ class DefaultNavigator @Inject constructor( ?.let { avatarUrl -> val intent = BigImageViewerActivity.newIntent(activity, matrixItem.getBestName(), avatarUrl) val options = sharedElement?.let { - ActivityOptionsCompat.makeSceneTransitionAnimation(activity, it, ViewCompat.getTransitionName(it) ?: "") + ActivityOptionsCompat.makeSceneTransitionAnimation(activity, it, ViewCompat.getTransitionName(it) + ?: "") } activity.startActivity(intent, options?.toBundle()) } @@ -244,22 +242,38 @@ class DefaultNavigator @Inject constructor( context.startActivity(WidgetActivity.newIntent(context, widgetArgs)) } - override fun openImageViewer(activity: Activity, mediaData: ImageContentRenderer.Data, view: View, options: ((MutableList>) -> Unit)?) { - val intent = ImageMediaViewerActivity.newIntent(activity, mediaData, ViewCompat.getTransitionName(view)) - 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)) + override fun openImageViewer(activity: Activity, roomId: String?, mediaData: ImageContentRenderer.Data, view: View, options: ((MutableList>) -> Unit)?) { + VectorAttachmentViewerActivity.newIntent(activity, mediaData, roomId, mediaData.eventId, 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.navigationBarBackground)?.let { - pairs.add(Pair(it, Window.NAVIGATION_BAR_BACKGROUND_TRANSITION_NAME)) - } - } - pairs.add(Pair(view, ViewCompat.getTransitionName(view) ?: "")) - options?.invoke(pairs) + pairs.add(Pair(view, ViewCompat.getTransitionName(view) ?: "")) + options?.invoke(pairs) - val bundle = ActivityOptionsCompat.makeSceneTransitionAnimation(activity, *pairs.toTypedArray()).toBundle() - activity.startActivity(intent, bundle) + val bundle = ActivityOptionsCompat.makeSceneTransitionAnimation(activity, *pairs.toTypedArray()).toBundle() + activity.startActivity(intent, bundle) + } +// val intent = ImageMediaViewerActivity.newIntent(activity, mediaData, ViewCompat.getTransitionName(view)) +// 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)) +// } +// } +// pairs.add(Pair(view, ViewCompat.getTransitionName(view) ?: "")) +// options?.invoke(pairs) +// +// val bundle = ActivityOptionsCompat.makeSceneTransitionAnimation(activity, *pairs.toTypedArray()).toBundle() +// activity.startActivity(intent, bundle) } override fun openVideoViewer(activity: Activity, mediaData: VideoContentRenderer.Data) { 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 ce4d5ef3ea..54c0f55a7b 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 @@ -91,7 +91,7 @@ interface Navigator { fun openRoomWidget(context: Context, roomId: String, widget: Widget) - fun openImageViewer(activity: Activity, mediaData: ImageContentRenderer.Data, view: View, options: ((MutableList>) -> Unit)?) + fun openImageViewer(activity: Activity, roomId: String?, mediaData: ImageContentRenderer.Data, view: View, options: ((MutableList>) -> Unit)?) fun openVideoViewer(activity: Activity, mediaData: VideoContentRenderer.Data) } diff --git a/vector/src/main/java/im/vector/riotx/features/popup/PopupAlertManager.kt b/vector/src/main/java/im/vector/riotx/features/popup/PopupAlertManager.kt index 78a0cece41..e5b2f34f61 100644 --- a/vector/src/main/java/im/vector/riotx/features/popup/PopupAlertManager.kt +++ b/vector/src/main/java/im/vector/riotx/features/popup/PopupAlertManager.kt @@ -26,6 +26,7 @@ import com.tapadoo.alerter.Alerter import com.tapadoo.alerter.OnHideAlertListener import dagger.Lazy import im.vector.riotx.R +import im.vector.riotx.core.platform.VectorBaseActivity import im.vector.riotx.features.home.AvatarRenderer import im.vector.riotx.features.themes.ThemeUtils import timber.log.Timber @@ -83,7 +84,7 @@ class PopupAlertManager @Inject constructor(private val avatarRenderer: Lazy + + \ No newline at end of file From 2d4a728af47898f77579b26530b5519ae5caac42 Mon Sep 17 00:00:00 2001 From: Valere Date: Mon, 6 Jul 2020 09:45:42 +0200 Subject: [PATCH 02/25] Gif support --- .../AnimatedImageViewHolder.kt | 68 +++++++++++++++++++ .../AttachmentSourceProvider.kt | 4 +- .../attachment_viewer/AttachmentsAdapter.kt | 17 +++-- ...ewHolder.kt => ZoomableImageViewHolder.kt} | 2 +- .../layout/item_animated_image_attachment.xml | 22 ++++++ .../features/media/RoomAttachmentProvider.kt | 26 +++++-- 6 files changed, 127 insertions(+), 12 deletions(-) create mode 100644 attachment-viewer/src/main/java/im/vector/riotx/attachment_viewer/AnimatedImageViewHolder.kt rename attachment-viewer/src/main/java/im/vector/riotx/attachment_viewer/{ImageViewHolder.kt => ZoomableImageViewHolder.kt} (97%) create mode 100644 attachment-viewer/src/main/res/layout/item_animated_image_attachment.xml diff --git a/attachment-viewer/src/main/java/im/vector/riotx/attachment_viewer/AnimatedImageViewHolder.kt b/attachment-viewer/src/main/java/im/vector/riotx/attachment_viewer/AnimatedImageViewHolder.kt new file mode 100644 index 0000000000..10b3cf8ffc --- /dev/null +++ b/attachment-viewer/src/main/java/im/vector/riotx/attachment_viewer/AnimatedImageViewHolder.kt @@ -0,0 +1,68 @@ +/* + * 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.attachment_viewer + +import android.graphics.drawable.Animatable +import android.graphics.drawable.Drawable +import android.util.Log +import android.view.View +import android.widget.ImageView +import android.widget.LinearLayout +import android.widget.ProgressBar +import androidx.core.view.isVisible +import androidx.core.view.updateLayoutParams +import com.bumptech.glide.request.target.CustomViewTarget +import com.bumptech.glide.request.transition.Transition +import com.github.chrisbanes.photoview.PhotoView + +class AnimatedImageViewHolder constructor(itemView: View) : + BaseViewHolder(itemView) { + + val touchImageView: ImageView = itemView.findViewById(R.id.imageView) + val imageLoaderProgress: ProgressBar = itemView.findViewById(R.id.imageLoaderProgress) + + val customTargetView = object : CustomViewTarget(touchImageView) { + + override fun onResourceLoading(placeholder: Drawable?) { + imageLoaderProgress.isVisible = true + } + + override fun onLoadFailed(errorDrawable: Drawable?) { + imageLoaderProgress.isVisible = false + } + + override fun onResourceCleared(placeholder: Drawable?) { + touchImageView.setImageDrawable(placeholder) + } + + override fun onResourceReady(resource: Drawable, transition: Transition?) { + imageLoaderProgress.isVisible = false + // Glide mess up the view size :/ + touchImageView.updateLayoutParams { + width = LinearLayout.LayoutParams.MATCH_PARENT + height = LinearLayout.LayoutParams.MATCH_PARENT + } + touchImageView.setImageDrawable(resource) + if (resource is Animatable) { + resource.start(); + } + } + } + + override fun bind(attachmentInfo: AttachmentInfo) { + } +} diff --git a/attachment-viewer/src/main/java/im/vector/riotx/attachment_viewer/AttachmentSourceProvider.kt b/attachment-viewer/src/main/java/im/vector/riotx/attachment_viewer/AttachmentSourceProvider.kt index 9fd2902970..f88083f818 100644 --- a/attachment-viewer/src/main/java/im/vector/riotx/attachment_viewer/AttachmentSourceProvider.kt +++ b/attachment-viewer/src/main/java/im/vector/riotx/attachment_viewer/AttachmentSourceProvider.kt @@ -18,6 +18,7 @@ package im.vector.riotx.attachment_viewer sealed class AttachmentInfo { data class Image(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 Audio(val url: String, val data: Any) : AttachmentInfo() data class File(val url: String, val data: Any) : AttachmentInfo() @@ -32,5 +33,6 @@ interface AttachmentSourceProvider { fun getAttachmentInfoAt(position: Int): AttachmentInfo - fun loadImage(holder: ImageViewHolder, info: AttachmentInfo.Image) + fun loadImage(holder: ZoomableImageViewHolder, info: AttachmentInfo.Image) + fun loadImage(holder: AnimatedImageViewHolder, info: AttachmentInfo.AnimatedImage) } diff --git a/attachment-viewer/src/main/java/im/vector/riotx/attachment_viewer/AttachmentsAdapter.kt b/attachment-viewer/src/main/java/im/vector/riotx/attachment_viewer/AttachmentsAdapter.kt index b9914e4dda..f762a6ea3e 100644 --- a/attachment-viewer/src/main/java/im/vector/riotx/attachment_viewer/AttachmentsAdapter.kt +++ b/attachment-viewer/src/main/java/im/vector/riotx/attachment_viewer/AttachmentsAdapter.kt @@ -60,7 +60,8 @@ class AttachmentsAdapter() : RecyclerView.Adapter() { val inflater = LayoutInflater.from(parent.context) val itemView = inflater.inflate(viewType, parent, false) return when (viewType) { - R.layout.item_image_attachment -> ImageViewHolder(itemView) + R.layout.item_image_attachment -> ZoomableImageViewHolder(itemView) + R.layout.item_animated_image_attachment -> AnimatedImageViewHolder(itemView) else -> AttachmentViewHolder(itemView) } } @@ -70,6 +71,7 @@ class AttachmentsAdapter() : RecyclerView.Adapter() { return when (info) { is AttachmentInfo.Image -> R.layout.item_image_attachment is AttachmentInfo.Video -> R.layout.item_video_attachment + is AttachmentInfo.AnimatedImage -> R.layout.item_animated_image_attachment is AttachmentInfo.Audio -> TODO() is AttachmentInfo.File -> TODO() } @@ -83,15 +85,22 @@ class AttachmentsAdapter() : RecyclerView.Adapter() { override fun onBindViewHolder(holder: BaseViewHolder, position: Int) { attachmentSourceProvider?.getAttachmentInfoAt(position)?.let { holder.bind(it) - if (it is AttachmentInfo.Image) { - attachmentSourceProvider?.loadImage(holder as ImageViewHolder, it) + when(it) { + is AttachmentInfo.Image -> { + attachmentSourceProvider?.loadImage(holder as ZoomableImageViewHolder, it) + } + is AttachmentInfo.AnimatedImage -> { + attachmentSourceProvider?.loadImage(holder as AnimatedImageViewHolder, it) + } + else -> {} } + } } fun isScaled(position: Int): Boolean { val holder = recyclerView?.findViewHolderForAdapterPosition(position) - if (holder is ImageViewHolder) { + if (holder is ZoomableImageViewHolder) { return holder.touchImageView.attacher.scale > 1f } return false diff --git a/attachment-viewer/src/main/java/im/vector/riotx/attachment_viewer/ImageViewHolder.kt b/attachment-viewer/src/main/java/im/vector/riotx/attachment_viewer/ZoomableImageViewHolder.kt similarity index 97% rename from attachment-viewer/src/main/java/im/vector/riotx/attachment_viewer/ImageViewHolder.kt rename to attachment-viewer/src/main/java/im/vector/riotx/attachment_viewer/ZoomableImageViewHolder.kt index cac6a4fd9e..6dd387b870 100644 --- a/attachment-viewer/src/main/java/im/vector/riotx/attachment_viewer/ImageViewHolder.kt +++ b/attachment-viewer/src/main/java/im/vector/riotx/attachment_viewer/ZoomableImageViewHolder.kt @@ -27,7 +27,7 @@ import com.bumptech.glide.request.target.CustomViewTarget import com.bumptech.glide.request.transition.Transition import com.github.chrisbanes.photoview.PhotoView -class ImageViewHolder constructor(itemView: View) : +class ZoomableImageViewHolder constructor(itemView: View) : BaseViewHolder(itemView) { val touchImageView: PhotoView = itemView.findViewById(R.id.touchImageView) diff --git a/attachment-viewer/src/main/res/layout/item_animated_image_attachment.xml b/attachment-viewer/src/main/res/layout/item_animated_image_attachment.xml new file mode 100644 index 0000000000..1096267124 --- /dev/null +++ b/attachment-viewer/src/main/res/layout/item_animated_image_attachment.xml @@ -0,0 +1,22 @@ + + + + + + + + \ No newline at end of file diff --git a/vector/src/main/java/im/vector/riotx/features/media/RoomAttachmentProvider.kt b/vector/src/main/java/im/vector/riotx/features/media/RoomAttachmentProvider.kt index 991ecaafde..079c435001 100644 --- a/vector/src/main/java/im/vector/riotx/features/media/RoomAttachmentProvider.kt +++ b/vector/src/main/java/im/vector/riotx/features/media/RoomAttachmentProvider.kt @@ -24,9 +24,10 @@ import im.vector.matrix.android.api.session.room.model.message.MessageWithAttach 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.attachment_viewer.AnimatedImageViewHolder import im.vector.riotx.attachment_viewer.AttachmentInfo import im.vector.riotx.attachment_viewer.AttachmentSourceProvider -import im.vector.riotx.attachment_viewer.ImageViewHolder +import im.vector.riotx.attachment_viewer.ZoomableImageViewHolder import javax.inject.Inject class RoomAttachmentProvider( @@ -53,14 +54,27 @@ class RoomAttachmentProvider( width = null, height = null ) - AttachmentInfo.Image( - content?.url ?: "", - data - ) + if (content?.mimeType == "image/gif") { + AttachmentInfo.AnimatedImage( + content.url ?: "", + data + ) + } else { + AttachmentInfo.Image( + content?.url ?: "", + data + ) + } } } - override fun loadImage(holder: ImageViewHolder, info: AttachmentInfo.Image) { + override fun loadImage(holder: ZoomableImageViewHolder, info: AttachmentInfo.Image) { + (info.data as? ImageContentRenderer.Data)?.let { + imageContentRenderer.render(it, holder.touchImageView, holder.customTargetView as CustomViewTarget<*, Drawable>) + } + } + + override fun loadImage(holder: AnimatedImageViewHolder, info: AttachmentInfo.AnimatedImage) { (info.data as? ImageContentRenderer.Data)?.let { imageContentRenderer.render(it, holder.touchImageView, holder.customTargetView as CustomViewTarget<*, Drawable>) } From 76133ab55e08838d59eec730d9f0d9aac8d28f55 Mon Sep 17 00:00:00 2001 From: Valere Date: Mon, 6 Jul 2020 15:47:15 +0200 Subject: [PATCH 03/25] Simple overlay --- attachment-viewer/build.gradle | 4 - .../AttachmentSourceProvider.kt | 6 ++ .../AttachmentViewerActivity.kt | 49 +++++++++-- .../features/media/AttachmentOverlayView.kt | 58 +++++++++++++ .../features/media/RoomAttachmentProvider.kt | 41 ++++++++- .../media/VectorAttachmentViewerActivity.kt | 24 ++++-- .../layout/merge_image_attachment_overlay.xml | 83 +++++++++++++++++++ vector/src/main/res/values/colors_riotx.xml | 1 + 8 files changed, 244 insertions(+), 22 deletions(-) create mode 100644 vector/src/main/java/im/vector/riotx/features/media/AttachmentOverlayView.kt create mode 100644 vector/src/main/res/layout/merge_image_attachment_overlay.xml diff --git a/attachment-viewer/build.gradle b/attachment-viewer/build.gradle index 7fcda7a742..ac41c3ed75 100644 --- a/attachment-viewer/build.gradle +++ b/attachment-viewer/build.gradle @@ -34,16 +34,12 @@ buildscript { android { compileSdkVersion 29 - buildToolsVersion "29.0.3" defaultConfig { minSdkVersion 21 targetSdkVersion 29 versionCode 1 versionName "1.0" - - testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" - consumerProguardFiles "consumer-rules.pro" } buildTypes { diff --git a/attachment-viewer/src/main/java/im/vector/riotx/attachment_viewer/AttachmentSourceProvider.kt b/attachment-viewer/src/main/java/im/vector/riotx/attachment_viewer/AttachmentSourceProvider.kt index f88083f818..7b24f4bb46 100644 --- a/attachment-viewer/src/main/java/im/vector/riotx/attachment_viewer/AttachmentSourceProvider.kt +++ b/attachment-viewer/src/main/java/im/vector/riotx/attachment_viewer/AttachmentSourceProvider.kt @@ -16,6 +16,9 @@ package im.vector.riotx.attachment_viewer +import android.content.Context +import android.view.View + sealed class AttachmentInfo { data class Image(val url: String, val data: Any?) : AttachmentInfo() data class AnimatedImage(val url: String, val data: Any?) : AttachmentInfo() @@ -34,5 +37,8 @@ interface AttachmentSourceProvider { fun getAttachmentInfoAt(position: Int): AttachmentInfo fun loadImage(holder: ZoomableImageViewHolder, info: AttachmentInfo.Image) + fun loadImage(holder: AnimatedImageViewHolder, info: AttachmentInfo.AnimatedImage) + + fun overlayViewAtPosition(context: Context, position: Int) : View? } diff --git a/attachment-viewer/src/main/java/im/vector/riotx/attachment_viewer/AttachmentViewerActivity.kt b/attachment-viewer/src/main/java/im/vector/riotx/attachment_viewer/AttachmentViewerActivity.kt index 2d4cbff00d..4d2b4e3459 100644 --- a/attachment-viewer/src/main/java/im/vector/riotx/attachment_viewer/AttachmentViewerActivity.kt +++ b/attachment-viewer/src/main/java/im/vector/riotx/attachment_viewer/AttachmentViewerActivity.kt @@ -25,7 +25,9 @@ import android.view.View import android.view.ViewGroup import android.widget.ImageView import androidx.appcompat.app.AppCompatActivity +import androidx.core.view.ViewCompat import androidx.core.view.isVisible +import androidx.core.view.updatePadding import androidx.viewpager2.widget.ViewPager2 import kotlinx.android.synthetic.main.activity_attachment_viewer.* import kotlin.math.abs @@ -36,8 +38,16 @@ abstract class AttachmentViewerActivity : AppCompatActivity() { lateinit var imageTransitionView: ImageView lateinit var transitionImageContainer: ViewGroup - // TODO + var topInset = 0 + private var overlayView: View? = null + set(value) { + if (value == overlayView) return + overlayView?.let { rootContainer.removeView(it) } + rootContainer.addView(value) + value?.updatePadding(top = topInset) + field = value + } private lateinit var swipeDismissHandler: SwipeToDismissHandler private lateinit var directionDetector: SwipeDirectionDetector @@ -53,6 +63,7 @@ abstract class AttachmentViewerActivity : AppCompatActivity() { private var wasScaled: Boolean = false private var isSwipeToDismissAllowed: Boolean = true private lateinit var attachmentsAdapter: AttachmentsAdapter + private var isOverlayWasClicked = false // private val shouldDismissToBottom: Boolean // get() = e == null @@ -67,6 +78,20 @@ abstract class AttachmentViewerActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) + + // This is important for the dispatchTouchEvent, if not we must correct + // the touch coordinates + window.decorView.systemUiVisibility = ( + View.SYSTEM_UI_FLAG_LAYOUT_STABLE + or View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN) + +// // clear FLAG_TRANSLUCENT_STATUS flag: +// window.clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS); +// +//// add FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS flag to the window +// window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS); + + setContentView(R.layout.activity_attachment_viewer) attachmentPager.orientation = ViewPager2.ORIENTATION_HORIZONTAL attachmentsAdapter = AttachmentsAdapter() @@ -83,6 +108,7 @@ abstract class AttachmentViewerActivity : AppCompatActivity() { override fun onPageSelected(position: Int) { currentPosition = position + overlayView = attachmentsAdapter.attachmentSourceProvider?.overlayViewAtPosition(this@AttachmentViewerActivity, position) } }) @@ -92,6 +118,13 @@ abstract class AttachmentViewerActivity : AppCompatActivity() { scaleDetector = createScaleGestureDetector() + + ViewCompat.setOnApplyWindowInsetsListener(rootContainer) { _, insets -> + overlayView?.updatePadding(top = insets.systemWindowInsetTop) + topInset = insets.systemWindowInsetTop + insets + } + } override fun dispatchTouchEvent(ev: MotionEvent): Boolean { @@ -99,9 +132,9 @@ abstract class AttachmentViewerActivity : AppCompatActivity() { // The zoomable view is configured to disallow interception when image is zoomed // Check if the overlay is visible, and wants to handle the click -// if (overlayView.isVisible && overlayView?.dispatchTouchEvent(event) == true) { -// return true -// } + if (overlayView?.isVisible == true && overlayView?.dispatchTouchEvent(ev) == true) { + return true + } Log.v("ATTACHEMENTS", "================\ndispatchTouchEvent $ev") @@ -143,14 +176,14 @@ abstract class AttachmentViewerActivity : AppCompatActivity() { attachmentPager.dispatchTouchEvent(event) swipeDismissHandler.onTouch(rootContainer, event) -// isOverlayWasClicked = dispatchOverlayTouch(event) + isOverlayWasClicked = dispatchOverlayTouch(event) } private fun handleEventActionUp(event: MotionEvent) { // wasDoubleTapped = false swipeDismissHandler.onTouch(rootContainer, event) attachmentPager.dispatchTouchEvent(event) -// isOverlayWasClicked = dispatchOverlayTouch(event) + isOverlayWasClicked = dispatchOverlayTouch(event) } private fun handleTouchIfNotScaled(event: MotionEvent): Boolean { @@ -159,7 +192,7 @@ abstract class AttachmentViewerActivity : AppCompatActivity() { directionDetector.handleTouchEvent(event) return when (swipeDirection) { - SwipeDirection.Up, SwipeDirection.Down -> { + SwipeDirection.Up, SwipeDirection.Down -> { if (isSwipeToDismissAllowed && !wasScaled && isImagePagerIdle) { swipeDismissHandler.onTouch(rootContainer, event) } else true @@ -167,7 +200,7 @@ abstract class AttachmentViewerActivity : AppCompatActivity() { SwipeDirection.Left, SwipeDirection.Right -> { attachmentPager.dispatchTouchEvent(event) } - else -> true + else -> true } } diff --git a/vector/src/main/java/im/vector/riotx/features/media/AttachmentOverlayView.kt b/vector/src/main/java/im/vector/riotx/features/media/AttachmentOverlayView.kt new file mode 100644 index 0000000000..49930fde76 --- /dev/null +++ b/vector/src/main/java/im/vector/riotx/features/media/AttachmentOverlayView.kt @@ -0,0 +1,58 @@ +/* + * 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.Color +import android.util.AttributeSet +import android.view.View +import android.widget.ImageView +import android.widget.TextView +import androidx.constraintlayout.widget.ConstraintLayout +import androidx.core.view.updateLayoutParams +import im.vector.riotx.R +import im.vector.riotx.attachment_viewer.AttachmentInfo + +class AttachmentOverlayView @JvmOverloads constructor( + context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0 +) : ConstraintLayout(context, attrs, defStyleAttr) { + + + var onShareCallback: (() -> Unit) ? = null + var onBack: (() -> Unit) ? = null + + private val counterTextView: TextView + private val infoTextView: TextView + private val shareImage: ImageView + + init { + View.inflate(context, R.layout.merge_image_attachment_overlay, this) + setBackgroundColor(Color.TRANSPARENT) + counterTextView = findViewById(R.id.overlayCounterText) + infoTextView = findViewById(R.id.overlayInfoText) + shareImage = findViewById(R.id.overlayShareButton) + + findViewById(R.id.overlayBackButton).setOnClickListener { + onBack?.invoke() + } + } + + fun updateWith(counter: String, senderInfo : String) { + counterTextView.text = counter + infoTextView.text = senderInfo + } +} diff --git a/vector/src/main/java/im/vector/riotx/features/media/RoomAttachmentProvider.kt b/vector/src/main/java/im/vector/riotx/features/media/RoomAttachmentProvider.kt index 079c435001..099d4fed5d 100644 --- a/vector/src/main/java/im/vector/riotx/features/media/RoomAttachmentProvider.kt +++ b/vector/src/main/java/im/vector/riotx/features/media/RoomAttachmentProvider.kt @@ -16,7 +16,9 @@ package im.vector.riotx.features.media +import android.content.Context import android.graphics.drawable.Drawable +import android.view.View import com.bumptech.glide.request.target.CustomViewTarget import im.vector.matrix.android.api.session.events.model.toModel import im.vector.matrix.android.api.session.room.model.message.MessageContent @@ -28,14 +30,26 @@ import im.vector.riotx.attachment_viewer.AnimatedImageViewHolder import im.vector.riotx.attachment_viewer.AttachmentInfo import im.vector.riotx.attachment_viewer.AttachmentSourceProvider import im.vector.riotx.attachment_viewer.ZoomableImageViewHolder +import im.vector.riotx.core.date.VectorDateFormatter +import im.vector.riotx.core.extensions.localDateTime import javax.inject.Inject class RoomAttachmentProvider( private val attachments: List, private val initialIndex: Int, - private val imageContentRenderer: ImageContentRenderer + private val imageContentRenderer: ImageContentRenderer, + private val dateFormatter: VectorDateFormatter ) : AttachmentSourceProvider { + interface InteractionListener { + fun onDismissTapped() + fun onShareTapped() + } + + var interactionListener: InteractionListener? = null + + private var overlayView: AttachmentOverlayView? = null + override fun getItemCount(): Int { return attachments.size } @@ -79,6 +93,26 @@ class RoomAttachmentProvider( imageContentRenderer.render(it, holder.touchImageView, holder.customTargetView as CustomViewTarget<*, Drawable>) } } + + override fun overlayViewAtPosition(context: Context, position: Int): View? { + if (overlayView == null) { + overlayView = AttachmentOverlayView(context) + overlayView?.onBack = { + interactionListener?.onDismissTapped() + } + overlayView?.onShareCallback = { + interactionListener?.onShareTapped() + } + } + val item = attachments[position] + val dateString = item.root.localDateTime().let { + "${dateFormatter.formatMessageDay(it)} at ${dateFormatter.formatMessageHour(it)} " + } + overlayView?.updateWith("${position + 1} of ${attachments.size}","${item.senderInfo.displayName} $dateString" ) + 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) @@ -87,10 +121,11 @@ class RoomAttachmentProvider( } class RoomAttachmentProviderFactory @Inject constructor( - private val imageContentRenderer: ImageContentRenderer + private val imageContentRenderer: ImageContentRenderer, + private val vectorDateFormatter: VectorDateFormatter ) { fun createProvider(attachments: List, initialIndex: Int): RoomAttachmentProvider { - return RoomAttachmentProvider(attachments, initialIndex, imageContentRenderer) + return RoomAttachmentProvider(attachments, initialIndex, imageContentRenderer, vectorDateFormatter) } } 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 2df8bfd0f6..efc9eca517 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 @@ -22,17 +22,15 @@ import android.os.Parcelable import android.view.View import android.view.ViewTreeObserver import androidx.core.app.ActivityCompat +import androidx.core.content.ContextCompat import androidx.core.transition.addListener import androidx.core.view.ViewCompat import androidx.core.view.isInvisible import androidx.core.view.isVisible import androidx.transition.Transition +import im.vector.riotx.R import im.vector.riotx.attachment_viewer.AttachmentViewerActivity -import im.vector.riotx.core.di.ActiveSessionHolder -import im.vector.riotx.core.di.DaggerScreenComponent -import im.vector.riotx.core.di.HasVectorInjector -import im.vector.riotx.core.di.ScreenComponent -import im.vector.riotx.core.di.VectorComponent +import im.vector.riotx.core.di.* import im.vector.riotx.features.themes.ActivityOtherThemes import im.vector.riotx.features.themes.ThemeUtils import kotlinx.android.parcel.Parcelize @@ -40,7 +38,7 @@ import timber.log.Timber import javax.inject.Inject import kotlin.system.measureTimeMillis -class VectorAttachmentViewerActivity : AttachmentViewerActivity() { +class VectorAttachmentViewerActivity : AttachmentViewerActivity(), RoomAttachmentProvider.InteractionListener { @Parcelize data class Args( @@ -103,11 +101,15 @@ class VectorAttachmentViewerActivity : AttachmentViewerActivity() { } } - setSourceProvider(dataSourceFactory.createProvider(events, index)) + val sourceProvider = dataSourceFactory.createProvider(events, index) + sourceProvider.interactionListener = this + setSourceProvider(sourceProvider) if (savedInstanceState == null) { pager2.setCurrentItem(index, false) } + window.statusBarColor = ContextCompat.getColor(this, R.color.black_alpha) + } private fun getOtherThemes() = ActivityOtherThemes.VectorAttachmentsPreview @@ -204,4 +206,12 @@ class VectorAttachmentViewerActivity : AttachmentViewerActivity() { } } + + override fun onDismissTapped() { + animateClose() + } + + override fun onShareTapped() { + TODO("Not yet implemented") + } } diff --git a/vector/src/main/res/layout/merge_image_attachment_overlay.xml b/vector/src/main/res/layout/merge_image_attachment_overlay.xml new file mode 100644 index 0000000000..07d4baedc1 --- /dev/null +++ b/vector/src/main/res/layout/merge_image_attachment_overlay.xml @@ -0,0 +1,83 @@ + + + + + + + + + + + + + + + + + diff --git a/vector/src/main/res/values/colors_riotx.xml b/vector/src/main/res/values/colors_riotx.xml index a9cb32c3fd..c9d1c2a223 100644 --- a/vector/src/main/res/values/colors_riotx.xml +++ b/vector/src/main/res/values/colors_riotx.xml @@ -40,6 +40,7 @@ #FF000000 #FFFFFFFF + #55000000 Ongoing conference call.\nJoin as %1$s or %2$s Voice From 8c4c909f44c14ff9fd354259f3d7dcd4fb33d48e Mon Sep 17 00:00:00 2001 From: Valere Date: Wed, 8 Jul 2020 22:27:00 +0200 Subject: [PATCH 09/25] share action --- .../features/media/AttachmentOverlayView.kt | 3 ++ .../media/VectorAttachmentViewerActivity.kt | 38 ++++++++++++++++++- 2 files changed, 40 insertions(+), 1 deletion(-) diff --git a/vector/src/main/java/im/vector/riotx/features/media/AttachmentOverlayView.kt b/vector/src/main/java/im/vector/riotx/features/media/AttachmentOverlayView.kt index ebd54bcd0b..05ebe17dea 100644 --- a/vector/src/main/java/im/vector/riotx/features/media/AttachmentOverlayView.kt +++ b/vector/src/main/java/im/vector/riotx/features/media/AttachmentOverlayView.kt @@ -58,6 +58,9 @@ class AttachmentOverlayView @JvmOverloads constructor( findViewById(R.id.overlayBackButton).setOnClickListener { onBack?.invoke() } + findViewById(R.id.overlayShareButton).setOnClickListener { + onShareCallback?.invoke() + } } fun updateWith(counter: String, senderInfo: String) { 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 4c310b9c47..44b536b2ae 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 @@ -23,11 +23,21 @@ import android.view.View import android.view.ViewTreeObserver import androidx.core.app.ActivityCompat import androidx.core.content.ContextCompat +import androidx.core.net.toUri import androidx.core.transition.addListener import androidx.core.view.ViewCompat 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.AttachmentViewerActivity import im.vector.riotx.core.di.ActiveSessionHolder @@ -35,10 +45,13 @@ import im.vector.riotx.core.di.DaggerScreenComponent import im.vector.riotx.core.di.HasVectorInjector import im.vector.riotx.core.di.ScreenComponent import im.vector.riotx.core.di.VectorComponent +import im.vector.riotx.core.intent.getMimeTypeFromUri +import im.vector.riotx.core.utils.shareMedia 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 @@ -64,6 +77,7 @@ class VectorAttachmentViewerActivity : AttachmentViewerActivity(), RoomAttachmen private var initialIndex = 0 private var isAnimatingOut = false + private var eventList: List? = null override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) @@ -81,6 +95,7 @@ class VectorAttachmentViewerActivity : AttachmentViewerActivity(), RoomAttachmen 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 @@ -228,6 +243,27 @@ class VectorAttachmentViewerActivity : AttachmentViewerActivity(), RoomAttachmen } override fun onShareTapped() { - TODO("Not yet implemented") + // 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())) + } + } + } + ) + } } } From e9778d6febdc57a516fd58e2b1e35bf2a00a227c Mon Sep 17 00:00:00 2001 From: Valere Date: Wed, 8 Jul 2020 22:41:17 +0200 Subject: [PATCH 10/25] Video stop/resume when paging or bg/fg --- .../AttachmentViewerActivity.kt | 9 +++++++ .../attachmentviewer/AttachmentsAdapter.kt | 13 ++++++++++ .../riotx/attachmentviewer/VideoViewHolder.kt | 25 ++++++++++++++++++- 3 files changed, 46 insertions(+), 1 deletion(-) diff --git a/attachment-viewer/src/main/java/im/vector/riotx/attachmentviewer/AttachmentViewerActivity.kt b/attachment-viewer/src/main/java/im/vector/riotx/attachmentviewer/AttachmentViewerActivity.kt index 99a90eb033..2a83ab21c7 100644 --- a/attachment-viewer/src/main/java/im/vector/riotx/attachmentviewer/AttachmentViewerActivity.kt +++ b/attachment-viewer/src/main/java/im/vector/riotx/attachmentviewer/AttachmentViewerActivity.kt @@ -143,6 +143,15 @@ abstract class AttachmentViewerActivity : AppCompatActivity(), AttachmentEventLi overlayView = attachmentsAdapter.attachmentSourceProvider?.overlayViewAtPosition(this@AttachmentViewerActivity, position) } + override fun onPause() { + attachmentsAdapter.onPause(currentPosition) + super.onPause() + } + + override fun onResume() { + super.onResume() + attachmentsAdapter.onResume(currentPosition) + } override fun dispatchTouchEvent(ev: MotionEvent): Boolean { // The zoomable view is configured to disallow interception when image is zoomed diff --git a/attachment-viewer/src/main/java/im/vector/riotx/attachmentviewer/AttachmentsAdapter.kt b/attachment-viewer/src/main/java/im/vector/riotx/attachmentviewer/AttachmentsAdapter.kt index 333a1b3625..d1929f271e 100644 --- a/attachment-viewer/src/main/java/im/vector/riotx/attachmentviewer/AttachmentsAdapter.kt +++ b/attachment-viewer/src/main/java/im/vector/riotx/attachmentviewer/AttachmentsAdapter.kt @@ -28,6 +28,8 @@ abstract class BaseViewHolder constructor(itemView: View) : open fun onRecycled() {} open fun onAttached() {} open fun onDetached() {} + open fun entersBackground() {} + open fun entersForeground() {} open fun onSelected(selected: Boolean) {} } @@ -121,6 +123,17 @@ class AttachmentsAdapter() : RecyclerView.Adapter() { return false } + + fun onPause(position: Int) { + val holder = recyclerView?.findViewHolderForAdapterPosition(position) as? BaseViewHolder + holder?.entersBackground() + } + + fun onResume(position: Int) { + val holder = recyclerView?.findViewHolderForAdapterPosition(position) as? BaseViewHolder + holder?.entersForeground() + + } // override fun getItemCount(): Int { // return 8 // } diff --git a/attachment-viewer/src/main/java/im/vector/riotx/attachmentviewer/VideoViewHolder.kt b/attachment-viewer/src/main/java/im/vector/riotx/attachmentviewer/VideoViewHolder.kt index 38b656559e..ea5fed1acb 100644 --- a/attachment-viewer/src/main/java/im/vector/riotx/attachmentviewer/VideoViewHolder.kt +++ b/attachment-viewer/src/main/java/im/vector/riotx/attachmentviewer/VideoViewHolder.kt @@ -26,6 +26,7 @@ import androidx.core.view.isVisible import io.reactivex.Observable import io.reactivex.android.schedulers.AndroidSchedulers import io.reactivex.disposables.Disposable +import kotlinx.coroutines.selects.select import java.io.File import java.lang.ref.WeakReference import java.util.concurrent.TimeUnit @@ -39,6 +40,7 @@ class VideoViewHolder constructor(itemView: View) : private var isSelected = false private var mVideoPath: String? = null private var progressDisposable: Disposable? = null + private var progress: Int = 0 var eventListener: WeakReference? = null @@ -89,12 +91,30 @@ class VideoViewHolder constructor(itemView: View) : } } + override fun entersBackground() { + if (videoView.isPlaying) { + progress = videoView.currentPosition + progressDisposable?.dispose() + progressDisposable = null + videoView.stopPlayback() + videoView.pause() + } + + } + + override fun entersForeground() { + onSelected(isSelected) + } + override fun onSelected(selected: Boolean) { if (!selected) { if (videoView.isPlaying) { + progress = videoView.currentPosition videoView.stopPlayback() progressDisposable?.dispose() progressDisposable = null + } else { + progress = 0 } } else { if (mVideoPath != null) { @@ -125,9 +145,12 @@ class VideoViewHolder constructor(itemView: View) : videoView.setVideoPath(mVideoPath) videoView.start() + if (progress > 0) { + videoView.seekTo(progress) + } } override fun bind(attachmentInfo: AttachmentInfo) { - Log.v("FOO", "") + progress = 0 } } From e24d5b3ca403ea42f72992c15c5dda89ff135dbe Mon Sep 17 00:00:00 2001 From: Valere Date: Wed, 8 Jul 2020 22:58:27 +0200 Subject: [PATCH 11/25] Simple play/pause overlay --- .../attachmentviewer/AttachmentEvents.kt | 6 +++- .../AttachmentViewerActivity.kt | 7 +++++ .../attachmentviewer/AttachmentsAdapter.kt | 4 +-- .../riotx/attachmentviewer/VideoViewHolder.kt | 30 ++++++++++++++----- .../features/media/AttachmentOverlayView.kt | 7 +++++ .../features/media/RoomAttachmentProvider.kt | 4 +++ .../media/VectorAttachmentViewerActivity.kt | 5 ++++ 7 files changed, 53 insertions(+), 10 deletions(-) diff --git a/attachment-viewer/src/main/java/im/vector/riotx/attachmentviewer/AttachmentEvents.kt b/attachment-viewer/src/main/java/im/vector/riotx/attachmentviewer/AttachmentEvents.kt index 997790a938..5b1f2ab90d 100644 --- a/attachment-viewer/src/main/java/im/vector/riotx/attachmentviewer/AttachmentEvents.kt +++ b/attachment-viewer/src/main/java/im/vector/riotx/attachmentviewer/AttachmentEvents.kt @@ -21,6 +21,10 @@ sealed class AttachmentEvents { } interface AttachmentEventListener { - fun onEvent(event: AttachmentEvents) } + +sealed class AttachmentCommands { + object PauseVideo : AttachmentCommands() + object StartVideo : AttachmentCommands() +} diff --git a/attachment-viewer/src/main/java/im/vector/riotx/attachmentviewer/AttachmentViewerActivity.kt b/attachment-viewer/src/main/java/im/vector/riotx/attachmentviewer/AttachmentViewerActivity.kt index 2a83ab21c7..6f2436f261 100644 --- a/attachment-viewer/src/main/java/im/vector/riotx/attachmentviewer/AttachmentViewerActivity.kt +++ b/attachment-viewer/src/main/java/im/vector/riotx/attachmentviewer/AttachmentViewerActivity.kt @@ -152,6 +152,7 @@ abstract class AttachmentViewerActivity : AppCompatActivity(), AttachmentEventLi super.onResume() attachmentsAdapter.onResume(currentPosition) } + override fun dispatchTouchEvent(ev: MotionEvent): Boolean { // The zoomable view is configured to disallow interception when image is zoomed @@ -302,6 +303,12 @@ abstract class AttachmentViewerActivity : AppCompatActivity(), AttachmentEventLi finish() } + public fun handle(commands: AttachmentCommands) { + (attachmentsAdapter.recyclerView?.findViewHolderForAdapterPosition(currentPosition) as? BaseViewHolder)?.let { + it.handleCommand(commands) + } + } + private fun hideSystemUI() { systemUiVisibility = false // Enables regular immersive mode. diff --git a/attachment-viewer/src/main/java/im/vector/riotx/attachmentviewer/AttachmentsAdapter.kt b/attachment-viewer/src/main/java/im/vector/riotx/attachmentviewer/AttachmentsAdapter.kt index d1929f271e..b0cb5193e8 100644 --- a/attachment-viewer/src/main/java/im/vector/riotx/attachmentviewer/AttachmentsAdapter.kt +++ b/attachment-viewer/src/main/java/im/vector/riotx/attachmentviewer/AttachmentsAdapter.kt @@ -31,6 +31,8 @@ abstract class BaseViewHolder constructor(itemView: View) : open fun entersBackground() {} open fun entersForeground() {} open fun onSelected(selected: Boolean) {} + + open fun handleCommand(commands: AttachmentCommands) {} } class AttachmentViewHolder constructor(itemView: View) : @@ -123,7 +125,6 @@ class AttachmentsAdapter() : RecyclerView.Adapter() { return false } - fun onPause(position: Int) { val holder = recyclerView?.findViewHolderForAdapterPosition(position) as? BaseViewHolder holder?.entersBackground() @@ -132,7 +133,6 @@ class AttachmentsAdapter() : RecyclerView.Adapter() { fun onResume(position: Int) { val holder = recyclerView?.findViewHolderForAdapterPosition(position) as? BaseViewHolder holder?.entersForeground() - } // override fun getItemCount(): Int { // return 8 diff --git a/attachment-viewer/src/main/java/im/vector/riotx/attachmentviewer/VideoViewHolder.kt b/attachment-viewer/src/main/java/im/vector/riotx/attachmentviewer/VideoViewHolder.kt index ea5fed1acb..a2424dda57 100644 --- a/attachment-viewer/src/main/java/im/vector/riotx/attachmentviewer/VideoViewHolder.kt +++ b/attachment-viewer/src/main/java/im/vector/riotx/attachmentviewer/VideoViewHolder.kt @@ -26,7 +26,6 @@ import androidx.core.view.isVisible import io.reactivex.Observable import io.reactivex.android.schedulers.AndroidSchedulers import io.reactivex.disposables.Disposable -import kotlinx.coroutines.selects.select import java.io.File import java.lang.ref.WeakReference import java.util.concurrent.TimeUnit @@ -41,6 +40,7 @@ class VideoViewHolder constructor(itemView: View) : private var mVideoPath: String? = null private var progressDisposable: Disposable? = null private var progress: Int = 0 + private var wasPaused = false var eventListener: WeakReference? = null @@ -99,7 +99,6 @@ class VideoViewHolder constructor(itemView: View) : videoView.stopPlayback() videoView.pause() } - } override fun entersForeground() { @@ -111,11 +110,11 @@ class VideoViewHolder constructor(itemView: View) : if (videoView.isPlaying) { progress = videoView.currentPosition videoView.stopPlayback() - progressDisposable?.dispose() - progressDisposable = null } else { progress = 0 } + progressDisposable?.dispose() + progressDisposable = null } else { if (mVideoPath != null) { startPlaying() @@ -144,13 +143,30 @@ class VideoViewHolder constructor(itemView: View) : } videoView.setVideoPath(mVideoPath) - videoView.start() - if (progress > 0) { - videoView.seekTo(progress) + if (!wasPaused) { + videoView.start() + if (progress > 0) { + videoView.seekTo(progress) + } + } + } + + override fun handleCommand(commands: AttachmentCommands) { + if (!isSelected) return + when (commands) { + AttachmentCommands.StartVideo -> { + wasPaused = false + videoView.start() + } + AttachmentCommands.PauseVideo -> { + wasPaused = true + videoView.pause() + } } } override fun bind(attachmentInfo: AttachmentInfo) { progress = 0 + wasPaused = false } } diff --git a/vector/src/main/java/im/vector/riotx/features/media/AttachmentOverlayView.kt b/vector/src/main/java/im/vector/riotx/features/media/AttachmentOverlayView.kt index 05ebe17dea..a2657f7daf 100644 --- a/vector/src/main/java/im/vector/riotx/features/media/AttachmentOverlayView.kt +++ b/vector/src/main/java/im/vector/riotx/features/media/AttachmentOverlayView.kt @@ -35,6 +35,7 @@ class AttachmentOverlayView @JvmOverloads constructor( var onShareCallback: (() -> Unit)? = null var onBack: (() -> Unit)? = null + var onPlayPause: ((play: Boolean) -> Unit)? = null private val counterTextView: TextView private val infoTextView: TextView @@ -42,6 +43,8 @@ class AttachmentOverlayView @JvmOverloads constructor( private val overlayPlayPauseButton: ImageView private val overlaySeekBar: SeekBar + var isPlaying = false + val videoControlsGroup: Group init { @@ -61,6 +64,9 @@ class AttachmentOverlayView @JvmOverloads constructor( findViewById(R.id.overlayShareButton).setOnClickListener { onShareCallback?.invoke() } + findViewById(R.id.overlayPlayPauseButton).setOnClickListener { + onPlayPause?.invoke(!isPlaying) + } } fun updateWith(counter: String, senderInfo: String) { @@ -74,6 +80,7 @@ class AttachmentOverlayView @JvmOverloads constructor( 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) + isPlaying = event.isPlaying overlaySeekBar.progress = percent } } diff --git a/vector/src/main/java/im/vector/riotx/features/media/RoomAttachmentProvider.kt b/vector/src/main/java/im/vector/riotx/features/media/RoomAttachmentProvider.kt index 09459b20d1..9f6080d95f 100644 --- a/vector/src/main/java/im/vector/riotx/features/media/RoomAttachmentProvider.kt +++ b/vector/src/main/java/im/vector/riotx/features/media/RoomAttachmentProvider.kt @@ -57,6 +57,7 @@ class RoomAttachmentProvider( interface InteractionListener { fun onDismissTapped() fun onShareTapped() + fun onPlayPause(play: Boolean) } var interactionListener: InteractionListener? = null @@ -197,6 +198,9 @@ class RoomAttachmentProvider( overlayView?.onShareCallback = { interactionListener?.onShareTapped() } + overlayView?.onPlayPause = { play -> + interactionListener?.onPlayPause(play) + } } val item = attachments[position] val dateString = item.root.localDateTime().let { 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 44b536b2ae..2606f0bb76 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 @@ -39,6 +39,7 @@ 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 import im.vector.riotx.core.di.ActiveSessionHolder import im.vector.riotx.core.di.DaggerScreenComponent @@ -242,6 +243,10 @@ class VectorAttachmentViewerActivity : AttachmentViewerActivity(), RoomAttachmen animateClose() } + override fun onPlayPause(play: Boolean) { + handle(if (play) AttachmentCommands.StartVideo else AttachmentCommands.PauseVideo) + } + override fun onShareTapped() { // Share eventList?.get(currentPosition)?.let { timelineEvent -> From bf2d937ad62556232b8e8ab6bf5fdd6e6e52de02 Mon Sep 17 00:00:00 2001 From: Valere Date: Thu, 9 Jul 2020 08:59:06 +0200 Subject: [PATCH 12/25] Basic video seekTo support --- .../attachmentviewer/AttachmentEvents.kt | 1 + .../AttachmentViewerActivity.kt | 7 ++--- .../riotx/attachmentviewer/VideoViewHolder.kt | 7 +++++ .../features/media/AttachmentOverlayView.kt | 31 +++++++++++++++---- .../features/media/RoomAttachmentProvider.kt | 4 +++ .../media/VectorAttachmentViewerActivity.kt | 4 +++ 6 files changed, 44 insertions(+), 10 deletions(-) diff --git a/attachment-viewer/src/main/java/im/vector/riotx/attachmentviewer/AttachmentEvents.kt b/attachment-viewer/src/main/java/im/vector/riotx/attachmentviewer/AttachmentEvents.kt index 5b1f2ab90d..b2b6c9fe16 100644 --- a/attachment-viewer/src/main/java/im/vector/riotx/attachmentviewer/AttachmentEvents.kt +++ b/attachment-viewer/src/main/java/im/vector/riotx/attachmentviewer/AttachmentEvents.kt @@ -27,4 +27,5 @@ interface AttachmentEventListener { sealed class AttachmentCommands { object PauseVideo : AttachmentCommands() object StartVideo : AttachmentCommands() + data class SeekTo(val percentProgress: Int) : AttachmentCommands() } diff --git a/attachment-viewer/src/main/java/im/vector/riotx/attachmentviewer/AttachmentViewerActivity.kt b/attachment-viewer/src/main/java/im/vector/riotx/attachmentviewer/AttachmentViewerActivity.kt index 6f2436f261..029064e058 100644 --- a/attachment-viewer/src/main/java/im/vector/riotx/attachmentviewer/AttachmentViewerActivity.kt +++ b/attachment-viewer/src/main/java/im/vector/riotx/attachmentviewer/AttachmentViewerActivity.kt @@ -303,10 +303,9 @@ abstract class AttachmentViewerActivity : AppCompatActivity(), AttachmentEventLi finish() } - public fun handle(commands: AttachmentCommands) { - (attachmentsAdapter.recyclerView?.findViewHolderForAdapterPosition(currentPosition) as? BaseViewHolder)?.let { - it.handleCommand(commands) - } + fun handle(commands: AttachmentCommands) { + (attachmentsAdapter.recyclerView?.findViewHolderForAdapterPosition(currentPosition) as? BaseViewHolder) + ?.handleCommand(commands) } private fun hideSystemUI() { diff --git a/attachment-viewer/src/main/java/im/vector/riotx/attachmentviewer/VideoViewHolder.kt b/attachment-viewer/src/main/java/im/vector/riotx/attachmentviewer/VideoViewHolder.kt index a2424dda57..5718147bab 100644 --- a/attachment-viewer/src/main/java/im/vector/riotx/attachmentviewer/VideoViewHolder.kt +++ b/attachment-viewer/src/main/java/im/vector/riotx/attachmentviewer/VideoViewHolder.kt @@ -162,6 +162,13 @@ class VideoViewHolder constructor(itemView: View) : wasPaused = true videoView.pause() } + is AttachmentCommands.SeekTo -> { + val duration = videoView.duration + if (duration > 0) { + val seekDuration = duration * (commands.percentProgress / 100f) + videoView.seekTo(seekDuration.toInt()) + } + } } } diff --git a/vector/src/main/java/im/vector/riotx/features/media/AttachmentOverlayView.kt b/vector/src/main/java/im/vector/riotx/features/media/AttachmentOverlayView.kt index a2657f7daf..2812b011f9 100644 --- a/vector/src/main/java/im/vector/riotx/features/media/AttachmentOverlayView.kt +++ b/vector/src/main/java/im/vector/riotx/features/media/AttachmentOverlayView.kt @@ -36,6 +36,7 @@ class AttachmentOverlayView @JvmOverloads constructor( var onShareCallback: (() -> Unit)? = null var onBack: (() -> Unit)? = null var onPlayPause: ((play: Boolean) -> Unit)? = null + var videoSeekTo: ((progress: Int) -> Unit)? = null private val counterTextView: TextView private val infoTextView: TextView @@ -47,6 +48,8 @@ class AttachmentOverlayView @JvmOverloads constructor( val videoControlsGroup: Group + var suspendSeekBarUpdate = false + init { View.inflate(context, R.layout.merge_image_attachment_overlay, this) setBackgroundColor(Color.TRANSPARENT) @@ -56,8 +59,6 @@ class AttachmentOverlayView @JvmOverloads constructor( videoControlsGroup = findViewById(R.id.overlayVideoControlsGroup) overlayPlayPauseButton = findViewById(R.id.overlayPlayPauseButton) overlaySeekBar = findViewById(R.id.overlaySeekBar) - - overlaySeekBar.isEnabled = false findViewById(R.id.overlayBackButton).setOnClickListener { onBack?.invoke() } @@ -67,6 +68,22 @@ class AttachmentOverlayView @JvmOverloads constructor( findViewById(R.id.overlayPlayPauseButton).setOnClickListener { onPlayPause?.invoke(!isPlaying) } + + overlaySeekBar.setOnSeekBarChangeListener(object : SeekBar.OnSeekBarChangeListener { + override fun onProgressChanged(seekBar: SeekBar?, progress: Int, fromUser: Boolean) { + if (fromUser) { + videoSeekTo?.invoke(progress) + } + } + + override fun onStartTrackingTouch(seekBar: SeekBar?) { + suspendSeekBarUpdate = true + } + + override fun onStopTrackingTouch(seekBar: SeekBar?) { + suspendSeekBarUpdate = false + } + }) } fun updateWith(counter: String, senderInfo: String) { @@ -78,10 +95,12 @@ class AttachmentOverlayView @JvmOverloads constructor( 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) - isPlaying = event.isPlaying - overlaySeekBar.progress = percent + if (!suspendSeekBarUpdate) { + val safeDuration = (if (event.duration == 0) 100 else event.duration).toFloat() + val percent = ((event.progress / safeDuration) * 100f).toInt().coerceAtMost(100) + isPlaying = event.isPlaying + overlaySeekBar.progress = percent + } } } } diff --git a/vector/src/main/java/im/vector/riotx/features/media/RoomAttachmentProvider.kt b/vector/src/main/java/im/vector/riotx/features/media/RoomAttachmentProvider.kt index 9f6080d95f..4e30e0179a 100644 --- a/vector/src/main/java/im/vector/riotx/features/media/RoomAttachmentProvider.kt +++ b/vector/src/main/java/im/vector/riotx/features/media/RoomAttachmentProvider.kt @@ -58,6 +58,7 @@ class RoomAttachmentProvider( fun onDismissTapped() fun onShareTapped() fun onPlayPause(play: Boolean) + fun videoSeekTo(percent: Int) } var interactionListener: InteractionListener? = null @@ -201,6 +202,9 @@ class RoomAttachmentProvider( overlayView?.onPlayPause = { play -> interactionListener?.onPlayPause(play) } + overlayView?.videoSeekTo = { percent -> + interactionListener?.videoSeekTo(percent) + } } val item = attachments[position] val dateString = item.root.localDateTime().let { 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 2606f0bb76..10483f3fa9 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 @@ -247,6 +247,10 @@ class VectorAttachmentViewerActivity : AttachmentViewerActivity(), RoomAttachmen handle(if (play) AttachmentCommands.StartVideo else AttachmentCommands.PauseVideo) } + override fun videoSeekTo(percent: Int) { + handle(AttachmentCommands.SeekTo(percent)) + } + override fun onShareTapped() { // Share eventList?.get(currentPosition)?.let { timelineEvent -> From aa3e68f3fd433099e10c1ef8f4818e3ac48ba9b6 Mon Sep 17 00:00:00 2001 From: Valere Date: Thu, 9 Jul 2020 10:08:55 +0200 Subject: [PATCH 13/25] Refactoring Remove glide dependency + protect against cell reuse bugs --- attachment-viewer/build.gradle | 2 - .../AnimatedImageViewHolder.kt | 38 +------ .../AttachmentSourceProvider.kt | 25 ++--- .../attachmentviewer/AttachmentsAdapter.kt | 75 ++++--------- .../attachmentviewer/ImageLoaderTarget.kt | 103 ++++++++++++++++++ .../attachmentviewer/VideoLoaderTarget.kt | 76 +++++++++++++ .../riotx/attachmentviewer/VideoViewHolder.kt | 33 +----- .../ZoomableImageViewHolder.kt | 34 +----- .../features/media/RoomAttachmentProvider.kt | 82 +++++++++----- 9 files changed, 279 insertions(+), 189 deletions(-) create mode 100644 attachment-viewer/src/main/java/im/vector/riotx/attachmentviewer/ImageLoaderTarget.kt create mode 100644 attachment-viewer/src/main/java/im/vector/riotx/attachmentviewer/VideoLoaderTarget.kt diff --git a/attachment-viewer/build.gradle b/attachment-viewer/build.gradle index 6b64e661fa..3a5c3298d4 100644 --- a/attachment-viewer/build.gradle +++ b/attachment-viewer/build.gradle @@ -58,9 +58,7 @@ android { } dependencies { -// implementation 'com.github.MikeOrtiz:TouchImageView:3.0.2' implementation 'com.github.chrisbanes:PhotoView:2.0.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' diff --git a/attachment-viewer/src/main/java/im/vector/riotx/attachmentviewer/AnimatedImageViewHolder.kt b/attachment-viewer/src/main/java/im/vector/riotx/attachmentviewer/AnimatedImageViewHolder.kt index 9f512e78be..f00a4eff30 100644 --- a/attachment-viewer/src/main/java/im/vector/riotx/attachmentviewer/AnimatedImageViewHolder.kt +++ b/attachment-viewer/src/main/java/im/vector/riotx/attachmentviewer/AnimatedImageViewHolder.kt @@ -16,16 +16,9 @@ package im.vector.riotx.attachmentviewer -import android.graphics.drawable.Animatable -import android.graphics.drawable.Drawable import android.view.View import android.widget.ImageView -import android.widget.LinearLayout import android.widget.ProgressBar -import androidx.core.view.isVisible -import androidx.core.view.updateLayoutParams -import com.bumptech.glide.request.target.CustomViewTarget -import com.bumptech.glide.request.transition.Transition class AnimatedImageViewHolder constructor(itemView: View) : BaseViewHolder(itemView) { @@ -33,34 +26,5 @@ class AnimatedImageViewHolder constructor(itemView: View) : val touchImageView: ImageView = itemView.findViewById(R.id.imageView) val imageLoaderProgress: ProgressBar = itemView.findViewById(R.id.imageLoaderProgress) - val customTargetView = object : CustomViewTarget(touchImageView) { - - override fun onResourceLoading(placeholder: Drawable?) { - imageLoaderProgress.isVisible = true - } - - override fun onLoadFailed(errorDrawable: Drawable?) { - imageLoaderProgress.isVisible = false - } - - override fun onResourceCleared(placeholder: Drawable?) { - touchImageView.setImageDrawable(placeholder) - } - - override fun onResourceReady(resource: Drawable, transition: Transition?) { - imageLoaderProgress.isVisible = false - // Glide mess up the view size :/ - touchImageView.updateLayoutParams { - width = LinearLayout.LayoutParams.MATCH_PARENT - height = LinearLayout.LayoutParams.MATCH_PARENT - } - touchImageView.setImageDrawable(resource) - if (resource is Animatable) { - resource.start() - } - } - } - - override fun bind(attachmentInfo: AttachmentInfo) { - } + internal val target = DefaultImageLoaderTarget(this, this.touchImageView) } diff --git a/attachment-viewer/src/main/java/im/vector/riotx/attachmentviewer/AttachmentSourceProvider.kt b/attachment-viewer/src/main/java/im/vector/riotx/attachmentviewer/AttachmentSourceProvider.kt index 930fc62658..ce725afec2 100644 --- a/attachment-viewer/src/main/java/im/vector/riotx/attachmentviewer/AttachmentSourceProvider.kt +++ b/attachment-viewer/src/main/java/im/vector/riotx/attachmentviewer/AttachmentSourceProvider.kt @@ -19,15 +19,12 @@ package im.vector.riotx.attachmentviewer import android.content.Context import android.view.View -sealed class AttachmentInfo { - data class Image(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, val thumbnail: Image?) : AttachmentInfo() - data class Audio(val url: String, val data: Any) : AttachmentInfo() - data class File(val url: String, val data: Any) : AttachmentInfo() - - fun bind() { - } +sealed class AttachmentInfo(open val uid: String) { + data class Image(override val uid: String, val url: String, val data: Any?) : AttachmentInfo(uid) + data class AnimatedImage(override val uid: String, val url: String, val data: Any?) : AttachmentInfo(uid) + data class Video(override val uid: String, val url: String, val data: Any, val thumbnail: Image?) : AttachmentInfo(uid) + data class Audio(override val uid: String, val url: String, val data: Any) : AttachmentInfo(uid) + data class File(override val uid: String, val url: String, val data: Any) : AttachmentInfo(uid) } interface AttachmentSourceProvider { @@ -36,11 +33,13 @@ interface AttachmentSourceProvider { fun getAttachmentInfoAt(position: Int): AttachmentInfo - fun loadImage(holder: ZoomableImageViewHolder, info: AttachmentInfo.Image) + fun loadImage(target: ImageLoaderTarget, info: AttachmentInfo.Image) - fun loadImage(holder: AnimatedImageViewHolder, info: AttachmentInfo.AnimatedImage) + fun loadImage(target: ImageLoaderTarget, info: AttachmentInfo.AnimatedImage) - fun loadVideo(holder: VideoViewHolder, info: AttachmentInfo.Video) + fun loadVideo(target: VideoLoaderTarget, info: AttachmentInfo.Video) - fun overlayViewAtPosition(context: Context, position: Int) : View? + fun overlayViewAtPosition(context: Context, position: Int): View? + + fun clear(id: String) } diff --git a/attachment-viewer/src/main/java/im/vector/riotx/attachmentviewer/AttachmentsAdapter.kt b/attachment-viewer/src/main/java/im/vector/riotx/attachmentviewer/AttachmentsAdapter.kt index b0cb5193e8..2f453b58a8 100644 --- a/attachment-viewer/src/main/java/im/vector/riotx/attachmentviewer/AttachmentsAdapter.kt +++ b/attachment-viewer/src/main/java/im/vector/riotx/attachmentviewer/AttachmentsAdapter.kt @@ -24,8 +24,10 @@ import androidx.recyclerview.widget.RecyclerView abstract class BaseViewHolder constructor(itemView: View) : RecyclerView.ViewHolder(itemView) { - open fun bind(attachmentInfo: AttachmentInfo) {} - open fun onRecycled() {} + open fun onRecycled() { + boundResourceUid = null + } + open fun onAttached() {} open fun onDetached() {} open fun entersBackground() {} @@ -33,16 +35,17 @@ abstract class BaseViewHolder constructor(itemView: View) : open fun onSelected(selected: Boolean) {} open fun handleCommand(commands: AttachmentCommands) {} -} -class AttachmentViewHolder constructor(itemView: View) : - BaseViewHolder(itemView) { + var boundResourceUid: String? = null - override fun bind(attachmentInfo: AttachmentInfo) { + open fun bind(attachmentInfo: AttachmentInfo) { + boundResourceUid = attachmentInfo.uid } } -// class AttachmentsAdapter(fragmentManager: FragmentManager, lifecycle: Lifecycle) : FragmentStateAdapter(fragmentManager, lifecycle) { +class AttachmentViewHolder constructor(itemView: View) : + BaseViewHolder(itemView) + class AttachmentsAdapter() : RecyclerView.Adapter() { var attachmentSourceProvider: AttachmentSourceProvider? = null @@ -65,21 +68,21 @@ class AttachmentsAdapter() : RecyclerView.Adapter() { val inflater = LayoutInflater.from(parent.context) val itemView = inflater.inflate(viewType, parent, false) 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_video_attachment -> VideoViewHolder(itemView) - else -> AttachmentViewHolder(itemView) + R.layout.item_video_attachment -> VideoViewHolder(itemView) + else -> AttachmentViewHolder(itemView) } } override fun getItemViewType(position: Int): Int { val info = attachmentSourceProvider!!.getAttachmentInfoAt(position) return when (info) { - is AttachmentInfo.Image -> R.layout.item_image_attachment - is AttachmentInfo.Video -> R.layout.item_video_attachment + is AttachmentInfo.Image -> R.layout.item_image_attachment + is AttachmentInfo.Video -> R.layout.item_video_attachment is AttachmentInfo.AnimatedImage -> R.layout.item_animated_image_attachment - is AttachmentInfo.Audio -> TODO() - is AttachmentInfo.File -> TODO() + is AttachmentInfo.Audio -> TODO() + is AttachmentInfo.File -> TODO() } } @@ -91,16 +94,17 @@ class AttachmentsAdapter() : RecyclerView.Adapter() { attachmentSourceProvider?.getAttachmentInfoAt(position)?.let { holder.bind(it) when (it) { - is AttachmentInfo.Image -> { - attachmentSourceProvider?.loadImage(holder as ZoomableImageViewHolder, it) + is AttachmentInfo.Image -> { + attachmentSourceProvider?.loadImage((holder as ZoomableImageViewHolder).target, it) } is AttachmentInfo.AnimatedImage -> { - attachmentSourceProvider?.loadImage(holder as AnimatedImageViewHolder, it) + attachmentSourceProvider?.loadImage((holder as AnimatedImageViewHolder).target, it) } - is AttachmentInfo.Video -> { - attachmentSourceProvider?.loadVideo(holder as VideoViewHolder, it) + is AttachmentInfo.Video -> { + attachmentSourceProvider?.loadVideo((holder as VideoViewHolder).target, it) + } + else -> { } - else -> {} } } } @@ -134,35 +138,4 @@ class AttachmentsAdapter() : RecyclerView.Adapter() { val holder = recyclerView?.findViewHolderForAdapterPosition(position) as? BaseViewHolder holder?.entersForeground() } -// override fun getItemCount(): Int { -// return 8 -// } -// -// override fun createFragment(position: Int): Fragment { -// // Return a NEW fragment instance in createFragment(int) -// val fragment = DemoObjectFragment() -// fragment.arguments = Bundle().apply { -// // Our object is just an integer :-P -// putInt(ARG_OBJECT, position + 1) -// } -// return fragment -// } } - -// private const val ARG_OBJECT = "object" -// -// // Instances of this class are fragments representing a single -// // object in our collection. -// class DemoObjectFragment : Fragment() { -// -// override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { -// return inflater.inflate(R.layout.view_image_attachment, container, false) -// } -// -// override fun onViewCreated(view: View, savedInstanceState: Bundle?) { -// arguments?.takeIf { it.containsKey(ARG_OBJECT) }?.apply { -// val textView: TextView = view.findViewById(R.id.testPage) -// textView.text = getInt(ARG_OBJECT).toString() -// } -// } -// } diff --git a/attachment-viewer/src/main/java/im/vector/riotx/attachmentviewer/ImageLoaderTarget.kt b/attachment-viewer/src/main/java/im/vector/riotx/attachmentviewer/ImageLoaderTarget.kt new file mode 100644 index 0000000000..bb59c9e01e --- /dev/null +++ b/attachment-viewer/src/main/java/im/vector/riotx/attachmentviewer/ImageLoaderTarget.kt @@ -0,0 +1,103 @@ +/* + * 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.graphics.drawable.Animatable +import android.graphics.drawable.Drawable +import android.widget.ImageView +import android.widget.LinearLayout +import androidx.core.view.isVisible +import androidx.core.view.updateLayoutParams + +interface ImageLoaderTarget { + + fun contextView(): ImageView + + fun onResourceLoading(uid: String, placeholder: Drawable?) + + fun onLoadFailed(uid: String, errorDrawable: Drawable?) + + fun onResourceCleared(uid: String, placeholder: Drawable?) + + fun onResourceReady(uid: String, resource: Drawable) +} + +internal class DefaultImageLoaderTarget(val holder: AnimatedImageViewHolder, private val contextView: ImageView) + : ImageLoaderTarget { + override fun contextView(): ImageView { + return contextView + } + + override fun onResourceLoading(uid: String, placeholder: Drawable?) { + if (holder.boundResourceUid != uid) return + holder.imageLoaderProgress.isVisible = true + } + + override fun onLoadFailed(uid: String, errorDrawable: Drawable?) { + if (holder.boundResourceUid != uid) return + holder.imageLoaderProgress.isVisible = false + } + + override fun onResourceCleared(uid: String, placeholder: Drawable?) { + if (holder.boundResourceUid != uid) return + holder.touchImageView.setImageDrawable(placeholder) + } + + override fun onResourceReady(uid: String, resource: Drawable) { + if (holder.boundResourceUid != uid) return + holder.imageLoaderProgress.isVisible = false + // Glide mess up the view size :/ + holder.touchImageView.updateLayoutParams { + width = LinearLayout.LayoutParams.MATCH_PARENT + height = LinearLayout.LayoutParams.MATCH_PARENT + } + holder.touchImageView.setImageDrawable(resource) + if (resource is Animatable) { + resource.start() + } + } + + internal class ZoomableImageTarget(val holder: ZoomableImageViewHolder, private val contextView: ImageView) : ImageLoaderTarget { + override fun contextView() = contextView + + override fun onResourceLoading(uid: String, placeholder: Drawable?) { + if (holder.boundResourceUid != uid) return + holder.imageLoaderProgress.isVisible = true + } + + override fun onLoadFailed(uid: String, errorDrawable: Drawable?) { + if (holder.boundResourceUid != uid) return + holder.imageLoaderProgress.isVisible = false + } + + override fun onResourceCleared(uid: String, placeholder: Drawable?) { + if (holder.boundResourceUid != uid) return + holder.touchImageView.setImageDrawable(placeholder) + } + + override fun onResourceReady(uid: String, resource: Drawable) { + if (holder.boundResourceUid != uid) return + holder.imageLoaderProgress.isVisible = false + // Glide mess up the view size :/ + holder.touchImageView.updateLayoutParams { + width = LinearLayout.LayoutParams.MATCH_PARENT + height = LinearLayout.LayoutParams.MATCH_PARENT + } + holder.touchImageView.setImageDrawable(resource) + } + } +} diff --git a/attachment-viewer/src/main/java/im/vector/riotx/attachmentviewer/VideoLoaderTarget.kt b/attachment-viewer/src/main/java/im/vector/riotx/attachmentviewer/VideoLoaderTarget.kt new file mode 100644 index 0000000000..548c6431e5 --- /dev/null +++ b/attachment-viewer/src/main/java/im/vector/riotx/attachmentviewer/VideoLoaderTarget.kt @@ -0,0 +1,76 @@ +/* + * 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.graphics.drawable.Drawable +import android.widget.ImageView +import androidx.core.view.isVisible +import java.io.File + +interface VideoLoaderTarget { + fun contextView(): ImageView + + fun onThumbnailResourceLoading(uid: String, placeholder: Drawable?) + + fun onThumbnailLoadFailed(uid: String, errorDrawable: Drawable?) + + fun onThumbnailResourceCleared(uid: String, placeholder: Drawable?) + + fun onThumbnailResourceReady(uid: String, resource: Drawable) + + fun onVideoFileLoading(uid: String) + fun onVideoFileLoadFailed(uid: String) + fun onVideoFileReady(uid: String, file: File) +} + +internal class DefaultVideoLoaderTarget(val holder: VideoViewHolder, private val contextView: ImageView) : VideoLoaderTarget { + override fun contextView(): ImageView = contextView + + override fun onThumbnailResourceLoading(uid: String, placeholder: Drawable?) { + } + + override fun onThumbnailLoadFailed(uid: String, errorDrawable: Drawable?) { + } + + override fun onThumbnailResourceCleared(uid: String, placeholder: Drawable?) { + } + + override fun onThumbnailResourceReady(uid: String, resource: Drawable) { + if (holder.boundResourceUid != uid) return + holder.thumbnailImage.setImageDrawable(resource) + } + + override fun onVideoFileLoading(uid: String) { + if (holder.boundResourceUid != uid) return + holder.thumbnailImage.isVisible = true + holder.loaderProgressBar.isVisible = true + holder.videoView.isVisible = false + } + + override fun onVideoFileLoadFailed(uid: String) { + if (holder.boundResourceUid != uid) return + holder.videoFileLoadError() + } + + override fun onVideoFileReady(uid: String, file: File) { + if (holder.boundResourceUid != uid) return + holder.thumbnailImage.isVisible = false + holder.loaderProgressBar.isVisible = false + holder.videoView.isVisible = true + holder.videoReady(file) + } +} diff --git a/attachment-viewer/src/main/java/im/vector/riotx/attachmentviewer/VideoViewHolder.kt b/attachment-viewer/src/main/java/im/vector/riotx/attachmentviewer/VideoViewHolder.kt index 5718147bab..2b417baecc 100644 --- a/attachment-viewer/src/main/java/im/vector/riotx/attachmentviewer/VideoViewHolder.kt +++ b/attachment-viewer/src/main/java/im/vector/riotx/attachmentviewer/VideoViewHolder.kt @@ -44,38 +44,13 @@ class VideoViewHolder constructor(itemView: View) : var eventListener: WeakReference? = 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?) { -// } -// } + internal val target = DefaultVideoLoaderTarget(this, thumbnailImage) override fun onRecycled() { super.onRecycled() @@ -91,6 +66,9 @@ class VideoViewHolder constructor(itemView: View) : } } + fun videoFileLoadError() { + } + override fun entersBackground() { if (videoView.isPlaying) { progress = videoView.currentPosition @@ -162,7 +140,7 @@ class VideoViewHolder constructor(itemView: View) : wasPaused = true videoView.pause() } - is AttachmentCommands.SeekTo -> { + is AttachmentCommands.SeekTo -> { val duration = videoView.duration if (duration > 0) { val seekDuration = duration * (commands.percentProgress / 100f) @@ -173,6 +151,7 @@ class VideoViewHolder constructor(itemView: View) : } override fun bind(attachmentInfo: AttachmentInfo) { + super.bind(attachmentInfo) progress = 0 wasPaused = false } diff --git a/attachment-viewer/src/main/java/im/vector/riotx/attachmentviewer/ZoomableImageViewHolder.kt b/attachment-viewer/src/main/java/im/vector/riotx/attachmentviewer/ZoomableImageViewHolder.kt index 00a8ad275a..aeaf612bbc 100644 --- a/attachment-viewer/src/main/java/im/vector/riotx/attachmentviewer/ZoomableImageViewHolder.kt +++ b/attachment-viewer/src/main/java/im/vector/riotx/attachmentviewer/ZoomableImageViewHolder.kt @@ -16,15 +16,9 @@ package im.vector.riotx.attachmentviewer -import android.graphics.drawable.Drawable import android.util.Log import android.view.View -import android.widget.LinearLayout import android.widget.ProgressBar -import androidx.core.view.isVisible -import androidx.core.view.updateLayoutParams -import com.bumptech.glide.request.target.CustomViewTarget -import com.bumptech.glide.request.transition.Transition import com.github.chrisbanes.photoview.PhotoView class ZoomableImageViewHolder constructor(itemView: View) : @@ -45,31 +39,5 @@ class ZoomableImageViewHolder constructor(itemView: View) : touchImageView.setAllowParentInterceptOnEdge(true) } - val customTargetView = object : CustomViewTarget(touchImageView) { - - override fun onResourceLoading(placeholder: Drawable?) { - imageLoaderProgress.isVisible = true - } - - override fun onLoadFailed(errorDrawable: Drawable?) { - imageLoaderProgress.isVisible = false - } - - override fun onResourceCleared(placeholder: Drawable?) { - touchImageView.setImageDrawable(placeholder) - } - - override fun onResourceReady(resource: Drawable, transition: Transition?) { - imageLoaderProgress.isVisible = false - // Glide mess up the view size :/ - touchImageView.updateLayoutParams { - width = LinearLayout.LayoutParams.MATCH_PARENT - height = LinearLayout.LayoutParams.MATCH_PARENT - } - touchImageView.setImageDrawable(resource) - } - } - - override fun bind(attachmentInfo: AttachmentInfo) { - } + internal val target = DefaultImageLoaderTarget.ZoomableImageTarget(this, touchImageView) } diff --git a/vector/src/main/java/im/vector/riotx/features/media/RoomAttachmentProvider.kt b/vector/src/main/java/im/vector/riotx/features/media/RoomAttachmentProvider.kt index 4e30e0179a..f7299bf714 100644 --- a/vector/src/main/java/im/vector/riotx/features/media/RoomAttachmentProvider.kt +++ b/vector/src/main/java/im/vector/riotx/features/media/RoomAttachmentProvider.kt @@ -35,11 +35,10 @@ import im.vector.matrix.android.api.session.room.model.message.MessageWithAttach 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.AnimatedImageViewHolder import im.vector.riotx.attachmentviewer.AttachmentInfo import im.vector.riotx.attachmentviewer.AttachmentSourceProvider -import im.vector.riotx.attachmentviewer.VideoViewHolder -import im.vector.riotx.attachmentviewer.ZoomableImageViewHolder +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 @@ -86,13 +85,15 @@ class RoomAttachmentProvider( ) if (content.mimeType == "image/gif") { AttachmentInfo.AnimatedImage( - content.url ?: "", - data + uid = it.eventId, + url = content.url ?: "", + data = data ) } else { AttachmentInfo.Image( - content.url ?: "", - data + uid = it.eventId, + url = content.url ?: "", + data = data ) } } else if (content is MessageVideoContent) { @@ -117,9 +118,11 @@ class RoomAttachmentProvider( thumbnailMediaData = thumbnailData ) AttachmentInfo.Video( - content.getFileUrl() ?: "", - data, - AttachmentInfo.Image( + uid = it.eventId, + url = content.getFileUrl() ?: "", + data = data, + thumbnail = AttachmentInfo.Image( + uid = it.eventId, url = content.videoInfo?.thumbnailFile?.url ?: content.videoInfo?.thumbnailUrl ?: "", data = thumbnailData @@ -128,49 +131,72 @@ class RoomAttachmentProvider( ) } else { AttachmentInfo.Image( - "", - null + uid = it.eventId, + url = "", + data = null ) } } } - override fun loadImage(holder: ZoomableImageViewHolder, info: AttachmentInfo.Image) { + override fun loadImage(target: ImageLoaderTarget, info: AttachmentInfo.Image) { (info.data as? ImageContentRenderer.Data)?.let { - imageContentRenderer.render(it, holder.touchImageView, holder.customTargetView as CustomViewTarget<*, Drawable>) + 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(holder: AnimatedImageViewHolder, info: AttachmentInfo.AnimatedImage) { + override fun loadImage(target: ImageLoaderTarget, info: AttachmentInfo.AnimatedImage) { (info.data as? ImageContentRenderer.Data)?.let { - imageContentRenderer.render(it, holder.touchImageView, holder.customTargetView as CustomViewTarget<*, Drawable>) + 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(holder: VideoViewHolder, info: AttachmentInfo.Video) { + 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, holder.thumbnailImage, object : CustomViewTarget(holder.thumbnailImage) { + imageContentRenderer.render(data.thumbnailMediaData, target.contextView(), object : CustomViewTarget(target.contextView()) { override fun onLoadFailed(errorDrawable: Drawable?) { - holder.thumbnailImage.setImageDrawable(errorDrawable) + target.onThumbnailLoadFailed(info.uid, errorDrawable) } override fun onResourceCleared(placeholder: Drawable?) { + target.onThumbnailResourceCleared(info.uid, placeholder) } override fun onResourceReady(resource: Drawable, transition: Transition?) { - holder.thumbnailImage.setImageDrawable(resource) + target.onThumbnailResourceReady(info.uid, resource) } }) - holder.thumbnailImage.isVisible = false - holder.loaderProgressBar.isVisible = false - holder.videoView.isVisible = false - + target.onVideoFileLoading(info.uid) fileService.downloadFile( downloadMode = FileService.DownloadMode.FOR_INTERNAL_USE, id = data.eventId, @@ -180,11 +206,11 @@ class RoomAttachmentProvider( url = data.url, callback = object : MatrixCallback { override fun onSuccess(data: File) { - holder.videoReady(data) + target.onVideoFileReady(info.uid, data) } override fun onFailure(failure: Throwable) { - holder.videoView.isVisible = false + target.onVideoFileLoadFailed(info.uid) } } ) @@ -214,6 +240,10 @@ class RoomAttachmentProvider( overlayView?.videoControlsGroup?.isVisible = item.root.isVideoMessage() return overlayView } + + override fun clear(id: String) { + // TODO("Not yet implemented") + } } class RoomAttachmentProviderFactory @Inject constructor( From 868d9cf55c06ac786d407ec4fce460fb308dfcaa Mon Sep 17 00:00:00 2001 From: Valere Date: Thu, 9 Jul 2020 10:11:10 +0200 Subject: [PATCH 14/25] Cleaning (remove audio and file as not supported yet) --- .../AttachmentSourceProvider.kt | 4 +- .../attachmentviewer/AttachmentsAdapter.kt | 38 +++------------- .../riotx/attachmentviewer/BaseViewHolder.kt | 45 +++++++++++++++++++ 3 files changed, 53 insertions(+), 34 deletions(-) create mode 100644 attachment-viewer/src/main/java/im/vector/riotx/attachmentviewer/BaseViewHolder.kt diff --git a/attachment-viewer/src/main/java/im/vector/riotx/attachmentviewer/AttachmentSourceProvider.kt b/attachment-viewer/src/main/java/im/vector/riotx/attachmentviewer/AttachmentSourceProvider.kt index ce725afec2..92a4f1d9e4 100644 --- a/attachment-viewer/src/main/java/im/vector/riotx/attachmentviewer/AttachmentSourceProvider.kt +++ b/attachment-viewer/src/main/java/im/vector/riotx/attachmentviewer/AttachmentSourceProvider.kt @@ -23,8 +23,8 @@ sealed class AttachmentInfo(open val uid: String) { data class Image(override val uid: String, val url: String, val data: Any?) : AttachmentInfo(uid) data class AnimatedImage(override val uid: String, val url: String, val data: Any?) : AttachmentInfo(uid) data class Video(override val uid: String, val url: String, val data: Any, val thumbnail: Image?) : AttachmentInfo(uid) - data class Audio(override val uid: String, val url: String, val data: Any) : AttachmentInfo(uid) - data class File(override val uid: String, val url: String, val data: Any) : AttachmentInfo(uid) +// data class Audio(override val uid: String, val url: String, val data: Any) : AttachmentInfo(uid) +// data class File(override val uid: String, val url: String, val data: Any) : AttachmentInfo(uid) } interface AttachmentSourceProvider { diff --git a/attachment-viewer/src/main/java/im/vector/riotx/attachmentviewer/AttachmentsAdapter.kt b/attachment-viewer/src/main/java/im/vector/riotx/attachmentviewer/AttachmentsAdapter.kt index 2f453b58a8..90020f2cb0 100644 --- a/attachment-viewer/src/main/java/im/vector/riotx/attachmentviewer/AttachmentsAdapter.kt +++ b/attachment-viewer/src/main/java/im/vector/riotx/attachmentviewer/AttachmentsAdapter.kt @@ -17,36 +17,10 @@ package im.vector.riotx.attachmentviewer import android.view.LayoutInflater -import android.view.View import android.view.ViewGroup import androidx.recyclerview.widget.RecyclerView -abstract class BaseViewHolder constructor(itemView: View) : - RecyclerView.ViewHolder(itemView) { - - open fun onRecycled() { - boundResourceUid = null - } - - open fun onAttached() {} - open fun onDetached() {} - open fun entersBackground() {} - open fun entersForeground() {} - open fun onSelected(selected: Boolean) {} - - open fun handleCommand(commands: AttachmentCommands) {} - - var boundResourceUid: String? = null - - open fun bind(attachmentInfo: AttachmentInfo) { - boundResourceUid = attachmentInfo.uid - } -} - -class AttachmentViewHolder constructor(itemView: View) : - BaseViewHolder(itemView) - -class AttachmentsAdapter() : RecyclerView.Adapter() { +class AttachmentsAdapter : RecyclerView.Adapter() { var attachmentSourceProvider: AttachmentSourceProvider? = null set(value) { @@ -71,7 +45,7 @@ class AttachmentsAdapter() : RecyclerView.Adapter() { R.layout.item_image_attachment -> ZoomableImageViewHolder(itemView) R.layout.item_animated_image_attachment -> AnimatedImageViewHolder(itemView) R.layout.item_video_attachment -> VideoViewHolder(itemView) - else -> AttachmentViewHolder(itemView) + else -> UnsupportedViewHolder(itemView) } } @@ -81,8 +55,8 @@ class AttachmentsAdapter() : RecyclerView.Adapter() { is AttachmentInfo.Image -> R.layout.item_image_attachment is AttachmentInfo.Video -> R.layout.item_video_attachment is AttachmentInfo.AnimatedImage -> R.layout.item_animated_image_attachment - is AttachmentInfo.Audio -> TODO() - is AttachmentInfo.File -> TODO() +// is AttachmentInfo.Audio -> TODO() +// is AttachmentInfo.File -> TODO() } } @@ -103,8 +77,8 @@ class AttachmentsAdapter() : RecyclerView.Adapter() { is AttachmentInfo.Video -> { attachmentSourceProvider?.loadVideo((holder as VideoViewHolder).target, it) } - else -> { - } +// else -> { +//// } } } } diff --git a/attachment-viewer/src/main/java/im/vector/riotx/attachmentviewer/BaseViewHolder.kt b/attachment-viewer/src/main/java/im/vector/riotx/attachmentviewer/BaseViewHolder.kt new file mode 100644 index 0000000000..49b47c11ff --- /dev/null +++ b/attachment-viewer/src/main/java/im/vector/riotx/attachmentviewer/BaseViewHolder.kt @@ -0,0 +1,45 @@ +/* + * 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.view.View +import androidx.recyclerview.widget.RecyclerView + +abstract class BaseViewHolder constructor(itemView: View) : + RecyclerView.ViewHolder(itemView) { + + open fun onRecycled() { + boundResourceUid = null + } + + open fun onAttached() {} + open fun onDetached() {} + open fun entersBackground() {} + open fun entersForeground() {} + open fun onSelected(selected: Boolean) {} + + open fun handleCommand(commands: AttachmentCommands) {} + + var boundResourceUid: String? = null + + open fun bind(attachmentInfo: AttachmentInfo) { + boundResourceUid = attachmentInfo.uid + } +} + +class UnsupportedViewHolder constructor(itemView: View) : + BaseViewHolder(itemView) From e38cb7c1a6ced6b7933d14bc0803ee26ce0a3b77 Mon Sep 17 00:00:00 2001 From: Valere Date: Thu, 9 Jul 2020 10:16:38 +0200 Subject: [PATCH 15/25] Unwanted logs --- .../AttachmentViewerActivity.kt | 19 +++++++++---------- .../attachmentviewer/AttachmentsAdapter.kt | 2 +- .../riotx/attachmentviewer/VideoViewHolder.kt | 3 +-- .../ZoomableImageViewHolder.kt | 3 +-- 4 files changed, 12 insertions(+), 15 deletions(-) diff --git a/attachment-viewer/src/main/java/im/vector/riotx/attachmentviewer/AttachmentViewerActivity.kt b/attachment-viewer/src/main/java/im/vector/riotx/attachmentviewer/AttachmentViewerActivity.kt index 029064e058..d6cf7c606a 100644 --- a/attachment-viewer/src/main/java/im/vector/riotx/attachmentviewer/AttachmentViewerActivity.kt +++ b/attachment-viewer/src/main/java/im/vector/riotx/attachmentviewer/AttachmentViewerActivity.kt @@ -18,7 +18,6 @@ package im.vector.riotx.attachmentviewer import android.graphics.Color import android.os.Bundle -import android.util.Log import android.view.GestureDetector import android.view.MotionEvent import android.view.ScaleGestureDetector @@ -161,26 +160,26 @@ abstract class AttachmentViewerActivity : AppCompatActivity(), AttachmentEventLi return true } - Log.v("ATTACHEMENTS", "================\ndispatchTouchEvent $ev") + // Log.v("ATTACHEMENTS", "================\ndispatchTouchEvent $ev") handleUpDownEvent(ev) - Log.v("ATTACHEMENTS", "scaleDetector is in progress ${scaleDetector.isInProgress}") - Log.v("ATTACHEMENTS", "pointerCount ${ev.pointerCount}") - Log.v("ATTACHEMENTS", "wasScaled $wasScaled") + // Log.v("ATTACHEMENTS", "scaleDetector is in progress ${scaleDetector.isInProgress}") + // Log.v("ATTACHEMENTS", "pointerCount ${ev.pointerCount}") + // Log.v("ATTACHEMENTS", "wasScaled $wasScaled") if (swipeDirection == null && (scaleDetector.isInProgress || ev.pointerCount > 1 || wasScaled)) { wasScaled = true - Log.v("ATTACHEMENTS", "dispatch to pager") +// Log.v("ATTACHEMENTS", "dispatch to pager") return attachmentPager.dispatchTouchEvent(ev) } - Log.v("ATTACHEMENTS", "is current item scaled ${isScaled()}") + // Log.v("ATTACHEMENTS", "is current item scaled ${isScaled()}") return (if (isScaled()) super.dispatchTouchEvent(ev) else handleTouchIfNotScaled(ev)).also { - Log.v("ATTACHEMENTS", "\n================") +// Log.v("ATTACHEMENTS", "\n================") } } private fun handleUpDownEvent(event: MotionEvent) { - Log.v("ATTACHEMENTS", "handleUpDownEvent $event") + // Log.v("ATTACHEMENTS", "handleUpDownEvent $event") if (event.action == MotionEvent.ACTION_UP) { handleEventActionUp(event) } @@ -232,7 +231,7 @@ abstract class AttachmentViewerActivity : AppCompatActivity(), AttachmentEventLi } private fun handleTouchIfNotScaled(event: MotionEvent): Boolean { - Log.v("ATTACHEMENTS", "handleTouchIfNotScaled $event") +// Log.v("ATTACHEMENTS", "handleTouchIfNotScaled $event") directionDetector.handleTouchEvent(event) return when (swipeDirection) { diff --git a/attachment-viewer/src/main/java/im/vector/riotx/attachmentviewer/AttachmentsAdapter.kt b/attachment-viewer/src/main/java/im/vector/riotx/attachmentviewer/AttachmentsAdapter.kt index 90020f2cb0..27bdfdc91d 100644 --- a/attachment-viewer/src/main/java/im/vector/riotx/attachmentviewer/AttachmentsAdapter.kt +++ b/attachment-viewer/src/main/java/im/vector/riotx/attachmentviewer/AttachmentsAdapter.kt @@ -78,7 +78,7 @@ class AttachmentsAdapter : RecyclerView.Adapter() { attachmentSourceProvider?.loadVideo((holder as VideoViewHolder).target, it) } // else -> { -//// } +// // } } } } diff --git a/attachment-viewer/src/main/java/im/vector/riotx/attachmentviewer/VideoViewHolder.kt b/attachment-viewer/src/main/java/im/vector/riotx/attachmentviewer/VideoViewHolder.kt index 2b417baecc..e1a5a9864f 100644 --- a/attachment-viewer/src/main/java/im/vector/riotx/attachmentviewer/VideoViewHolder.kt +++ b/attachment-viewer/src/main/java/im/vector/riotx/attachmentviewer/VideoViewHolder.kt @@ -16,7 +16,6 @@ package im.vector.riotx.attachmentviewer -import android.util.Log import android.view.View import android.widget.ImageView import android.widget.ProgressBar @@ -115,7 +114,7 @@ class VideoViewHolder constructor(itemView: View) : val duration = videoView.duration val progress = videoView.currentPosition val isPlaying = videoView.isPlaying - Log.v("FOO", "isPlaying $isPlaying $progress/$duration") +// Log.v("FOO", "isPlaying $isPlaying $progress/$duration") eventListener?.get()?.onEvent(AttachmentEvents.VideoEvent(isPlaying, progress, duration)) } } diff --git a/attachment-viewer/src/main/java/im/vector/riotx/attachmentviewer/ZoomableImageViewHolder.kt b/attachment-viewer/src/main/java/im/vector/riotx/attachmentviewer/ZoomableImageViewHolder.kt index aeaf612bbc..3eb06e4c27 100644 --- a/attachment-viewer/src/main/java/im/vector/riotx/attachmentviewer/ZoomableImageViewHolder.kt +++ b/attachment-viewer/src/main/java/im/vector/riotx/attachmentviewer/ZoomableImageViewHolder.kt @@ -16,7 +16,6 @@ package im.vector.riotx.attachmentviewer -import android.util.Log import android.view.View import android.widget.ProgressBar import com.github.chrisbanes.photoview.PhotoView @@ -30,7 +29,7 @@ class ZoomableImageViewHolder constructor(itemView: View) : init { touchImageView.setAllowParentInterceptOnEdge(false) touchImageView.setOnScaleChangeListener { scaleFactor, _, _ -> - Log.v("ATTACHEMENTS", "scaleFactor $scaleFactor") + // Log.v("ATTACHEMENTS", "scaleFactor $scaleFactor") // It's a bit annoying but when you pitch down the scaling // is not exactly one :/ touchImageView.setAllowParentInterceptOnEdge(scaleFactor <= 1.0008f) From 195e2703b91fe9c61b95de99516f0b98c51b1226 Mon Sep 17 00:00:00 2001 From: Valere Date: Thu, 9 Jul 2020 15:22:34 +0200 Subject: [PATCH 16/25] Support open from upload media tab --- .../features/media/BaseAttachmentProvider.kt | 148 ++++++++++++++++ .../media/DataAttachmentRoomProvider.kt | 112 +++++++++++++ ...der.kt => RoomEventsAttachmentProvider.kt} | 158 ++++-------------- .../media/VectorAttachmentViewerActivity.kt | 96 +++++------ .../features/navigation/DefaultNavigator.kt | 33 ++-- .../riotx/features/navigation/Navigator.kt | 6 +- .../uploads/media/RoomUploadsMediaFragment.kt | 83 ++++++++- .../uploads/media/UploadsImageItem.kt | 2 + .../uploads/media/UploadsVideoItem.kt | 2 + .../main/res/layout/fragment_room_uploads.xml | 2 + 10 files changed, 455 insertions(+), 187 deletions(-) create mode 100644 vector/src/main/java/im/vector/riotx/features/media/BaseAttachmentProvider.kt create mode 100644 vector/src/main/java/im/vector/riotx/features/media/DataAttachmentRoomProvider.kt rename vector/src/main/java/im/vector/riotx/features/media/{RoomAttachmentProvider.kt => RoomEventsAttachmentProvider.kt} (53%) 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 @@ From eff08955f147faf809dea6df30cfa88c970047f9 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Fri, 10 Jul 2020 12:37:48 +0200 Subject: [PATCH 17/25] Fix a11y --- vector/src/main/res/layout/merge_image_attachment_overlay.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vector/src/main/res/layout/merge_image_attachment_overlay.xml b/vector/src/main/res/layout/merge_image_attachment_overlay.xml index db22c0112c..6188ad564a 100644 --- a/vector/src/main/res/layout/merge_image_attachment_overlay.xml +++ b/vector/src/main/res/layout/merge_image_attachment_overlay.xml @@ -24,7 +24,7 @@ android:layout_marginEnd="16dp" android:background="?attr/selectableItemBackgroundBorderless" android:clickable="true" - android:contentDescription="@string/share" + android:contentDescription="@string/action_close" android:focusable="true" android:padding="6dp" android:scaleType="centerInside" From e979bee9206b6100b0f3d112d3deff94a4210bb0 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Fri, 10 Jul 2020 12:37:59 +0200 Subject: [PATCH 18/25] Format --- .../riotx/features/navigation/DefaultNavigator.kt | 3 ++- .../im/vector/riotx/features/navigation/Navigator.kt | 3 ++- .../res/layout/merge_image_attachment_overlay.xml | 12 ++++++------ 3 files changed, 10 insertions(+), 8 deletions(-) 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 8940ac6791..6acd041ecf 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 @@ -289,7 +289,8 @@ class DefaultNavigator @Inject constructor( } override fun openVideoViewer(activity: Activity, - roomId: String, mediaData: VideoContentRenderer.Data, + roomId: String, + mediaData: AttachmentData, view: View, inMemory: List?, options: ((MutableList>) -> Unit)?) { 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 f925344570..6d036f1468 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 @@ -99,7 +99,8 @@ interface Navigator { 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/res/layout/merge_image_attachment_overlay.xml b/vector/src/main/res/layout/merge_image_attachment_overlay.xml index 6188ad564a..b0e769579c 100644 --- a/vector/src/main/res/layout/merge_image_attachment_overlay.xml +++ b/vector/src/main/res/layout/merge_image_attachment_overlay.xml @@ -74,8 +74,8 @@ android:layout_marginEnd="16dp" android:background="?attr/selectableItemBackgroundBorderless" android:clickable="true" - android:focusable="true" android:contentDescription="@string/share" + android:focusable="true" android:padding="6dp" android:tint="@color/white" app:layout_constraintBottom_toBottomOf="@id/overlayTopBackground" @@ -85,12 +85,12 @@ + app:constraint_referenced_ids="overlayBottomBackground,overlayBackButton,overlayPlayPauseButton,overlaySeekBar" + tools:visibility="visible" /> Date: Fri, 10 Jul 2020 12:48:35 +0200 Subject: [PATCH 19/25] Cleanup Navigator --- .../home/room/detail/RoomDetailFragment.kt | 16 ++++-- .../media/VectorAttachmentViewerActivity.kt | 5 +- .../features/navigation/DefaultNavigator.kt | 52 +------------------ .../riotx/features/navigation/Navigator.kt | 11 +--- .../uploads/media/RoomUploadsMediaFragment.kt | 19 +++++-- 5 files changed, 34 insertions(+), 69 deletions(-) diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailFragment.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailFragment.kt index a457587aa8..938ae6a1bb 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailFragment.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailFragment.kt @@ -1171,14 +1171,24 @@ class RoomDetailFragment @Inject constructor( } override fun onImageMessageClicked(messageImageContent: MessageImageInfoContent, mediaData: ImageContentRenderer.Data, view: View) { - navigator.openImageViewer(requireActivity(), roomDetailArgs.roomId, mediaData, view) { pairs -> + navigator.openMediaViewer( + activity = requireActivity(), + roomId = roomDetailArgs.roomId, + mediaData = mediaData, + view = view + ) { pairs -> pairs.add(Pair(roomToolbar, ViewCompat.getTransitionName(roomToolbar) ?: "")) pairs.add(Pair(composerLayout, ViewCompat.getTransitionName(composerLayout) ?: "")) } } override fun onVideoMessageClicked(messageVideoContent: MessageVideoContent, mediaData: VideoContentRenderer.Data, view: View) { - navigator.openVideoViewer(requireActivity(), roomDetailArgs.roomId, mediaData, view) { pairs -> + navigator.openMediaViewer( + activity = requireActivity(), + roomId = roomDetailArgs.roomId, + mediaData = mediaData, + view = view + ) { pairs -> pairs.add(Pair(roomToolbar, ViewCompat.getTransitionName(roomToolbar) ?: "")) pairs.add(Pair(composerLayout, ViewCompat.getTransitionName(composerLayout) ?: "")) } @@ -1199,7 +1209,7 @@ class RoomDetailFragment @Inject constructor( override fun onRequestPermissionsResult(requestCode: Int, permissions: Array, grantResults: IntArray) { if (allGranted(grantResults)) { when (requestCode) { - SAVE_ATTACHEMENT_REQUEST_CODE -> { + SAVE_ATTACHEMENT_REQUEST_CODE -> { sharedActionViewModel.pendingAction?.let { handleActions(it) sharedActionViewModel.pendingAction = null 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 c0b822c13a..38e3ccc69c 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 @@ -237,7 +237,6 @@ class VectorAttachmentViewerActivity : AttachmentViewerActivity(), BaseAttachmen } companion object { - const val EXTRA_ARGS = "EXTRA_ARGS" const val EXTRA_IMAGE_DATA = "EXTRA_IMAGE_DATA" const val EXTRA_IN_MEMORY_DATA = "EXTRA_IN_MEMORY_DATA" @@ -246,11 +245,11 @@ class VectorAttachmentViewerActivity : AttachmentViewerActivity(), BaseAttachmen mediaData: AttachmentData, roomId: String?, eventId: String, - inMemoryData: List?, + 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) { + if (inMemoryData.isNotEmpty()) { it.putParcelableArrayListExtra(EXTRA_IN_MEMORY_DATA, ArrayList(inMemoryData)) } } 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 6acd041ecf..99eb69f539 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 @@ -19,7 +19,6 @@ package im.vector.riotx.features.navigation import android.app.Activity import android.content.Context import android.content.Intent -import android.os.Build import android.view.View import android.view.Window import androidx.core.app.ActivityOptionsCompat @@ -52,7 +51,6 @@ 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.VectorAttachmentViewerActivity -import im.vector.riotx.features.media.VideoContentRenderer import im.vector.riotx.features.roomdirectory.RoomDirectoryActivity import im.vector.riotx.features.roomdirectory.createroom.CreateRoomActivity import im.vector.riotx.features.roomdirectory.roompreview.RoomPreviewActivity @@ -245,11 +243,11 @@ class DefaultNavigator @Inject constructor( context.startActivity(WidgetActivity.newIntent(context, widgetArgs)) } - override fun openImageViewer(activity: Activity, + override fun openMediaViewer(activity: Activity, roomId: String, mediaData: AttachmentData, view: View, - inMemory: List?, + inMemory: List, options: ((MutableList>) -> Unit)?) { VectorAttachmentViewerActivity.newIntent(activity, mediaData, @@ -268,52 +266,6 @@ class DefaultNavigator @Inject constructor( pairs.add(Pair(view, ViewCompat.getTransitionName(view) ?: "")) options?.invoke(pairs) - val bundle = ActivityOptionsCompat.makeSceneTransitionAnimation(activity, *pairs.toTypedArray()).toBundle() - activity.startActivity(intent, bundle) - } -// val intent = ImageMediaViewerActivity.newIntent(activity, mediaData, ViewCompat.getTransitionName(view)) -// 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)) -// } -// } -// pairs.add(Pair(view, ViewCompat.getTransitionName(view) ?: "")) -// options?.invoke(pairs) -// -// val bundle = ActivityOptionsCompat.makeSceneTransitionAnimation(activity, *pairs.toTypedArray()).toBundle() -// activity.startActivity(intent, bundle) - } - - override fun openVideoViewer(activity: Activity, - roomId: String, - mediaData: AttachmentData, - view: View, - inMemory: List?, - options: ((MutableList>) -> Unit)?) { -// val intent = VideoMediaViewerActivity.newIntent(activity, mediaData) -// activity.startActivity(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)) - } - } - pairs.add(Pair(view, ViewCompat.getTransitionName(view) ?: "")) - options?.invoke(pairs) - val bundle = ActivityOptionsCompat.makeSceneTransitionAnimation(activity, *pairs.toTypedArray()).toBundle() activity.startActivity(intent, bundle) } 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 6d036f1468..273734916d 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 @@ -91,17 +91,10 @@ interface Navigator { fun openRoomWidget(context: Context, roomId: String, widget: Widget) - fun openImageViewer(activity: Activity, + fun openMediaViewer(activity: Activity, roomId: String, mediaData: AttachmentData, view: View, - inMemory: List? = null, - options: ((MutableList>) -> Unit)?) - - fun openVideoViewer(activity: Activity, - roomId: String, - mediaData: VideoContentRenderer.Data, - view: View, - inMemory: List? = null, + inMemory: List = emptyList(), 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 e0758c7d72..dda070bf48 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 @@ -89,7 +89,7 @@ class RoomUploadsMediaFragment @Inject constructor( // It's very strange i can't just access // the app bar using find by id... - private fun trickFindAppBar() : AppBarLayout? { + private fun trickFindAppBar(): AppBarLayout? { return activity?.supportFragmentManager?.fragments ?.filterIsInstance() ?.firstOrNull() @@ -97,9 +97,14 @@ class RoomUploadsMediaFragment @Inject constructor( } override fun onOpenImageClicked(view: View, mediaData: ImageContentRenderer.Data) = withState(uploadsViewModel) { state -> - val inMemory = getItemsArgs(state) - navigator.openImageViewer(requireActivity(), state.roomId, mediaData, view, inMemory) { pairs -> + navigator.openMediaViewer( + activity = requireActivity(), + roomId = state.roomId, + mediaData = mediaData, + view = view, + inMemory = inMemory + ) { pairs -> trickFindAppBar()?.let { pairs.add(Pair(it, ViewCompat.getTransitionName(it) ?: "")) } @@ -151,7 +156,13 @@ class RoomUploadsMediaFragment @Inject constructor( override fun onOpenVideoClicked(view: View, mediaData: VideoContentRenderer.Data) = withState(uploadsViewModel) { state -> val inMemory = getItemsArgs(state) - navigator.openVideoViewer(requireActivity(), state.roomId, mediaData, view, inMemory) { pairs -> + navigator.openMediaViewer( + activity = requireActivity(), + roomId = state.roomId, + mediaData = mediaData, + view = view, + inMemory = inMemory + ) { pairs -> trickFindAppBar()?.let { pairs.add(Pair(it, ViewCompat.getTransitionName(it) ?: "")) } From ea3e467dc44b042f56a7482db3468d1019b68379 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Fri, 10 Jul 2020 12:52:54 +0200 Subject: [PATCH 20/25] Format --- settings.gradle | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/settings.gradle b/settings.gradle index 3a7aa9ac1c..76a15a206d 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1,2 +1,6 @@ -include ':vector', ':matrix-sdk-android', ':matrix-sdk-android-rx', ':diff-match-patch', ':attachment-viewer' -include ':multipicker' \ No newline at end of file +include ':vector' +include ':matrix-sdk-android' +include ':matrix-sdk-android-rx' +include ':diff-match-patch' +include ':attachment-viewer' +include ':multipicker' From 6c0f775c4b1058d589eabfd1dc0ea2444999ba7e Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Fri, 10 Jul 2020 13:07:14 +0200 Subject: [PATCH 21/25] Cleanup --- attachment-viewer/src/main/AndroidManifest.xml | 11 +---------- attachment-viewer/src/main/res/values/dimens.xml | 3 --- attachment-viewer/src/main/res/values/strings.xml | 11 ----------- attachment-viewer/src/main/res/values/styles.xml | 12 ------------ 4 files changed, 1 insertion(+), 36 deletions(-) delete mode 100644 attachment-viewer/src/main/res/values/dimens.xml delete mode 100644 attachment-viewer/src/main/res/values/strings.xml delete mode 100644 attachment-viewer/src/main/res/values/styles.xml diff --git a/attachment-viewer/src/main/AndroidManifest.xml b/attachment-viewer/src/main/AndroidManifest.xml index 4c48526635..ff8ec394d2 100644 --- a/attachment-viewer/src/main/AndroidManifest.xml +++ b/attachment-viewer/src/main/AndroidManifest.xml @@ -1,11 +1,2 @@ - - - - - - - \ No newline at end of file + diff --git a/attachment-viewer/src/main/res/values/dimens.xml b/attachment-viewer/src/main/res/values/dimens.xml deleted file mode 100644 index 125df87119..0000000000 --- a/attachment-viewer/src/main/res/values/dimens.xml +++ /dev/null @@ -1,3 +0,0 @@ - - 16dp - \ No newline at end of file diff --git a/attachment-viewer/src/main/res/values/strings.xml b/attachment-viewer/src/main/res/values/strings.xml deleted file mode 100644 index 6dcb56555a..0000000000 --- a/attachment-viewer/src/main/res/values/strings.xml +++ /dev/null @@ -1,11 +0,0 @@ - - AttachementViewerActivity - - First Fragment - Second Fragment - Next - Previous - - Hello first fragment - Hello second fragment. Arg: %1$s - \ No newline at end of file diff --git a/attachment-viewer/src/main/res/values/styles.xml b/attachment-viewer/src/main/res/values/styles.xml deleted file mode 100644 index a81174782e..0000000000 --- a/attachment-viewer/src/main/res/values/styles.xml +++ /dev/null @@ -1,12 +0,0 @@ - - - - - \ No newline at end of file From e8b1e418fa116ebe0a9464d1d6f55b3ec70b6e93 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Fri, 10 Jul 2020 14:37:57 +0200 Subject: [PATCH 22/25] ktlint --- .../main/java/im/vector/riotx/features/navigation/Navigator.kt | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) 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 273734916d..2403cfa0a9 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 @@ -23,11 +23,10 @@ import androidx.core.util.Pair import androidx.fragment.app.Fragment import im.vector.matrix.android.api.session.room.model.roomdirectory.PublicRoom import im.vector.matrix.android.api.session.terms.TermsService -import im.vector.matrix.android.api.util.MatrixItem import im.vector.matrix.android.api.session.widgets.model.Widget +import im.vector.matrix.android.api.util.MatrixItem import im.vector.riotx.features.home.room.detail.widget.WidgetRequestCodes import im.vector.riotx.features.media.AttachmentData -import im.vector.riotx.features.media.VideoContentRenderer import im.vector.riotx.features.settings.VectorSettingsActivity import im.vector.riotx.features.share.SharedData import im.vector.riotx.features.terms.ReviewTermsActivity From 9f2631110eb84c824ba6ae91ad670ac3f6c82764 Mon Sep 17 00:00:00 2001 From: Valere Date: Fri, 10 Jul 2020 14:38:23 +0200 Subject: [PATCH 23/25] Missing copyrights --- .../vector/riotx/attachmentviewer/AttachmentViewerActivity.kt | 1 + .../java/im/vector/riotx/attachmentviewer/SwipeDirection.kt | 1 + .../im/vector/riotx/attachmentviewer/SwipeDirectionDetector.kt | 1 + .../im/vector/riotx/attachmentviewer/SwipeToDismissHandler.kt | 1 + vector/src/main/assets/open_source_licenses.html | 3 +++ 5 files changed, 7 insertions(+) diff --git a/attachment-viewer/src/main/java/im/vector/riotx/attachmentviewer/AttachmentViewerActivity.kt b/attachment-viewer/src/main/java/im/vector/riotx/attachmentviewer/AttachmentViewerActivity.kt index d6cf7c606a..8c2d4e9833 100644 --- a/attachment-viewer/src/main/java/im/vector/riotx/attachmentviewer/AttachmentViewerActivity.kt +++ b/attachment-viewer/src/main/java/im/vector/riotx/attachmentviewer/AttachmentViewerActivity.kt @@ -1,5 +1,6 @@ /* * Copyright (c) 2020 New Vector Ltd + * Copyright (C) 2018 stfalcon.com * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/attachment-viewer/src/main/java/im/vector/riotx/attachmentviewer/SwipeDirection.kt b/attachment-viewer/src/main/java/im/vector/riotx/attachmentviewer/SwipeDirection.kt index e552d55efb..ebe8784e15 100644 --- a/attachment-viewer/src/main/java/im/vector/riotx/attachmentviewer/SwipeDirection.kt +++ b/attachment-viewer/src/main/java/im/vector/riotx/attachmentviewer/SwipeDirection.kt @@ -1,5 +1,6 @@ /* * Copyright (c) 2020 New Vector Ltd + * Copyright (C) 2018 stfalcon.com * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/attachment-viewer/src/main/java/im/vector/riotx/attachmentviewer/SwipeDirectionDetector.kt b/attachment-viewer/src/main/java/im/vector/riotx/attachmentviewer/SwipeDirectionDetector.kt index cedbcd0180..0cf9a19ab1 100644 --- a/attachment-viewer/src/main/java/im/vector/riotx/attachmentviewer/SwipeDirectionDetector.kt +++ b/attachment-viewer/src/main/java/im/vector/riotx/attachmentviewer/SwipeDirectionDetector.kt @@ -1,5 +1,6 @@ /* * Copyright (c) 2020 New Vector Ltd + * Copyright (C) 2018 stfalcon.com * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/attachment-viewer/src/main/java/im/vector/riotx/attachmentviewer/SwipeToDismissHandler.kt b/attachment-viewer/src/main/java/im/vector/riotx/attachmentviewer/SwipeToDismissHandler.kt index e52c72cba0..ca93d4f73a 100644 --- a/attachment-viewer/src/main/java/im/vector/riotx/attachmentviewer/SwipeToDismissHandler.kt +++ b/attachment-viewer/src/main/java/im/vector/riotx/attachmentviewer/SwipeToDismissHandler.kt @@ -1,5 +1,6 @@ /* * Copyright (c) 2020 New Vector Ltd + * Copyright (C) 2018 stfalcon.com * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/vector/src/main/assets/open_source_licenses.html b/vector/src/main/assets/open_source_licenses.html index 3af564aaca..58d63bf5a7 100755 --- a/vector/src/main/assets/open_source_licenses.html +++ b/vector/src/main/assets/open_source_licenses.html @@ -389,6 +389,9 @@ SOFTWARE.
  • BillCarsonFr/JsonViewer
  • +
  • + Copyright (C) 2018 stfalcon.com +
  •  Apache License
    
    From 1b6b71ed986a793314ecf9ccb063a69042ffaede Mon Sep 17 00:00:00 2001
    From: Valere 
    Date: Fri, 10 Jul 2020 14:38:31 +0200
    Subject: [PATCH 24/25] Debounce clicks
    
    ---
     .../features/roomprofile/uploads/media/UploadsImageItem.kt | 7 ++++++-
     .../features/roomprofile/uploads/media/UploadsVideoItem.kt | 7 ++++++-
     2 files changed, 12 insertions(+), 2 deletions(-)
    
    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 f994ad0110..3b83e99656 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
    @@ -24,6 +24,7 @@ import com.airbnb.epoxy.EpoxyModelClass
     import im.vector.riotx.R
     import im.vector.riotx.core.epoxy.VectorEpoxyHolder
     import im.vector.riotx.core.epoxy.VectorEpoxyModel
    +import im.vector.riotx.core.utils.DebouncedClickListener
     import im.vector.riotx.features.media.ImageContentRenderer
     
     @EpoxyModelClass(layout = R.layout.item_uploads_image)
    @@ -36,7 +37,11 @@ abstract class UploadsImageItem : VectorEpoxyModel() {
     
         override fun bind(holder: Holder) {
             super.bind(holder)
    -        holder.view.setOnClickListener { listener?.onItemClicked(holder.imageView, data) }
    +        holder.view.setOnClickListener(
    +                DebouncedClickListener(View.OnClickListener { _ ->
    +                    listener?.onItemClicked(holder.imageView, data)
    +                })
    +        )
             imageContentRenderer.render(data, holder.imageView, IMAGE_SIZE_DP)
             ViewCompat.setTransitionName(holder.imageView, "imagePreview_${id()}")
         }
    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 1c9ab4ae74..f20f6ed5b1 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
    @@ -24,6 +24,7 @@ import com.airbnb.epoxy.EpoxyModelClass
     import im.vector.riotx.R
     import im.vector.riotx.core.epoxy.VectorEpoxyHolder
     import im.vector.riotx.core.epoxy.VectorEpoxyModel
    +import im.vector.riotx.core.utils.DebouncedClickListener
     import im.vector.riotx.features.media.ImageContentRenderer
     import im.vector.riotx.features.media.VideoContentRenderer
     
    @@ -37,7 +38,11 @@ abstract class UploadsVideoItem : VectorEpoxyModel() {
     
         override fun bind(holder: Holder) {
             super.bind(holder)
    -        holder.view.setOnClickListener { listener?.onItemClicked(holder.imageView, data) }
    +        holder.view.setOnClickListener(
    +            DebouncedClickListener(View.OnClickListener { _ ->
    +                listener?.onItemClicked(holder.imageView, data)
    +            })
    +        )
             imageContentRenderer.render(data.thumbnailMediaData, holder.imageView, IMAGE_SIZE_DP)
             ViewCompat.setTransitionName(holder.imageView, "videoPreview_${id()}")
         }
    
    From 08bc487f170ca1de284d1d601e262d28660df12c Mon Sep 17 00:00:00 2001
    From: Valere 
    Date: Fri, 10 Jul 2020 14:39:08 +0200
    Subject: [PATCH 25/25] klint
    
    ---
     .../main/java/im/vector/riotx/features/navigation/Navigator.kt   | 1 -
     1 file changed, 1 deletion(-)
    
    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 273734916d..c86aa0aca9 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
    @@ -27,7 +27,6 @@ import im.vector.matrix.android.api.util.MatrixItem
     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.media.AttachmentData
    -import im.vector.riotx.features.media.VideoContentRenderer
     import im.vector.riotx.features.settings.VectorSettingsActivity
     import im.vector.riotx.features.share.SharedData
     import im.vector.riotx.features.terms.ReviewTermsActivity