diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 5c897d688..968c0c978 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -403,7 +403,7 @@
{
+ return PROPERTIES
+ }
+
+ override fun captureStartValues(transitionValues: TransitionValues) {
+ if (ViewCompat.getTransitionName(transitionValues.view) == "story") {
+ captureValues(transitionValues)
+ }
+ }
+
+ override fun captureEndValues(transitionValues: TransitionValues) {
+ if (ViewCompat.getTransitionName(transitionValues.view) == "story") {
+ resetValues(transitionValues.view)
+ captureValues(transitionValues)
+ }
+ }
+
+ private fun captureValues(transitionValues: TransitionValues) = with(transitionValues.view) {
+ transitionValues.values[LAYOUT_WIDTH] = width.toFloat()
+ transitionValues.values[LAYOUT_HEIGHT] = height.toFloat()
+ transitionValues.values[POSITION_X] = x
+ transitionValues.values[POSITION_Y] = y
+ transitionValues.values[SCALE_X] = scaleX
+ transitionValues.values[SCALE_Y] = scaleY
+ }
+
+ private fun resetValues(view: View) = with(view) {
+ translationX = 0f
+ translationY = 0f
+ scaleX = 1f
+ scaleY = 1f
+ }
+
+ override fun createAnimator(
+ sceneRoot: ViewGroup,
+ start: TransitionValues?,
+ end: TransitionValues?
+ ): Animator? {
+ if (start == null || end == null) {
+ return null
+ }
+
+ val startWidth = start.values[LAYOUT_WIDTH] as Float
+ val endWidth = end.values[LAYOUT_WIDTH] as Float
+ val startHeight = start.values[LAYOUT_HEIGHT] as Float
+ val endHeight = end.values[LAYOUT_HEIGHT] as Float
+
+ val startX = start.values[POSITION_X] as Float
+ val endX = end.values[POSITION_X] as Float
+ val startY = start.values[POSITION_Y] as Float
+ val endY = end.values[POSITION_Y] as Float
+
+ val startScaleX = start.values[SCALE_X] as Float
+ val startScaleY = start.values[SCALE_Y] as Float
+
+ end.view.translationX = (startX - endX) - (endWidth - startWidth) / 2
+ end.view.translationY = (startY - endY) - (endHeight - startHeight) / 2
+
+ end.view.scaleX = (startWidth / endWidth) * startScaleX
+ end.view.scaleY = (startHeight / endHeight) * startScaleY
+
+ return ObjectAnimator.ofPropertyValuesHolder(end.view,
+ ofFloat(View.TRANSLATION_X, 0f),
+ ofFloat(View.TRANSLATION_Y, 0f),
+ ofFloat(View.SCALE_X, 1f),
+ ofFloat(View.SCALE_Y, 1f)).apply {
+ addListener(object : AnimatorListenerAdapter() {
+ override fun onAnimationEnd(animation: Animator?) {
+ resetValues(start.view)
+ resetValues(end.view)
+ }
+ })
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/org/thoughtcrime/securesms/stories/StoryTextPostView.kt b/app/src/main/java/org/thoughtcrime/securesms/stories/StoryTextPostView.kt
index d7607ab60..20bda20ca 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/stories/StoryTextPostView.kt
+++ b/app/src/main/java/org/thoughtcrime/securesms/stories/StoryTextPostView.kt
@@ -7,17 +7,15 @@ import android.graphics.drawable.Drawable
import android.util.AttributeSet
import android.util.TypedValue
import android.view.View
-import android.widget.TextView
import androidx.annotation.ColorInt
import androidx.annotation.Px
+import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.content.res.AppCompatResources
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.core.view.doOnNextLayout
import androidx.core.view.isVisible
-import androidx.core.widget.doAfterTextChanged
import com.airbnb.lottie.SimpleColorFilter
import com.google.android.material.imageview.ShapeableImageView
-import org.signal.core.util.DimensionUnit
import org.thoughtcrime.securesms.R
import org.thoughtcrime.securesms.conversation.colors.ChatColors
import org.thoughtcrime.securesms.database.model.databaseprotos.StoryTextPost
@@ -30,7 +28,6 @@ import org.thoughtcrime.securesms.mediasend.v2.text.TextStoryPostCreationState
import org.thoughtcrime.securesms.mediasend.v2.text.TextStoryScale
import org.thoughtcrime.securesms.mediasend.v2.text.TextStoryTextWatcher
import org.thoughtcrime.securesms.stories.viewer.page.StoryDisplay
-import org.thoughtcrime.securesms.util.ViewUtil
import org.thoughtcrime.securesms.util.concurrent.ListenableFuture
import org.thoughtcrime.securesms.util.visible
import java.util.Locale
@@ -47,26 +44,13 @@ class StoryTextPostView @JvmOverloads constructor(
private var textAlignment: TextAlignment? = null
private val backgroundView: ShapeableImageView = findViewById(R.id.text_story_post_background)
- private val textView: TextView = findViewById(R.id.text_story_post_text)
+ private val textView: StoryTextView = findViewById(R.id.text_story_post_text)
private val linkPreviewView: StoryLinkPreviewView = findViewById(R.id.text_story_post_link_preview)
private var isPlaceholder: Boolean = true
init {
- backgroundView.addOnLayoutChangeListener { _, _, _, _, _, _, _, _, _ ->
- textView.maxWidth = backgroundView.measuredWidth - DimensionUnit.DP.toPixels(40f).toInt()
-
- textAlignment?.apply {
- adjustTextTranslationX(this)
- }
- }
-
TextStoryTextWatcher.install(textView)
- textView.doAfterTextChanged {
- textAlignment?.apply {
- adjustTextTranslationX(this)
- }
- }
val displaySize = StoryDisplay.getStoryDisplay(
resources.displayMetrics.widthPixels.toFloat(),
@@ -126,13 +110,7 @@ class StoryTextPostView @JvmOverloads constructor(
}
fun setTextBackgroundColor(@ColorInt color: Int) {
- if (color == Color.TRANSPARENT) {
- textView.background = null
- } else {
- textView.background = AppCompatResources.getDrawable(context, R.drawable.rounded_rectangle_secondary_18)?.apply {
- colorFilter = SimpleColorFilter(color)
- }
- }
+ textView.setWrappedBackgroundColor(color)
}
fun bindFromCreationState(state: TextStoryPostCreationState) {
@@ -151,7 +129,6 @@ class StoryTextPostView @JvmOverloads constructor(
setTextGravity(state.textAlignment)
setTextScale(state.textScale)
- postAdjustTextTranslationX(state.textAlignment)
postAdjustLinkPreviewTranslationY()
}
@@ -173,8 +150,11 @@ class StoryTextPostView @JvmOverloads constructor(
hideCloseButton()
- postAdjustTextTranslationX(TextAlignment.CENTER)
postAdjustLinkPreviewTranslationY()
+
+ doOnNextLayout {
+ (context as? AppCompatActivity)?.supportStartPostponedEnterTransition()
+ }
}
fun bindLinkPreview(linkPreview: LinkPreview?): ListenableFuture {
@@ -192,12 +172,6 @@ class StoryTextPostView @JvmOverloads constructor(
}
}
- private fun postAdjustTextTranslationX(textAlignment: TextAlignment) {
- doOnNextLayout {
- adjustTextTranslationX(textAlignment)
- }
- }
-
fun setTextViewClickListener(onClickListener: OnClickListener) {
setOnClickListener(onClickListener)
}
@@ -250,34 +224,4 @@ class StoryTextPostView @JvmOverloads constructor(
textView.translationY = desiredPoint - originPoint
}
}
-
- private fun alignTextLeft() {
- textView.translationX = DimensionUnit.DP.toPixels(20f)
- }
-
- private fun alignTextRight() {
- textView.translationX = backgroundView.measuredWidth - textView.measuredWidth - DimensionUnit.DP.toPixels(20f)
- }
-
- private fun adjustTextTranslationX(textAlignment: TextAlignment) {
- when (textAlignment) {
- TextAlignment.CENTER -> {
- textView.translationX = backgroundView.measuredWidth / 2f - textView.measuredWidth / 2f
- }
- TextAlignment.START -> {
- if (ViewUtil.isLtr(textView)) {
- alignTextLeft()
- } else {
- alignTextRight()
- }
- }
- TextAlignment.END -> {
- if (ViewUtil.isRtl(textView)) {
- alignTextLeft()
- } else {
- alignTextRight()
- }
- }
- }
- }
}
diff --git a/app/src/main/java/org/thoughtcrime/securesms/stories/StoryTextView.kt b/app/src/main/java/org/thoughtcrime/securesms/stories/StoryTextView.kt
new file mode 100644
index 000000000..9419ca255
--- /dev/null
+++ b/app/src/main/java/org/thoughtcrime/securesms/stories/StoryTextView.kt
@@ -0,0 +1,58 @@
+package org.thoughtcrime.securesms.stories
+
+import android.content.Context
+import android.graphics.Canvas
+import android.graphics.Color
+import android.graphics.Paint
+import android.graphics.Rect
+import android.graphics.RectF
+import android.util.AttributeSet
+import androidx.annotation.ColorInt
+import org.signal.core.util.DimensionUnit
+import org.thoughtcrime.securesms.components.emoji.EmojiTextView
+
+class StoryTextView @JvmOverloads constructor(
+ context: Context, attrs: AttributeSet? = null
+) : EmojiTextView(context, attrs) {
+
+ private val textBounds: RectF = RectF()
+ private val wrappedBackgroundPaint = Paint().apply {
+ style = Paint.Style.FILL
+ isAntiAlias = true
+ color = Color.TRANSPARENT
+ }
+
+ init {
+ if (isInEditMode) {
+ wrappedBackgroundPaint.color = Color.RED
+ }
+ }
+
+ fun setWrappedBackgroundColor(@ColorInt color: Int) {
+ wrappedBackgroundPaint.color = color
+ invalidate()
+ }
+
+ override fun onDraw(canvas: Canvas) {
+ if (wrappedBackgroundPaint.color != Color.TRANSPARENT) {
+ textBounds.set(left.toFloat(), top.toFloat(), right.toFloat(), bottom.toFloat())
+
+ val maxWidth = (0 until layout.lineCount).map {
+ layout.getLineWidth(it)
+ }.maxOrNull()
+
+ if (maxWidth != null) {
+ textBounds.inset((width - maxWidth - paddingStart - paddingEnd) / 2f, 0f)
+
+ canvas.drawRoundRect(
+ textBounds,
+ DimensionUnit.DP.toPixels(18f),
+ DimensionUnit.DP.toPixels(18f),
+ wrappedBackgroundPaint
+ )
+ }
+ }
+
+ super.onDraw(canvas)
+ }
+}
\ No newline at end of file
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 b5398f03e..ec59d5e70 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
@@ -10,6 +10,8 @@ import android.view.MenuItem
import android.view.View
import android.widget.Toast
import androidx.activity.OnBackPressedCallback
+import androidx.core.app.ActivityOptionsCompat
+import androidx.core.view.ViewCompat
import androidx.fragment.app.viewModels
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.google.android.material.snackbar.BaseTransientBottomBar
@@ -140,14 +142,15 @@ class StoriesLandingFragment : DSLSettingsFragment(layoutId = R.layout.stories_l
private fun createStoryLandingItem(data: StoriesLandingItemData): StoriesLandingItem.Model {
return StoriesLandingItem.Model(
data = data,
- onRowClick = {
- if (it.data.storyRecipient.isMyStory) {
+ onRowClick = { model, preview ->
+ if (model.data.storyRecipient.isMyStory) {
startActivity(Intent(requireContext(), MyStoriesActivity::class.java))
- } else if (it.data.primaryStory.messageRecord.isOutgoing && it.data.primaryStory.messageRecord.isFailed) {
- lifecycleDisposable += viewModel.resend(it.data.primaryStory.messageRecord).subscribe()
+ } else if (model.data.primaryStory.messageRecord.isOutgoing && model.data.primaryStory.messageRecord.isFailed) {
+ lifecycleDisposable += viewModel.resend(model.data.primaryStory.messageRecord).subscribe()
Toast.makeText(requireContext(), R.string.message_recipients_list_item__resend, Toast.LENGTH_SHORT).show()
} else {
- startActivity(StoryViewerActivity.createIntent(requireContext(), it.data.storyRecipient.id))
+ val options = ActivityOptionsCompat.makeSceneTransitionAnimation(requireActivity(), preview, ViewCompat.getTransitionName(preview) ?: "")
+ startActivity(StoryViewerActivity.createIntent(requireContext(), model.data.storyRecipient.id), options.toBundle())
}
},
onForwardStory = {
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 8783f984c..aad593329 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
@@ -35,7 +35,7 @@ object StoriesLandingItem {
class Model(
val data: StoriesLandingItemData,
- val onRowClick: (Model) -> Unit,
+ val onRowClick: (Model, View) -> Unit,
val onHideStory: (Model) -> Unit,
val onForwardStory: (Model) -> Unit,
val onShareStory: (Model) -> Unit,
@@ -166,7 +166,7 @@ object StoriesLandingItem {
}
private fun setUpClickListeners(model: Model) {
- itemView.setOnClickListener { model.onRowClick(model) }
+ itemView.setOnClickListener { model.onRowClick(model, storyPreview) }
if (model.data.storyRecipient.isMyStory) {
itemView.setOnLongClickListener(null)
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 556c8dab5..8360faf7b 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
@@ -1,6 +1,9 @@
package org.thoughtcrime.securesms.stories.my
import android.widget.Toast
+import androidx.activity.OnBackPressedCallback
+import androidx.core.app.ActivityOptionsCompat
+import androidx.core.view.ViewCompat
import androidx.fragment.app.viewModels
import org.thoughtcrime.securesms.R
import org.thoughtcrime.securesms.components.settings.DSLConfiguration
@@ -31,6 +34,12 @@ class MyStoriesFragment : DSLSettingsFragment(
override fun bindAdapter(adapter: DSLSettingsAdapter) {
MyStoriesItem.register(adapter)
+ requireActivity().onBackPressedDispatcher.addCallback(viewLifecycleOwner, object : OnBackPressedCallback(true) {
+ override fun handleOnBackPressed() {
+ requireActivity().finish()
+ }
+ })
+
lifecycleDisposable.bindTo(viewLifecycleOwner)
viewModel.state.observe(viewLifecycleOwner) {
adapter.submitList(getConfiguration(it).toMappingModelList())
@@ -56,7 +65,7 @@ class MyStoriesFragment : DSLSettingsFragment(
customPref(
MyStoriesItem.Model(
distributionStory = conversationMessage,
- onClick = {
+ onClick = { it, preview ->
if (it.distributionStory.messageRecord.isOutgoing && it.distributionStory.messageRecord.isFailed) {
lifecycleDisposable += viewModel.resend(it.distributionStory.messageRecord).subscribe()
Toast.makeText(requireContext(), R.string.message_recipients_list_item__resend, Toast.LENGTH_SHORT).show()
@@ -67,7 +76,8 @@ class MyStoriesFragment : DSLSettingsFragment(
Recipient.self().id
}
- startActivity(StoryViewerActivity.createIntent(requireContext(), recipientId, conversationMessage.messageRecord.id))
+ val options = ActivityOptionsCompat.makeSceneTransitionAnimation(requireActivity(), preview, ViewCompat.getTransitionName(preview) ?: "")
+ startActivity(StoryViewerActivity.createIntent(requireContext(), recipientId, conversationMessage.messageRecord.id), options.toBundle())
}
},
onSaveClick = {
diff --git a/app/src/main/java/org/thoughtcrime/securesms/stories/my/MyStoriesItem.kt b/app/src/main/java/org/thoughtcrime/securesms/stories/my/MyStoriesItem.kt
index 3a08af107..a778c186c 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/stories/my/MyStoriesItem.kt
+++ b/app/src/main/java/org/thoughtcrime/securesms/stories/my/MyStoriesItem.kt
@@ -32,7 +32,7 @@ object MyStoriesItem {
class Model(
val distributionStory: ConversationMessage,
- val onClick: (Model) -> Unit,
+ val onClick: (Model, View) -> Unit,
val onSaveClick: (Model) -> Unit,
val onDeleteClick: (Model) -> Unit,
val onForwardClick: (Model) -> Unit,
@@ -81,7 +81,7 @@ object MyStoriesItem {
override fun bind(model: Model) {
storyPreview.isClickable = false
- itemView.setOnClickListener { model.onClick(model) }
+ itemView.setOnClickListener { model.onClick(model, storyPreview) }
downloadTarget.setOnClickListener { model.onSaveClick(model) }
moreTarget.setOnClickListener { showContextMenu(model) }
presentDateOrStatus(model)
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 f9bc8f841..9c6652ab5 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
@@ -16,6 +16,8 @@ class StoryViewerActivity : PassphraseRequiredActivity() {
}
override fun onCreate(savedInstanceState: Bundle?, ready: Boolean) {
+ supportPostponeEnterTransition()
+
super.onCreate(savedInstanceState, ready)
setContentView(R.layout.fragment_container)
diff --git a/app/src/main/res/layout/stories_landing_item.xml b/app/src/main/res/layout/stories_landing_item.xml
index 6bd0ca989..cc3f411fa 100644
--- a/app/src/main/res/layout/stories_landing_item.xml
+++ b/app/src/main/res/layout/stories_landing_item.xml
@@ -102,6 +102,7 @@
android:layout_marginTop="10dp"
android:layout_marginBottom="10dp"
android:padding="2dp"
+ android:transitionName="story"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
diff --git a/app/src/main/res/layout/stories_my_stories_item.xml b/app/src/main/res/layout/stories_my_stories_item.xml
index 119656f91..5e4080b63 100644
--- a/app/src/main/res/layout/stories_my_stories_item.xml
+++ b/app/src/main/res/layout/stories_my_stories_item.xml
@@ -14,6 +14,7 @@
android:layout_height="84dp"
android:layout_marginTop="12dp"
android:layout_marginBottom="12dp"
+ android:transitionName="story"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
diff --git a/app/src/main/res/layout/stories_text_post_view.xml b/app/src/main/res/layout/stories_text_post_view.xml
index 7761b1386..b473af33e 100644
--- a/app/src/main/res/layout/stories_text_post_view.xml
+++ b/app/src/main/res/layout/stories_text_post_view.xml
@@ -12,12 +12,11 @@
android:layout_height="match_parent"
app:shapeAppearanceOverlay="@style/ShapeAppearanceOverlay.Signal.Story.Text" />
-
+ app:layout_constraintTop_toTopOf="parent"
+ app:layout_constraintVertical_bias="0">
diff --git a/app/src/main/res/transition/change_transform.xml b/app/src/main/res/transition/change_transform.xml
new file mode 100644
index 000000000..d1d906fda
--- /dev/null
+++ b/app/src/main/res/transition/change_transform.xml
@@ -0,0 +1,4 @@
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/values-v21/themes.xml b/app/src/main/res/values-v21/themes.xml
index 8cc467c02..ddbf7c253 100644
--- a/app/src/main/res/values-v21/themes.xml
+++ b/app/src/main/res/values-v21/themes.xml
@@ -16,6 +16,12 @@
- @color/core_ultramarine_light
+
+
diff --git a/app/src/main/res/values/themes.xml b/app/src/main/res/values/themes.xml
index 89d0a2ea2..3aa760e2c 100644
--- a/app/src/main/res/values/themes.xml
+++ b/app/src/main/res/values/themes.xml
@@ -35,6 +35,9 @@
+
+