diff --git a/CHANGES.md b/CHANGES.md index 909b8512..f96588e5 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -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: - diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/pushrules/PushRuleService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/pushrules/PushRuleService.kt index b00450b5..1fc37cd4 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/pushrules/PushRuleService.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/pushrules/PushRuleService.kt @@ -41,6 +41,7 @@ interface PushRuleService { interface PushRuleListener { fun onMatchRule(event: Event, actions: List) + fun onRoomLeft(roomId: String) fun batchFinish() } } \ No newline at end of file diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/notification/DefaultPushRuleService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/notification/DefaultPushRuleService.kt index 5b21f0e5..7397f0ee 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/notification/DefaultPushRuleService.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/notification/DefaultPushRuleService.kt @@ -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") } } } \ No newline at end of file diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/notification/ProcessEventForPushTask.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/notification/ProcessEventForPushTask.kt index fc6c8838..d196e563 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/notification/ProcessEventForPushTask.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/notification/ProcessEventForPushTask.kt @@ -44,6 +44,10 @@ internal class DefaultProcessEventForPushTask @Inject constructor( override suspend fun execute(params: ProcessEventForPushTask.Params): Try { 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) } 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 b7491ae6..cabb4795 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 @@ -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,9 +538,14 @@ class RoomDetailFragment : private fun renderRoomSummary(state: RoomDetailViewState) { state.asyncRoomSummary()?.let { - roomToolbarTitleView.text = it.displayName - avatarRenderer.render(it, roomToolbarAvatarImageView) - roomToolbarSubtitleView.setTextOrHide(it.topic) + 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) + } } } @@ -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) { diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/item/MessageImageVideoItem.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/item/MessageImageVideoItem.kt index d551e44c..6a68557f 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/item/MessageImageVideoItem.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/item/MessageImageVideoItem.kt @@ -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 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 { + 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 + } + + }) + .fitCenter() + .into(imageView) + } fun render(data: Data, imageView: BigImageView) { 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 bd3f4480..a44672a5 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 @@ -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,11 +57,31 @@ 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(EXTRA_MEDIA_DATA) + mediaData = intent.getParcelableExtra(EXTRA_MEDIA_DATA) + intent.extras.getString(EXTRA_SHARED_TRANSITION_NAME)?.let { + ViewCompat.setTransitionName(imageTransitionView, it) + } if (mediaData.url.isNullOrEmpty()) { finish() + 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 { - configureToolbar(imageMediaViewerToolbar, mediaData) + imageTransitionView.isVisible = false if (mediaData.elementToDecrypt != null) { // Encrypted image @@ -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 { + override fun onLoadFailed(e: GlideException?, + model: Any?, + target: Target?, + 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?, + 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) } } } diff --git a/vector/src/main/java/im/vector/riotx/features/notifications/PushRuleTriggerListener.kt b/vector/src/main/java/im/vector/riotx/features/notifications/PushRuleTriggerListener.kt index 760128de..d404b64c 100644 --- a/vector/src/main/java/im/vector/riotx/features/notifications/PushRuleTriggerListener.kt +++ b/vector/src/main/java/im/vector/riotx/features/notifications/PushRuleTriggerListener.kt @@ -56,6 +56,10 @@ class PushRuleTriggerListener @Inject constructor( } } + override fun onRoomLeft(roomId: String) { + notificationDrawerManager.clearMessageEventOfRoom(roomId) + } + override fun batchFinish() { notificationDrawerManager.refreshNotificationDrawer() } diff --git a/vector/src/main/java/im/vector/riotx/features/settings/VectorSettingsActivity.kt b/vector/src/main/java/im/vector/riotx/features/settings/VectorSettingsActivity.kt index 5db218c3..fcf7efa4 100755 --- a/vector/src/main/java/im/vector/riotx/features/settings/VectorSettingsActivity.kt +++ b/vector/src/main/java/im/vector/riotx/features/settings/VectorSettingsActivity.kt @@ -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 { - try { - pref?.fragment?.let { - oFragment = supportFragmentManager.fragmentFactory - .instantiate(classLoader, it) + 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 { + supportFragmentManager.fragmentFactory.instantiate(classLoader, it) + } + } catch (e: Throwable) { + showSnackbar(getString(R.string.not_implemented)) + Timber.e(e) + null } - } catch (e: Throwable) { - showSnackbar(getString(R.string.not_implemented)) - Timber.e(e) - } } 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 diff --git a/vector/src/main/res/layout/activity_image_media_viewer.xml b/vector/src/main/res/layout/activity_image_media_viewer.xml index 61d5d286..f1d55749 100644 --- a/vector/src/main/res/layout/activity_image_media_viewer.xml +++ b/vector/src/main/res/layout/activity_image_media_viewer.xml @@ -12,18 +12,35 @@ android:layout_height="?attr/actionBarSize" android:elevation="4dp" /> - - + android:layout_height="match_parent"> + + + + + + + + \ No newline at end of file diff --git a/vector/src/main/res/layout/activity_vector_settings.xml b/vector/src/main/res/layout/activity_vector_settings.xml index 55716157..eaba9b33 100755 --- a/vector/src/main/res/layout/activity_vector_settings.xml +++ b/vector/src/main/res/layout/activity_vector_settings.xml @@ -9,8 +9,10 @@ android:layout_height="match_parent" android:orientation="vertical"> + @@ -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" /> diff --git a/vector/src/main/res/transition/image_preview_transition.xml b/vector/src/main/res/transition/image_preview_transition.xml new file mode 100644 index 00000000..3674324c --- /dev/null +++ b/vector/src/main/res/transition/image_preview_transition.xml @@ -0,0 +1,15 @@ + + + + + + + + + + + + diff --git a/vector/src/main/res/values-v21/theme_black.xml b/vector/src/main/res/values-v21/theme_black.xml index 5ab2ed89..74ec2cd9 100644 --- a/vector/src/main/res/values-v21/theme_black.xml +++ b/vector/src/main/res/values-v21/theme_black.xml @@ -7,6 +7,11 @@ true + + + @transition/image_preview_transition + @transition/image_preview_transition + + +