mirror of
https://github.com/signalapp/Signal-Android.git
synced 2026-02-15 07:28:30 +00:00
Add animation to pinned messages.
This commit is contained in:
committed by
jeffrey-signal
parent
630c21b07a
commit
3185bb6031
@@ -16,6 +16,7 @@ import android.view.View
|
||||
import androidx.appcompat.widget.LinearLayoutCompat
|
||||
import androidx.compose.ui.platform.ComposeView
|
||||
import androidx.core.transition.addListener
|
||||
import androidx.interpolator.view.animation.FastOutSlowInInterpolator
|
||||
import org.thoughtcrime.securesms.R
|
||||
import org.thoughtcrime.securesms.banner.Banner
|
||||
import org.thoughtcrime.securesms.banner.BannerManager
|
||||
@@ -49,6 +50,11 @@ class ConversationBannerView @JvmOverloads constructor(
|
||||
attrs: AttributeSet? = null,
|
||||
defStyleAttr: Int = 0
|
||||
) : LinearLayoutCompat(context, attrs, defStyleAttr) {
|
||||
|
||||
companion object {
|
||||
private const val ANIMATION_DURATION = 500L
|
||||
}
|
||||
|
||||
private val unverifiedBannerStub: Stub<UnverifiedBannerView> by lazy { ViewUtil.findStubById(this, R.id.unverified_banner_stub) }
|
||||
private val bannerStub: Stub<ComposeView> by lazy { ViewUtil.findStubById(this, R.id.banner_stub) }
|
||||
private val reviewBannerStub: Stub<ReviewBannerView> by lazy { ViewUtil.findStubById(this, R.id.review_banner_stub) }
|
||||
@@ -134,24 +140,39 @@ class ConversationBannerView @JvmOverloads constructor(
|
||||
}
|
||||
|
||||
fun showPinnedMessageStub(messages: List<ConversationMessage>, canUnpin: Boolean, hasWallpaper: Boolean) {
|
||||
show(
|
||||
stub = pinnedMessageStub
|
||||
) {
|
||||
this.apply {
|
||||
setContent {
|
||||
SignalTheme(isDarkMode = DynamicTheme.isDarkTheme(context)) {
|
||||
PinnedMessagesBanner(
|
||||
messages = messages,
|
||||
canUnpin = canUnpin,
|
||||
hasWallpaper = hasWallpaper,
|
||||
onUnpinMessage = { messageId -> listener?.onUnpinMessage(messageId) },
|
||||
onGoToMessage = { messageId -> listener?.onGoToMessage(messageId) },
|
||||
onViewAllMessages = { listener?.onViewAllMessages() }
|
||||
)
|
||||
}
|
||||
val firstRender = !pinnedMessageStub.isVisible
|
||||
|
||||
val view = pinnedMessageStub.get()
|
||||
view.apply {
|
||||
setContent {
|
||||
SignalTheme(isDarkMode = DynamicTheme.isDarkTheme(context)) {
|
||||
PinnedMessagesBanner(
|
||||
messages = messages,
|
||||
canUnpin = canUnpin,
|
||||
hasWallpaper = hasWallpaper,
|
||||
onUnpinMessage = { messageId -> listener?.onUnpinMessage(messageId) },
|
||||
onGoToMessage = { messageId -> listener?.onGoToMessage(messageId) },
|
||||
onViewAllMessages = { listener?.onViewAllMessages() }
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (firstRender) {
|
||||
view.visibility = INVISIBLE
|
||||
view.post {
|
||||
view.visible = true
|
||||
view.translationY = -view.height.toFloat()
|
||||
view.alpha = 0f
|
||||
|
||||
view.animate()
|
||||
.translationY(0f)
|
||||
.setInterpolator(FastOutSlowInInterpolator())
|
||||
.alpha(1f)
|
||||
.setDuration(ANIMATION_DURATION)
|
||||
.start()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun hidePinnedMessageStub() {
|
||||
|
||||
@@ -3,12 +3,21 @@ package org.thoughtcrime.securesms.conversation.v2
|
||||
import android.text.SpannableString
|
||||
import android.text.SpannableStringBuilder
|
||||
import android.text.TextUtils
|
||||
import androidx.compose.animation.AnimatedContent
|
||||
import androidx.compose.animation.AnimatedContentTransitionScope
|
||||
import androidx.compose.animation.core.tween
|
||||
import androidx.compose.animation.fadeIn
|
||||
import androidx.compose.animation.fadeOut
|
||||
import androidx.compose.animation.togetherWith
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.IntrinsicSize
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.defaultMinSize
|
||||
import androidx.compose.foundation.layout.fillMaxHeight
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
@@ -36,6 +45,7 @@ import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.res.vectorResource
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import androidx.compose.ui.viewinterop.AndroidView
|
||||
import androidx.core.view.doOnPreDraw
|
||||
import org.signal.core.ui.compose.DropdownMenus
|
||||
@@ -55,6 +65,7 @@ import org.thoughtcrime.securesms.mms.StickerSlide
|
||||
import org.thoughtcrime.securesms.mms.VideoSlide
|
||||
import org.thoughtcrime.securesms.util.DynamicTheme
|
||||
import org.thoughtcrime.securesms.util.hasSharedContact
|
||||
import org.thoughtcrime.securesms.util.hasSticker
|
||||
import org.thoughtcrime.securesms.util.isPoll
|
||||
import org.thoughtcrime.securesms.util.isViewOnceMessage
|
||||
import org.whispersystems.signalservice.api.payments.FormatterOptions
|
||||
@@ -78,7 +89,9 @@ fun PinnedMessagesBanner(
|
||||
val message = conversationMessage.messageRecord as MmsMessageRecord
|
||||
val (glyph, body, showThumbnail) = getMessageMetadata(conversationMessage)
|
||||
|
||||
Column {
|
||||
Column(
|
||||
modifier = Modifier.fillMaxWidth().defaultMinSize(minHeight = 54.dp)
|
||||
) {
|
||||
HorizontalDivider(
|
||||
thickness = 1.dp,
|
||||
color = if (DynamicTheme.isDarkTheme(LocalContext.current)) Color(0XFF4A4C52) else Color(0XFFCCCFD5)
|
||||
@@ -92,61 +105,91 @@ fun PinnedMessagesBanner(
|
||||
index = (index + 1) % messages.size
|
||||
onGoToMessage(message.id)
|
||||
}
|
||||
.padding(8.dp)
|
||||
.height(IntrinsicSize.Min)
|
||||
) {
|
||||
if (messages.size > 1) {
|
||||
Heading(index, messages.size)
|
||||
} else {
|
||||
Spacer(modifier = Modifier.size(16.dp))
|
||||
}
|
||||
|
||||
if (showThumbnail && message.slideDeck.firstSlide?.uri != null) {
|
||||
GlideImage(
|
||||
model = DecryptableUri(message.slideDeck.firstSlide!!.uri!!),
|
||||
modifier = Modifier
|
||||
.padding(start = 8.dp)
|
||||
.size(32.dp)
|
||||
.clip(RoundedCornerShape(12.dp))
|
||||
)
|
||||
}
|
||||
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.weight(1f)
|
||||
.padding(start = 8.dp)
|
||||
) {
|
||||
Text(
|
||||
text = if (message.fromRecipient.isSelf) {
|
||||
stringResource(R.string.Recipient_you)
|
||||
} else {
|
||||
message.fromRecipient.getDisplayName(LocalContext.current)
|
||||
},
|
||||
fontWeight = FontWeight.Bold,
|
||||
color = MaterialTheme.colorScheme.onSurface
|
||||
)
|
||||
|
||||
val displayBody = if (glyph != null) {
|
||||
SpannableStringBuilder()
|
||||
.append(getSpannedString(LocalContext.current, SignalSymbols.Weight.REGULAR, glyph, -1))
|
||||
.append(" ")
|
||||
.append(body)
|
||||
} else {
|
||||
body
|
||||
AnimatedContent(
|
||||
modifier = Modifier.weight(1f),
|
||||
targetState = message,
|
||||
transitionSpec = {
|
||||
slideIntoContainer(towards = AnimatedContentTransitionScope.SlideDirection.Up, animationSpec = tween(150)) togetherWith
|
||||
slideOutOfContainer(towards = AnimatedContentTransitionScope.SlideDirection.Up, animationSpec = tween(150))
|
||||
}
|
||||
) { message ->
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
modifier = Modifier
|
||||
.clickable(
|
||||
onClick = {
|
||||
index = (index + 1) % messages.size
|
||||
onGoToMessage(message.id)
|
||||
},
|
||||
indication = null,
|
||||
interactionSource = remember { MutableInteractionSource() },
|
||||
onClickLabel = stringResource(R.string.SignalCheckbox_accessibility_on_click_label),
|
||||
enabled = true
|
||||
)
|
||||
.padding(vertical = 8.dp)
|
||||
) {
|
||||
if (showThumbnail &&
|
||||
!message.hasSticker() &&
|
||||
message.slideDeck.firstSlide?.uri != null &&
|
||||
!message.slideDeck.firstSlide!!.isVideoGif
|
||||
) {
|
||||
GlideImage(
|
||||
model = DecryptableUri(message.slideDeck.firstSlide!!.uri!!),
|
||||
modifier = Modifier
|
||||
.padding(end = 8.dp)
|
||||
.size(32.dp)
|
||||
.clip(RoundedCornerShape(10.dp))
|
||||
)
|
||||
}
|
||||
|
||||
AndroidView(
|
||||
factory = ::EmojiTextView
|
||||
) { view ->
|
||||
view.enableRenderSpoilers()
|
||||
view.text = displayBody
|
||||
view.ellipsize = TextUtils.TruncateAt.END
|
||||
view.maxLines = 1
|
||||
view.doOnPreDraw {
|
||||
(it as EmojiTextView).ellipsizeEmojiTextForMaxLines()
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.weight(1f)
|
||||
) {
|
||||
Text(
|
||||
text = if (message.fromRecipient.isSelf) {
|
||||
stringResource(R.string.Recipient_you)
|
||||
} else {
|
||||
message.fromRecipient.getDisplayName(LocalContext.current)
|
||||
},
|
||||
fontWeight = FontWeight.Bold,
|
||||
color = MaterialTheme.colorScheme.onSurface,
|
||||
fontSize = 13.sp
|
||||
)
|
||||
|
||||
val displayBody = if (glyph != null) {
|
||||
SpannableStringBuilder()
|
||||
.append(getSpannedString(LocalContext.current, SignalSymbols.Weight.REGULAR, glyph, -1))
|
||||
.append(" ")
|
||||
.append(body)
|
||||
} else {
|
||||
body
|
||||
}
|
||||
|
||||
AndroidView(
|
||||
factory = ::EmojiTextView
|
||||
) { view ->
|
||||
view.enableRenderSpoilers()
|
||||
view.text = displayBody
|
||||
view.ellipsize = TextUtils.TruncateAt.END
|
||||
view.maxLines = 1
|
||||
view.doOnPreDraw {
|
||||
(it as EmojiTextView).ellipsizeEmojiTextForMaxLines()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Box(modifier = Modifier.padding(horizontal = 8.dp)) {
|
||||
Box(modifier = Modifier.padding(8.dp).padding(end = 8.dp)) {
|
||||
Icon(
|
||||
imageVector = ImageVector.vectorResource(R.drawable.symbol_pin_24),
|
||||
contentDescription = stringResource(R.string.PinnedMessage__pinned),
|
||||
@@ -175,26 +218,34 @@ fun PinnedMessagesBanner(
|
||||
*/
|
||||
@Composable
|
||||
fun Heading(selectedIndex: Int, size: Int) {
|
||||
Column(
|
||||
modifier = Modifier.fillMaxHeight()
|
||||
) {
|
||||
for (i in 0 until size) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.padding(vertical = 2.dp)
|
||||
.width(2.dp)
|
||||
.weight(1f)
|
||||
.background(
|
||||
color = if (i == selectedIndex) {
|
||||
MaterialTheme.colorScheme.onSurface
|
||||
} else if (DynamicTheme.isDarkTheme(LocalContext.current)) {
|
||||
MaterialTheme.colorScheme.secondaryContainer
|
||||
} else {
|
||||
SignalTheme.colors.colorTransparentInverse2
|
||||
},
|
||||
shape = RoundedCornerShape(16.dp)
|
||||
)
|
||||
)
|
||||
AnimatedContent(
|
||||
targetState = selectedIndex,
|
||||
transitionSpec = {
|
||||
fadeIn(tween(durationMillis = 150)).togetherWith(fadeOut(tween(durationMillis = 150)))
|
||||
}
|
||||
) { selectedIndex ->
|
||||
Column(
|
||||
modifier = Modifier.fillMaxHeight().padding(vertical = 8.dp)
|
||||
) {
|
||||
for (i in 0 until size) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.padding(vertical = 2.dp)
|
||||
.padding(horizontal = 7.dp)
|
||||
.width(2.dp)
|
||||
.weight(1f)
|
||||
.background(
|
||||
color = if (i == selectedIndex) {
|
||||
MaterialTheme.colorScheme.onSurface
|
||||
} else if (DynamicTheme.isDarkTheme(LocalContext.current)) {
|
||||
MaterialTheme.colorScheme.secondaryContainer
|
||||
} else {
|
||||
SignalTheme.colors.colorTransparentInverse2
|
||||
},
|
||||
shape = RoundedCornerShape(16.dp)
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -162,6 +162,14 @@
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<ViewStub
|
||||
android:id="@+id/pinned_message_stub"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:visibility="gone"
|
||||
android:inflatedId="@+id/pinned_message_compose_view"
|
||||
android:layout="@layout/pinned_message_stub" />
|
||||
|
||||
<ViewStub
|
||||
android:id="@+id/voice_note_player_stub"
|
||||
android:layout_width="match_parent"
|
||||
@@ -190,13 +198,6 @@
|
||||
android:inflatedId="@+id/banner_compose_view"
|
||||
android:layout="@layout/conversation_list_banner_view" />
|
||||
|
||||
<ViewStub
|
||||
android:id="@+id/pinned_message_stub"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:inflatedId="@+id/pinned_message_compose_view"
|
||||
android:layout="@layout/pinned_message_stub" />
|
||||
|
||||
</org.thoughtcrime.securesms.conversation.v2.ConversationBannerView>
|
||||
|
||||
</FrameLayout>
|
||||
|
||||
Reference in New Issue
Block a user