11package com.getcode.network
22
3- import androidx.paging.Pager
4- import androidx.paging.PagingConfig
53import androidx.paging.PagingData
64import androidx.paging.PagingSource
7- import androidx.paging.cachedIn
85import com.getcode.db.AppDatabase
96import com.getcode.db.Database
107import com.getcode.ed25519.Ed25519.KeyPair
118import com.getcode.manager.SessionManager
129import com.getcode.mapper.ConversationMapper
1310import com.getcode.mapper.ConversationMessageMapper
14- import com.getcode.model.Conversation
15- import com.getcode.model.chat.Chat
16- import com.getcode.model.chat.ChatMessage
1711import com.getcode.model.Cursor
1812import com.getcode.model.ID
1913import com.getcode.model.MessageStatus
2014import com.getcode.model.chat.ChatMember
15+ import com.getcode.model.chat.ChatMessage
16+ import com.getcode.model.chat.ConversationEntity
2117import com.getcode.model.chat.Identity
2218import com.getcode.model.chat.Platform
2319import com.getcode.model.chat.Title
2420import com.getcode.model.chat.isConversation
25- import com.getcode.model.chat.isNotification
2621import com.getcode.model.chat.selfId
2722import com.getcode.network.client.Client
2823import com.getcode.network.client.advancePointer
29- import com.getcode.network.client.fetchChats
3024import com.getcode.network.client.fetchMessagesFor
31- import com.getcode.network.client.setMuted
32- import com.getcode.network.client.setSubscriptionState
25+ import com.getcode.network.client.fetchV2Chats
3326import com.getcode.network.repository.encodeBase64
34- import com.getcode.network.source.ChatMessagePagingSource
35- import com.getcode.util.resources.ResourceHelper
36- import com.getcode.util.resources.ResourceType
3727import com.getcode.utils.TraceType
3828import com.getcode.utils.trace
3929import kotlinx.coroutines.CoroutineScope
4030import kotlinx.coroutines.Dispatchers
41- import kotlinx.coroutines.GlobalScope
4231import kotlinx.coroutines.flow.Flow
4332import kotlinx.coroutines.flow.MutableStateFlow
4433import kotlinx.coroutines.flow.SharingStarted
@@ -48,25 +37,20 @@ import kotlinx.coroutines.flow.map
4837import kotlinx.coroutines.flow.stateIn
4938import kotlinx.coroutines.flow.update
5039import timber.log.Timber
51- import java.util.Locale
5240import javax.inject.Inject
5341import javax.inject.Singleton
5442
5543@Singleton
5644class ChatHistoryController @Inject constructor(
5745 private val client : Client ,
58- private val tipController : TipController ,
46+ private val twitterUserController : TwitterUserController ,
5947 private val conversationMapper : ConversationMapper ,
6048 private val conversationMessageMapper : ConversationMessageMapper ,
6149) : CoroutineScope by CoroutineScope(Dispatchers .IO ) {
6250
63- private val chatEntries = MutableStateFlow <List <Chat >? > (null )
64- val notifications: StateFlow <List <Chat >? >
65- get() = chatEntries
66- .map { it?.filter { entry -> entry.isNotification } }
67- .stateIn(this , SharingStarted .Eagerly , emptyList())
51+ private val chatEntries = MutableStateFlow <List <ConversationEntity >? > (null )
6852
69- val chats: StateFlow <List <Chat >? >
53+ val chats: StateFlow <List <ConversationEntity >? >
7054 get() = chatEntries
7155 .map { it?.filter { entry -> entry.isConversation } }
7256 .stateIn(this , SharingStarted .Eagerly , emptyList())
@@ -78,28 +62,12 @@ class ChatHistoryController @Inject constructor(
7862 private val pagerMap = mutableMapOf<ID , PagingSource <Cursor , ChatMessage >>()
7963 private val chatFlows = mutableMapOf<ID , Flow <PagingData <ChatMessage >>>()
8064
81- private val pagingConfig = PagingConfig (pageSize = 20 )
82-
8365 fun reset () {
8466 pagerMap.clear()
8567 chatFlows.clear()
8668 }
8769
88- private fun chatMessagePager (chatId : ID ) = Pager (pagingConfig) {
89- pagerMap[chatId] ? : ChatMessagePagingSource (
90- client = client,
91- owner = owner()!! ,
92- chat = chatEntries.value?.find { it.id == chatId },
93- onMessagesFetched = { messages ->
94- val chat = chatEntries.value?.find { it.id == chatId } ? : return @ChatMessagePagingSource
95- updateChatWithMessages(chat, messages)
96- }
97- ).also {
98- pagerMap[chatId] = it
99- }
100- }
101-
102- fun updateChatWithMessages (chat : Chat , messages : List <ChatMessage >) {
70+ fun updateChatWithMessages (chat : ConversationEntity , messages : List <ChatMessage >) {
10371 val updatedMessages = (chat.messages + messages).distinctBy { it.id }
10472 val updatedChat = chat.copy(messages = updatedMessages)
10573 val chats = chatEntries.value?.map {
@@ -112,29 +80,18 @@ class ChatHistoryController @Inject constructor(
11280 chatEntries.update { chats }
11381 }
11482
115- fun chatFlow (chatId : ID ) =
116- chatFlows[chatId] ? : chatMessagePager(chatId).flow.cachedIn(GlobalScope ).also {
117- chatFlows[chatId] = it
118- }
119-
120- val notificationsUnreadCount = notifications
121- .filterNotNull()
122- // Ignore muted chats and unsubscribed chats
123- .map { it.filter { c -> ! c.isMuted && c.isSubscribed } }
124- .map { it.sumOf { c -> c.unreadCount } }
125-
126- val chatUnreadCount = chats
83+ val unreadCount = chats
12784 .filterNotNull()
12885 // Ignore muted chats and unsubscribed chats
12986 .map { it.filter { c -> ! c.isMuted && c.isSubscribed } }
13087 .map { it.sumOf { c -> c.unreadCount } }
13188
13289 private fun owner (): KeyPair ? = SessionManager .getKeyPair()
13390
134- suspend fun fetchChats (update : Boolean = false) {
91+ suspend fun fetch (update : Boolean = false) {
13592 if (loadingMessages) return
13693
137- val updatedWithMessages = mutableListOf<Chat >()
94+ val updatedWithMessages = mutableListOf<ConversationEntity >()
13895 val containers = fetchChatsWithoutMessages()
13996 trace(message = " Fetched ${containers.count()} chats" , type = TraceType .Silent )
14097
@@ -163,40 +120,15 @@ class ChatHistoryController @Inject constructor(
163120 chatEntries.value = updatedWithMessages.sortedByDescending { it.lastMessageMillis }
164121 }
165122
166- fun addChat (chat : Chat ) {
123+ fun addChat (chat : ConversationEntity ) {
167124 chatEntries.value = (chatEntries.value.orEmpty() + chat)
168125 .sortedByDescending { it.lastMessageMillis }
169126 }
170127
171- fun findChat (predicate : (Chat ) -> Boolean ): Chat ? {
128+ fun findChat (predicate : (ConversationEntity ) -> Boolean ): ConversationEntity ? {
172129 return chatEntries.value?.firstOrNull(predicate)
173130 }
174131
175- suspend fun advanceReadPointer (chatId : ID ) {
176- val owner = owner() ? : return
177-
178- chatEntries.update {
179- it?.toMutableList()?.apply chats@{
180- indexOfFirst { chat -> chat.id == chatId }
181- .takeIf { index -> index >= 0 }
182- ?.let { index ->
183- val chat = this [index]
184- val newestMessage = chat.newestMessage
185- if (newestMessage != null ) {
186- client.advancePointer(
187- owner = owner,
188- chat = chat,
189- to = newestMessage.id,
190- status = MessageStatus .Read
191- ).onSuccess {
192- this [index] = chat.resetUnreadCount()
193- }
194- }
195- }
196- }?.toList()
197- }
198- }
199-
200132 fun resetUnreadCount (chatId : ID ) {
201133 chatEntries.update {
202134 it?.toMutableList()?.apply chats@{
@@ -210,43 +142,7 @@ class ChatHistoryController @Inject constructor(
210142 }
211143 }
212144
213- suspend fun setMuted (chat : Chat , muted : Boolean ): Result <Boolean > {
214- val owner = owner() ? : return Result .failure(Throwable (" No owner detected" ))
215-
216- chatEntries.update {
217- it?.toMutableList()?.apply chats@{
218- indexOfFirst { item -> item.id == chat.id }
219- .takeIf { index -> index >= 0 }
220- ?.let { index ->
221- val c = this [index]
222- Timber .d(" changing mute state for chat locally" )
223- this [index] = c.setMuteState(muted)
224- }
225- }?.toList()
226- }
227-
228- return client.setMuted(owner, chat, muted)
229- }
230-
231- suspend fun setSubscribed (chat : Chat , subscribed : Boolean ): Result <Boolean > {
232- val owner = owner() ? : return Result .failure(Throwable (" No owner detected" ))
233-
234- chatEntries.update {
235- it?.toMutableList()?.apply chats@{
236- indexOfFirst { item -> item.id == chat.id }
237- .takeIf { index -> index >= 0 }
238- ?.let { index ->
239- val c = this [index]
240- Timber .d(" changing subscribed state for chat locally" )
241- this [index] = c.setSubscriptionState(subscribed)
242- }
243- }?.toList()
244- }
245-
246- return client.setSubscriptionState(owner, chat, subscribed)
247- }
248-
249- private suspend fun fetchLatestMessageForChat (chat : Chat ): Result <ChatMessage ?> {
145+ private suspend fun fetchLatestMessageForChat (chat : ConversationEntity ): Result <ChatMessage ?> {
250146 val encodedId = chat.id.toByteArray().encodeBase64()
251147 Timber .d(" fetching last message for $encodedId " )
252148 val owner = owner() ? : return Result .success(null )
@@ -271,9 +167,9 @@ class ChatHistoryController @Inject constructor(
271167 }.map { it.getOrNull(0 ) }
272168 }
273169
274- private suspend fun fetchChatsWithoutMessages (): List <Chat > {
170+ private suspend fun fetchChatsWithoutMessages (): List <ConversationEntity > {
275171 val owner = owner() ? : return emptyList()
276- val result = client.fetchChats (owner)
172+ val result = client.fetchV2Chats (owner)
277173 .map { chats ->
278174 chats.map { chat ->
279175 // map revealed identity as title if known
@@ -303,14 +199,14 @@ class ChatHistoryController @Inject constructor(
303199 return result.getOrNull().orEmpty()
304200 }
305201
306- private suspend fun fetchMemberImages (chat : Chat ): List <ChatMember > {
202+ private suspend fun fetchMemberImages (chat : ConversationEntity ): List <ChatMember > {
307203 return chat.members
308204 .map { member ->
309205 if (member.isSelf) return @map member
310206 if (member.identity == null ) return @map member
311207 if (member.identity.imageUrl != null ) return @map member
312208 val metadata = runCatching {
313- tipController.fetch (member.identity.username)
209+ twitterUserController.fetchUser (member.identity.username)
314210 }.getOrNull() ? : return @map member
315211
316212 member.copy(
@@ -322,23 +218,4 @@ class ChatHistoryController @Inject constructor(
322218 )
323219 }
324220 }
325- }
326-
327- fun Title?.localized (resources : ResourceHelper ): String {
328- return when (val t = this ) {
329- is Title .Domain -> {
330- t.value.capitalize(Locale .getDefault())
331- }
332-
333- is Title .Localized -> {
334- val resId = resources.getIdentifier(
335- t.value,
336- ResourceType .String ,
337- ).let { if (it == 0 ) null else it }
338-
339- resId?.let { resources.getString(it) } ? : t.value
340- }
341-
342- else -> " Anonymous"
343- }
344221}
0 commit comments