Skip to content

Commit 68b0a74

Browse files
committed
Flip chat-history row icon when selection toggles
Animate a 300ms Y-axis flip on the row's icon container when the user toggles a row in or out of selection, swapping the chat-type drawable and accent background at the apex. The flip runs only on selection changes detected via DiffUtil.getChangePayload and routed through the 3-arg onBindViewHolder, so scrolling and full rebinds stay snappy.
1 parent dc44fe1 commit 68b0a74

2 files changed

Lines changed: 55 additions & 3 deletions

File tree

duckchat/duckchat-impl/src/main/java/com/duckduckgo/duckchat/impl/history/ChatHistoryAdapter.kt

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,20 @@ class ChatHistoryAdapter(
5858
}
5959
}
6060

61+
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int, payloads: MutableList<Any>) {
62+
val selectionChange = payloads.firstOrNull { it is RowChange.SelectionChanged } as? RowChange.SelectionChanged
63+
val entry = getItem(position)
64+
if (selectionChange != null && holder is ChatHistoryViewHolder && entry is ChatHistoryListEntry.Row) {
65+
holder.animateSelectionChange(entry.item, selectionChange.selected)
66+
} else {
67+
onBindViewHolder(holder, position)
68+
}
69+
}
70+
71+
private sealed interface RowChange {
72+
data class SelectionChanged(val selected: Boolean) : RowChange
73+
}
74+
6175
private object Diff : DiffUtil.ItemCallback<ChatHistoryListEntry>() {
6276
override fun areItemsTheSame(oldItem: ChatHistoryListEntry, newItem: ChatHistoryListEntry): Boolean = when {
6377
oldItem is ChatHistoryListEntry.Header && newItem is ChatHistoryListEntry.Header ->
@@ -70,6 +84,15 @@ class ChatHistoryAdapter(
7084

7185
override fun areContentsTheSame(oldItem: ChatHistoryListEntry, newItem: ChatHistoryListEntry): Boolean =
7286
oldItem == newItem
87+
88+
override fun getChangePayload(oldItem: ChatHistoryListEntry, newItem: ChatHistoryListEntry): Any? {
89+
if (oldItem is ChatHistoryListEntry.Row && newItem is ChatHistoryListEntry.Row &&
90+
oldItem.item == newItem.item && oldItem.selected != newItem.selected
91+
) {
92+
return RowChange.SelectionChanged(newItem.selected)
93+
}
94+
return null
95+
}
7396
}
7497

7598
private companion object {

duckchat/duckchat-impl/src/main/java/com/duckduckgo/duckchat/impl/history/ChatHistoryViewHolder.kt

Lines changed: 32 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@ package com.duckduckgo.duckchat.impl.history
1919
import android.view.LayoutInflater
2020
import android.view.View
2121
import android.view.ViewGroup
22+
import android.view.animation.AccelerateInterpolator
23+
import android.view.animation.DecelerateInterpolator
2224
import android.widget.FrameLayout
2325
import android.widget.ImageView
2426
import androidx.recyclerview.widget.RecyclerView
@@ -41,7 +43,34 @@ class ChatHistoryViewHolder(
4143
onMoreClick: (ChatHistoryItem, View) -> Unit,
4244
onLongClick: (ChatHistoryItem) -> Boolean = { false },
4345
) {
46+
iconContainer.animate().cancel()
47+
iconContainer.rotationY = 0f
4448
title.text = item.displayTitle
49+
applySelectionState(item, selected)
50+
itemView.setOnClickListener { onClick(item) }
51+
itemView.setOnLongClickListener { onLongClick(item) }
52+
moreButton.setOnClickListener { anchor -> onMoreClick(item, anchor) }
53+
}
54+
55+
fun animateSelectionChange(item: ChatHistoryItem, selected: Boolean) {
56+
iconContainer.animate().cancel()
57+
iconContainer.animate()
58+
.rotationY(HALF_FLIP_DEGREES)
59+
.setDuration(HALF_FLIP_DURATION_MS)
60+
.setInterpolator(AccelerateInterpolator())
61+
.withEndAction {
62+
applySelectionState(item, selected)
63+
iconContainer.rotationY = -HALF_FLIP_DEGREES
64+
iconContainer.animate()
65+
.rotationY(0f)
66+
.setDuration(HALF_FLIP_DURATION_MS)
67+
.setInterpolator(DecelerateInterpolator())
68+
.start()
69+
}
70+
.start()
71+
}
72+
73+
private fun applySelectionState(item: ChatHistoryItem, selected: Boolean) {
4574
if (selected) {
4675
iconContainer.setBackgroundResource(R.drawable.bg_chat_history_circle_accent)
4776
typeIcon.setImageResource(com.duckduckgo.mobile.android.R.drawable.ic_check_24)
@@ -53,9 +82,6 @@ class ChatHistoryViewHolder(
5382
typeIcon.colorFilter = null
5483
iconContainer.contentDescription = null
5584
}
56-
itemView.setOnClickListener { onClick(item) }
57-
itemView.setOnLongClickListener { onLongClick(item) }
58-
moreButton.setOnClickListener { anchor -> onMoreClick(item, anchor) }
5985
}
6086

6187
private fun iconFor(type: ChatType, pinned: Boolean): Int = when (type) {
@@ -65,6 +91,9 @@ class ChatHistoryViewHolder(
6591
}
6692

6793
companion object {
94+
private const val HALF_FLIP_DEGREES = 90f
95+
private const val HALF_FLIP_DURATION_MS = 150L
96+
6897
fun create(parent: ViewGroup): ChatHistoryViewHolder {
6998
val view = LayoutInflater.from(parent.context)
7099
.inflate(R.layout.view_chat_history_item, parent, false)

0 commit comments

Comments
 (0)