Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,8 @@ class ChatsListRepositoryImpl(

private val modelCache = SynchronizedLruMap<Long, ChatModel>(MODEL_CACHE_SIZE)
private val invalidatedModels = ConcurrentHashMap.newKeySet<Long>()
@Volatile
private var invalidateAllModels = true
private var lastList: List<ChatModel>? = null
private var lastListFolderId: Int = -1

Expand Down Expand Up @@ -293,7 +295,11 @@ class ChatsListRepositoryImpl(
}

private fun rebuildChatModels(limit: Int): List<ChatModel> {
return listManager.rebuildChatList(limit, emptyList()) { chat, order, isPinned ->
if (!invalidateAllModels) {
rebuildVisibleModels(limit)?.let { return it }
}

val rebuilt = listManager.rebuildChatList(limit, emptyList()) { chat, order, isPinned ->
val cached = modelCache[chat.id]
if (cached != null &&
cached.order == order &&
Expand All @@ -308,6 +314,37 @@ class ChatsListRepositoryImpl(
}
}
}
invalidatedModels.clear()
invalidateAllModels = false
return rebuilt
}

private fun rebuildVisibleModels(limit: Int): List<ChatModel>? {
val previous = lastList ?: return null
if (lastListFolderId != activeFolderId) return null
if (invalidatedModels.isEmpty()) return previous

val visibleIndexes = previous.mapIndexed { index, chat -> chat.id to index }.toMap()
val updated = previous.toMutableList()

for (chatId in invalidatedModels.toList()) {
val index = visibleIndexes[chatId] ?: return null
val chat = cache.allChats[chatId] ?: return null
val position = cache.activeListPositions[chatId] ?: return null
val oldModel = previous[index]
if (oldModel.order != position.order || oldModel.isPinned != position.isPinned) {
return null
}
updated[index] = modelFactory.mapChatToModel(chat, position.order, position.isPinned).also { mapped ->
modelCache[chatId] = mapped
}
invalidatedModels.remove(chatId)
}

if (updated.size > limit) {
return null
}
return updated
}

private fun shouldEmitList(folderId: Int, newList: List<ChatModel>): Boolean {
Expand All @@ -324,6 +361,7 @@ class ChatsListRepositoryImpl(
private fun clearTransientState() {
modelCache.clear()
invalidatedModels.clear()
invalidateAllModels = true
lastList = null
lastListFolderId = -1
_chatListFlow.value = emptyList()
Expand All @@ -332,6 +370,7 @@ class ChatsListRepositoryImpl(

private fun triggerUpdate(chatId: Long? = null) {
if (chatId == null) {
invalidateAllModels = true
invalidatedModels.addAll(cache.activeListPositions.keys)
} else {
invalidatedModels.add(chatId)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,18 @@
package org.monogram.presentation.features.chats

import androidx.compose.runtime.Immutable
import kotlinx.coroutines.flow.StateFlow
import org.monogram.domain.models.*
import org.monogram.domain.repository.ConnectionStatus
import org.monogram.presentation.core.util.AppPreferences

interface ChatListComponent {
val state: StateFlow<State>
val uiState: StateFlow<UiState>
val foldersState: StateFlow<FoldersState>
val chatsState: StateFlow<ChatsState>
val selectionState: StateFlow<SelectionState>
val searchState: StateFlow<SearchState>

val appPreferences: AppPreferences

fun onChatClicked(id: Long)
Expand Down Expand Up @@ -52,26 +58,14 @@ interface ChatListComponent {

fun updateScrollPosition(folderId: Int, index: Int, offset: Int)

data class State(
val chatsByFolder: Map<Int, List<ChatModel>> = emptyMap(),
val folders: List<FolderModel> = emptyList(),
val selectedFolderId: Int = -1,
@Immutable
data class UiState(
val currentUser: UserModel? = null,
val isLoadingByFolder: Map<Int, Boolean> = emptyMap(),
val selectedChatIds: Set<Long> = emptySet(),
val isSearchActive: Boolean = false,
val searchQuery: String = "",
val searchResults: List<ChatModel> = emptyList(),
val globalSearchResults: List<ChatModel> = emptyList(),
val messageSearchResults: List<MessageModel> = emptyList(),
val searchHistory: List<ChatModel> = emptyList(),
val connectionStatus: ConnectionStatus = ConnectionStatus.Connected,
val isArchivePinned: Boolean = true,
val isArchiveAlwaysVisible: Boolean = false,
val isForwarding: Boolean = false,
val canLoadMoreMessages: Boolean = false,
val instantViewUrl: String? = null,
val activeChatId: Long? = null,
val isProxyEnabled: Boolean = false,
val attachMenuBots: List<AttachMenuBotModel> = emptyList(),
val botWebAppUrl: String? = null,
Expand All @@ -80,10 +74,38 @@ interface ChatListComponent {
val webAppBotId: Long? = null,
val webAppBotName: String? = null,
val webViewUrl: String? = null,
val updateState: UpdateState = UpdateState.Idle,
val updateState: UpdateState = UpdateState.Idle
)

@Immutable
data class FoldersState(
val chatsByFolder: Map<Int, List<ChatModel>> = emptyMap(),
val folders: List<FolderModel> = emptyList(),
val selectedFolderId: Int = -1,
val isLoadingByFolder: Map<Int, Boolean> = emptyMap(),
val scrollPositions: Map<Int, Pair<Int, Int>> = emptyMap()
) {
val chats: List<ChatModel> get() = chatsByFolder[selectedFolderId] ?: emptyList()
val isLoading: Boolean get() = isLoadingByFolder[selectedFolderId] ?: false
}
)

data class ChatsState(
val chats: List<ChatModel> = emptyList(),
val isLoading: Boolean = false
)

@Immutable
data class SelectionState(
val selectedChatIds: Set<Long> = emptySet(),
val activeChatId: Long? = null
)

@Immutable
data class SearchState(
val isSearchActive: Boolean = false,
val searchQuery: String = "",
val searchResults: List<ChatModel> = emptyList(),
val globalSearchResults: List<ChatModel> = emptyList(),
val messageSearchResults: List<MessageModel> = emptyList(),
val recentUsers: List<ChatModel> = emptyList(),
val recentOthers: List<ChatModel> = emptyList(),
val canLoadMoreMessages: Boolean = false
)
}

This file was deleted.

This file was deleted.

Loading