Skip to content

Commit 6c4812a

Browse files
committed
feat(ui/message/list): animate items in
Signed-off-by: Brandon McAnsh <[email protected]>
1 parent 0954eaa commit 6c4812a

File tree

5 files changed

+31
-23
lines changed

5 files changed

+31
-23
lines changed

flipchatApp/src/main/kotlin/xyz/flipchat/app/features/chat/conversation/ConversationScreen.kt

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -159,8 +159,6 @@ data class ConversationScreen(
159159
}
160160

161161
val state by vm.stateFlow.collectAsState()
162-
val messages = vm.messages.collectAsLazyPagingItems()
163-
164162

165163
val goBack = {
166164
composeScope.launch {
@@ -219,6 +217,9 @@ data class ConversationScreen(
219217
AppBarDefaults.Overflow { openRoomDetails() }
220218
}
221219
)
220+
221+
val messages = vm.messages.collectAsLazyPagingItems()
222+
222223
ConversationScreenContent(
223224
state = state,
224225
messages = messages,

flipchatApp/src/main/kotlin/xyz/flipchat/app/features/chat/conversation/ConversationViewModel.kt

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -642,11 +642,7 @@ class ConversationViewModel @Inject constructor(
642642
.map { it.conversationId }
643643
.filterNotNull()
644644
.distinctUntilChanged()
645-
.flatMapLatest {
646-
roomController
647-
.messages(it).flow
648-
.cachedIn(viewModelScope)
649-
}
645+
.flatMapLatest { roomController.messages(it).flow }
650646
.map { page ->
651647
page.map { mwc ->
652648
if (mwc.message.isDeleted) {
@@ -750,7 +746,7 @@ class ConversationViewModel @Inject constructor(
750746
// No separator in other cases
751747
null
752748
}
753-
}
749+
}.cachedIn(viewModelScope)
754750

755751
private fun buildMessageActions(
756752
message: ConversationMessage,

ui/components/src/main/kotlin/com/getcode/ui/components/chat/MessageList.kt

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import androidx.compose.runtime.remember
1515
import androidx.compose.runtime.saveable.rememberSaveable
1616
import androidx.compose.runtime.setValue
1717
import androidx.compose.runtime.snapshotFlow
18+
import androidx.compose.runtime.snapshots.Snapshot
1819
import androidx.compose.ui.Modifier
1920
import androidx.compose.ui.platform.LocalDensity
2021
import androidx.compose.ui.text.TextStyle
@@ -30,6 +31,7 @@ import com.getcode.theme.CodeTheme
3031
import com.getcode.ui.components.chat.messagecontents.MessageControlAction
3132
import com.getcode.ui.components.chat.utils.ChatItem
3233
import com.getcode.ui.components.text.markup.Markup
34+
import com.getcode.ui.utils.addIf
3335
import com.getcode.util.formatDateRelatively
3436
import kotlinx.coroutines.flow.combine
3537
import kotlinx.coroutines.flow.distinctUntilChanged
@@ -100,7 +102,9 @@ fun MessageList(
100102
) { index ->
101103
when (val item = messages[index]) {
102104
is ChatItem.Date -> DateBubble(
103-
modifier = Modifier.padding(vertical = CodeTheme.dimens.grid.x2),
105+
modifier = Modifier
106+
.padding(vertical = CodeTheme.dimens.grid.x2)
107+
.animateItem(),
104108
date = item.dateString
105109
)
106110

@@ -173,7 +177,8 @@ fun MessageList(
173177
UnreadSeparator(
174178
modifier = Modifier
175179
.fillParentMaxWidth()
176-
.padding(vertical = CodeTheme.dimens.grid.x2),
180+
.padding(vertical = CodeTheme.dimens.grid.x2)
181+
.animateItem(),
177182
count = item.count
178183
)
179184
}
@@ -192,12 +197,27 @@ fun MessageList(
192197
date.formatDateRelatively()
193198
}
194199
DateBubble(
195-
modifier = Modifier.padding(bottom = CodeTheme.dimens.grid.x2),
200+
modifier = Modifier
201+
.padding(bottom = CodeTheme.dimens.grid.x2)
202+
.animateItem(),
196203
date = dateString
197204
)
198205
}
199206
}
200207
}
208+
209+
// opts out of the list maintaining
210+
// scroll position when adding elements before the first item
211+
// we are checking first visible item index to ensure
212+
// the list doesn't shift when viewing scroll back
213+
Snapshot.withoutReadObservation {
214+
if (listState.firstVisibleItemIndex == 0) {
215+
listState.requestScrollToItem(
216+
index = listState.firstVisibleItemIndex,
217+
scrollOffset = listState.firstVisibleItemScrollOffset
218+
)
219+
}
220+
}
201221
}
202222
}
203223

ui/components/src/main/kotlin/com/getcode/ui/components/chat/MessageNode.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -242,7 +242,7 @@ fun MessageNode(
242242

243243
val density = LocalDensity.current
244244

245-
BoxWithConstraints(modifier = Modifier.fillMaxWidth()) {
245+
BoxWithConstraints(modifier = modifier.fillMaxWidth()) {
246246
val maxWidth = maxWidth
247247
val swipeThreshold = with(density) { maxWidth.toPx() } * 0.40f
248248
var hasReachedThreshold by remember { mutableStateOf(false) }
@@ -295,7 +295,7 @@ fun MessageNode(
295295
}
296296

297297
Box(
298-
modifier = modifier
298+
modifier = Modifier
299299
.graphicsLayer { compositingStrategy = CompositingStrategy.Offscreen }
300300
.nestedScroll(nestedScrollConnection)
301301
.anchoredDraggable(

ui/components/src/main/kotlin/com/getcode/ui/components/chat/utils/HandleMessageChanges.kt

Lines changed: 1 addition & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -53,16 +53,7 @@ fun HandleMessageChanges(
5353
} else {
5454
listState.handleAndReplayAfter(300) {
5555
if (newMessage.date.toEpochMilliseconds() > lastMessageReceived) {
56-
if (!listState.canScrollBackward) {
57-
// Android 10 we have to utilize a mimic for IME nested scrolling
58-
// using the [LazyListState#isScrollInProgress] which animateScrollToItem triggers
59-
// thus causing the IME to be dismissed when we trigger the sync.
60-
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) {
61-
listState.scrollToItem(0)
62-
} else {
63-
listState.animateScrollToItem(0)
64-
}
65-
} else {
56+
if (listState.canScrollBackward) {
6657
if (newMessage.status == MessageStatus.Unknown) {
6758
onMessageDelivered(newMessage)
6859
}

0 commit comments

Comments
 (0)