mirror of
https://github.com/anyproto/anytype-kotlin.git
synced 2025-06-07 21:37:02 +09:00
DROID-2966 Chats | Enhancement | Misc. design fixes (#2498)
This commit is contained in:
parent
7517060aa9
commit
f55144794a
7 changed files with 92 additions and 40 deletions
|
@ -1,8 +1,6 @@
|
|||
package com.anytypeio.anytype.domain.chats
|
||||
|
||||
import com.anytypeio.anytype.core_models.Command
|
||||
import com.anytypeio.anytype.core_models.DVFilter
|
||||
import com.anytypeio.anytype.core_models.DVFilterCondition
|
||||
import com.anytypeio.anytype.core_models.Event
|
||||
import com.anytypeio.anytype.core_models.Id
|
||||
import com.anytypeio.anytype.core_models.ObjectWrapper
|
||||
|
@ -12,12 +10,8 @@ import com.anytypeio.anytype.core_models.primitives.Space
|
|||
import com.anytypeio.anytype.domain.block.repo.BlockRepository
|
||||
import com.anytypeio.anytype.domain.debugging.Logger
|
||||
import com.anytypeio.anytype.domain.library.StoreSearchByIdsParams
|
||||
import com.anytypeio.anytype.domain.library.StoreSearchParams
|
||||
import com.anytypeio.anytype.domain.library.StorelessSubscriptionContainer
|
||||
import javax.inject.Inject
|
||||
import kotlin.collections.isNotEmpty
|
||||
import kotlin.collections.toList
|
||||
import kotlin.math.log
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||
|
@ -116,7 +110,7 @@ class ChatContainer @Inject constructor(
|
|||
listOf("$chat/$ATTACHMENT_SUBSCRIPTION_POSTFIX")
|
||||
)
|
||||
}.onFailure {
|
||||
logger.logWarning("DROID-2966 Error while unsubscribing from chat")
|
||||
logger.logWarning("DROID-2966 Error while unsubscribing from chat:\n${it.message}")
|
||||
}.onSuccess {
|
||||
logger.logInfo("DROID-2966 Successfully unsubscribed from chat")
|
||||
}
|
||||
|
@ -136,6 +130,8 @@ class ChatContainer @Inject constructor(
|
|||
|
||||
var intent: Intent = Intent.None
|
||||
|
||||
var initialUnreadSectionMessageId: Id? = null
|
||||
|
||||
val initial = buildList {
|
||||
if (initialState.hasUnReadMessages && !initialState.oldestMessageOrderId.isNullOrEmpty()) {
|
||||
// Starting from the unread-messages window.
|
||||
|
@ -147,8 +143,10 @@ class ChatContainer @Inject constructor(
|
|||
if (target != null) {
|
||||
intent = Intent.ScrollToMessage(
|
||||
id = target.id,
|
||||
smooth = false
|
||||
smooth = false,
|
||||
startOfUnreadMessageSection = true
|
||||
)
|
||||
initialUnreadSectionMessageId = target.id
|
||||
}
|
||||
}
|
||||
addAll(aroundUnread)
|
||||
|
@ -169,7 +167,8 @@ class ChatContainer @Inject constructor(
|
|||
initial = ChatStreamState(
|
||||
messages = initial,
|
||||
state = initialState,
|
||||
intent = intent
|
||||
intent = intent,
|
||||
initialUnreadSectionMessageId = initialUnreadSectionMessageId
|
||||
)
|
||||
) { state, transform ->
|
||||
when (transform) {
|
||||
|
@ -184,7 +183,8 @@ class ChatContainer @Inject constructor(
|
|||
ChatStreamState(
|
||||
messages = loadTheNextPage(state.messages, chat),
|
||||
intent = Intent.None,
|
||||
state = state.state
|
||||
state = state.state,
|
||||
initialUnreadSectionMessageId = null
|
||||
)
|
||||
}
|
||||
is Transformation.Commands.LoadAround -> {
|
||||
|
@ -230,7 +230,8 @@ class ChatContainer @Inject constructor(
|
|||
ChatStreamState(
|
||||
messages = messages,
|
||||
intent = Intent.ScrollToBottom,
|
||||
state = state.state
|
||||
state = state.state,
|
||||
initialUnreadSectionMessageId = initialUnreadSectionMessageId
|
||||
)
|
||||
} else {
|
||||
val messages = try {
|
||||
|
@ -243,7 +244,8 @@ class ChatContainer @Inject constructor(
|
|||
ChatStreamState(
|
||||
messages = messages,
|
||||
intent = Intent.ScrollToBottom,
|
||||
state = state.state
|
||||
state = state.state,
|
||||
initialUnreadSectionMessageId = initialUnreadSectionMessageId
|
||||
)
|
||||
}
|
||||
} else {
|
||||
|
@ -257,7 +259,8 @@ class ChatContainer @Inject constructor(
|
|||
ChatStreamState(
|
||||
messages = messages,
|
||||
intent = Intent.ScrollToBottom,
|
||||
state = state.state
|
||||
state = state.state,
|
||||
initialUnreadSectionMessageId = null
|
||||
)
|
||||
}
|
||||
} else {
|
||||
|
@ -281,7 +284,8 @@ class ChatContainer @Inject constructor(
|
|||
ChatStreamState(
|
||||
messages = messages,
|
||||
intent = Intent.ScrollToBottom,
|
||||
state = state.state
|
||||
state = state.state,
|
||||
initialUnreadSectionMessageId = null
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -589,7 +593,8 @@ class ChatContainer @Inject constructor(
|
|||
|
||||
return ChatStreamState(
|
||||
messages = messageList,
|
||||
state = countersState
|
||||
state = countersState,
|
||||
initialUnreadSectionMessageId = initialUnreadSectionMessageId
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -711,11 +716,13 @@ class ChatContainer @Inject constructor(
|
|||
|
||||
/**
|
||||
* Messages sorted — from the oldest to the latest.
|
||||
* @property [initialUnreadSectionMessageId] used when opening chat with unread messages.
|
||||
*/
|
||||
data class ChatStreamState(
|
||||
val messages: List<Chat.Message>,
|
||||
val state: Chat.State = Chat.State(),
|
||||
val intent: Intent = Intent.None
|
||||
val intent: Intent = Intent.None,
|
||||
val initialUnreadSectionMessageId: String? = null
|
||||
)
|
||||
|
||||
sealed class Intent {
|
||||
|
@ -727,7 +734,11 @@ class ChatContainer @Inject constructor(
|
|||
* Defaults to `false` for performance reasons, as smooth scrolling may introduce
|
||||
* delays or unnecessary animations in certain scenarios.
|
||||
*/
|
||||
data class ScrollToMessage(val id: Id, val smooth: Boolean = false) : Intent()
|
||||
data class ScrollToMessage(
|
||||
val id: Id,
|
||||
val smooth: Boolean = false,
|
||||
val startOfUnreadMessageSection: Boolean = false
|
||||
) : Intent()
|
||||
data class Highlight(val id: Id) : Intent()
|
||||
data object ScrollToBottom : Intent()
|
||||
data object None : Intent()
|
||||
|
|
|
@ -31,7 +31,8 @@ sealed interface ChatView {
|
|||
val shouldHideUsername: Boolean = false,
|
||||
val isEdited: Boolean = false,
|
||||
val avatar: Avatar = Avatar.Initials(),
|
||||
val reply: Reply? = null
|
||||
val reply: Reply? = null,
|
||||
val startOfUnreadMessageSection: Boolean = false
|
||||
) : ChatView {
|
||||
|
||||
val isMaxReactionCountReached: Boolean =
|
||||
|
|
|
@ -190,7 +190,7 @@ class ChatViewModel @Inject constructor(
|
|||
chatContainer.subscribeToAttachments(vmParams.ctx, vmParams.space).distinctUntilChanged(),
|
||||
chatContainer.fetchReplies(chat = chat).distinctUntilChanged()
|
||||
) { result, dependencies, replies ->
|
||||
Timber.d("DROID-2966 Chat counter state from container: ${result.state}")
|
||||
Timber.d("DROID-2966 Chat counter state from container: ${result.state}, unread section: ${result.initialUnreadSectionMessageId}")
|
||||
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
|
||||
|
@ -371,7 +371,8 @@ class ChatViewModel @Inject constructor(
|
|||
)
|
||||
} else {
|
||||
ChatView.Message.Avatar.Initials(member?.name.orEmpty())
|
||||
}
|
||||
},
|
||||
startOfUnreadMessageSection = result.initialUnreadSectionMessageId == msg.id
|
||||
)
|
||||
val currDate = ChatView.DateSection(
|
||||
formattedDate = dateFormatter.format(msg.createdAt * 1000),
|
||||
|
|
|
@ -1,14 +1,11 @@
|
|||
package com.anytypeio.anytype.feature_chats.ui
|
||||
|
||||
import android.content.res.Configuration
|
||||
import androidx.compose.foundation.ExperimentalFoundationApi
|
||||
import androidx.compose.foundation.Image
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.border
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.combinedClickable
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.ColumnScope
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.aspectRatio
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
|
@ -18,22 +15,18 @@ import androidx.compose.foundation.layout.size
|
|||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material.Text
|
||||
import androidx.compose.material3.Card
|
||||
import androidx.compose.material3.CardColors
|
||||
import androidx.compose.material3.CardDefaults
|
||||
import androidx.compose.material3.CircularProgressIndicator
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.layout.ContentScale
|
||||
import androidx.compose.ui.res.colorResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import coil3.compose.rememberAsyncImagePainter
|
||||
import com.anytypeio.anytype.core_models.Url
|
||||
import com.anytypeio.anytype.core_ui.common.DefaultPreviews
|
||||
import com.anytypeio.anytype.core_ui.views.PreviewTitle2Medium
|
||||
import com.anytypeio.anytype.core_ui.views.Relations3
|
||||
|
@ -46,6 +39,7 @@ import com.bumptech.glide.integration.compose.ExperimentalGlideComposeApi
|
|||
import com.bumptech.glide.integration.compose.GlideImage
|
||||
import com.bumptech.glide.load.DecodeFormat
|
||||
import com.bumptech.glide.load.engine.DiskCacheStrategy
|
||||
|
||||
@Composable
|
||||
@OptIn(ExperimentalGlideComposeApi::class, ExperimentalFoundationApi::class)
|
||||
fun BubbleAttachments(
|
||||
|
@ -266,21 +260,27 @@ fun Bookmark(
|
|||
text = url,
|
||||
modifier = Modifier.padding(horizontal = 12.dp),
|
||||
style = Relations3,
|
||||
color = colorResource(R.color.transparent_active)
|
||||
color = colorResource(R.color.transparent_active),
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis
|
||||
)
|
||||
Spacer(modifier = Modifier.height(2.dp))
|
||||
Text(
|
||||
text = title,
|
||||
modifier = Modifier.padding(horizontal = 12.dp),
|
||||
style = Title2,
|
||||
color = colorResource(R.color.text_primary)
|
||||
color = colorResource(R.color.text_primary),
|
||||
maxLines = 2,
|
||||
overflow = TextOverflow.Ellipsis
|
||||
)
|
||||
Spacer(modifier = Modifier.height(2.dp))
|
||||
Text(
|
||||
text = description,
|
||||
modifier = Modifier.padding(horizontal = 12.dp),
|
||||
style = Relations3,
|
||||
color = colorResource(R.color.transparent_active)
|
||||
color = colorResource(R.color.transparent_active),
|
||||
maxLines = 3,
|
||||
overflow = TextOverflow.Ellipsis
|
||||
)
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
}
|
||||
|
|
|
@ -29,6 +29,7 @@ import androidx.compose.foundation.layout.width
|
|||
import androidx.compose.foundation.shape.CircleShape
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.foundation.text.BasicTextField
|
||||
import androidx.compose.foundation.text.KeyboardOptions
|
||||
import androidx.compose.material.DropdownMenu
|
||||
import androidx.compose.material.MaterialTheme
|
||||
import androidx.compose.material.Text
|
||||
|
@ -56,6 +57,7 @@ import androidx.compose.ui.text.TextRange
|
|||
import androidx.compose.ui.text.font.FontFamily
|
||||
import androidx.compose.ui.text.font.FontStyle
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.text.input.KeyboardCapitalization
|
||||
import androidx.compose.ui.text.input.TextFieldValue
|
||||
import androidx.compose.ui.text.style.TextDecoration
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
|
@ -581,7 +583,10 @@ private fun ChatBoxUserInput(
|
|||
textStyle = ContentMiscChat.copy(color = colorResource(R.color.text_tertiary))
|
||||
)
|
||||
},
|
||||
visualTransformation = AnnotatedTextTransformation(spans)
|
||||
visualTransformation = AnnotatedTextTransformation(spans),
|
||||
keyboardOptions = KeyboardOptions(
|
||||
capitalization = KeyboardCapitalization.Sentences
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
@ -3,6 +3,7 @@ package com.anytypeio.anytype.feature_chats.ui
|
|||
import android.content.res.Configuration
|
||||
import android.net.Uri
|
||||
import android.provider.OpenableColumns
|
||||
import androidx.compose.foundation.ExperimentalFoundationApi
|
||||
import androidx.compose.foundation.Image
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.clickable
|
||||
|
@ -405,6 +406,8 @@ fun ChatScreen(
|
|||
|
||||
val isPerformingScrollIntent = remember { mutableStateOf(false) }
|
||||
|
||||
val offsetPx = with(LocalDensity.current) { 50.dp.toPx().toInt() }
|
||||
|
||||
// Applying view model intents
|
||||
LaunchedEffect(intent) {
|
||||
when (intent) {
|
||||
|
@ -419,7 +422,11 @@ fun ChatScreen(
|
|||
if (intent.smooth) {
|
||||
lazyListState.animateScrollToItem(index)
|
||||
} else {
|
||||
lazyListState.scrollToItem(index)
|
||||
if (intent.startOfUnreadMessageSection) {
|
||||
lazyListState.scrollToItem(index, scrollOffset = -offsetPx)
|
||||
} else {
|
||||
lazyListState.scrollToItem(index)
|
||||
}
|
||||
}
|
||||
awaitFrame()
|
||||
} else {
|
||||
|
@ -597,10 +604,12 @@ fun ChatScreen(
|
|||
) {
|
||||
Text(
|
||||
text = counter.mentions.toString(),
|
||||
modifier = Modifier.align(Alignment.Center).padding(
|
||||
horizontal = 5.dp,
|
||||
vertical = 2.dp
|
||||
),
|
||||
modifier = Modifier
|
||||
.align(Alignment.Center)
|
||||
.padding(
|
||||
horizontal = 5.dp,
|
||||
vertical = 2.dp
|
||||
),
|
||||
color = colorResource(R.color.glyph_white),
|
||||
style = Caption1Regular
|
||||
)
|
||||
|
@ -642,10 +651,12 @@ fun ChatScreen(
|
|||
) {
|
||||
Text(
|
||||
text = counter.messages.toString(),
|
||||
modifier = Modifier.align(Alignment.Center).padding(
|
||||
horizontal = 5.dp,
|
||||
vertical = 2.dp
|
||||
),
|
||||
modifier = Modifier
|
||||
.align(Alignment.Center)
|
||||
.padding(
|
||||
horizontal = 5.dp,
|
||||
vertical = 2.dp
|
||||
),
|
||||
color = colorResource(R.color.glyph_white),
|
||||
style = Caption1Regular
|
||||
)
|
||||
|
@ -704,6 +715,7 @@ fun ChatScreen(
|
|||
end = span.end + lengthDifference
|
||||
)
|
||||
}
|
||||
|
||||
is ChatBoxSpan.Markup -> {
|
||||
span.copy(
|
||||
start = span.start + lengthDifference,
|
||||
|
@ -801,6 +813,7 @@ fun ChatScreen(
|
|||
}
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalFoundationApi::class)
|
||||
@Composable
|
||||
fun Messages(
|
||||
modifier: Modifier = Modifier,
|
||||
|
@ -824,6 +837,7 @@ fun Messages(
|
|||
) {
|
||||
// Timber.d("DROID-2966 Messages composition: ${messages.map { if (it is ChatView.Message) it.content.msg else it }}")
|
||||
val scope = rememberCoroutineScope()
|
||||
|
||||
LazyColumn(
|
||||
modifier = modifier,
|
||||
reverseLayout = true,
|
||||
|
@ -917,6 +931,25 @@ fun Messages(
|
|||
if (idx == messages.lastIndex) {
|
||||
Spacer(modifier = Modifier.height(36.dp))
|
||||
}
|
||||
|
||||
if (msg.startOfUnreadMessageSection) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.height(50.dp)
|
||||
.fillParentMaxWidth()
|
||||
) {
|
||||
Text(
|
||||
text = stringResource(R.string.chat_new_messages_section_text),
|
||||
modifier = Modifier
|
||||
.align(Alignment.Center)
|
||||
.fillMaxWidth(),
|
||||
style = Caption1Medium,
|
||||
color = colorResource(R.color.transparent_active),
|
||||
textAlign = TextAlign.Center
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
} else if (msg is ChatView.DateSection) {
|
||||
Text(
|
||||
text = msg.formattedDate,
|
||||
|
|
|
@ -2085,5 +2085,6 @@ Please provide specific details of your needs here.</string>
|
|||
<string name="vault_create_space_description">For organized content and data</string>
|
||||
|
||||
<string name="vault_empty_state_text">There are no spaces yet</string>
|
||||
<string name="chat_new_messages_section_text">New messages</string>
|
||||
|
||||
</resources>
|
Loading…
Add table
Add a link
Reference in a new issue