Simple overlay

This commit is contained in:
Valere 2020-07-06 15:47:15 +02:00
parent 2d4a728af4
commit 76133ab55e
8 changed files with 244 additions and 22 deletions

View file

@ -34,16 +34,12 @@ buildscript {
android { android {
compileSdkVersion 29 compileSdkVersion 29
buildToolsVersion "29.0.3"
defaultConfig { defaultConfig {
minSdkVersion 21 minSdkVersion 21
targetSdkVersion 29 targetSdkVersion 29
versionCode 1 versionCode 1
versionName "1.0" versionName "1.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
consumerProguardFiles "consumer-rules.pro"
} }
buildTypes { buildTypes {

View file

@ -16,6 +16,9 @@
package im.vector.riotx.attachment_viewer package im.vector.riotx.attachment_viewer
import android.content.Context
import android.view.View
sealed class AttachmentInfo { sealed class AttachmentInfo {
data class Image(val url: String, val data: Any?) : AttachmentInfo() data class Image(val url: String, val data: Any?) : AttachmentInfo()
data class AnimatedImage(val url: String, val data: Any?) : AttachmentInfo() data class AnimatedImage(val url: String, val data: Any?) : AttachmentInfo()
@ -34,5 +37,8 @@ interface AttachmentSourceProvider {
fun getAttachmentInfoAt(position: Int): AttachmentInfo fun getAttachmentInfoAt(position: Int): AttachmentInfo
fun loadImage(holder: ZoomableImageViewHolder, info: AttachmentInfo.Image) fun loadImage(holder: ZoomableImageViewHolder, info: AttachmentInfo.Image)
fun loadImage(holder: AnimatedImageViewHolder, info: AttachmentInfo.AnimatedImage) fun loadImage(holder: AnimatedImageViewHolder, info: AttachmentInfo.AnimatedImage)
fun overlayViewAtPosition(context: Context, position: Int) : View?
} }

View file

@ -25,7 +25,9 @@ import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.widget.ImageView import android.widget.ImageView
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.core.view.ViewCompat
import androidx.core.view.isVisible import androidx.core.view.isVisible
import androidx.core.view.updatePadding
import androidx.viewpager2.widget.ViewPager2 import androidx.viewpager2.widget.ViewPager2
import kotlinx.android.synthetic.main.activity_attachment_viewer.* import kotlinx.android.synthetic.main.activity_attachment_viewer.*
import kotlin.math.abs import kotlin.math.abs
@ -36,8 +38,16 @@ abstract class AttachmentViewerActivity : AppCompatActivity() {
lateinit var imageTransitionView: ImageView lateinit var imageTransitionView: ImageView
lateinit var transitionImageContainer: ViewGroup lateinit var transitionImageContainer: ViewGroup
// TODO var topInset = 0
private var overlayView: View? = null 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 swipeDismissHandler: SwipeToDismissHandler
private lateinit var directionDetector: SwipeDirectionDetector private lateinit var directionDetector: SwipeDirectionDetector
@ -53,6 +63,7 @@ abstract class AttachmentViewerActivity : AppCompatActivity() {
private var wasScaled: Boolean = false private var wasScaled: Boolean = false
private var isSwipeToDismissAllowed: Boolean = true private var isSwipeToDismissAllowed: Boolean = true
private lateinit var attachmentsAdapter: AttachmentsAdapter private lateinit var attachmentsAdapter: AttachmentsAdapter
private var isOverlayWasClicked = false
// private val shouldDismissToBottom: Boolean // private val shouldDismissToBottom: Boolean
// get() = e == null // get() = e == null
@ -67,6 +78,20 @@ abstract class AttachmentViewerActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) 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) setContentView(R.layout.activity_attachment_viewer)
attachmentPager.orientation = ViewPager2.ORIENTATION_HORIZONTAL attachmentPager.orientation = ViewPager2.ORIENTATION_HORIZONTAL
attachmentsAdapter = AttachmentsAdapter() attachmentsAdapter = AttachmentsAdapter()
@ -83,6 +108,7 @@ abstract class AttachmentViewerActivity : AppCompatActivity() {
override fun onPageSelected(position: Int) { override fun onPageSelected(position: Int) {
currentPosition = position currentPosition = position
overlayView = attachmentsAdapter.attachmentSourceProvider?.overlayViewAtPosition(this@AttachmentViewerActivity, position)
} }
}) })
@ -92,6 +118,13 @@ abstract class AttachmentViewerActivity : AppCompatActivity() {
scaleDetector = createScaleGestureDetector() scaleDetector = createScaleGestureDetector()
ViewCompat.setOnApplyWindowInsetsListener(rootContainer) { _, insets ->
overlayView?.updatePadding(top = insets.systemWindowInsetTop)
topInset = insets.systemWindowInsetTop
insets
}
} }
override fun dispatchTouchEvent(ev: MotionEvent): Boolean { 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 // The zoomable view is configured to disallow interception when image is zoomed
// Check if the overlay is visible, and wants to handle the click // Check if the overlay is visible, and wants to handle the click
// if (overlayView.isVisible && overlayView?.dispatchTouchEvent(event) == true) { if (overlayView?.isVisible == true && overlayView?.dispatchTouchEvent(ev) == true) {
// return true return true
// } }
Log.v("ATTACHEMENTS", "================\ndispatchTouchEvent $ev") Log.v("ATTACHEMENTS", "================\ndispatchTouchEvent $ev")
@ -143,14 +176,14 @@ abstract class AttachmentViewerActivity : AppCompatActivity() {
attachmentPager.dispatchTouchEvent(event) attachmentPager.dispatchTouchEvent(event)
swipeDismissHandler.onTouch(rootContainer, event) swipeDismissHandler.onTouch(rootContainer, event)
// isOverlayWasClicked = dispatchOverlayTouch(event) isOverlayWasClicked = dispatchOverlayTouch(event)
} }
private fun handleEventActionUp(event: MotionEvent) { private fun handleEventActionUp(event: MotionEvent) {
// wasDoubleTapped = false // wasDoubleTapped = false
swipeDismissHandler.onTouch(rootContainer, event) swipeDismissHandler.onTouch(rootContainer, event)
attachmentPager.dispatchTouchEvent(event) attachmentPager.dispatchTouchEvent(event)
// isOverlayWasClicked = dispatchOverlayTouch(event) isOverlayWasClicked = dispatchOverlayTouch(event)
} }
private fun handleTouchIfNotScaled(event: MotionEvent): Boolean { private fun handleTouchIfNotScaled(event: MotionEvent): Boolean {
@ -159,7 +192,7 @@ abstract class AttachmentViewerActivity : AppCompatActivity() {
directionDetector.handleTouchEvent(event) directionDetector.handleTouchEvent(event)
return when (swipeDirection) { return when (swipeDirection) {
SwipeDirection.Up, SwipeDirection.Down -> { SwipeDirection.Up, SwipeDirection.Down -> {
if (isSwipeToDismissAllowed && !wasScaled && isImagePagerIdle) { if (isSwipeToDismissAllowed && !wasScaled && isImagePagerIdle) {
swipeDismissHandler.onTouch(rootContainer, event) swipeDismissHandler.onTouch(rootContainer, event)
} else true } else true
@ -167,7 +200,7 @@ abstract class AttachmentViewerActivity : AppCompatActivity() {
SwipeDirection.Left, SwipeDirection.Right -> { SwipeDirection.Left, SwipeDirection.Right -> {
attachmentPager.dispatchTouchEvent(event) attachmentPager.dispatchTouchEvent(event)
} }
else -> true else -> true
} }
} }

View file

@ -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<ImageView>(R.id.overlayBackButton).setOnClickListener {
onBack?.invoke()
}
}
fun updateWith(counter: String, senderInfo : String) {
counterTextView.text = counter
infoTextView.text = senderInfo
}
}

View file

@ -16,7 +16,9 @@
package im.vector.riotx.features.media package im.vector.riotx.features.media
import android.content.Context
import android.graphics.drawable.Drawable import android.graphics.drawable.Drawable
import android.view.View
import com.bumptech.glide.request.target.CustomViewTarget import com.bumptech.glide.request.target.CustomViewTarget
import im.vector.matrix.android.api.session.events.model.toModel import im.vector.matrix.android.api.session.events.model.toModel
import im.vector.matrix.android.api.session.room.model.message.MessageContent 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.AttachmentInfo
import im.vector.riotx.attachment_viewer.AttachmentSourceProvider import im.vector.riotx.attachment_viewer.AttachmentSourceProvider
import im.vector.riotx.attachment_viewer.ZoomableImageViewHolder 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 import javax.inject.Inject
class RoomAttachmentProvider( class RoomAttachmentProvider(
private val attachments: List<TimelineEvent>, private val attachments: List<TimelineEvent>,
private val initialIndex: Int, private val initialIndex: Int,
private val imageContentRenderer: ImageContentRenderer private val imageContentRenderer: ImageContentRenderer,
private val dateFormatter: VectorDateFormatter
) : AttachmentSourceProvider { ) : AttachmentSourceProvider {
interface InteractionListener {
fun onDismissTapped()
fun onShareTapped()
}
var interactionListener: InteractionListener? = null
private var overlayView: AttachmentOverlayView? = null
override fun getItemCount(): Int { override fun getItemCount(): Int {
return attachments.size return attachments.size
} }
@ -79,6 +93,26 @@ class RoomAttachmentProvider(
imageContentRenderer.render(it, holder.touchImageView, holder.customTargetView as CustomViewTarget<*, Drawable>) 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) { // override fun loadImage(holder: ImageViewHolder, info: AttachmentInfo.Image) {
// (info.data as? ImageContentRenderer.Data)?.let { // (info.data as? ImageContentRenderer.Data)?.let {
// imageContentRenderer.render(it, ImageContentRenderer.Mode.FULL_SIZE, holder.touchImageView) // imageContentRenderer.render(it, ImageContentRenderer.Mode.FULL_SIZE, holder.touchImageView)
@ -87,10 +121,11 @@ class RoomAttachmentProvider(
} }
class RoomAttachmentProviderFactory @Inject constructor( class RoomAttachmentProviderFactory @Inject constructor(
private val imageContentRenderer: ImageContentRenderer private val imageContentRenderer: ImageContentRenderer,
private val vectorDateFormatter: VectorDateFormatter
) { ) {
fun createProvider(attachments: List<TimelineEvent>, initialIndex: Int): RoomAttachmentProvider { fun createProvider(attachments: List<TimelineEvent>, initialIndex: Int): RoomAttachmentProvider {
return RoomAttachmentProvider(attachments, initialIndex, imageContentRenderer) return RoomAttachmentProvider(attachments, initialIndex, imageContentRenderer, vectorDateFormatter)
} }
} }

View file

@ -22,17 +22,15 @@ import android.os.Parcelable
import android.view.View import android.view.View
import android.view.ViewTreeObserver import android.view.ViewTreeObserver
import androidx.core.app.ActivityCompat import androidx.core.app.ActivityCompat
import androidx.core.content.ContextCompat
import androidx.core.transition.addListener import androidx.core.transition.addListener
import androidx.core.view.ViewCompat import androidx.core.view.ViewCompat
import androidx.core.view.isInvisible import androidx.core.view.isInvisible
import androidx.core.view.isVisible import androidx.core.view.isVisible
import androidx.transition.Transition import androidx.transition.Transition
import im.vector.riotx.R
import im.vector.riotx.attachment_viewer.AttachmentViewerActivity import im.vector.riotx.attachment_viewer.AttachmentViewerActivity
import im.vector.riotx.core.di.ActiveSessionHolder import im.vector.riotx.core.di.*
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.ActivityOtherThemes
import im.vector.riotx.features.themes.ThemeUtils import im.vector.riotx.features.themes.ThemeUtils
import kotlinx.android.parcel.Parcelize import kotlinx.android.parcel.Parcelize
@ -40,7 +38,7 @@ import timber.log.Timber
import javax.inject.Inject import javax.inject.Inject
import kotlin.system.measureTimeMillis import kotlin.system.measureTimeMillis
class VectorAttachmentViewerActivity : AttachmentViewerActivity() { class VectorAttachmentViewerActivity : AttachmentViewerActivity(), RoomAttachmentProvider.InteractionListener {
@Parcelize @Parcelize
data class Args( 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) { if (savedInstanceState == null) {
pager2.setCurrentItem(index, false) pager2.setCurrentItem(index, false)
} }
window.statusBarColor = ContextCompat.getColor(this, R.color.black_alpha)
} }
private fun getOtherThemes() = ActivityOtherThemes.VectorAttachmentsPreview private fun getOtherThemes() = ActivityOtherThemes.VectorAttachmentsPreview
@ -204,4 +206,12 @@ class VectorAttachmentViewerActivity : AttachmentViewerActivity() {
} }
} }
override fun onDismissTapped() {
animateClose()
}
override fun onShareTapped() {
TODO("Not yet implemented")
}
} }

View file

@ -0,0 +1,83 @@
<?xml version="1.0" encoding="utf-8"?>
<merge xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:parentTag="androidx.constraintlayout.widget.ConstraintLayout">
<View
android:id="@+id/overlayTopBackground"
android:layout_width="match_parent"
android:layout_height="60dp"
android:background="@color/black_alpha">
</View>
<ImageView
android:id="@+id/overlayBackButton"
android:layout_width="44dp"
android:layout_height="44dp"
android:layout_marginStart="8dp"
android:layout_marginEnd="16dp"
android:scaleType="centerInside"
android:clickable="true"
android:focusable="true"
android:background="?attr/selectableItemBackgroundBorderless"
android:contentDescription="@string/share"
android:padding="6dp"
android:tint="@color/white"
app:layout_constraintBottom_toBottomOf="@id/overlayTopBackground"
app:layout_constraintStart_toStartOf="@id/overlayTopBackground"
app:layout_constraintTop_toTopOf="@id/overlayTopBackground"
app:srcCompat="@drawable/ic_back_24dp" />
<TextView
android:id="@+id/overlayCounterText"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginEnd="8dp"
android:ellipsize="end"
android:maxLines="1"
android:textColor="@color/white"
android:textSize="14sp"
android:textStyle="bold"
app:layout_constraintBottom_toTopOf="@id/overlayInfoText"
app:layout_constraintEnd_toStartOf="@id/overlayShareButton"
app:layout_constraintStart_toEndOf="@id/overlayBackButton"
app:layout_constraintTop_toTopOf="@id/overlayTopBackground"
app:layout_constraintVertical_chainStyle="packed"
tools:text="1 of 200" />
<TextView
android:id="@+id/overlayInfoText"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginEnd="8dp"
android:ellipsize="end"
android:maxLines="1"
android:textColor="@color/white"
android:textSize="14sp"
app:layout_constraintBottom_toBottomOf="@id/overlayTopBackground"
app:layout_constraintEnd_toStartOf="@id/overlayShareButton"
app:layout_constraintStart_toStartOf="@id/overlayCounterText"
app:layout_constraintTop_toBottomOf="@id/overlayCounterText"
tools:text="Bill 29 Jun at 19:42" />
<ImageView
android:id="@+id/overlayShareButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="16dp"
android:background="?attr/selectableItemBackgroundBorderless"
android:contentDescription="@string/share"
android:padding="6dp"
android:tint="@color/white"
app:layout_constraintBottom_toBottomOf="@id/overlayTopBackground"
app:layout_constraintEnd_toEndOf="@id/overlayTopBackground"
app:layout_constraintTop_toTopOf="@id/overlayTopBackground"
app:srcCompat="@drawable/ic_share" />
</merge>

View file

@ -40,6 +40,7 @@
<!-- Other usefull color --> <!-- Other usefull color -->
<color name="black">#FF000000</color> <color name="black">#FF000000</color>
<color name="white">#FFFFFFFF</color> <color name="white">#FFFFFFFF</color>
<color name="black_alpha">#55000000</color>
<!-- Palette: format fo naming: <!-- Palette: format fo naming:
'riotx_<name in the palette snake case>_<theme>' 'riotx_<name in the palette snake case>_<theme>'