android: persist chats search history and recent in datastore
This commit is contained in:
@@ -577,3 +577,14 @@
|
|||||||
- empty query -> `Recent` list block (history-style chat rows),
|
- empty query -> `Recent` list block (history-style chat rows),
|
||||||
- non-empty query -> local matches + `Global search` and `Messages` sections.
|
- non-empty query -> local matches + `Global search` and `Messages` sections.
|
||||||
- Kept search action in chats top bar; while search mode is active, app bar switches to back-navigation + empty title (content drives the page).
|
- Kept search action in chats top bar; while search mode is active, app bar switches to back-navigation + empty title (content drives the page).
|
||||||
|
|
||||||
|
### Step 91 - Search history/recent persistence + clear action
|
||||||
|
- Added `ChatSearchRepository` abstraction and `DataStoreChatSearchRepository` implementation.
|
||||||
|
- Persisted chats search metadata in `DataStore`:
|
||||||
|
- recent opened chats list,
|
||||||
|
- search history list (bounded).
|
||||||
|
- Wired chats fullscreen search to persisted data:
|
||||||
|
- green recent avatars strip now reads saved recent chats,
|
||||||
|
- red `Recent` list now reads saved history with fallback.
|
||||||
|
- Connected `Очистить` action to real history cleanup in `DataStore`.
|
||||||
|
- On opening a chat from search results/messages/history, the chat is now stored in recent/history.
|
||||||
|
|||||||
@@ -0,0 +1,67 @@
|
|||||||
|
package ru.daemonlord.messenger.data.chat.repository
|
||||||
|
|
||||||
|
import androidx.datastore.core.DataStore
|
||||||
|
import androidx.datastore.preferences.core.Preferences
|
||||||
|
import androidx.datastore.preferences.core.edit
|
||||||
|
import androidx.datastore.preferences.core.stringPreferencesKey
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
import kotlinx.coroutines.flow.map
|
||||||
|
import ru.daemonlord.messenger.domain.chat.repository.ChatSearchRepository
|
||||||
|
import javax.inject.Inject
|
||||||
|
import javax.inject.Singleton
|
||||||
|
|
||||||
|
@Singleton
|
||||||
|
class DataStoreChatSearchRepository @Inject constructor(
|
||||||
|
private val dataStore: DataStore<Preferences>,
|
||||||
|
) : ChatSearchRepository {
|
||||||
|
|
||||||
|
override fun observeHistoryChatIds(): Flow<List<Long>> {
|
||||||
|
return dataStore.data.map { prefs -> decodeIds(prefs[HISTORY_IDS_KEY]) }
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun observeRecentChatIds(): Flow<List<Long>> {
|
||||||
|
return dataStore.data.map { prefs -> decodeIds(prefs[RECENT_IDS_KEY]) }
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun addHistoryChat(chatId: Long) {
|
||||||
|
dataStore.edit { prefs ->
|
||||||
|
prefs[HISTORY_IDS_KEY] = encodeIds(prepend(chatId, decodeIds(prefs[HISTORY_IDS_KEY])))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun addRecentChat(chatId: Long) {
|
||||||
|
dataStore.edit { prefs ->
|
||||||
|
prefs[RECENT_IDS_KEY] = encodeIds(prepend(chatId, decodeIds(prefs[RECENT_IDS_KEY])))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun clearHistory() {
|
||||||
|
dataStore.edit { prefs ->
|
||||||
|
prefs.remove(HISTORY_IDS_KEY)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun prepend(chatId: Long, source: List<Long>): List<Long> {
|
||||||
|
return buildList {
|
||||||
|
add(chatId)
|
||||||
|
addAll(source.filterNot { it == chatId })
|
||||||
|
}.take(MAX_SIZE)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun decodeIds(raw: String?): List<Long> {
|
||||||
|
if (raw.isNullOrBlank()) return emptyList()
|
||||||
|
return raw.split(',')
|
||||||
|
.mapNotNull { it.trim().toLongOrNull() }
|
||||||
|
.distinct()
|
||||||
|
.take(MAX_SIZE)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun encodeIds(ids: List<Long>): String = ids.joinToString(",")
|
||||||
|
|
||||||
|
private companion object {
|
||||||
|
const val MAX_SIZE = 30
|
||||||
|
val HISTORY_IDS_KEY = stringPreferencesKey("chat_search_history_ids")
|
||||||
|
val RECENT_IDS_KEY = stringPreferencesKey("chat_search_recent_ids")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -7,6 +7,7 @@ import dagger.hilt.components.SingletonComponent
|
|||||||
import ru.daemonlord.messenger.data.auth.repository.NetworkAuthRepository
|
import ru.daemonlord.messenger.data.auth.repository.NetworkAuthRepository
|
||||||
import ru.daemonlord.messenger.data.auth.repository.DefaultSessionCleanupRepository
|
import ru.daemonlord.messenger.data.auth.repository.DefaultSessionCleanupRepository
|
||||||
import ru.daemonlord.messenger.data.chat.repository.NetworkChatRepository
|
import ru.daemonlord.messenger.data.chat.repository.NetworkChatRepository
|
||||||
|
import ru.daemonlord.messenger.data.chat.repository.DataStoreChatSearchRepository
|
||||||
import ru.daemonlord.messenger.data.media.repository.NetworkMediaRepository
|
import ru.daemonlord.messenger.data.media.repository.NetworkMediaRepository
|
||||||
import ru.daemonlord.messenger.data.message.repository.NetworkMessageRepository
|
import ru.daemonlord.messenger.data.message.repository.NetworkMessageRepository
|
||||||
import ru.daemonlord.messenger.data.notifications.repository.DataStoreNotificationSettingsRepository
|
import ru.daemonlord.messenger.data.notifications.repository.DataStoreNotificationSettingsRepository
|
||||||
@@ -15,6 +16,7 @@ import ru.daemonlord.messenger.domain.account.repository.AccountRepository
|
|||||||
import ru.daemonlord.messenger.domain.auth.repository.AuthRepository
|
import ru.daemonlord.messenger.domain.auth.repository.AuthRepository
|
||||||
import ru.daemonlord.messenger.domain.auth.repository.SessionCleanupRepository
|
import ru.daemonlord.messenger.domain.auth.repository.SessionCleanupRepository
|
||||||
import ru.daemonlord.messenger.domain.chat.repository.ChatRepository
|
import ru.daemonlord.messenger.domain.chat.repository.ChatRepository
|
||||||
|
import ru.daemonlord.messenger.domain.chat.repository.ChatSearchRepository
|
||||||
import ru.daemonlord.messenger.domain.media.repository.MediaRepository
|
import ru.daemonlord.messenger.domain.media.repository.MediaRepository
|
||||||
import ru.daemonlord.messenger.domain.message.repository.MessageRepository
|
import ru.daemonlord.messenger.domain.message.repository.MessageRepository
|
||||||
import ru.daemonlord.messenger.domain.notifications.repository.NotificationSettingsRepository
|
import ru.daemonlord.messenger.domain.notifications.repository.NotificationSettingsRepository
|
||||||
@@ -42,6 +44,12 @@ abstract class RepositoryModule {
|
|||||||
repository: NetworkChatRepository,
|
repository: NetworkChatRepository,
|
||||||
): ChatRepository
|
): ChatRepository
|
||||||
|
|
||||||
|
@Binds
|
||||||
|
@Singleton
|
||||||
|
abstract fun bindChatSearchRepository(
|
||||||
|
repository: DataStoreChatSearchRepository,
|
||||||
|
): ChatSearchRepository
|
||||||
|
|
||||||
@Binds
|
@Binds
|
||||||
@Singleton
|
@Singleton
|
||||||
abstract fun bindMessageRepository(
|
abstract fun bindMessageRepository(
|
||||||
|
|||||||
@@ -0,0 +1,12 @@
|
|||||||
|
package ru.daemonlord.messenger.domain.chat.repository
|
||||||
|
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
|
||||||
|
interface ChatSearchRepository {
|
||||||
|
fun observeHistoryChatIds(): Flow<List<Long>>
|
||||||
|
fun observeRecentChatIds(): Flow<List<Long>>
|
||||||
|
suspend fun addHistoryChat(chatId: Long)
|
||||||
|
suspend fun addRecentChat(chatId: Long)
|
||||||
|
suspend fun clearHistory()
|
||||||
|
}
|
||||||
|
|
||||||
@@ -110,6 +110,8 @@ fun ChatListRoute(
|
|||||||
onFilterSelected = viewModel::onFilterSelected,
|
onFilterSelected = viewModel::onFilterSelected,
|
||||||
onSearchChanged = viewModel::onSearchChanged,
|
onSearchChanged = viewModel::onSearchChanged,
|
||||||
onGlobalSearchChanged = viewModel::onGlobalSearchChanged,
|
onGlobalSearchChanged = viewModel::onGlobalSearchChanged,
|
||||||
|
onSearchResultOpened = viewModel::onSearchResultOpened,
|
||||||
|
onClearSearchHistory = viewModel::clearSearchHistory,
|
||||||
onRefresh = viewModel::onPullToRefresh,
|
onRefresh = viewModel::onPullToRefresh,
|
||||||
onOpenChat = onOpenChat,
|
onOpenChat = onOpenChat,
|
||||||
isMainBarVisible = isMainBarVisible,
|
isMainBarVisible = isMainBarVisible,
|
||||||
@@ -137,6 +139,8 @@ fun ChatListScreen(
|
|||||||
onFilterSelected: (ChatListFilter) -> Unit,
|
onFilterSelected: (ChatListFilter) -> Unit,
|
||||||
onSearchChanged: (String) -> Unit,
|
onSearchChanged: (String) -> Unit,
|
||||||
onGlobalSearchChanged: (String) -> Unit,
|
onGlobalSearchChanged: (String) -> Unit,
|
||||||
|
onSearchResultOpened: (Long) -> Unit,
|
||||||
|
onClearSearchHistory: () -> Unit,
|
||||||
onRefresh: () -> Unit,
|
onRefresh: () -> Unit,
|
||||||
onOpenChat: (Long) -> Unit,
|
onOpenChat: (Long) -> Unit,
|
||||||
isMainBarVisible: Boolean,
|
isMainBarVisible: Boolean,
|
||||||
@@ -496,6 +500,8 @@ fun ChatListScreen(
|
|||||||
onGlobalSearchChanged(it)
|
onGlobalSearchChanged(it)
|
||||||
},
|
},
|
||||||
onSectionChanged = { searchSection = it },
|
onSectionChanged = { searchSection = it },
|
||||||
|
onSearchResultOpened = onSearchResultOpened,
|
||||||
|
onClearHistory = onClearSearchHistory,
|
||||||
onOpenChat = onOpenChat,
|
onOpenChat = onOpenChat,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -692,6 +698,8 @@ private fun ChatSearchFullscreen(
|
|||||||
searchQuery: String,
|
searchQuery: String,
|
||||||
onSearchChanged: (String) -> Unit,
|
onSearchChanged: (String) -> Unit,
|
||||||
onSectionChanged: (SearchSection) -> Unit,
|
onSectionChanged: (SearchSection) -> Unit,
|
||||||
|
onSearchResultOpened: (Long) -> Unit,
|
||||||
|
onClearHistory: () -> Unit,
|
||||||
onOpenChat: (Long) -> Unit,
|
onOpenChat: (Long) -> Unit,
|
||||||
) {
|
) {
|
||||||
val trimmedQuery = searchQuery.trim()
|
val trimmedQuery = searchQuery.trim()
|
||||||
@@ -715,8 +723,14 @@ private fun ChatSearchFullscreen(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
val recentCircleChats = remember(sectionChats) { sectionChats.take(8) }
|
val recentCircleChats = remember(state.searchRecentChats, sectionChats) {
|
||||||
val recentHistoryChats = remember(sectionChats) { sectionChats.take(14) }
|
val byId = sectionChats.associateBy { it.id }
|
||||||
|
state.searchRecentChats.mapNotNull { byId[it.id] }.take(8).ifEmpty { sectionChats.take(8) }
|
||||||
|
}
|
||||||
|
val recentHistoryChats = remember(state.searchHistoryChats, sectionChats) {
|
||||||
|
val byId = sectionChats.associateBy { it.id }
|
||||||
|
state.searchHistoryChats.mapNotNull { byId[it.id] }.take(20).ifEmpty { sectionChats.take(14) }
|
||||||
|
}
|
||||||
|
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
@@ -778,7 +792,10 @@ private fun ChatSearchFullscreen(
|
|||||||
horizontalAlignment = Alignment.CenterHorizontally,
|
horizontalAlignment = Alignment.CenterHorizontally,
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.widthIn(max = 74.dp)
|
.widthIn(max = 74.dp)
|
||||||
.clickable { onOpenChat(chat.id) },
|
.clickable {
|
||||||
|
onSearchResultOpened(chat.id)
|
||||||
|
onOpenChat(chat.id)
|
||||||
|
},
|
||||||
) {
|
) {
|
||||||
ChatAvatar(chat = chat, size = 56.dp)
|
ChatAvatar(chat = chat, size = 56.dp)
|
||||||
Text(
|
Text(
|
||||||
@@ -808,18 +825,31 @@ private fun ChatSearchFullscreen(
|
|||||||
text = "Очистить",
|
text = "Очистить",
|
||||||
style = MaterialTheme.typography.labelLarge,
|
style = MaterialTheme.typography.labelLarge,
|
||||||
color = MaterialTheme.colorScheme.primary,
|
color = MaterialTheme.colorScheme.primary,
|
||||||
|
modifier = Modifier.clickable(onClick = onClearHistory),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
LazyColumn(modifier = Modifier.fillMaxSize()) {
|
LazyColumn(modifier = Modifier.fillMaxSize()) {
|
||||||
items(recentHistoryChats, key = { "recent_${it.id}" }) { chat ->
|
items(recentHistoryChats, key = { "recent_${it.id}" }) { chat ->
|
||||||
SearchChatRow(chat = chat, onClick = { onOpenChat(chat.id) })
|
SearchChatRow(
|
||||||
|
chat = chat,
|
||||||
|
onClick = {
|
||||||
|
onSearchResultOpened(chat.id)
|
||||||
|
onOpenChat(chat.id)
|
||||||
|
},
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
LazyColumn(modifier = Modifier.fillMaxSize()) {
|
LazyColumn(modifier = Modifier.fillMaxSize()) {
|
||||||
if (localQueryResults.isNotEmpty()) {
|
if (localQueryResults.isNotEmpty()) {
|
||||||
items(localQueryResults.take(6), key = { "local_${it.id}" }) { chat ->
|
items(localQueryResults.take(6), key = { "local_${it.id}" }) { chat ->
|
||||||
SearchChatRow(chat = chat, onClick = { onOpenChat(chat.id) })
|
SearchChatRow(
|
||||||
|
chat = chat,
|
||||||
|
onClick = {
|
||||||
|
onSearchResultOpened(chat.id)
|
||||||
|
onOpenChat(chat.id)
|
||||||
|
},
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
item(key = "global_header") {
|
item(key = "global_header") {
|
||||||
@@ -859,7 +889,10 @@ private fun ChatSearchFullscreen(
|
|||||||
state = state,
|
state = state,
|
||||||
messageText = message.text?.take(70).orEmpty().ifBlank { "[${message.type}]" },
|
messageText = message.text?.take(70).orEmpty().ifBlank { "[${message.type}]" },
|
||||||
time = formatChatTime(message.createdAt),
|
time = formatChatTime(message.createdAt),
|
||||||
onClick = { onOpenChat(message.chatId) },
|
onClick = {
|
||||||
|
onSearchResultOpened(message.chatId)
|
||||||
|
onOpenChat(message.chatId)
|
||||||
|
},
|
||||||
chatId = message.chatId,
|
chatId = message.chatId,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,6 +16,8 @@ data class ChatListUiState(
|
|||||||
val isConnecting: Boolean = false,
|
val isConnecting: Boolean = false,
|
||||||
val errorMessage: String? = null,
|
val errorMessage: String? = null,
|
||||||
val chats: List<ChatItem> = emptyList(),
|
val chats: List<ChatItem> = emptyList(),
|
||||||
|
val searchHistoryChats: List<ChatItem> = emptyList(),
|
||||||
|
val searchRecentChats: List<ChatItem> = emptyList(),
|
||||||
val archivedChatsCount: Int = 0,
|
val archivedChatsCount: Int = 0,
|
||||||
val archivedUnreadCount: Int = 0,
|
val archivedUnreadCount: Int = 0,
|
||||||
val isJoiningInvite: Boolean = false,
|
val isJoiningInvite: Boolean = false,
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ import kotlinx.coroutines.flow.update
|
|||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import ru.daemonlord.messenger.domain.chat.model.ChatItem
|
import ru.daemonlord.messenger.domain.chat.model.ChatItem
|
||||||
import ru.daemonlord.messenger.domain.chat.repository.ChatRepository
|
import ru.daemonlord.messenger.domain.chat.repository.ChatRepository
|
||||||
|
import ru.daemonlord.messenger.domain.chat.repository.ChatSearchRepository
|
||||||
import ru.daemonlord.messenger.domain.account.repository.AccountRepository
|
import ru.daemonlord.messenger.domain.account.repository.AccountRepository
|
||||||
import ru.daemonlord.messenger.domain.chat.usecase.JoinByInviteUseCase
|
import ru.daemonlord.messenger.domain.chat.usecase.JoinByInviteUseCase
|
||||||
import ru.daemonlord.messenger.domain.chat.usecase.ObserveChatsUseCase
|
import ru.daemonlord.messenger.domain.chat.usecase.ObserveChatsUseCase
|
||||||
@@ -36,6 +37,7 @@ class ChatListViewModel @Inject constructor(
|
|||||||
private val handleRealtimeEventsUseCase: HandleRealtimeEventsUseCase,
|
private val handleRealtimeEventsUseCase: HandleRealtimeEventsUseCase,
|
||||||
private val realtimeManager: RealtimeManager,
|
private val realtimeManager: RealtimeManager,
|
||||||
private val chatRepository: ChatRepository,
|
private val chatRepository: ChatRepository,
|
||||||
|
private val chatSearchRepository: ChatSearchRepository,
|
||||||
private val accountRepository: AccountRepository,
|
private val accountRepository: AccountRepository,
|
||||||
private val messageRepository: MessageRepository,
|
private val messageRepository: MessageRepository,
|
||||||
) : ViewModel() {
|
) : ViewModel() {
|
||||||
@@ -43,12 +45,15 @@ class ChatListViewModel @Inject constructor(
|
|||||||
private val selectedTab = MutableStateFlow(ChatTab.ALL)
|
private val selectedTab = MutableStateFlow(ChatTab.ALL)
|
||||||
private val selectedFilter = MutableStateFlow(ChatListFilter.ALL)
|
private val selectedFilter = MutableStateFlow(ChatListFilter.ALL)
|
||||||
private val searchQuery = MutableStateFlow("")
|
private val searchQuery = MutableStateFlow("")
|
||||||
|
private val searchHistoryIds = MutableStateFlow<List<Long>>(emptyList())
|
||||||
|
private val searchRecentIds = MutableStateFlow<List<Long>>(emptyList())
|
||||||
private var lastHandledInviteToken: String? = null
|
private var lastHandledInviteToken: String? = null
|
||||||
private val _uiState = MutableStateFlow(ChatListUiState())
|
private val _uiState = MutableStateFlow(ChatListUiState())
|
||||||
val uiState: StateFlow<ChatListUiState> = _uiState.asStateFlow()
|
val uiState: StateFlow<ChatListUiState> = _uiState.asStateFlow()
|
||||||
|
|
||||||
init {
|
init {
|
||||||
handleRealtimeEventsUseCase.start()
|
handleRealtimeEventsUseCase.start()
|
||||||
|
observeSearchStore()
|
||||||
observeConnectionState()
|
observeConnectionState()
|
||||||
observeChatStream()
|
observeChatStream()
|
||||||
}
|
}
|
||||||
@@ -132,6 +137,19 @@ class ChatListViewModel @Inject constructor(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun onSearchResultOpened(chatId: Long) {
|
||||||
|
viewModelScope.launch {
|
||||||
|
chatSearchRepository.addHistoryChat(chatId)
|
||||||
|
chatSearchRepository.addRecentChat(chatId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun clearSearchHistory() {
|
||||||
|
viewModelScope.launch {
|
||||||
|
chatSearchRepository.clearHistory()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fun onManagementChatSelected(chatId: Long?) {
|
fun onManagementChatSelected(chatId: Long?) {
|
||||||
_uiState.update { it.copy(selectedManageChatId = chatId) }
|
_uiState.update { it.copy(selectedManageChatId = chatId) }
|
||||||
if (chatId != null) {
|
if (chatId != null) {
|
||||||
@@ -299,16 +317,30 @@ class ChatListViewModel @Inject constructor(
|
|||||||
.combine(selectedFilter) { (chats, query), filter ->
|
.combine(selectedFilter) { (chats, query), filter ->
|
||||||
chats.filterByQueryAndType(query = query, filter = filter)
|
chats.filterByQueryAndType(query = query, filter = filter)
|
||||||
}
|
}
|
||||||
|
.combine(searchHistoryIds) { filtered, historyIds ->
|
||||||
|
filtered to historyIds
|
||||||
|
}
|
||||||
|
.combine(searchRecentIds) { (filtered, historyIds), recentIds ->
|
||||||
|
Triple(filtered, historyIds, recentIds)
|
||||||
|
}
|
||||||
.combine(archiveStatsFlow) { filtered, stats ->
|
.combine(archiveStatsFlow) { filtered, stats ->
|
||||||
filtered to stats
|
filtered to stats
|
||||||
}
|
}
|
||||||
.collectLatest { (filtered, stats) ->
|
.collectLatest { (filteredWithSearch, stats) ->
|
||||||
|
val filtered = filteredWithSearch.first
|
||||||
|
val historyIds = filteredWithSearch.second
|
||||||
|
val recentIds = filteredWithSearch.third
|
||||||
|
val byId = filtered.associateBy { it.id }
|
||||||
|
val historyChats = historyIds.mapNotNull(byId::get)
|
||||||
|
val recentChats = recentIds.mapNotNull(byId::get)
|
||||||
_uiState.update {
|
_uiState.update {
|
||||||
it.copy(
|
it.copy(
|
||||||
isLoading = false,
|
isLoading = false,
|
||||||
isRefreshing = false,
|
isRefreshing = false,
|
||||||
errorMessage = null,
|
errorMessage = null,
|
||||||
chats = filtered,
|
chats = filtered,
|
||||||
|
searchHistoryChats = historyChats,
|
||||||
|
searchRecentChats = recentChats,
|
||||||
archivedChatsCount = stats.first,
|
archivedChatsCount = stats.first,
|
||||||
archivedUnreadCount = stats.second,
|
archivedUnreadCount = stats.second,
|
||||||
managementMessage = null,
|
managementMessage = null,
|
||||||
@@ -331,6 +363,19 @@ class ChatListViewModel @Inject constructor(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun observeSearchStore() {
|
||||||
|
viewModelScope.launch {
|
||||||
|
chatSearchRepository.observeHistoryChatIds().collectLatest { ids ->
|
||||||
|
searchHistoryIds.value = ids
|
||||||
|
}
|
||||||
|
}
|
||||||
|
viewModelScope.launch {
|
||||||
|
chatSearchRepository.observeRecentChatIds().collectLatest { ids ->
|
||||||
|
searchRecentIds.value = ids
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun createChatInternal(
|
private fun createChatInternal(
|
||||||
type: String,
|
type: String,
|
||||||
title: String,
|
title: String,
|
||||||
|
|||||||
Reference in New Issue
Block a user