Fix crossfade target aspect ratio.
This commit is contained in:
parent
c3e7d6c74c
commit
2d60a88a75
16 changed files with 124 additions and 47 deletions
|
@ -1244,7 +1244,7 @@ public class ConversationParentFragment extends Fragment
|
||||||
}
|
}
|
||||||
|
|
||||||
private void handleStoryRingClick() {
|
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() {
|
private void handleConversationSettings() {
|
||||||
|
|
|
@ -140,7 +140,7 @@ final class RecipientDialogViewModel extends ViewModel {
|
||||||
if (storyViewState.getValue() == null || storyViewState.getValue() == StoryViewState.NONE) {
|
if (storyViewState.getValue() == null || storyViewState.getValue() == StoryViewState.NONE) {
|
||||||
onMessageClicked(activity);
|
onMessageClicked(activity);
|
||||||
} else {
|
} 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) {
|
if (storyViewState.getValue() == null || storyViewState.getValue() == StoryViewState.NONE) {
|
||||||
activity.startActivity(ConversationSettingsActivity.forRecipient(activity, recipientDialogRepository.getRecipientId()));
|
activity.startActivity(ConversationSettingsActivity.forRecipient(activity, recipientDialogRepository.getRecipientId()));
|
||||||
} else {
|
} 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));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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
|
|
||||||
}
|
|
|
@ -33,10 +33,6 @@ object MyStoriesItem {
|
||||||
private val badgeView: BadgeImageView = itemView.findViewById(R.id.badge)
|
private val badgeView: BadgeImageView = itemView.findViewById(R.id.badge)
|
||||||
|
|
||||||
override fun bind(model: Model) {
|
override fun bind(model: Model) {
|
||||||
if (payload.contains(LandingPayload.RESUMED)) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
itemView.setOnClickListener { model.onClick() }
|
itemView.setOnClickListener { model.onClick() }
|
||||||
thumbnail.setOnClickListener { model.onClickThumbnail() }
|
thumbnail.setOnClickListener { model.onClickThumbnail() }
|
||||||
|
|
||||||
|
|
|
@ -80,7 +80,7 @@ class StoriesLandingFragment : DSLSettingsFragment(layoutId = R.layout.stories_l
|
||||||
|
|
||||||
override fun onResume() {
|
override fun onResume() {
|
||||||
super.onResume()
|
super.onResume()
|
||||||
adapter.notifyItemRangeChanged(0, adapter.itemCount, LandingPayload.RESUMED)
|
viewModel.isTransitioningToAnotherScreen = false
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun bindAdapter(adapter: DSLSettingsAdapter) {
|
override fun bindAdapter(adapter: DSLSettingsAdapter) {
|
||||||
|
@ -114,7 +114,9 @@ class StoriesLandingFragment : DSLSettingsFragment(layoutId = R.layout.stories_l
|
||||||
.ifNecessary()
|
.ifNecessary()
|
||||||
.withRationaleDialog(getString(R.string.ConversationActivity_to_capture_photos_and_video_allow_signal_access_to_the_camera), R.drawable.ic_camera_24)
|
.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))
|
.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() }
|
.onAnyDenied { Toast.makeText(requireContext(), R.string.ConversationActivity_signal_needs_camera_permissions_to_take_photos_or_video, Toast.LENGTH_LONG).show() }
|
||||||
.execute()
|
.execute()
|
||||||
}
|
}
|
||||||
|
@ -148,7 +150,7 @@ class StoriesLandingFragment : DSLSettingsFragment(layoutId = R.layout.stories_l
|
||||||
customPref(
|
customPref(
|
||||||
MyStoriesItem.Model(
|
MyStoriesItem.Model(
|
||||||
onClick = {
|
onClick = {
|
||||||
startActivity(Intent(requireContext(), MyStoriesActivity::class.java))
|
startActivityIfAble(Intent(requireContext(), MyStoriesActivity::class.java))
|
||||||
},
|
},
|
||||||
onClickThumbnail = {
|
onClickThumbnail = {
|
||||||
cameraFab.performClick()
|
cameraFab.performClick()
|
||||||
|
@ -184,7 +186,7 @@ class StoriesLandingFragment : DSLSettingsFragment(layoutId = R.layout.stories_l
|
||||||
data = data,
|
data = data,
|
||||||
onRowClick = { model, preview ->
|
onRowClick = { model, preview ->
|
||||||
if (model.data.storyRecipient.isMyStory) {
|
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) {
|
} else if (model.data.primaryStory.messageRecord.isOutgoing && model.data.primaryStory.messageRecord.isFailed) {
|
||||||
if (model.data.primaryStory.messageRecord.isIdentityMismatchFailure) {
|
if (model.data.primaryStory.messageRecord.isIdentityMismatchFailure) {
|
||||||
SafetyNumberChangeDialog.show(requireContext(), childFragmentManager, model.data.primaryStory.messageRecord)
|
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 options = ActivityOptionsCompat.makeSceneTransitionAnimation(requireActivity(), preview, ViewCompat.getTransitionName(preview) ?: "")
|
||||||
|
|
||||||
val record = model.data.primaryStory.messageRecord as MmsMessageRecord
|
val record = model.data.primaryStory.messageRecord as MmsMessageRecord
|
||||||
|
val blur = record.slideDeck.thumbnailSlide?.placeholderBlur
|
||||||
val (text: StoryTextPostModel?, image: Uri?) = if (record.storyType.isTextStory) {
|
val (text: StoryTextPostModel?, image: Uri?) = if (record.storyType.isTextStory) {
|
||||||
StoryTextPostModel.parseFrom(record) to null
|
StoryTextPostModel.parseFrom(record) to null
|
||||||
} else {
|
} else {
|
||||||
null to record.slideDeck.thumbnailSlide?.uri
|
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 = {
|
onForwardStory = {
|
||||||
|
@ -212,7 +215,7 @@ class StoriesLandingFragment : DSLSettingsFragment(layoutId = R.layout.stories_l
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
onGoToChat = {
|
onGoToChat = {
|
||||||
startActivity(ConversationIntents.createBuilder(requireContext(), it.data.storyRecipient.id, -1L).build())
|
startActivityIfAble(ConversationIntents.createBuilder(requireContext(), it.data.storyRecipient.id, -1L).build())
|
||||||
},
|
},
|
||||||
onHideStory = {
|
onHideStory = {
|
||||||
if (!it.data.isHidden) {
|
if (!it.data.isHidden) {
|
||||||
|
@ -256,7 +259,7 @@ class StoriesLandingFragment : DSLSettingsFragment(layoutId = R.layout.stories_l
|
||||||
|
|
||||||
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
||||||
return if (item.itemId == R.id.action_settings) {
|
return if (item.itemId == R.id.action_settings) {
|
||||||
startActivity(StorySettingsActivity.getIntent(requireContext()))
|
startActivityIfAble(StorySettingsActivity.getIntent(requireContext()))
|
||||||
true
|
true
|
||||||
} else {
|
} else {
|
||||||
false
|
false
|
||||||
|
@ -266,4 +269,13 @@ class StoriesLandingFragment : DSLSettingsFragment(layoutId = R.layout.stories_l
|
||||||
override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<out String>, grantResults: IntArray) {
|
override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<out String>, grantResults: IntArray) {
|
||||||
Permissions.onRequestPermissionsResult(this, requestCode, permissions, grantResults)
|
Permissions.onRequestPermissionsResult(this, requestCode, permissions, grantResults)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun startActivityIfAble(intent: Intent, options: Bundle? = null) {
|
||||||
|
if (viewModel.isTransitioningToAnotherScreen) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
viewModel.isTransitioningToAnotherScreen = true
|
||||||
|
startActivity(intent, options)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -107,7 +107,7 @@ object StoriesLandingItem {
|
||||||
presentDateOrStatus(model)
|
presentDateOrStatus(model)
|
||||||
setUpClickListeners(model)
|
setUpClickListeners(model)
|
||||||
|
|
||||||
if (payload.contains(STATUS_CHANGE) || payload.contains(LandingPayload.RESUMED)) {
|
if (payload.contains(STATUS_CHANGE)) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -223,11 +223,6 @@ object StoriesLandingItem {
|
||||||
|
|
||||||
private fun setUpClickListeners(model: Model) {
|
private fun setUpClickListeners(model: Model) {
|
||||||
itemView.setOnClickListener {
|
itemView.setOnClickListener {
|
||||||
if (!itemView.isClickable) {
|
|
||||||
return@setOnClickListener
|
|
||||||
}
|
|
||||||
|
|
||||||
itemView.isClickable = false
|
|
||||||
model.onRowClick(model, storyPreview)
|
model.onRowClick(model, storyPreview)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -15,6 +15,7 @@ class StoriesLandingViewModel(private val storiesLandingRepository: StoriesLandi
|
||||||
private val disposables = CompositeDisposable()
|
private val disposables = CompositeDisposable()
|
||||||
|
|
||||||
val state: LiveData<StoriesLandingState> = store.stateLiveData
|
val state: LiveData<StoriesLandingState> = store.stateLiveData
|
||||||
|
var isTransitioningToAnotherScreen: Boolean = false
|
||||||
|
|
||||||
init {
|
init {
|
||||||
disposables += storiesLandingRepository.getStories().subscribe { stories ->
|
disposables += storiesLandingRepository.getStories().subscribe { stories ->
|
||||||
|
|
|
@ -93,6 +93,7 @@ class MyStoriesFragment : DSLSettingsFragment(
|
||||||
}
|
}
|
||||||
|
|
||||||
val record = it.distributionStory.messageRecord as MmsMessageRecord
|
val record = it.distributionStory.messageRecord as MmsMessageRecord
|
||||||
|
val blur = record.slideDeck.thumbnailSlide?.placeholderBlur
|
||||||
val (text: StoryTextPostModel?, image: Uri?) = if (record.storyType.isTextStory) {
|
val (text: StoryTextPostModel?, image: Uri?) = if (record.storyType.isTextStory) {
|
||||||
StoryTextPostModel.parseFrom(record) to null
|
StoryTextPostModel.parseFrom(record) to null
|
||||||
} else {
|
} else {
|
||||||
|
@ -100,7 +101,7 @@ class MyStoriesFragment : DSLSettingsFragment(
|
||||||
}
|
}
|
||||||
|
|
||||||
val options = ActivityOptionsCompat.makeSceneTransitionAnimation(requireActivity(), preview, ViewCompat.getTransitionName(preview) ?: "")
|
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 = {
|
onLongClick = {
|
||||||
|
|
|
@ -8,6 +8,7 @@ import android.os.Bundle
|
||||||
import androidx.appcompat.app.AppCompatDelegate
|
import androidx.appcompat.app.AppCompatDelegate
|
||||||
import org.thoughtcrime.securesms.PassphraseRequiredActivity
|
import org.thoughtcrime.securesms.PassphraseRequiredActivity
|
||||||
import org.thoughtcrime.securesms.R
|
import org.thoughtcrime.securesms.R
|
||||||
|
import org.thoughtcrime.securesms.blurhash.BlurHash
|
||||||
import org.thoughtcrime.securesms.components.voice.VoiceNoteMediaController
|
import org.thoughtcrime.securesms.components.voice.VoiceNoteMediaController
|
||||||
import org.thoughtcrime.securesms.components.voice.VoiceNoteMediaControllerOwner
|
import org.thoughtcrime.securesms.components.voice.VoiceNoteMediaControllerOwner
|
||||||
import org.thoughtcrime.securesms.recipients.RecipientId
|
import org.thoughtcrime.securesms.recipients.RecipientId
|
||||||
|
@ -39,7 +40,8 @@ class StoryViewerActivity : PassphraseRequiredActivity(), VoiceNoteMediaControll
|
||||||
intent.getLongExtra(ARG_START_STORY_ID, -1L),
|
intent.getLongExtra(ARG_START_STORY_ID, -1L),
|
||||||
intent.getBooleanExtra(ARG_HIDDEN_STORIES, false),
|
intent.getBooleanExtra(ARG_HIDDEN_STORIES, false),
|
||||||
intent.getParcelableExtra(ARG_CROSSFADE_TEXT_MODEL),
|
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()
|
.commit()
|
||||||
|
@ -58,6 +60,7 @@ class StoryViewerActivity : PassphraseRequiredActivity(), VoiceNoteMediaControll
|
||||||
private const val ARG_HIDDEN_STORIES = "hidden_stories"
|
private const val ARG_HIDDEN_STORIES = "hidden_stories"
|
||||||
private const val ARG_CROSSFADE_TEXT_MODEL = "crossfade.text.model"
|
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_URI = "crossfade.image.uri"
|
||||||
|
private const val ARG_CROSSFADE_IMAGE_BLUR = "crossfade.image.blur"
|
||||||
|
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
fun createIntent(
|
fun createIntent(
|
||||||
|
@ -66,7 +69,8 @@ class StoryViewerActivity : PassphraseRequiredActivity(), VoiceNoteMediaControll
|
||||||
storyId: Long = -1L,
|
storyId: Long = -1L,
|
||||||
onlyIncludeHiddenStories: Boolean = false,
|
onlyIncludeHiddenStories: Boolean = false,
|
||||||
storyThumbTextModel: StoryTextPostModel? = null,
|
storyThumbTextModel: StoryTextPostModel? = null,
|
||||||
storyThumbUri: Uri? = null
|
storyThumbUri: Uri? = null,
|
||||||
|
storyThumbBlur: BlurHash? = null
|
||||||
): Intent {
|
): Intent {
|
||||||
return Intent(context, StoryViewerActivity::class.java)
|
return Intent(context, StoryViewerActivity::class.java)
|
||||||
.putExtra(ARG_START_RECIPIENT_ID, recipientId)
|
.putExtra(ARG_START_RECIPIENT_ID, recipientId)
|
||||||
|
@ -74,6 +78,7 @@ class StoryViewerActivity : PassphraseRequiredActivity(), VoiceNoteMediaControll
|
||||||
.putExtra(ARG_HIDDEN_STORIES, onlyIncludeHiddenStories)
|
.putExtra(ARG_HIDDEN_STORIES, onlyIncludeHiddenStories)
|
||||||
.putExtra(ARG_CROSSFADE_TEXT_MODEL, storyThumbTextModel)
|
.putExtra(ARG_CROSSFADE_TEXT_MODEL, storyThumbTextModel)
|
||||||
.putExtra(ARG_CROSSFADE_IMAGE_URI, storyThumbUri)
|
.putExtra(ARG_CROSSFADE_IMAGE_URI, storyThumbUri)
|
||||||
|
.putExtra(ARG_CROSSFADE_IMAGE_BLUR, storyThumbBlur?.hash)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,6 +8,7 @@ import androidx.fragment.app.viewModels
|
||||||
import androidx.lifecycle.LiveDataReactiveStreams
|
import androidx.lifecycle.LiveDataReactiveStreams
|
||||||
import androidx.viewpager2.widget.ViewPager2
|
import androidx.viewpager2.widget.ViewPager2
|
||||||
import org.thoughtcrime.securesms.R
|
import org.thoughtcrime.securesms.R
|
||||||
|
import org.thoughtcrime.securesms.blurhash.BlurHash
|
||||||
import org.thoughtcrime.securesms.recipients.RecipientId
|
import org.thoughtcrime.securesms.recipients.RecipientId
|
||||||
import org.thoughtcrime.securesms.stories.StoryTextPostModel
|
import org.thoughtcrime.securesms.stories.StoryTextPostModel
|
||||||
import org.thoughtcrime.securesms.stories.viewer.page.StoryViewerPageFragment
|
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(
|
private val viewModel: StoryViewerViewModel by viewModels(
|
||||||
factoryProducer = {
|
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?
|
private val storyThumbUri: Uri?
|
||||||
get() = requireArguments().getParcelable(ARG_CROSSFADE_IMAGE_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?) {
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
storyPager = view.findViewById(R.id.story_item_pager)
|
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_HIDDEN_STORIES = "hidden_stories"
|
||||||
private const val ARG_CROSSFADE_TEXT_MODEL = "crossfade.text.model"
|
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_URI = "crossfade.image.uri"
|
||||||
|
private const val ARG_CROSSFADE_IMAGE_BLUR = "crossfade.image.blur"
|
||||||
|
|
||||||
fun create(
|
fun create(
|
||||||
storyRecipientId: RecipientId,
|
storyRecipientId: RecipientId,
|
||||||
storyId: Long,
|
storyId: Long,
|
||||||
onlyIncludeHiddenStories: Boolean,
|
onlyIncludeHiddenStories: Boolean,
|
||||||
storyThumbTextModel: StoryTextPostModel? = null,
|
storyThumbTextModel: StoryTextPostModel? = null,
|
||||||
storyThumbUri: Uri? = null
|
storyThumbUri: Uri? = null,
|
||||||
|
storyThumbBlur: String? = null
|
||||||
): Fragment {
|
): Fragment {
|
||||||
return StoryViewerFragment().apply {
|
return StoryViewerFragment().apply {
|
||||||
arguments = Bundle().apply {
|
arguments = Bundle().apply {
|
||||||
|
@ -123,6 +129,7 @@ class StoryViewerFragment : Fragment(R.layout.stories_viewer_fragment), StoryVie
|
||||||
putBoolean(ARG_HIDDEN_STORIES, onlyIncludeHiddenStories)
|
putBoolean(ARG_HIDDEN_STORIES, onlyIncludeHiddenStories)
|
||||||
putParcelable(ARG_CROSSFADE_TEXT_MODEL, storyThumbTextModel)
|
putParcelable(ARG_CROSSFADE_TEXT_MODEL, storyThumbTextModel)
|
||||||
putParcelable(ARG_CROSSFADE_IMAGE_URI, storyThumbUri)
|
putParcelable(ARG_CROSSFADE_IMAGE_URI, storyThumbUri)
|
||||||
|
putString(ARG_CROSSFADE_IMAGE_BLUR, storyThumbBlur)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
package org.thoughtcrime.securesms.stories.viewer
|
package org.thoughtcrime.securesms.stories.viewer
|
||||||
|
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
|
import org.thoughtcrime.securesms.blurhash.BlurHash
|
||||||
import org.thoughtcrime.securesms.recipients.RecipientId
|
import org.thoughtcrime.securesms.recipients.RecipientId
|
||||||
import org.thoughtcrime.securesms.stories.StoryTextPostModel
|
import org.thoughtcrime.securesms.stories.StoryTextPostModel
|
||||||
|
|
||||||
|
@ -13,7 +14,7 @@ data class StoryViewerState(
|
||||||
) {
|
) {
|
||||||
sealed class CrossfadeSource {
|
sealed class CrossfadeSource {
|
||||||
object None : 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()
|
class TextModel(val storyTextPostModel: StoryTextPostModel) : CrossfadeSource()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -8,6 +8,7 @@ import androidx.lifecycle.ViewModelProvider
|
||||||
import io.reactivex.rxjava3.core.Flowable
|
import io.reactivex.rxjava3.core.Flowable
|
||||||
import io.reactivex.rxjava3.disposables.CompositeDisposable
|
import io.reactivex.rxjava3.disposables.CompositeDisposable
|
||||||
import io.reactivex.rxjava3.kotlin.plusAssign
|
import io.reactivex.rxjava3.kotlin.plusAssign
|
||||||
|
import org.thoughtcrime.securesms.blurhash.BlurHash
|
||||||
import org.thoughtcrime.securesms.recipients.RecipientId
|
import org.thoughtcrime.securesms.recipients.RecipientId
|
||||||
import org.thoughtcrime.securesms.stories.StoryTextPostModel
|
import org.thoughtcrime.securesms.stories.StoryTextPostModel
|
||||||
import org.thoughtcrime.securesms.util.rx.RxStore
|
import org.thoughtcrime.securesms.util.rx.RxStore
|
||||||
|
@ -18,6 +19,7 @@ class StoryViewerViewModel(
|
||||||
private val onlyIncludeHiddenStories: Boolean,
|
private val onlyIncludeHiddenStories: Boolean,
|
||||||
storyThumbTextModel: StoryTextPostModel?,
|
storyThumbTextModel: StoryTextPostModel?,
|
||||||
storyThumbUri: Uri?,
|
storyThumbUri: Uri?,
|
||||||
|
storyThumbBlur: BlurHash?,
|
||||||
private val repository: StoryViewerRepository,
|
private val repository: StoryViewerRepository,
|
||||||
) : ViewModel() {
|
) : ViewModel() {
|
||||||
|
|
||||||
|
@ -25,7 +27,7 @@ class StoryViewerViewModel(
|
||||||
StoryViewerState(
|
StoryViewerState(
|
||||||
crossfadeSource = when {
|
crossfadeSource = when {
|
||||||
storyThumbTextModel != null -> StoryViewerState.CrossfadeSource.TextModel(storyThumbTextModel)
|
storyThumbTextModel != null -> StoryViewerState.CrossfadeSource.TextModel(storyThumbTextModel)
|
||||||
storyThumbUri != null -> StoryViewerState.CrossfadeSource.ImageUri(storyThumbUri)
|
storyThumbUri != null -> StoryViewerState.CrossfadeSource.ImageUri(storyThumbUri, storyThumbBlur)
|
||||||
else -> StoryViewerState.CrossfadeSource.None
|
else -> StoryViewerState.CrossfadeSource.None
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
@ -154,10 +156,11 @@ class StoryViewerViewModel(
|
||||||
private val onlyIncludeHiddenStories: Boolean,
|
private val onlyIncludeHiddenStories: Boolean,
|
||||||
private val storyThumbTextModel: StoryTextPostModel?,
|
private val storyThumbTextModel: StoryTextPostModel?,
|
||||||
private val storyThumbUri: Uri?,
|
private val storyThumbUri: Uri?,
|
||||||
|
private val storyThumbBlur: BlurHash?,
|
||||||
private val repository: StoryViewerRepository
|
private val repository: StoryViewerRepository
|
||||||
) : ViewModelProvider.Factory {
|
) : ViewModelProvider.Factory {
|
||||||
override fun <T : ViewModel?> create(modelClass: Class<T>): T {
|
override fun <T : ViewModel?> create(modelClass: Class<T>): T {
|
||||||
return modelClass.cast(StoryViewerViewModel(startRecipientId, onlyIncludeHiddenStories, storyThumbTextModel, storyThumbUri, repository)) as T
|
return modelClass.cast(StoryViewerViewModel(startRecipientId, onlyIncludeHiddenStories, storyThumbTextModel, storyThumbUri, storyThumbBlur, repository)) as T
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -260,7 +260,7 @@ class StoryViewerPageFragment :
|
||||||
viewModel.setIsSelectedPage(true)
|
viewModel.setIsSelectedPage(true)
|
||||||
when (parentState.crossfadeSource) {
|
when (parentState.crossfadeSource) {
|
||||||
is StoryViewerState.CrossfadeSource.TextModel -> storyCrossfader.setSourceView(parentState.crossfadeSource.storyTextPostModel)
|
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 -> onReadyToAnimate()
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -14,6 +14,7 @@ import com.bumptech.glide.request.target.Target
|
||||||
import org.signal.core.util.DimensionUnit
|
import org.signal.core.util.DimensionUnit
|
||||||
import org.thoughtcrime.securesms.R
|
import org.thoughtcrime.securesms.R
|
||||||
import org.thoughtcrime.securesms.animation.transitions.CrossfaderTransition
|
import org.thoughtcrime.securesms.animation.transitions.CrossfaderTransition
|
||||||
|
import org.thoughtcrime.securesms.blurhash.BlurHash
|
||||||
import org.thoughtcrime.securesms.database.model.MmsMessageRecord
|
import org.thoughtcrime.securesms.database.model.MmsMessageRecord
|
||||||
import org.thoughtcrime.securesms.mms.DecryptableStreamUriLoader
|
import org.thoughtcrime.securesms.mms.DecryptableStreamUriLoader
|
||||||
import org.thoughtcrime.securesms.mms.GlideApp
|
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 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 targetView: ImageView = findViewById(R.id.target_image)
|
||||||
|
private val targetBlurView: ImageView = findViewById(R.id.target_image_blur)
|
||||||
|
|
||||||
private var isSourceReady: Boolean = false
|
private var isSourceReady: Boolean = false
|
||||||
|
private var isSourceBlurReady: Boolean = false
|
||||||
private var isTargetReady: Boolean = false
|
private var isTargetReady: Boolean = false
|
||||||
|
private var isTargetBlurReady: Boolean = false
|
||||||
|
|
||||||
private var latestSource: Any? = null
|
private var latestSource: Any? = null
|
||||||
private var latestTarget: Any? = null
|
private var latestTarget: Any? = null
|
||||||
|
@ -64,9 +69,12 @@ class StoriesSharedElementCrossFaderView @JvmOverloads constructor(
|
||||||
.dontAnimate()
|
.dontAnimate()
|
||||||
.centerCrop()
|
.centerCrop()
|
||||||
.into(sourceView)
|
.into(sourceView)
|
||||||
|
|
||||||
|
GlideApp.with(sourceBlurView).clear(sourceBlurView)
|
||||||
|
isSourceBlurReady = true
|
||||||
}
|
}
|
||||||
|
|
||||||
fun setSourceView(uri: Uri) {
|
fun setSourceView(uri: Uri, blur: BlurHash?) {
|
||||||
if (latestSource == uri) {
|
if (latestSource == uri) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -84,13 +92,31 @@ class StoriesSharedElementCrossFaderView @JvmOverloads constructor(
|
||||||
.dontAnimate()
|
.dontAnimate()
|
||||||
.centerCrop()
|
.centerCrop()
|
||||||
.into(sourceView)
|
.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 {
|
fun setTargetView(messageRecord: MmsMessageRecord): Boolean {
|
||||||
val thumbUri = messageRecord.slideDeck.thumbnailSlide?.uri
|
val thumbUri = messageRecord.slideDeck.thumbnailSlide?.uri
|
||||||
|
val thumbBlur: BlurHash? = messageRecord.slideDeck.thumbnailSlide?.placeholderBlur
|
||||||
when {
|
when {
|
||||||
messageRecord.storyType.isTextStory -> setTargetView(StoryTextPostModel.parseFrom(messageRecord))
|
messageRecord.storyType.isTextStory -> setTargetView(StoryTextPostModel.parseFrom(messageRecord))
|
||||||
thumbUri != null -> setTargetView(thumbUri)
|
thumbUri != null -> setTargetView(thumbUri, thumbBlur)
|
||||||
else -> return false
|
else -> return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -116,9 +142,12 @@ class StoriesSharedElementCrossFaderView @JvmOverloads constructor(
|
||||||
.placeholder(storyTextPostModel.getPlaceholder())
|
.placeholder(storyTextPostModel.getPlaceholder())
|
||||||
.centerCrop()
|
.centerCrop()
|
||||||
.into(targetView)
|
.into(targetView)
|
||||||
|
|
||||||
|
GlideApp.with(sourceBlurView).clear(sourceBlurView)
|
||||||
|
isTargetBlurReady = true
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun setTargetView(uri: Uri) {
|
private fun setTargetView(uri: Uri, blur: BlurHash?) {
|
||||||
if (latestTarget == uri) {
|
if (latestTarget == uri) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -134,12 +163,29 @@ class StoriesSharedElementCrossFaderView @JvmOverloads constructor(
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
.dontAnimate()
|
.dontAnimate()
|
||||||
.centerCrop()
|
.fitCenter()
|
||||||
.into(targetView)
|
.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() {
|
private fun notifyIfReady() {
|
||||||
if (isSourceReady && isTargetReady) {
|
if (isSourceReady && isTargetReady && isSourceBlurReady && isTargetBlurReady) {
|
||||||
callback?.onReadyToAnimate()
|
callback?.onReadyToAnimate()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -159,11 +205,15 @@ class StoriesSharedElementCrossFaderView @JvmOverloads constructor(
|
||||||
override fun onCrossfadeAnimationUpdated(progress: Float, reverse: Boolean) {
|
override fun onCrossfadeAnimationUpdated(progress: Float, reverse: Boolean) {
|
||||||
if (reverse) {
|
if (reverse) {
|
||||||
sourceView.alpha = progress
|
sourceView.alpha = progress
|
||||||
|
sourceBlurView.alpha = progress
|
||||||
targetView.alpha = 1f - progress
|
targetView.alpha = 1f - progress
|
||||||
|
targetBlurView.alpha = 1f - progress
|
||||||
radius = CORNER_RADIUS_EVALUATOR.evaluate(progress, CORNER_RADIUS_END, CORNER_RADIUS_START)
|
radius = CORNER_RADIUS_EVALUATOR.evaluate(progress, CORNER_RADIUS_END, CORNER_RADIUS_START)
|
||||||
} else {
|
} else {
|
||||||
sourceView.alpha = 1f - progress
|
sourceView.alpha = 1f - progress
|
||||||
|
sourceBlurView.alpha = 1f - progress
|
||||||
targetView.alpha = progress
|
targetView.alpha = progress
|
||||||
|
targetBlurView.alpha = progress
|
||||||
radius = CORNER_RADIUS_EVALUATOR.evaluate(progress, CORNER_RADIUS_START, CORNER_RADIUS_END)
|
radius = CORNER_RADIUS_EVALUATOR.evaluate(progress, CORNER_RADIUS_START, CORNER_RADIUS_END)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -172,7 +222,9 @@ class StoriesSharedElementCrossFaderView @JvmOverloads constructor(
|
||||||
alpha = 1f
|
alpha = 1f
|
||||||
|
|
||||||
sourceView.alpha = if (reverse) 0f else 1f
|
sourceView.alpha = if (reverse) 0f else 1f
|
||||||
|
sourceBlurView.alpha = if (reverse) 0f else 1f
|
||||||
targetView.alpha = if (reverse) 1f else 0f
|
targetView.alpha = if (reverse) 1f else 0f
|
||||||
|
targetBlurView.alpha = if (reverse) 1f else 0f
|
||||||
|
|
||||||
radius = if (reverse) CORNER_RADIUS_END else CORNER_RADIUS_START
|
radius = if (reverse) CORNER_RADIUS_END else CORNER_RADIUS_START
|
||||||
|
|
||||||
|
|
|
@ -34,8 +34,8 @@
|
||||||
android:focusable="true"
|
android:focusable="true"
|
||||||
android:theme="@style/Widget.Material3.FloatingActionButton.Secondary"
|
android:theme="@style/Widget.Material3.FloatingActionButton.Secondary"
|
||||||
android:transitionName="camera_fab"
|
android:transitionName="camera_fab"
|
||||||
app:elevation="0dp"
|
|
||||||
app:backgroundTint="@color/signal_colorSecondaryContainer"
|
app:backgroundTint="@color/signal_colorSecondaryContainer"
|
||||||
|
app:elevation="0dp"
|
||||||
app:layout_constraintBottom_toBottomOf="parent"
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
app:srcCompat="@drawable/ic_camera_outline_24"
|
app:srcCompat="@drawable/ic_camera_outline_24"
|
||||||
|
|
|
@ -12,6 +12,15 @@
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent">
|
android:layout_height="match_parent">
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/source_image_blur"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:importantForAccessibility="no"
|
||||||
|
app:layout_constraintDimensionRatio="48:72"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent" />
|
||||||
|
|
||||||
<ImageView
|
<ImageView
|
||||||
android:id="@+id/source_image"
|
android:id="@+id/source_image"
|
||||||
android:layout_width="0dp"
|
android:layout_width="0dp"
|
||||||
|
@ -21,6 +30,13 @@
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
app:layout_constraintStart_toStartOf="parent" />
|
app:layout_constraintStart_toStartOf="parent" />
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/target_image_blur"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:alpha="0"
|
||||||
|
android:importantForAccessibility="no" />
|
||||||
|
|
||||||
<ImageView
|
<ImageView
|
||||||
android:id="@+id/target_image"
|
android:id="@+id/target_image"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
|
|
Loading…
Add table
Reference in a new issue