Conversation tab bar animations.

fork-5.53.8
Alex Hart 2022-03-24 13:21:02 -03:00 zatwierdzone przez Greyson Parrelli
rodzic 2b5d65ae04
commit fd930d0b1d
5 zmienionych plików z 130 dodań i 34 usunięć

Wyświetl plik

@ -1,10 +1,20 @@
package org.thoughtcrime.securesms.stories.tabs package org.thoughtcrime.securesms.stories.tabs
import android.animation.Animator
import android.animation.AnimatorSet
import android.animation.ValueAnimator
import android.os.Bundle import android.os.Bundle
import android.view.View import android.view.View
import android.widget.ImageView
import android.widget.TextView import android.widget.TextView
import androidx.core.content.ContextCompat
import androidx.core.view.animation.PathInterpolatorCompat
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import androidx.fragment.app.viewModels import androidx.fragment.app.viewModels
import com.airbnb.lottie.LottieAnimationView
import com.airbnb.lottie.LottieProperty
import com.airbnb.lottie.model.KeyPath
import org.signal.core.util.DimensionUnit
import org.thoughtcrime.securesms.R import org.thoughtcrime.securesms.R
import org.thoughtcrime.securesms.util.visible import org.thoughtcrime.securesms.util.visible
import java.text.NumberFormat import java.text.NumberFormat
@ -18,14 +28,32 @@ class ConversationListTabsFragment : Fragment(R.layout.conversation_list_tabs) {
private lateinit var chatsUnreadIndicator: TextView private lateinit var chatsUnreadIndicator: TextView
private lateinit var storiesUnreadIndicator: TextView private lateinit var storiesUnreadIndicator: TextView
private lateinit var chatsIcon: View private lateinit var chatsIcon: LottieAnimationView
private lateinit var storiesIcon: View private lateinit var storiesIcon: LottieAnimationView
private lateinit var chatsPill: ImageView
private lateinit var storiesPill: ImageView
private var pillAnimator: Animator? = null
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
chatsUnreadIndicator = view.findViewById(R.id.chats_unread_indicator) chatsUnreadIndicator = view.findViewById(R.id.chats_unread_indicator)
storiesUnreadIndicator = view.findViewById(R.id.stories_unread_indicator) storiesUnreadIndicator = view.findViewById(R.id.stories_unread_indicator)
chatsIcon = view.findViewById(R.id.chats_tab_icon) chatsIcon = view.findViewById(R.id.chats_tab_icon)
storiesIcon = view.findViewById(R.id.stories_tab_icon) storiesIcon = view.findViewById(R.id.stories_tab_icon)
chatsPill = view.findViewById(R.id.chats_pill)
storiesPill = view.findViewById(R.id.stories_pill)
val iconTint = ContextCompat.getColor(requireContext(), R.color.signal_colorOnSecondaryContainer)
chatsIcon.addValueCallback(
KeyPath("**"),
LottieProperty.COLOR
) { iconTint }
storiesIcon.addValueCallback(
KeyPath("**"),
LottieProperty.COLOR
) { iconTint }
view.findViewById<View>(R.id.chats_tab_touch_point).setOnClickListener { view.findViewById<View>(R.id.chats_tab_touch_point).setOnClickListener {
viewModel.onChatsSelected() viewModel.onChatsSelected()
@ -39,9 +67,19 @@ class ConversationListTabsFragment : Fragment(R.layout.conversation_list_tabs) {
} }
private fun update(state: ConversationListTabsState) { private fun update(state: ConversationListTabsState) {
val wasChatSelected = chatsIcon.isSelected
chatsIcon.isSelected = state.tab == ConversationListTab.CHATS chatsIcon.isSelected = state.tab == ConversationListTab.CHATS
storiesIcon.isSelected = state.tab == ConversationListTab.STORIES storiesIcon.isSelected = state.tab == ConversationListTab.STORIES
chatsPill.isSelected = chatsIcon.isSelected
storiesPill.isSelected = storiesIcon.isSelected
if (chatsIcon.isSelected xor wasChatSelected) {
runLottieAnimations(chatsIcon, storiesIcon)
runPillAnimation(chatsPill, storiesPill)
}
chatsUnreadIndicator.visible = state.unreadChatsCount > 0 chatsUnreadIndicator.visible = state.unreadChatsCount > 0
chatsUnreadIndicator.text = formatCount(state.unreadChatsCount) chatsUnreadIndicator.text = formatCount(state.unreadChatsCount)
@ -51,6 +89,47 @@ class ConversationListTabsFragment : Fragment(R.layout.conversation_list_tabs) {
requireView().visible = !state.isSearchOpen requireView().visible = !state.isSearchOpen
} }
private fun runLottieAnimations(vararg toAnimate: LottieAnimationView) {
toAnimate.forEach {
if (it.isSelected) {
it.resumeAnimation()
} else {
if (it.isAnimating) {
it.pauseAnimation()
}
it.progress = 0f
}
}
}
private fun runPillAnimation(vararg toAnimate: ImageView) {
val (selected, unselected) = toAnimate.partition { it.isSelected }
pillAnimator?.cancel()
pillAnimator = AnimatorSet().apply {
duration = 150
interpolator = PathInterpolatorCompat.create(0.17f, 0.17f, 0f, 1f)
playTogether(
selected.map { view ->
view.visibility = View.VISIBLE
ValueAnimator.ofInt(view.paddingLeft, 0).apply {
addUpdateListener {
view.setPadding(it.animatedValue as Int, 0, it.animatedValue as Int, 0)
}
}
}
)
start()
}
unselected.forEach {
val smallPad = DimensionUnit.DP.toPixels(16f).toInt()
it.setPadding(smallPad, 0, smallPad, 0)
it.visibility = View.INVISIBLE
}
}
private fun formatCount(count: Long): String { private fun formatCount(count: Long): String {
if (count > 99L) { if (count > 99L) {
return getString(R.string.ConversationListTabs__99p) return getString(R.string.ConversationListTabs__99p)

Wyświetl plik

@ -1,10 +1,6 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android"> <shape xmlns:android="http://schemas.android.com/apk/res/android"
<item android:state_selected="true"> android:shape="rectangle">
<shape android:shape="rectangle"> <solid android:color="@color/signal_colorPrimaryContainer" />
<solid android:color="@color/signal_colorPrimaryContainer" /> <corners android:radius="18dp" />
<corners android:radius="18dp" /> </shape>
</shape>
</item>
<item android:drawable="@color/transparent" />
</selector>

Wyświetl plik

@ -6,14 +6,12 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:background="@color/signal_colorSecondaryContainer" android:background="@color/signal_colorSecondaryContainer"
android:minHeight="80dp" android:minHeight="80dp"
android:paddingHorizontal="@dimen/dsl_settings_gutter" android:paddingHorizontal="@dimen/dsl_settings_gutter">
android:paddingTop="8dp"
android:paddingBottom="8dp">
<View <View
android:id="@+id/chats_tab_touch_point" android:id="@+id/chats_tab_touch_point"
android:layout_width="58dp" android:layout_width="80dp"
android:layout_height="48dp" android:layout_height="0dp"
android:background="?selectableItemBackgroundBorderless" android:background="?selectableItemBackgroundBorderless"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="@id/chats_tab_icon" app:layout_constraintEnd_toEndOf="@id/chats_tab_icon"
@ -22,28 +20,49 @@
<View <View
android:id="@+id/stories_tab_touch_point" android:id="@+id/stories_tab_touch_point"
android:layout_width="58dp" android:layout_width="80dp"
android:layout_height="48dp" android:layout_height="0dp"
android:background="?selectableItemBackgroundBorderless" android:background="?selectableItemBackgroundBorderless"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="@id/stories_tab_icon" app:layout_constraintEnd_toEndOf="@id/stories_tab_icon"
app:layout_constraintStart_toStartOf="@id/stories_tab_icon" app:layout_constraintStart_toStartOf="@id/stories_tab_icon"
app:layout_constraintTop_toTopOf="parent" /> app:layout_constraintTop_toTopOf="parent" />
<androidx.appcompat.widget.AppCompatImageView <ImageView
android:id="@+id/chats_tab_icon" android:id="@+id/chats_pill"
android:layout_width="64dp" android:layout_width="64dp"
android:layout_height="32dp" android:layout_height="32dp"
android:background="@drawable/conversation_tab_icon_background" android:importantForAccessibility="no"
android:src="@drawable/conversation_tab_icon_background"
app:layout_constraintBottom_toBottomOf="@id/chats_tab_icon"
app:layout_constraintEnd_toEndOf="@id/chats_tab_icon"
app:layout_constraintStart_toStartOf="@id/chats_tab_icon"
app:layout_constraintTop_toTopOf="@id/chats_tab_icon" />
<ImageView
android:id="@+id/stories_pill"
android:layout_width="64dp"
android:layout_height="32dp"
android:importantForAccessibility="no"
android:paddingHorizontal="16dp"
android:src="@drawable/conversation_tab_icon_background"
app:layout_constraintBottom_toBottomOf="@id/stories_tab_icon"
app:layout_constraintEnd_toEndOf="@id/stories_tab_icon"
app:layout_constraintStart_toStartOf="@id/stories_tab_icon"
app:layout_constraintTop_toTopOf="@id/stories_tab_icon" />
<com.airbnb.lottie.LottieAnimationView
android:id="@+id/chats_tab_icon"
android:layout_width="32dp"
android:layout_height="32dp"
android:importantForAccessibility="no" android:importantForAccessibility="no"
android:scaleType="centerInside" android:scaleType="centerInside"
android:tint="@color/signal_colorOnSecondaryContainer"
app:layout_constraintBottom_toTopOf="@id/chats_tab_label" app:layout_constraintBottom_toTopOf="@id/chats_tab_label"
app:layout_constraintEnd_toStartOf="@id/tabs_center_guide" app:layout_constraintEnd_toStartOf="@id/tabs_center_guide"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_chainStyle="packed" app:layout_constraintVertical_chainStyle="packed"
app:srcCompat="@drawable/ic_chat_message_24" /> app:lottie_rawRes="@raw/chats_32" />
<TextView <TextView
android:id="@+id/chats_tab_label" android:id="@+id/chats_tab_label"
@ -65,20 +84,18 @@
android:orientation="vertical" android:orientation="vertical"
app:layout_constraintGuide_percent="0.5" /> app:layout_constraintGuide_percent="0.5" />
<androidx.appcompat.widget.AppCompatImageView <com.airbnb.lottie.LottieAnimationView
android:id="@+id/stories_tab_icon" android:id="@+id/stories_tab_icon"
android:layout_width="64dp" android:layout_width="32dp"
android:layout_height="32dp" android:layout_height="32dp"
android:background="@drawable/conversation_tab_icon_background"
android:importantForAccessibility="no" android:importantForAccessibility="no"
android:scaleType="centerInside" android:scaleType="centerInside"
android:tint="@color/signal_colorOnSecondaryContainer"
app:layout_constraintBottom_toTopOf="@id/stories_tab_label" app:layout_constraintBottom_toTopOf="@id/stories_tab_label"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@id/tabs_center_guide" app:layout_constraintStart_toEndOf="@id/tabs_center_guide"
app:layout_constraintTop_toTopOf="parent" app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_chainStyle="packed" app:layout_constraintVertical_chainStyle="packed"
app:srcCompat="@drawable/ic_stories_24" /> app:lottie_rawRes="@raw/stories_32" />
<TextView <TextView
android:id="@+id/stories_tab_label" android:id="@+id/stories_tab_label"
@ -97,7 +114,8 @@
android:id="@+id/chats_unread_indicator" android:id="@+id/chats_unread_indicator"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="16sp" android:layout_height="16sp"
android:layout_marginBottom="21dp" android:layout_marginStart="28.5dp"
android:layout_marginTop="6dp"
android:background="@drawable/tab_unread_circle" android:background="@drawable/tab_unread_circle"
android:gravity="center_horizontal" android:gravity="center_horizontal"
android:minWidth="16sp" android:minWidth="16sp"
@ -106,15 +124,16 @@
android:textAppearance="@style/Signal.Text.Caption" android:textAppearance="@style/Signal.Text.Caption"
android:textColor="@color/white" android:textColor="@color/white"
android:textSize="11sp" android:textSize="11sp"
app:layout_constraintBottom_toBottomOf="@id/chats_tab_icon" app:layout_constraintStart_toStartOf="@id/chats_tab_icon"
app:layout_constraintEnd_toEndOf="@id/chats_tab_touch_point" app:layout_constraintTop_toTopOf="parent"
tools:text="3" /> tools:text="3" />
<TextView <TextView
android:id="@+id/stories_unread_indicator" android:id="@+id/stories_unread_indicator"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="16sp" android:layout_height="16sp"
android:layout_marginBottom="21dp" android:layout_marginStart="28.5dp"
android:layout_marginTop="6dp"
android:background="@drawable/tab_unread_circle" android:background="@drawable/tab_unread_circle"
android:gravity="center_horizontal" android:gravity="center_horizontal"
android:minWidth="16sp" android:minWidth="16sp"
@ -123,8 +142,8 @@
android:textAppearance="@style/Signal.Text.Caption" android:textAppearance="@style/Signal.Text.Caption"
android:textColor="@color/white" android:textColor="@color/white"
android:textSize="11sp" android:textSize="11sp"
app:layout_constraintBottom_toBottomOf="@id/stories_tab_icon" app:layout_constraintStart_toStartOf="@id/stories_tab_icon"
app:layout_constraintEnd_toEndOf="@id/stories_tab_touch_point" app:layout_constraintTop_toTopOf="parent"
tools:text="99+" /> tools:text="99+" />
</androidx.constraintlayout.widget.ConstraintLayout> </androidx.constraintlayout.widget.ConstraintLayout>

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long