Merge branch 'develop' into feature/pending_edits_ux

This commit is contained in:
Valere 2019-07-22 23:53:16 +02:00 committed by GitHub
commit 3aea0a50ca
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 304 additions and 45 deletions

View File

@ -6,12 +6,15 @@ Features:
Improvements:
- UI for pending edits (#193)
- UX image preview screen transition (#393)
Other changes:
-
Bugfix:
- Edited message: link confusion when (edited) appears in body (#398)
- Close detail room screen when the room is left with another client (#256)
- Clear notification for a room left on another client
Translations:
-

View File

@ -41,6 +41,7 @@ interface PushRuleService {
interface PushRuleListener {
fun onMatchRule(event: Event, actions: List<Action>)
fun onRoomLeft(roomId: String)
fun batchFinish()
}
}

View File

@ -20,12 +20,10 @@ import im.vector.matrix.android.api.MatrixCallback
import im.vector.matrix.android.api.auth.data.SessionParams
import im.vector.matrix.android.api.pushrules.Action
import im.vector.matrix.android.api.pushrules.PushRuleService
import im.vector.matrix.android.api.pushrules.rest.GetPushRulesResponse
import im.vector.matrix.android.api.pushrules.rest.PushRule
import im.vector.matrix.android.api.session.events.model.Event
import im.vector.matrix.android.internal.database.mapper.PushRulesMapper
import im.vector.matrix.android.internal.database.model.PushRulesEntity
import im.vector.matrix.android.internal.database.model.PusherEntityFields
import im.vector.matrix.android.internal.database.query.where
import im.vector.matrix.android.internal.session.SessionScope
import im.vector.matrix.android.internal.session.pushers.GetPushRulesTask
@ -122,13 +120,23 @@ internal class DefaultPushRuleService @Inject constructor(
}
}
fun dispatchRoomLeft(roomid: String) {
try {
listeners.forEach {
it.onRoomLeft(roomid)
}
} catch (e: Throwable) {
Timber.e(e, "Error while dispatching room left")
}
}
fun dispatchFinish() {
try {
listeners.forEach {
it.batchFinish()
}
} catch (e: Throwable) {
Timber.e(e, "Error while dispatching finish")
}
}
}

View File

@ -44,6 +44,10 @@ internal class DefaultProcessEventForPushTask @Inject constructor(
override suspend fun execute(params: ProcessEventForPushTask.Params): Try<Unit> {
return Try {
// Handle left rooms
params.syncResponse.leave.keys.forEach {
defaultPushRuleService.dispatchRoomLeft(it)
}
val newJoinEvents = params.syncResponse.join
.map { entries ->
entries.value.timeline?.events?.map { it.copy(roomId = entries.key) }

View File

@ -35,7 +35,9 @@ import android.widget.TextView
import android.widget.Toast
import androidx.annotation.DrawableRes
import androidx.appcompat.app.AlertDialog
import androidx.core.app.ActivityOptionsCompat
import androidx.core.content.ContextCompat
import androidx.core.view.ViewCompat
import androidx.lifecycle.ViewModelProviders
import androidx.recyclerview.widget.ItemTouchHelper
import androidx.recyclerview.widget.LinearLayoutManager
@ -536,11 +538,16 @@ class RoomDetailFragment :
private fun renderRoomSummary(state: RoomDetailViewState) {
state.asyncRoomSummary()?.let {
if (it.membership.isLeft()) {
Timber.w("The room has been left")
activity?.finish()
} else {
roomToolbarTitleView.text = it.displayName
avatarRenderer.render(it, roomToolbarAvatarImageView)
roomToolbarSubtitleView.setTextOrHide(it.topic)
}
}
}
private fun renderTextComposerState(state: TextComposerViewState) {
autocompleteUserPresenter.render(state.asyncUsers)
@ -618,8 +625,12 @@ class RoomDetailFragment :
override fun onImageMessageClicked(messageImageContent: MessageImageContent, mediaData: ImageContentRenderer.Data, view: View) {
// TODO Use navigator
val intent = ImageMediaViewerActivity.newIntent(vectorBaseActivity, mediaData)
startActivity(intent)
val intent = ImageMediaViewerActivity.newIntent(vectorBaseActivity, mediaData, ViewCompat.getTransitionName(view))
val bundle = ActivityOptionsCompat.makeSceneTransitionAnimation(
requireActivity(), view, ViewCompat.getTransitionName(view)
?: "").toBundle()
startActivity(intent, bundle)
}
override fun onVideoMessageClicked(messageVideoContent: MessageVideoContent, mediaData: VideoContentRenderer.Data, view: View) {

View File

@ -19,6 +19,7 @@ package im.vector.riotx.features.home.room.detail.timeline.item
import android.view.View
import android.view.ViewGroup
import android.widget.ImageView
import androidx.core.view.ViewCompat
import com.airbnb.epoxy.EpoxyAttribute
import com.airbnb.epoxy.EpoxyModelClass
import im.vector.riotx.R
@ -45,6 +46,7 @@ abstract class MessageImageVideoItem : AbsMessageItem<MessageImageVideoItem.Hold
contentUploadStateTrackerBinder.bind(informationData.eventId, mediaData, holder.progressLayout)
holder.imageView.setOnClickListener(clickListener)
holder.imageView.setOnLongClickListener(longClickListener)
ViewCompat.setTransitionName(holder.imageView,"imagePreview_${id()}")
holder.mediaContentView.setOnClickListener(cellClickListener)
holder.mediaContentView.setOnLongClickListener(longClickListener)
// The sending state color will be apply to the progress text

View File

@ -16,11 +16,16 @@
package im.vector.riotx.features.media
import android.graphics.drawable.Drawable
import android.net.Uri
import android.os.Parcelable
import android.widget.ImageView
import androidx.exifinterface.media.ExifInterface
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.Target
import com.github.piasy.biv.view.BigImageView
import im.vector.matrix.android.api.session.content.ContentUrlResolver
import im.vector.matrix.android.internal.crypto.attachments.ElementToDecrypt
@ -87,6 +92,55 @@ class ImageContentRenderer @Inject constructor(private val activeSessionHolder:
.transform(RoundedCorners(dpToPx(8, imageView.context)))
.thumbnail(0.3f)
.into(imageView)
}
fun renderFitTarget(data: Data, mode: Mode, imageView: ImageView, callback :((Boolean) -> Unit)? = null) {
val (width, height) = processSize(data, mode)
val glideRequest = if (data.elementToDecrypt != null) {
// Encrypted image
GlideApp
.with(imageView)
.load(data)
} else {
// Clear image
val contentUrlResolver = activeSessionHolder.getActiveSession().contentUrlResolver()
val resolvedUrl = when (mode) {
Mode.FULL_SIZE -> contentUrlResolver.resolveFullSize(data.url)
Mode.THUMBNAIL -> contentUrlResolver.resolveThumbnail(data.url, width, height, ContentUrlResolver.ThumbnailMethod.SCALE)
}
//Fallback to base url
?: data.url
GlideApp
.with(imageView)
.load(resolvedUrl)
}
glideRequest
.listener(object: RequestListener<Drawable> {
override fun onLoadFailed(e: GlideException?,
model: Any?,
target: Target<Drawable>?,
isFirstResource: Boolean): Boolean {
callback?.invoke(false)
return false
}
override fun onResourceReady(resource: Drawable?,
model: Any?,
target: Target<Drawable>?,
dataSource: DataSource?,
isFirstResource: Boolean): Boolean {
callback?.invoke(true)
return false
}
})
.fitCenter()
.into(imageView)
}
fun render(data: Data, imageView: BigImageView) {

View File

@ -18,15 +18,29 @@ package im.vector.riotx.features.media
import android.content.Context
import android.content.Intent
import android.graphics.drawable.Drawable
import android.os.Build
import android.os.Bundle
import android.view.View
import android.view.ViewTreeObserver
import androidx.annotation.RequiresApi
import androidx.appcompat.widget.Toolbar
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 com.bumptech.glide.load.DataSource
import com.bumptech.glide.load.engine.GlideException
import com.bumptech.glide.request.RequestListener
import com.bumptech.glide.request.target.Target
import com.github.piasy.biv.indicator.progresspie.ProgressPieIndicator
import com.github.piasy.biv.view.GlideImageViewFactory
import im.vector.riotx.core.di.ScreenComponent
import im.vector.riotx.core.glide.GlideApp
import im.vector.riotx.core.platform.VectorBaseActivity
import kotlinx.android.synthetic.main.activity_image_media_viewer.*
import timber.log.Timber
import javax.inject.Inject
@ -34,6 +48,8 @@ class ImageMediaViewerActivity : VectorBaseActivity() {
@Inject lateinit var imageContentRenderer: ImageContentRenderer
lateinit var mediaData: ImageContentRenderer.Data
override fun injectWith(injector: ScreenComponent) {
injector.inject(this)
}
@ -41,12 +57,32 @@ class ImageMediaViewerActivity : VectorBaseActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(im.vector.riotx.R.layout.activity_image_media_viewer)
val mediaData = intent.getParcelableExtra<ImageContentRenderer.Data>(EXTRA_MEDIA_DATA)
mediaData = intent.getParcelableExtra<ImageContentRenderer.Data>(EXTRA_MEDIA_DATA)
intent.extras.getString(EXTRA_SHARED_TRANSITION_NAME)?.let {
ViewCompat.setTransitionName(imageTransitionView, it)
}
if (mediaData.url.isNullOrEmpty()) {
finish()
} else {
return
}
configureToolbar(imageMediaViewerToolbar, mediaData)
if (isFirstCreation() && Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP && addTransitionListener()) {
// Encrypted image
imageTransitionView.isVisible = true
imageMediaViewerImageView.isVisible = false
encryptedImageView.isVisible = false
//Postpone transaction a bit until thumbnail is loaded
supportPostponeEnterTransition()
imageContentRenderer.renderFitTarget(mediaData, ImageContentRenderer.Mode.THUMBNAIL, imageTransitionView) {
//Proceed with transaction
scheduleStartPostponedTransition(imageTransitionView)
}
} else {
imageTransitionView.isVisible = false
if (mediaData.elementToDecrypt != null) {
// Encrypted image
imageMediaViewerImageView.isVisible = false
@ -78,13 +114,101 @@ class ImageMediaViewerActivity : VectorBaseActivity() {
}
}
override fun onBackPressed() {
//show again for exit animation
imageTransitionView.isVisible = true
super.onBackPressed()
}
private fun scheduleStartPostponedTransition(sharedElement: View) {
sharedElement.viewTreeObserver.addOnPreDrawListener(
object : ViewTreeObserver.OnPreDrawListener {
override fun onPreDraw(): Boolean {
sharedElement.viewTreeObserver.removeOnPreDrawListener(this)
supportStartPostponedEnterTransition()
return true
}
})
}
/**
* 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
*/
@RequiresApi(Build.VERSION_CODES.LOLLIPOP)
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 (mediaData.elementToDecrypt != null) {
// Encrypted image
GlideApp
.with(this)
.load(mediaData)
.dontAnimate()
.listener(object : RequestListener<Drawable> {
override fun onLoadFailed(e: GlideException?,
model: Any?,
target: Target<Drawable>?,
isFirstResource: Boolean): Boolean {
//TODO ?
Timber.e("TRANSITION onLoadFailed")
imageMediaViewerImageView.isVisible = false
encryptedImageView.isVisible = true
return false
}
override fun onResourceReady(resource: Drawable?,
model: Any?,
target: Target<Drawable>?,
dataSource: DataSource?,
isFirstResource: Boolean): Boolean {
Timber.e("TRANSITION onResourceReady")
imageTransitionView.isInvisible = true
imageMediaViewerImageView.isVisible = false
encryptedImageView.isVisible = true
return false
}
})
.into(encryptedImageView)
} else {
imageTransitionView.isInvisible = true
// Clear image
imageMediaViewerImageView.isVisible = true
encryptedImageView.isVisible = false
imageMediaViewerImageView.setImageViewFactory(GlideImageViewFactory())
imageMediaViewerImageView.setProgressIndicator(ProgressPieIndicator())
imageContentRenderer.render(mediaData, imageMediaViewerImageView)
}
},
onCancel = {
//Something to do?
}
)
return true
}
// If we reach here then we have not added a listener
return false
}
companion object {
private const val EXTRA_MEDIA_DATA = "EXTRA_MEDIA_DATA"
private const val EXTRA_SHARED_TRANSITION_NAME = "EXTRA_SHARED_TRANSITION_NAME"
fun newIntent(context: Context, mediaData: ImageContentRenderer.Data): Intent {
fun newIntent(context: Context, mediaData: ImageContentRenderer.Data, shareTransitionName: String?): Intent {
return Intent(context, ImageMediaViewerActivity::class.java).apply {
putExtra(EXTRA_MEDIA_DATA, mediaData)
putExtra(EXTRA_SHARED_TRANSITION_NAME, shareTransitionName)
}
}
}

View File

@ -56,6 +56,10 @@ class PushRuleTriggerListener @Inject constructor(
}
}
override fun onRoomLeft(roomId: String) {
notificationDrawerManager.clearMessageEventOfRoom(roomId)
}
override fun batchFinish() {
notificationDrawerManager.refreshNotificationDrawer()
}

View File

@ -17,7 +17,6 @@ package im.vector.riotx.features.settings
import android.content.Context
import android.content.Intent
import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentManager
import androidx.preference.Preference
import androidx.preference.PreferenceFragmentCompat
@ -75,32 +74,30 @@ class VectorSettingsActivity : VectorBaseActivity(),
}
}
override fun onPreferenceStartFragment(caller: PreferenceFragmentCompat?, pref: Preference?): Boolean {
var oFragment: Fragment? = null
if (VectorPreferences.SETTINGS_NOTIFICATION_TROUBLESHOOT_PREFERENCE_KEY == pref?.key) {
oFragment = VectorSettingsNotificationsTroubleshootFragment.newInstance(session.myUserId)
} else if (VectorPreferences.SETTINGS_NOTIFICATION_ADVANCED_PREFERENCE_KEY == pref?.key) {
oFragment = VectorSettingsAdvancedNotificationPreferenceFragment.newInstance(session.myUserId)
} else {
override fun onPreferenceStartFragment(caller: PreferenceFragmentCompat, pref: Preference): Boolean {
val oFragment = when {
VectorPreferences.SETTINGS_NOTIFICATION_TROUBLESHOOT_PREFERENCE_KEY == pref.key ->
VectorSettingsNotificationsTroubleshootFragment.newInstance(session.myUserId)
VectorPreferences.SETTINGS_NOTIFICATION_ADVANCED_PREFERENCE_KEY == pref.key ->
VectorSettingsAdvancedNotificationPreferenceFragment.newInstance(session.myUserId)
else ->
try {
pref?.fragment?.let {
oFragment = supportFragmentManager.fragmentFactory
.instantiate(classLoader, it)
pref.fragment?.let {
supportFragmentManager.fragmentFactory.instantiate(classLoader, it)
}
} catch (e: Throwable) {
showSnackbar(getString(R.string.not_implemented))
Timber.e(e)
null
}
}
if (oFragment != null) {
oFragment!!.setTargetFragment(caller, 0)
oFragment.setTargetFragment(caller, 0)
// Replace the existing Fragment with the new Fragment
supportFragmentManager.beginTransaction()
.setCustomAnimations(R.anim.right_in, R.anim.fade_out,
R.anim.fade_in, R.anim.right_out)
.replace(R.id.vector_settings_page, oFragment!!, pref?.title.toString())
.setCustomAnimations(R.anim.right_in, R.anim.fade_out, R.anim.fade_in, R.anim.right_out)
.replace(R.id.vector_settings_page, oFragment, pref.title.toString())
.addToBackStack(null)
.commit()
return true

View File

@ -12,6 +12,21 @@
android:layout_height="?attr/actionBarSize"
android:elevation="4dp" />
<FrameLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<ImageView
android:id="@+id/imageTransitionView"
android:transitionName="imagePreview"
android:scaleType="fitCenter"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:visibility="visible"
android:visibility="gone"
/>
<com.github.piasy.biv.view.BigImageView
android:id="@+id/imageMediaViewerImageView"
android:layout_width="match_parent"
@ -26,4 +41,6 @@
android:visibility="gone"
tools:visibility="visible" />
</FrameLayout>
</LinearLayout>

View File

@ -9,8 +9,10 @@
android:layout_height="match_parent"
android:orientation="vertical">
<!-- Use VectorToolbarStyleWithPadding on this screen for better alignment with setting items -->
<androidx.appcompat.widget.Toolbar
android:id="@+id/settingsToolbar"
style="@style/VectorToolbarStyleWithPadding"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:elevation="4dp" />
@ -19,7 +21,7 @@
android:id="@+id/vector_settings_page"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="?android:attr/colorBackground" />
android:background="?riotx_background" />
</LinearLayout>

View File

@ -0,0 +1,15 @@
<?xml version="1.0" encoding="utf-8"?><!--
The transitions which us used for the entrance and exit of shared elements. Here we declare
two different transitions which are targeting specific views.
-->
<transitionSet xmlns:android="http://schemas.android.com/apk/res/android" android:duration="200">
<!-- changeBounds is used for the TextViews which are shared -->
<changeBounds/>
<!-- changeImageTransform is used for the ImageViews which are shared -->
<changeImageTransform />
</transitionSet>

View File

@ -7,6 +7,11 @@
<!-- enable window content transitions -->
<item name="android:windowContentTransitions">true</item>
<!-- specify shared element enter and exit transitions -->
<item name="android:windowSharedElementEnterTransition">@transition/image_preview_transition</item>
<item name="android:windowSharedElementExitTransition">@transition/image_preview_transition</item>
</style>
<style name="AppTheme.Black" parent="AppTheme.Black.v21" />

View File

@ -7,6 +7,11 @@
<!-- enable window content transitions -->
<item name="android:windowContentTransitions">true</item>
<!-- specify shared element enter and exit transitions -->
<item name="android:windowSharedElementEnterTransition">@transition/image_preview_transition</item>
<item name="android:windowSharedElementExitTransition">@transition/image_preview_transition</item>
</style>
<style name="AppTheme.Dark" parent="AppTheme.Dark.v21" />

View File

@ -7,6 +7,10 @@
<!-- enable window content transitions -->
<item name="android:windowContentTransitions">true</item>
<!-- specify shared element enter and exit transitions -->
<item name="android:windowSharedElementEnterTransition">@transition/image_preview_transition</item>
<item name="android:windowSharedElementExitTransition">@transition/image_preview_transition</item>
</style>
<style name="AppTheme.Light" parent="AppTheme.Light.v21"/>

View File

@ -4,11 +4,14 @@
<!-- ************************ Common items ************************ -->
<!-- toolbar styles-->
<style name="VectorToolbarStyle" parent="Widget.MaterialComponents.Toolbar">
<style name="VectorToolbarStyleWithPadding" parent="Widget.MaterialComponents.Toolbar">
<!-- main text -->
<item name="titleTextAppearance">@style/Vector.Toolbar.Title</item>
<item name="subtitleTextAppearance">@style/Vector.Toolbar.SubTitle</item>
<item name="android:background">?riotx_background</item>
</style>
<style name="VectorToolbarStyle" parent="VectorToolbarStyleWithPadding">
<item name="contentInsetStartWithNavigation">0dp</item>
</style>