From 2d60a88a750524a72b51a28eb1c3583bbd42e6c7 Mon Sep 17 00:00:00 2001 From: Alex Hart Date: Thu, 14 Apr 2022 15:11:05 -0300 Subject: [PATCH] Fix crossfade target aspect ratio. --- .../ConversationParentFragment.java | 2 +- .../bottomsheet/RecipientDialogViewModel.java | 4 +- .../stories/landing/LandingPayload.kt | 12 ---- .../stories/landing/MyStoriesItem.kt | 4 -- .../stories/landing/StoriesLandingFragment.kt | 26 +++++--- .../stories/landing/StoriesLandingItem.kt | 7 +-- .../landing/StoriesLandingViewModel.kt | 1 + .../securesms/stories/my/MyStoriesFragment.kt | 3 +- .../stories/viewer/StoryViewerActivity.kt | 9 ++- .../stories/viewer/StoryViewerFragment.kt | 11 +++- .../stories/viewer/StoryViewerState.kt | 3 +- .../stories/viewer/StoryViewerViewModel.kt | 7 ++- .../viewer/page/StoryViewerPageFragment.kt | 2 +- .../StoriesSharedElementCrossFaderView.kt | 62 +++++++++++++++++-- .../res/layout/stories_landing_fragment.xml | 2 +- .../stories_shared_element_crossfader.xml | 16 +++++ 16 files changed, 124 insertions(+), 47 deletions(-) delete mode 100644 app/src/main/java/org/thoughtcrime/securesms/stories/landing/LandingPayload.kt diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationParentFragment.java b/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationParentFragment.java index 42b776fb05..7872f9264b 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationParentFragment.java +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationParentFragment.java @@ -1244,7 +1244,7 @@ public class ConversationParentFragment extends Fragment } private void handleStoryRingClick() { - startActivity(StoryViewerActivity.createIntent(requireContext(), recipient.getId(), -1L, recipient.get().shouldHideStory(), null, null)); + startActivity(StoryViewerActivity.createIntent(requireContext(), recipient.getId(), -1L, recipient.get().shouldHideStory(), null, null, null)); } private void handleConversationSettings() { diff --git a/app/src/main/java/org/thoughtcrime/securesms/recipients/ui/bottomsheet/RecipientDialogViewModel.java b/app/src/main/java/org/thoughtcrime/securesms/recipients/ui/bottomsheet/RecipientDialogViewModel.java index 079156a455..d13a1cfe6c 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/recipients/ui/bottomsheet/RecipientDialogViewModel.java +++ b/app/src/main/java/org/thoughtcrime/securesms/recipients/ui/bottomsheet/RecipientDialogViewModel.java @@ -140,7 +140,7 @@ final class RecipientDialogViewModel extends ViewModel { if (storyViewState.getValue() == null || storyViewState.getValue() == StoryViewState.NONE) { onMessageClicked(activity); } else { - activity.startActivity(StoryViewerActivity.createIntent(activity, recipientDialogRepository.getRecipientId(), -1L, recipient.getValue().shouldHideStory(), null, null)); + activity.startActivity(StoryViewerActivity.createIntent(activity, recipientDialogRepository.getRecipientId(), -1L, recipient.getValue().shouldHideStory(), null, null, null)); } } @@ -176,7 +176,7 @@ final class RecipientDialogViewModel extends ViewModel { if (storyViewState.getValue() == null || storyViewState.getValue() == StoryViewState.NONE) { activity.startActivity(ConversationSettingsActivity.forRecipient(activity, recipientDialogRepository.getRecipientId())); } else { - activity.startActivity(StoryViewerActivity.createIntent(activity, recipientDialogRepository.getRecipientId(), -1L, recipient.getValue().shouldHideStory(), null, null)); + activity.startActivity(StoryViewerActivity.createIntent(activity, recipientDialogRepository.getRecipientId(), -1L, recipient.getValue().shouldHideStory(), null, null, null)); } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/stories/landing/LandingPayload.kt b/app/src/main/java/org/thoughtcrime/securesms/stories/landing/LandingPayload.kt deleted file mode 100644 index 1c6c0810a3..0000000000 --- a/app/src/main/java/org/thoughtcrime/securesms/stories/landing/LandingPayload.kt +++ /dev/null @@ -1,12 +0,0 @@ -package org.thoughtcrime.securesms.stories.landing - -/** - * Global Landing page payloads. Currently the only "pulse" we send out - * to ViewHolders is RESUMED. - */ -enum class LandingPayload { - /** - * Notifies view holders when the fragment is resumed. - */ - RESUMED -} diff --git a/app/src/main/java/org/thoughtcrime/securesms/stories/landing/MyStoriesItem.kt b/app/src/main/java/org/thoughtcrime/securesms/stories/landing/MyStoriesItem.kt index 09091dd904..4cc1b738ba 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/stories/landing/MyStoriesItem.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/stories/landing/MyStoriesItem.kt @@ -33,10 +33,6 @@ object MyStoriesItem { private val badgeView: BadgeImageView = itemView.findViewById(R.id.badge) override fun bind(model: Model) { - if (payload.contains(LandingPayload.RESUMED)) { - return - } - itemView.setOnClickListener { model.onClick() } thumbnail.setOnClickListener { model.onClickThumbnail() } diff --git a/app/src/main/java/org/thoughtcrime/securesms/stories/landing/StoriesLandingFragment.kt b/app/src/main/java/org/thoughtcrime/securesms/stories/landing/StoriesLandingFragment.kt index 4f76966ff5..53ab7b1ccc 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/stories/landing/StoriesLandingFragment.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/stories/landing/StoriesLandingFragment.kt @@ -80,7 +80,7 @@ class StoriesLandingFragment : DSLSettingsFragment(layoutId = R.layout.stories_l override fun onResume() { super.onResume() - adapter.notifyItemRangeChanged(0, adapter.itemCount, LandingPayload.RESUMED) + viewModel.isTransitioningToAnotherScreen = false } override fun bindAdapter(adapter: DSLSettingsAdapter) { @@ -114,7 +114,9 @@ class StoriesLandingFragment : DSLSettingsFragment(layoutId = R.layout.stories_l .ifNecessary() .withRationaleDialog(getString(R.string.ConversationActivity_to_capture_photos_and_video_allow_signal_access_to_the_camera), R.drawable.ic_camera_24) .withPermanentDenialDialog(getString(R.string.ConversationActivity_signal_needs_the_camera_permission_to_take_photos_or_video)) - .onAllGranted { startActivity(MediaSelectionActivity.camera(requireContext(), isStory = true)) } + .onAllGranted { + startActivityIfAble(MediaSelectionActivity.camera(requireContext(), isStory = true)) + } .onAnyDenied { Toast.makeText(requireContext(), R.string.ConversationActivity_signal_needs_camera_permissions_to_take_photos_or_video, Toast.LENGTH_LONG).show() } .execute() } @@ -148,7 +150,7 @@ class StoriesLandingFragment : DSLSettingsFragment(layoutId = R.layout.stories_l customPref( MyStoriesItem.Model( onClick = { - startActivity(Intent(requireContext(), MyStoriesActivity::class.java)) + startActivityIfAble(Intent(requireContext(), MyStoriesActivity::class.java)) }, onClickThumbnail = { cameraFab.performClick() @@ -184,7 +186,7 @@ class StoriesLandingFragment : DSLSettingsFragment(layoutId = R.layout.stories_l data = data, onRowClick = { model, preview -> if (model.data.storyRecipient.isMyStory) { - startActivity(Intent(requireContext(), MyStoriesActivity::class.java)) + startActivityIfAble(Intent(requireContext(), MyStoriesActivity::class.java)) } else if (model.data.primaryStory.messageRecord.isOutgoing && model.data.primaryStory.messageRecord.isFailed) { if (model.data.primaryStory.messageRecord.isIdentityMismatchFailure) { SafetyNumberChangeDialog.show(requireContext(), childFragmentManager, model.data.primaryStory.messageRecord) @@ -197,13 +199,14 @@ class StoriesLandingFragment : DSLSettingsFragment(layoutId = R.layout.stories_l val options = ActivityOptionsCompat.makeSceneTransitionAnimation(requireActivity(), preview, ViewCompat.getTransitionName(preview) ?: "") val record = model.data.primaryStory.messageRecord as MmsMessageRecord + val blur = record.slideDeck.thumbnailSlide?.placeholderBlur val (text: StoryTextPostModel?, image: Uri?) = if (record.storyType.isTextStory) { StoryTextPostModel.parseFrom(record) to null } else { null to record.slideDeck.thumbnailSlide?.uri } - startActivity(StoryViewerActivity.createIntent(requireContext(), model.data.storyRecipient.id, -1L, model.data.isHidden, text, image), options.toBundle()) + startActivityIfAble(StoryViewerActivity.createIntent(requireContext(), model.data.storyRecipient.id, -1L, model.data.isHidden, text, image, blur), options.toBundle()) } }, onForwardStory = { @@ -212,7 +215,7 @@ class StoriesLandingFragment : DSLSettingsFragment(layoutId = R.layout.stories_l } }, onGoToChat = { - startActivity(ConversationIntents.createBuilder(requireContext(), it.data.storyRecipient.id, -1L).build()) + startActivityIfAble(ConversationIntents.createBuilder(requireContext(), it.data.storyRecipient.id, -1L).build()) }, onHideStory = { if (!it.data.isHidden) { @@ -256,7 +259,7 @@ class StoriesLandingFragment : DSLSettingsFragment(layoutId = R.layout.stories_l override fun onOptionsItemSelected(item: MenuItem): Boolean { return if (item.itemId == R.id.action_settings) { - startActivity(StorySettingsActivity.getIntent(requireContext())) + startActivityIfAble(StorySettingsActivity.getIntent(requireContext())) true } else { false @@ -266,4 +269,13 @@ class StoriesLandingFragment : DSLSettingsFragment(layoutId = R.layout.stories_l override fun onRequestPermissionsResult(requestCode: Int, permissions: Array, grantResults: IntArray) { Permissions.onRequestPermissionsResult(this, requestCode, permissions, grantResults) } + + private fun startActivityIfAble(intent: Intent, options: Bundle? = null) { + if (viewModel.isTransitioningToAnotherScreen) { + return + } + + viewModel.isTransitioningToAnotherScreen = true + startActivity(intent, options) + } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/stories/landing/StoriesLandingItem.kt b/app/src/main/java/org/thoughtcrime/securesms/stories/landing/StoriesLandingItem.kt index 89dd0bff66..765c597c38 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/stories/landing/StoriesLandingItem.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/stories/landing/StoriesLandingItem.kt @@ -107,7 +107,7 @@ object StoriesLandingItem { presentDateOrStatus(model) setUpClickListeners(model) - if (payload.contains(STATUS_CHANGE) || payload.contains(LandingPayload.RESUMED)) { + if (payload.contains(STATUS_CHANGE)) { return } @@ -223,11 +223,6 @@ object StoriesLandingItem { private fun setUpClickListeners(model: Model) { itemView.setOnClickListener { - if (!itemView.isClickable) { - return@setOnClickListener - } - - itemView.isClickable = false model.onRowClick(model, storyPreview) } diff --git a/app/src/main/java/org/thoughtcrime/securesms/stories/landing/StoriesLandingViewModel.kt b/app/src/main/java/org/thoughtcrime/securesms/stories/landing/StoriesLandingViewModel.kt index 302d9b31f1..fab28b69e0 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/stories/landing/StoriesLandingViewModel.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/stories/landing/StoriesLandingViewModel.kt @@ -15,6 +15,7 @@ class StoriesLandingViewModel(private val storiesLandingRepository: StoriesLandi private val disposables = CompositeDisposable() val state: LiveData = store.stateLiveData + var isTransitioningToAnotherScreen: Boolean = false init { disposables += storiesLandingRepository.getStories().subscribe { stories -> diff --git a/app/src/main/java/org/thoughtcrime/securesms/stories/my/MyStoriesFragment.kt b/app/src/main/java/org/thoughtcrime/securesms/stories/my/MyStoriesFragment.kt index 6d345e16de..6f13e248bc 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/stories/my/MyStoriesFragment.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/stories/my/MyStoriesFragment.kt @@ -93,6 +93,7 @@ class MyStoriesFragment : DSLSettingsFragment( } val record = it.distributionStory.messageRecord as MmsMessageRecord + val blur = record.slideDeck.thumbnailSlide?.placeholderBlur val (text: StoryTextPostModel?, image: Uri?) = if (record.storyType.isTextStory) { StoryTextPostModel.parseFrom(record) to null } else { @@ -100,7 +101,7 @@ class MyStoriesFragment : DSLSettingsFragment( } val options = ActivityOptionsCompat.makeSceneTransitionAnimation(requireActivity(), preview, ViewCompat.getTransitionName(preview) ?: "") - startActivity(StoryViewerActivity.createIntent(requireContext(), recipient.id, conversationMessage.messageRecord.id, recipient.shouldHideStory(), text, image), options.toBundle()) + startActivity(StoryViewerActivity.createIntent(requireContext(), recipient.id, conversationMessage.messageRecord.id, recipient.shouldHideStory(), text, image, blur), options.toBundle()) } }, onLongClick = { diff --git a/app/src/main/java/org/thoughtcrime/securesms/stories/viewer/StoryViewerActivity.kt b/app/src/main/java/org/thoughtcrime/securesms/stories/viewer/StoryViewerActivity.kt index 8034c88d18..1aa5d5949c 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/stories/viewer/StoryViewerActivity.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/stories/viewer/StoryViewerActivity.kt @@ -8,6 +8,7 @@ import android.os.Bundle import androidx.appcompat.app.AppCompatDelegate import org.thoughtcrime.securesms.PassphraseRequiredActivity import org.thoughtcrime.securesms.R +import org.thoughtcrime.securesms.blurhash.BlurHash import org.thoughtcrime.securesms.components.voice.VoiceNoteMediaController import org.thoughtcrime.securesms.components.voice.VoiceNoteMediaControllerOwner import org.thoughtcrime.securesms.recipients.RecipientId @@ -39,7 +40,8 @@ class StoryViewerActivity : PassphraseRequiredActivity(), VoiceNoteMediaControll intent.getLongExtra(ARG_START_STORY_ID, -1L), intent.getBooleanExtra(ARG_HIDDEN_STORIES, false), intent.getParcelableExtra(ARG_CROSSFADE_TEXT_MODEL), - intent.getParcelableExtra(ARG_CROSSFADE_IMAGE_URI) + intent.getParcelableExtra(ARG_CROSSFADE_IMAGE_URI), + intent.getStringExtra(ARG_CROSSFADE_IMAGE_BLUR) ) ) .commit() @@ -58,6 +60,7 @@ class StoryViewerActivity : PassphraseRequiredActivity(), VoiceNoteMediaControll private const val ARG_HIDDEN_STORIES = "hidden_stories" private const val ARG_CROSSFADE_TEXT_MODEL = "crossfade.text.model" private const val ARG_CROSSFADE_IMAGE_URI = "crossfade.image.uri" + private const val ARG_CROSSFADE_IMAGE_BLUR = "crossfade.image.blur" @JvmStatic fun createIntent( @@ -66,7 +69,8 @@ class StoryViewerActivity : PassphraseRequiredActivity(), VoiceNoteMediaControll storyId: Long = -1L, onlyIncludeHiddenStories: Boolean = false, storyThumbTextModel: StoryTextPostModel? = null, - storyThumbUri: Uri? = null + storyThumbUri: Uri? = null, + storyThumbBlur: BlurHash? = null ): Intent { return Intent(context, StoryViewerActivity::class.java) .putExtra(ARG_START_RECIPIENT_ID, recipientId) @@ -74,6 +78,7 @@ class StoryViewerActivity : PassphraseRequiredActivity(), VoiceNoteMediaControll .putExtra(ARG_HIDDEN_STORIES, onlyIncludeHiddenStories) .putExtra(ARG_CROSSFADE_TEXT_MODEL, storyThumbTextModel) .putExtra(ARG_CROSSFADE_IMAGE_URI, storyThumbUri) + .putExtra(ARG_CROSSFADE_IMAGE_BLUR, storyThumbBlur?.hash) } } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/stories/viewer/StoryViewerFragment.kt b/app/src/main/java/org/thoughtcrime/securesms/stories/viewer/StoryViewerFragment.kt index d5fbecd9a2..8b8f769059 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/stories/viewer/StoryViewerFragment.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/stories/viewer/StoryViewerFragment.kt @@ -8,6 +8,7 @@ import androidx.fragment.app.viewModels import androidx.lifecycle.LiveDataReactiveStreams import androidx.viewpager2.widget.ViewPager2 import org.thoughtcrime.securesms.R +import org.thoughtcrime.securesms.blurhash.BlurHash import org.thoughtcrime.securesms.recipients.RecipientId import org.thoughtcrime.securesms.stories.StoryTextPostModel import org.thoughtcrime.securesms.stories.viewer.page.StoryViewerPageFragment @@ -23,7 +24,7 @@ class StoryViewerFragment : Fragment(R.layout.stories_viewer_fragment), StoryVie private val viewModel: StoryViewerViewModel by viewModels( factoryProducer = { - StoryViewerViewModel.Factory(storyRecipientId, onlyIncludeHiddenStories, storyThumbTextModel, storyThumbUri, StoryViewerRepository()) + StoryViewerViewModel.Factory(storyRecipientId, onlyIncludeHiddenStories, storyThumbTextModel, storyThumbUri, storuThumbBlur, StoryViewerRepository()) } ) @@ -42,6 +43,9 @@ class StoryViewerFragment : Fragment(R.layout.stories_viewer_fragment), StoryVie private val storyThumbUri: Uri? get() = requireArguments().getParcelable(ARG_CROSSFADE_IMAGE_URI) + private val storuThumbBlur: BlurHash? + get() = requireArguments().getString(ARG_CROSSFADE_IMAGE_BLUR)?.let { BlurHash.parseOrNull(it) } + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { storyPager = view.findViewById(R.id.story_item_pager) @@ -108,13 +112,15 @@ class StoryViewerFragment : Fragment(R.layout.stories_viewer_fragment), StoryVie private const val ARG_HIDDEN_STORIES = "hidden_stories" private const val ARG_CROSSFADE_TEXT_MODEL = "crossfade.text.model" private const val ARG_CROSSFADE_IMAGE_URI = "crossfade.image.uri" + private const val ARG_CROSSFADE_IMAGE_BLUR = "crossfade.image.blur" fun create( storyRecipientId: RecipientId, storyId: Long, onlyIncludeHiddenStories: Boolean, storyThumbTextModel: StoryTextPostModel? = null, - storyThumbUri: Uri? = null + storyThumbUri: Uri? = null, + storyThumbBlur: String? = null ): Fragment { return StoryViewerFragment().apply { arguments = Bundle().apply { @@ -123,6 +129,7 @@ class StoryViewerFragment : Fragment(R.layout.stories_viewer_fragment), StoryVie putBoolean(ARG_HIDDEN_STORIES, onlyIncludeHiddenStories) putParcelable(ARG_CROSSFADE_TEXT_MODEL, storyThumbTextModel) putParcelable(ARG_CROSSFADE_IMAGE_URI, storyThumbUri) + putString(ARG_CROSSFADE_IMAGE_BLUR, storyThumbBlur) } } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/stories/viewer/StoryViewerState.kt b/app/src/main/java/org/thoughtcrime/securesms/stories/viewer/StoryViewerState.kt index e19a8c1dd8..bc732945de 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/stories/viewer/StoryViewerState.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/stories/viewer/StoryViewerState.kt @@ -1,6 +1,7 @@ package org.thoughtcrime.securesms.stories.viewer import android.net.Uri +import org.thoughtcrime.securesms.blurhash.BlurHash import org.thoughtcrime.securesms.recipients.RecipientId import org.thoughtcrime.securesms.stories.StoryTextPostModel @@ -13,7 +14,7 @@ data class StoryViewerState( ) { sealed class CrossfadeSource { object None : CrossfadeSource() - class ImageUri(val imageUri: Uri) : CrossfadeSource() + class ImageUri(val imageUri: Uri, val imageBlur: BlurHash?) : CrossfadeSource() class TextModel(val storyTextPostModel: StoryTextPostModel) : CrossfadeSource() } diff --git a/app/src/main/java/org/thoughtcrime/securesms/stories/viewer/StoryViewerViewModel.kt b/app/src/main/java/org/thoughtcrime/securesms/stories/viewer/StoryViewerViewModel.kt index a3e6044ab5..e22dc87e73 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/stories/viewer/StoryViewerViewModel.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/stories/viewer/StoryViewerViewModel.kt @@ -8,6 +8,7 @@ import androidx.lifecycle.ViewModelProvider import io.reactivex.rxjava3.core.Flowable import io.reactivex.rxjava3.disposables.CompositeDisposable import io.reactivex.rxjava3.kotlin.plusAssign +import org.thoughtcrime.securesms.blurhash.BlurHash import org.thoughtcrime.securesms.recipients.RecipientId import org.thoughtcrime.securesms.stories.StoryTextPostModel import org.thoughtcrime.securesms.util.rx.RxStore @@ -18,6 +19,7 @@ class StoryViewerViewModel( private val onlyIncludeHiddenStories: Boolean, storyThumbTextModel: StoryTextPostModel?, storyThumbUri: Uri?, + storyThumbBlur: BlurHash?, private val repository: StoryViewerRepository, ) : ViewModel() { @@ -25,7 +27,7 @@ class StoryViewerViewModel( StoryViewerState( crossfadeSource = when { storyThumbTextModel != null -> StoryViewerState.CrossfadeSource.TextModel(storyThumbTextModel) - storyThumbUri != null -> StoryViewerState.CrossfadeSource.ImageUri(storyThumbUri) + storyThumbUri != null -> StoryViewerState.CrossfadeSource.ImageUri(storyThumbUri, storyThumbBlur) else -> StoryViewerState.CrossfadeSource.None } ) @@ -154,10 +156,11 @@ class StoryViewerViewModel( private val onlyIncludeHiddenStories: Boolean, private val storyThumbTextModel: StoryTextPostModel?, private val storyThumbUri: Uri?, + private val storyThumbBlur: BlurHash?, private val repository: StoryViewerRepository ) : ViewModelProvider.Factory { override fun create(modelClass: Class): T { - return modelClass.cast(StoryViewerViewModel(startRecipientId, onlyIncludeHiddenStories, storyThumbTextModel, storyThumbUri, repository)) as T + return modelClass.cast(StoryViewerViewModel(startRecipientId, onlyIncludeHiddenStories, storyThumbTextModel, storyThumbUri, storyThumbBlur, repository)) as T } } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/stories/viewer/page/StoryViewerPageFragment.kt b/app/src/main/java/org/thoughtcrime/securesms/stories/viewer/page/StoryViewerPageFragment.kt index 137837fd7a..e7f95bb16d 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/stories/viewer/page/StoryViewerPageFragment.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/stories/viewer/page/StoryViewerPageFragment.kt @@ -260,7 +260,7 @@ class StoryViewerPageFragment : viewModel.setIsSelectedPage(true) when (parentState.crossfadeSource) { is StoryViewerState.CrossfadeSource.TextModel -> storyCrossfader.setSourceView(parentState.crossfadeSource.storyTextPostModel) - is StoryViewerState.CrossfadeSource.ImageUri -> storyCrossfader.setSourceView(parentState.crossfadeSource.imageUri) + is StoryViewerState.CrossfadeSource.ImageUri -> storyCrossfader.setSourceView(parentState.crossfadeSource.imageUri, parentState.crossfadeSource.imageBlur) else -> onReadyToAnimate() } } else { diff --git a/app/src/main/java/org/thoughtcrime/securesms/stories/viewer/reply/StoriesSharedElementCrossFaderView.kt b/app/src/main/java/org/thoughtcrime/securesms/stories/viewer/reply/StoriesSharedElementCrossFaderView.kt index 80c9167503..bb1f31f9de 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/stories/viewer/reply/StoriesSharedElementCrossFaderView.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/stories/viewer/reply/StoriesSharedElementCrossFaderView.kt @@ -14,6 +14,7 @@ import com.bumptech.glide.request.target.Target import org.signal.core.util.DimensionUnit import org.thoughtcrime.securesms.R import org.thoughtcrime.securesms.animation.transitions.CrossfaderTransition +import org.thoughtcrime.securesms.blurhash.BlurHash import org.thoughtcrime.securesms.database.model.MmsMessageRecord import org.thoughtcrime.securesms.mms.DecryptableStreamUriLoader import org.thoughtcrime.securesms.mms.GlideApp @@ -35,10 +36,14 @@ class StoriesSharedElementCrossFaderView @JvmOverloads constructor( } private val sourceView: ImageView = findViewById(R.id.source_image) + private val sourceBlurView: ImageView = findViewById(R.id.source_image_blur) private val targetView: ImageView = findViewById(R.id.target_image) + private val targetBlurView: ImageView = findViewById(R.id.target_image_blur) private var isSourceReady: Boolean = false + private var isSourceBlurReady: Boolean = false private var isTargetReady: Boolean = false + private var isTargetBlurReady: Boolean = false private var latestSource: Any? = null private var latestTarget: Any? = null @@ -64,9 +69,12 @@ class StoriesSharedElementCrossFaderView @JvmOverloads constructor( .dontAnimate() .centerCrop() .into(sourceView) + + GlideApp.with(sourceBlurView).clear(sourceBlurView) + isSourceBlurReady = true } - fun setSourceView(uri: Uri) { + fun setSourceView(uri: Uri, blur: BlurHash?) { if (latestSource == uri) { return } @@ -84,13 +92,31 @@ class StoriesSharedElementCrossFaderView @JvmOverloads constructor( .dontAnimate() .centerCrop() .into(sourceView) + + if (blur == null) { + GlideApp.with(sourceBlurView).clear(sourceBlurView) + isSourceBlurReady = true + } else { + GlideApp.with(sourceBlurView) + .load(blur) + .addListener( + OnReadyListener { + isSourceBlurReady = true + notifyIfReady() + } + ) + .dontAnimate() + .centerCrop() + .into(sourceBlurView) + } } fun setTargetView(messageRecord: MmsMessageRecord): Boolean { val thumbUri = messageRecord.slideDeck.thumbnailSlide?.uri + val thumbBlur: BlurHash? = messageRecord.slideDeck.thumbnailSlide?.placeholderBlur when { messageRecord.storyType.isTextStory -> setTargetView(StoryTextPostModel.parseFrom(messageRecord)) - thumbUri != null -> setTargetView(thumbUri) + thumbUri != null -> setTargetView(thumbUri, thumbBlur) else -> return false } @@ -116,9 +142,12 @@ class StoriesSharedElementCrossFaderView @JvmOverloads constructor( .placeholder(storyTextPostModel.getPlaceholder()) .centerCrop() .into(targetView) + + GlideApp.with(sourceBlurView).clear(sourceBlurView) + isTargetBlurReady = true } - private fun setTargetView(uri: Uri) { + private fun setTargetView(uri: Uri, blur: BlurHash?) { if (latestTarget == uri) { return } @@ -134,12 +163,29 @@ class StoriesSharedElementCrossFaderView @JvmOverloads constructor( } ) .dontAnimate() - .centerCrop() + .fitCenter() .into(targetView) + + if (blur == null) { + GlideApp.with(targetBlurView).clear(targetBlurView) + isTargetBlurReady = true + } else { + GlideApp.with(targetBlurView) + .load(blur) + .addListener( + OnReadyListener { + isTargetBlurReady = true + notifyIfReady() + } + ) + .dontAnimate() + .centerCrop() + .into(targetBlurView) + } } private fun notifyIfReady() { - if (isSourceReady && isTargetReady) { + if (isSourceReady && isTargetReady && isSourceBlurReady && isTargetBlurReady) { callback?.onReadyToAnimate() } } @@ -159,11 +205,15 @@ class StoriesSharedElementCrossFaderView @JvmOverloads constructor( override fun onCrossfadeAnimationUpdated(progress: Float, reverse: Boolean) { if (reverse) { sourceView.alpha = progress + sourceBlurView.alpha = progress targetView.alpha = 1f - progress + targetBlurView.alpha = 1f - progress radius = CORNER_RADIUS_EVALUATOR.evaluate(progress, CORNER_RADIUS_END, CORNER_RADIUS_START) } else { sourceView.alpha = 1f - progress + sourceBlurView.alpha = 1f - progress targetView.alpha = progress + targetBlurView.alpha = progress radius = CORNER_RADIUS_EVALUATOR.evaluate(progress, CORNER_RADIUS_START, CORNER_RADIUS_END) } } @@ -172,7 +222,9 @@ class StoriesSharedElementCrossFaderView @JvmOverloads constructor( alpha = 1f sourceView.alpha = if (reverse) 0f else 1f + sourceBlurView.alpha = if (reverse) 0f else 1f targetView.alpha = if (reverse) 1f else 0f + targetBlurView.alpha = if (reverse) 1f else 0f radius = if (reverse) CORNER_RADIUS_END else CORNER_RADIUS_START diff --git a/app/src/main/res/layout/stories_landing_fragment.xml b/app/src/main/res/layout/stories_landing_fragment.xml index 6b07570351..d5f56d16e1 100644 --- a/app/src/main/res/layout/stories_landing_fragment.xml +++ b/app/src/main/res/layout/stories_landing_fragment.xml @@ -34,8 +34,8 @@ android:focusable="true" android:theme="@style/Widget.Material3.FloatingActionButton.Secondary" android:transitionName="camera_fab" - app:elevation="0dp" app:backgroundTint="@color/signal_colorSecondaryContainer" + app:elevation="0dp" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" app:srcCompat="@drawable/ic_camera_outline_24" diff --git a/app/src/main/res/layout/stories_shared_element_crossfader.xml b/app/src/main/res/layout/stories_shared_element_crossfader.xml index ca1ad50236..2d54f412b9 100644 --- a/app/src/main/res/layout/stories_shared_element_crossfader.xml +++ b/app/src/main/res/layout/stories_shared_element_crossfader.xml @@ -12,6 +12,15 @@ android:layout_width="match_parent" android:layout_height="match_parent"> + + + +