mirror of
https://github.com/anyproto/anytype-kotlin.git
synced 2025-06-08 05:47:05 +09:00
DROID-2966 Chats | Tech | ChatPreviewContainer (#2446)
This commit is contained in:
parent
bd9ccaff50
commit
4783b8e79e
7 changed files with 272 additions and 7 deletions
|
@ -8,6 +8,8 @@ import com.anytypeio.anytype.domain.auth.repo.AuthRepository
|
|||
import com.anytypeio.anytype.domain.base.AppCoroutineDispatchers
|
||||
import com.anytypeio.anytype.domain.block.interactor.sets.GetObjectTypes
|
||||
import com.anytypeio.anytype.domain.block.repo.BlockRepository
|
||||
import com.anytypeio.anytype.domain.chats.ChatEventChannel
|
||||
import com.anytypeio.anytype.domain.chats.ChatPreviewContainer
|
||||
import com.anytypeio.anytype.domain.config.ConfigStorage
|
||||
import com.anytypeio.anytype.domain.debugging.DebugAccountSelectTrace
|
||||
import com.anytypeio.anytype.domain.debugging.Logger
|
||||
|
@ -238,6 +240,23 @@ object SubscriptionsModule {
|
|||
deviceTokenStoringService = deviceTokenStoringService
|
||||
)
|
||||
|
||||
@JvmStatic
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideChatPreviewContainer(
|
||||
@Named(DEFAULT_APP_COROUTINE_SCOPE) scope: CoroutineScope,
|
||||
dispatchers: AppCoroutineDispatchers,
|
||||
repo: BlockRepository,
|
||||
logger: Logger,
|
||||
events: ChatEventChannel
|
||||
): ChatPreviewContainer = ChatPreviewContainer.Default(
|
||||
repo = repo,
|
||||
dispatchers = dispatchers,
|
||||
scope = scope,
|
||||
logger = logger,
|
||||
events = events
|
||||
)
|
||||
|
||||
@JvmStatic
|
||||
@Provides
|
||||
@Singleton
|
||||
|
|
|
@ -322,6 +322,9 @@ sealed class Event {
|
|||
|
||||
sealed class Chats : Command() {
|
||||
|
||||
/**
|
||||
* @property [id] msg ID
|
||||
*/
|
||||
data class Add(
|
||||
override val context: Id,
|
||||
val id: Id,
|
||||
|
@ -329,6 +332,9 @@ sealed class Event {
|
|||
val message: Chat.Message
|
||||
) : Chats()
|
||||
|
||||
/**
|
||||
* @property [id] msg ID
|
||||
*/
|
||||
data class Update(
|
||||
override val context: Id,
|
||||
val id: Id,
|
||||
|
@ -337,9 +343,12 @@ sealed class Event {
|
|||
|
||||
data class Delete(
|
||||
override val context: Id,
|
||||
val id: Id
|
||||
val message: Id
|
||||
) : Chats()
|
||||
|
||||
/**
|
||||
* @property [id] msg ID
|
||||
*/
|
||||
data class UpdateReactions(
|
||||
override val context: Id,
|
||||
val id: Id,
|
||||
|
|
|
@ -7,6 +7,7 @@ import kotlinx.coroutines.flow.Flow
|
|||
|
||||
interface ChatEventRemoteChannel {
|
||||
fun observe(chat: Id): Flow<List<Event.Command.Chats>>
|
||||
fun subscribe(subscription: Id): Flow<List<Event.Command.Chats>>
|
||||
class Default(
|
||||
private val channel: ChatEventRemoteChannel
|
||||
) : ChatEventChannel {
|
||||
|
|
|
@ -478,7 +478,7 @@ class ChatContainer @Inject constructor(
|
|||
|
||||
is Event.Command.Chats.Update -> {
|
||||
if (messageList.isInCurrentWindow(event.id)) {
|
||||
val index = messageList.indexOfFirst { it.id == event.id }
|
||||
val index = messageList.indexOfFirst { it.id == event.message.id }
|
||||
messageList[index] = event.message
|
||||
}
|
||||
// Tracking the last message in the chat tail
|
||||
|
@ -486,12 +486,12 @@ class ChatContainer @Inject constructor(
|
|||
}
|
||||
|
||||
is Event.Command.Chats.Delete -> {
|
||||
if (messageList.isInCurrentWindow(event.id)) {
|
||||
val index = messageList.indexOfFirst { it.id == event.id }
|
||||
if (messageList.isInCurrentWindow(event.message)) {
|
||||
val index = messageList.indexOfFirst { it.id == event.message }
|
||||
messageList.removeAt(index)
|
||||
}
|
||||
// Tracking the last message in the chat tail
|
||||
lastMessages.remove(event.id)
|
||||
lastMessages.remove(event.message)
|
||||
}
|
||||
|
||||
is Event.Command.Chats.UpdateReactions -> {
|
||||
|
|
|
@ -0,0 +1,124 @@
|
|||
package com.anytypeio.anytype.domain.chats
|
||||
|
||||
import com.anytypeio.anytype.core_models.Event
|
||||
import com.anytypeio.anytype.core_models.chats.Chat
|
||||
import com.anytypeio.anytype.core_models.primitives.SpaceId
|
||||
import com.anytypeio.anytype.domain.base.AppCoroutineDispatchers
|
||||
import com.anytypeio.anytype.domain.block.repo.BlockRepository
|
||||
import com.anytypeio.anytype.domain.debugging.Logger
|
||||
import javax.inject.Inject
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.catch
|
||||
import kotlinx.coroutines.flow.flowOn
|
||||
import kotlinx.coroutines.flow.scan
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
/**
|
||||
* Cross-space (vault-level) chat previews container
|
||||
*/
|
||||
interface ChatPreviewContainer {
|
||||
|
||||
fun start()
|
||||
fun stop()
|
||||
|
||||
suspend fun getAll(): List<Chat.Preview>
|
||||
suspend fun getPreview(space: SpaceId): Chat.Preview?
|
||||
fun observePreviews() : Flow<List<Chat.Preview>>
|
||||
|
||||
class Default @Inject constructor(
|
||||
private val repo: BlockRepository,
|
||||
private val events: ChatEventChannel,
|
||||
private val dispatchers: AppCoroutineDispatchers,
|
||||
private val scope: CoroutineScope,
|
||||
private val logger: Logger
|
||||
) : ChatPreviewContainer {
|
||||
|
||||
private var job: Job? = null
|
||||
private val previews = MutableStateFlow<List<Chat.Preview>>(emptyList())
|
||||
|
||||
override fun start() {
|
||||
job?.cancel()
|
||||
job = scope.launch(dispatchers.io) {
|
||||
previews.value = emptyList()
|
||||
val initial = runCatching { repo.subscribeToMessagePreviews(SUBSCRIPTION_ID) }
|
||||
.onFailure { logger.logException(it, "DROID-2966 Error while getting initial previews") }
|
||||
.getOrDefault(emptyList())
|
||||
events
|
||||
.observe(SUBSCRIPTION_ID)
|
||||
.scan(initial = initial) { previews, events ->
|
||||
events.fold(previews) { state, event ->
|
||||
when (event) {
|
||||
is Event.Command.Chats.Update -> {
|
||||
state.map { preview ->
|
||||
if (preview.chat == event.context && preview.message?.id == event.id) {
|
||||
preview.copy(message = event.message)
|
||||
} else {
|
||||
preview
|
||||
}
|
||||
}
|
||||
}
|
||||
is Event.Command.Chats.UpdateState -> {
|
||||
state.map { preview ->
|
||||
if (preview.chat == event.context) {
|
||||
preview.copy(
|
||||
state = event.state
|
||||
)
|
||||
} else {
|
||||
preview
|
||||
}
|
||||
}
|
||||
}
|
||||
is Event.Command.Chats.Delete -> {
|
||||
state.map { preview ->
|
||||
if (preview.chat == event.context && preview.message?.id == event.message) {
|
||||
preview.copy(message = null)
|
||||
} else {
|
||||
preview
|
||||
}
|
||||
}
|
||||
}
|
||||
else -> state.also {
|
||||
logger.logInfo("DROID-2966 Ignoring event: $event")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.flowOn(dispatchers.io)
|
||||
.catch { logger.logException(it, "DROID-2966 Exception in chat preview flow") }
|
||||
.collect {
|
||||
previews.value = it
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun stop() {
|
||||
job?.cancel()
|
||||
job = null
|
||||
scope.launch(dispatchers.io) {
|
||||
previews.value = emptyList()
|
||||
runCatching {
|
||||
repo.unsubscribeFromMessagePreviews(subscription = SUBSCRIPTION_ID)
|
||||
}.onFailure {
|
||||
logger.logException(it, "DROID-2966 Error while unsubscribing from message previews")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun getAll(): List<Chat.Preview> = previews.value
|
||||
|
||||
override suspend fun getPreview(space: SpaceId): Chat.Preview? {
|
||||
return previews.value.firstOrNull { preview -> preview.space.id == space.id }
|
||||
}
|
||||
|
||||
override fun observePreviews(): Flow<List<Chat.Preview>> {
|
||||
return previews
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val SUBSCRIPTION_ID = "chat-previews-subscription"
|
||||
}
|
||||
}
|
||||
}
|
|
@ -299,7 +299,7 @@ fun anytype.Event.Message.toCoreModels(
|
|||
checkNotNull(event)
|
||||
Event.Command.Chats.Delete(
|
||||
context = context,
|
||||
id = event.id
|
||||
message = event.id
|
||||
)
|
||||
}
|
||||
chatUpdate != null -> {
|
||||
|
|
|
@ -24,6 +24,18 @@ class ChatEventMiddlewareChannel(
|
|||
events.isNotEmpty()
|
||||
}
|
||||
}
|
||||
|
||||
override fun subscribe(subscription: Id): Flow<List<Event.Command.Chats>> {
|
||||
return eventProxy
|
||||
.flow()
|
||||
.mapNotNull { item ->
|
||||
item.messages.mapNotNull { msg ->
|
||||
msg.payload(subscription = subscription, contextId = item.contextId)
|
||||
}
|
||||
}.filter { events ->
|
||||
events.isNotEmpty()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun MEventMessage.payload(contextId: Id) : Event.Command.Chats? {
|
||||
|
@ -78,7 +90,7 @@ fun MEventMessage.payload(contextId: Id) : Event.Command.Chats? {
|
|||
checkNotNull(event)
|
||||
Event.Command.Chats.Delete(
|
||||
context = contextId,
|
||||
id = event.id
|
||||
message = event.id
|
||||
)
|
||||
}
|
||||
chatUpdateReactions != null -> {
|
||||
|
@ -93,6 +105,106 @@ fun MEventMessage.payload(contextId: Id) : Event.Command.Chats? {
|
|||
)
|
||||
}
|
||||
|
||||
else -> {
|
||||
null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun MEventMessage.payload(subscription: Id, contextId: Id) : Event.Command.Chats? {
|
||||
return when {
|
||||
chatAdd != null -> {
|
||||
val event = chatAdd
|
||||
checkNotNull(event)
|
||||
if (event.subIds.contains(subscription)) {
|
||||
Event.Command.Chats.Add(
|
||||
context = contextId,
|
||||
order = event.orderId,
|
||||
id = event.id,
|
||||
message = requireNotNull(event.message?.core())
|
||||
)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
chatStateUpdate != null -> {
|
||||
val event = chatStateUpdate
|
||||
checkNotNull(event)
|
||||
if (event.subIds.contains(subscription)) {
|
||||
Event.Command.Chats.UpdateState(
|
||||
context = contextId,
|
||||
state = event.state?.core()
|
||||
)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
chatUpdateMessageReadStatus != null -> {
|
||||
val event = chatUpdateMessageReadStatus
|
||||
checkNotNull(event)
|
||||
if (event.subIds.contains(subscription)) {
|
||||
Event.Command.Chats.UpdateMessageReadStatus(
|
||||
context = contextId,
|
||||
messages = event.ids,
|
||||
isRead = event.isRead
|
||||
)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
chatUpdateMentionReadStatus != null -> {
|
||||
val event = chatUpdateMentionReadStatus
|
||||
checkNotNull(event)
|
||||
if (event.subIds.contains(subscription)) {
|
||||
Event.Command.Chats.UpdateMentionReadStatus(
|
||||
context = contextId,
|
||||
messages = event.ids,
|
||||
isRead = event.isRead
|
||||
)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
chatUpdate != null -> {
|
||||
val event = chatUpdate
|
||||
checkNotNull(event)
|
||||
if (event.subIds.contains(subscription)) {
|
||||
Event.Command.Chats.Update(
|
||||
context = contextId,
|
||||
id = event.id,
|
||||
message = requireNotNull(event.message?.core())
|
||||
)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
chatDelete != null -> {
|
||||
val event = chatDelete
|
||||
checkNotNull(event)
|
||||
if (event.subIds.contains(subscription)) {
|
||||
Event.Command.Chats.Delete(
|
||||
context = contextId,
|
||||
message = event.id
|
||||
)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
chatUpdateReactions != null -> {
|
||||
val event = chatUpdateReactions
|
||||
checkNotNull(event)
|
||||
if (event.subIds.contains(subscription)) {
|
||||
Event.Command.Chats.UpdateReactions(
|
||||
context = contextId,
|
||||
id = event.id,
|
||||
reactions = event.reactions?.reactions?.mapValues { (unicode, identities) ->
|
||||
identities.ids
|
||||
} ?: emptyMap()
|
||||
)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
else -> {
|
||||
null
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue