From 3c1fd69e725d5ae64ceccdc3e64c8b2f06bd7fb7 Mon Sep 17 00:00:00 2001 From: Evgenii Kozlov Date: Thu, 5 Jun 2025 11:07:09 +0200 Subject: [PATCH] DROID-2966 Chats | Enhancement | Add haptic feedback for bubble clicks + group message of the same user (#2495) --- .../feature_chats/presentation/ChatView.kt | 1 + .../presentation/ChatViewModel.kt | 19 ++++++++++++++- .../anytype/feature_chats/ui/ChatBubble.kt | 23 +++++++++++++++---- .../anytype/feature_chats/ui/ChatScreen.kt | 1 + .../anytype/presentation/confgs/Confgs.kt | 2 ++ 5 files changed, 40 insertions(+), 6 deletions(-) diff --git a/feature-chats/src/main/java/com/anytypeio/anytype/feature_chats/presentation/ChatView.kt b/feature-chats/src/main/java/com/anytypeio/anytype/feature_chats/presentation/ChatView.kt index 0d262f9f3e..34068bfebf 100644 --- a/feature-chats/src/main/java/com/anytypeio/anytype/feature_chats/presentation/ChatView.kt +++ b/feature-chats/src/main/java/com/anytypeio/anytype/feature_chats/presentation/ChatView.kt @@ -28,6 +28,7 @@ sealed interface ChatView { val attachments: List = emptyList(), val reactions: List = emptyList(), val isUserAuthor: Boolean = false, + val shouldHideUsername: Boolean = false, val isEdited: Boolean = false, val avatar: Avatar = Avatar.Initials(), val reply: Reply? = null diff --git a/feature-chats/src/main/java/com/anytypeio/anytype/feature_chats/presentation/ChatViewModel.kt b/feature-chats/src/main/java/com/anytypeio/anytype/feature_chats/presentation/ChatViewModel.kt index 95abfebd5a..fa325020dc 100644 --- a/feature-chats/src/main/java/com/anytypeio/anytype/feature_chats/presentation/ChatViewModel.kt +++ b/feature-chats/src/main/java/com/anytypeio/anytype/feature_chats/presentation/ChatViewModel.kt @@ -191,8 +191,21 @@ class ChatViewModel @Inject constructor( Timber.d("DROID-2966 Intent from container: ${result.intent}") Timber.d("DROID-2966 Message results size from container: ${result.messages.size}") var previousDate: ChatView.DateSection? = null - val messageViews = buildList { + val messageViews = buildList { + + var prevCreator: String? = null + var prevDateInterval: Long = 0 + result.messages.forEach { msg -> + + val isPrevTimeIntervalBig = if (prevDateInterval > 0) { + (msg.createdAt - prevDateInterval) > ChatConfig.GROUPING_DATE_INTERVAL_IN_SECONDS + } else { + false + } + + val shouldHideUsername = prevCreator == msg.creator && !isPrevTimeIntervalBig + val allMembers = members.get() val member = allMembers.let { type -> when (type) { @@ -265,6 +278,7 @@ class ChatViewModel @Inject constructor( author = member?.name ?: msg.creator.takeLast(5), creator = member?.id, isUserAuthor = msg.creator == account, + shouldHideUsername = shouldHideUsername, isEdited = msg.modifiedAt > msg.createdAt, reactions = msg.reactions .toList() @@ -365,6 +379,9 @@ class ChatViewModel @Inject constructor( previousDate = currDate } add(view) + + prevCreator = msg.creator + prevDateInterval = msg.createdAt } }.reversed() ChatViewState( diff --git a/feature-chats/src/main/java/com/anytypeio/anytype/feature_chats/ui/ChatBubble.kt b/feature-chats/src/main/java/com/anytypeio/anytype/feature_chats/ui/ChatBubble.kt index 69032e1f0d..5b41347977 100644 --- a/feature-chats/src/main/java/com/anytypeio/anytype/feature_chats/ui/ChatBubble.kt +++ b/feature-chats/src/main/java/com/anytypeio/anytype/feature_chats/ui/ChatBubble.kt @@ -1,7 +1,9 @@ package com.anytypeio.anytype.feature_chats.ui +import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.background import androidx.compose.foundation.clickable +import androidx.compose.foundation.combinedClickable import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.IntrinsicSize @@ -32,8 +34,10 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.draw.alpha import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.Color +import androidx.compose.ui.hapticfeedback.HapticFeedbackType import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.platform.LocalHapticFeedback import androidx.compose.ui.res.colorResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.LinkAnnotation @@ -70,7 +74,7 @@ import com.anytypeio.anytype.core_utils.ext.formatTimeInMillis import com.anytypeio.anytype.feature_chats.R import com.anytypeio.anytype.feature_chats.presentation.ChatView -@OptIn(ExperimentalMaterial3Api::class) +@OptIn(ExperimentalMaterial3Api::class, ExperimentalFoundationApi::class) @Composable fun Bubble( modifier: Modifier = Modifier, @@ -80,6 +84,7 @@ fun Bubble( timestamp: Long, attachments: List = emptyList(), isUserAuthor: Boolean = false, + shouldHideUsername: Boolean = false, isEdited: Boolean = false, isMaxReactionCountReached: Boolean = false, reactions: List = emptyList(), @@ -96,6 +101,7 @@ fun Bubble( onMentionClicked: (Id) -> Unit, isReadOnly: Boolean = false ) { + val haptic = LocalHapticFeedback.current var showDropdownMenu by remember { mutableStateOf(false) } var showDeleteMessageWarning by remember { mutableStateOf(false) } if (showDeleteMessageWarning) { @@ -140,7 +146,7 @@ fun Bubble( ) } // Bubble username section - if (!isUserAuthor) { + if (!isUserAuthor && !shouldHideUsername) { Text( text = name.ifEmpty { stringResource(R.string.untitled) }, style = Caption1Medium, @@ -167,9 +173,15 @@ fun Bubble( shape = RoundedCornerShape(16.dp) ) .clip(RoundedCornerShape(16.dp)) - .clickable { - showDropdownMenu = !showDropdownMenu - } + .combinedClickable( + onClick = { + showDropdownMenu = !showDropdownMenu + }, + onLongClick = { + haptic.performHapticFeedback(HapticFeedbackType.LongPress) + showDropdownMenu = !showDropdownMenu + } + ) .padding(vertical = 4.dp) ) { BubbleAttachments( @@ -177,6 +189,7 @@ fun Bubble( isUserAuthor = isUserAuthor, onAttachmentClicked = onAttachmentClicked, onAttachmentLongClicked = { + haptic.performHapticFeedback(HapticFeedbackType.LongPress) showDropdownMenu = true } ) diff --git a/feature-chats/src/main/java/com/anytypeio/anytype/feature_chats/ui/ChatScreen.kt b/feature-chats/src/main/java/com/anytypeio/anytype/feature_chats/ui/ChatScreen.kt index 1f59e3d05e..8c0f7ab860 100644 --- a/feature-chats/src/main/java/com/anytypeio/anytype/feature_chats/ui/ChatScreen.kt +++ b/feature-chats/src/main/java/com/anytypeio/anytype/feature_chats/ui/ChatScreen.kt @@ -860,6 +860,7 @@ fun Messages( timestamp = msg.timestamp, attachments = msg.attachments, isUserAuthor = msg.isUserAuthor, + shouldHideUsername = msg.shouldHideUsername, isMaxReactionCountReached = msg.isMaxReactionCountReached, isEdited = msg.isEdited, onReacted = { emoji -> diff --git a/presentation/src/main/java/com/anytypeio/anytype/presentation/confgs/Confgs.kt b/presentation/src/main/java/com/anytypeio/anytype/presentation/confgs/Confgs.kt index 55c9e87952..deccce76d5 100644 --- a/presentation/src/main/java/com/anytypeio/anytype/presentation/confgs/Confgs.kt +++ b/presentation/src/main/java/com/anytypeio/anytype/presentation/confgs/Confgs.kt @@ -12,6 +12,8 @@ object ChatConfig { const val MAX_MESSAGE_CHARACTER_LIMIT = 2000 const val MAX_MESSAGE_CHARACTER_OFFSET_LIMIT = 1950 + const val GROUPING_DATE_INTERVAL_IN_SECONDS = 60 + /** * Spaces for beta-testing space-level chats */