This commit is contained in:
@@ -130,3 +130,9 @@
|
|||||||
- Added `ObserveChatUseCase` to expose per-chat permission state to message screen.
|
- Added `ObserveChatUseCase` to expose per-chat permission state to message screen.
|
||||||
- Implemented channel send restrictions in `ChatViewModel`: sending/attach disabled for `member` role in `channel` chats.
|
- Implemented channel send restrictions in `ChatViewModel`: sending/attach disabled for `member` role in `channel` chats.
|
||||||
- Added composer-level restriction hint in Chat UI to explain blocked actions.
|
- Added composer-level restriction hint in Chat UI to explain blocked actions.
|
||||||
|
|
||||||
|
### Step 21 - Sprint P0 / 4) Invite join flow (minimum)
|
||||||
|
- Added chat API contracts for invite actions: `POST /api/v1/chats/{chat_id}/invite-link` and `POST /api/v1/chats/join-by-invite`.
|
||||||
|
- Added domain model/use-cases for invite-link creation and join-by-invite.
|
||||||
|
- Extended chat repository with invite operations and local chat upsert on successful join.
|
||||||
|
- Added minimal Chat List UI flow for join-by-invite token input with loading/error handling and auto-open of joined chat.
|
||||||
|
|||||||
@@ -1,8 +1,12 @@
|
|||||||
package ru.daemonlord.messenger.data.chat.api
|
package ru.daemonlord.messenger.data.chat.api
|
||||||
|
|
||||||
|
import retrofit2.http.Body
|
||||||
import retrofit2.http.GET
|
import retrofit2.http.GET
|
||||||
import retrofit2.http.Path
|
import retrofit2.http.Path
|
||||||
|
import retrofit2.http.POST
|
||||||
import retrofit2.http.Query
|
import retrofit2.http.Query
|
||||||
|
import ru.daemonlord.messenger.data.chat.dto.ChatInviteLinkDto
|
||||||
|
import ru.daemonlord.messenger.data.chat.dto.ChatJoinByInviteRequestDto
|
||||||
import ru.daemonlord.messenger.data.chat.dto.ChatReadDto
|
import ru.daemonlord.messenger.data.chat.dto.ChatReadDto
|
||||||
|
|
||||||
interface ChatApiService {
|
interface ChatApiService {
|
||||||
@@ -15,4 +19,14 @@ interface ChatApiService {
|
|||||||
suspend fun getChatById(
|
suspend fun getChatById(
|
||||||
@Path("chat_id") chatId: Long,
|
@Path("chat_id") chatId: Long,
|
||||||
): ChatReadDto
|
): ChatReadDto
|
||||||
|
|
||||||
|
@POST("/api/v1/chats/{chat_id}/invite-link")
|
||||||
|
suspend fun createInviteLink(
|
||||||
|
@Path("chat_id") chatId: Long,
|
||||||
|
): ChatInviteLinkDto
|
||||||
|
|
||||||
|
@POST("/api/v1/chats/join-by-invite")
|
||||||
|
suspend fun joinByInvite(
|
||||||
|
@Body request: ChatJoinByInviteRequestDto,
|
||||||
|
): ChatReadDto
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -45,3 +45,17 @@ data class ChatReadDto(
|
|||||||
@SerialName("created_at")
|
@SerialName("created_at")
|
||||||
val createdAt: String? = null,
|
val createdAt: String? = null,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class ChatInviteLinkDto(
|
||||||
|
@SerialName("chat_id")
|
||||||
|
val chatId: Long,
|
||||||
|
val token: String,
|
||||||
|
@SerialName("invite_url")
|
||||||
|
val inviteUrl: String,
|
||||||
|
)
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class ChatJoinByInviteRequestDto(
|
||||||
|
val token: String,
|
||||||
|
)
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package ru.daemonlord.messenger.data.chat.mapper
|
package ru.daemonlord.messenger.data.chat.mapper
|
||||||
|
|
||||||
import ru.daemonlord.messenger.data.chat.local.model.ChatListLocalModel
|
import ru.daemonlord.messenger.data.chat.local.model.ChatListLocalModel
|
||||||
|
import ru.daemonlord.messenger.data.chat.local.entity.ChatEntity
|
||||||
import ru.daemonlord.messenger.domain.chat.model.ChatItem
|
import ru.daemonlord.messenger.domain.chat.model.ChatItem
|
||||||
|
|
||||||
fun ChatListLocalModel.toDomain(): ChatItem {
|
fun ChatListLocalModel.toDomain(): ChatItem {
|
||||||
@@ -29,3 +30,30 @@ fun ChatListLocalModel.toDomain(): ChatItem {
|
|||||||
updatedSortAt = updatedSortAt,
|
updatedSortAt = updatedSortAt,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun ChatEntity.toDomain(): ChatItem {
|
||||||
|
return ChatItem(
|
||||||
|
id = id,
|
||||||
|
publicId = publicId,
|
||||||
|
type = type,
|
||||||
|
title = title,
|
||||||
|
displayTitle = displayTitle,
|
||||||
|
handle = handle,
|
||||||
|
avatarUrl = avatarUrl,
|
||||||
|
archived = archived,
|
||||||
|
pinned = pinned,
|
||||||
|
muted = muted,
|
||||||
|
unreadCount = unreadCount,
|
||||||
|
unreadMentionsCount = unreadMentionsCount,
|
||||||
|
counterpartUsername = counterpartUsername,
|
||||||
|
counterpartName = counterpartName,
|
||||||
|
counterpartAvatarUrl = counterpartAvatarUrl,
|
||||||
|
counterpartIsOnline = counterpartIsOnline,
|
||||||
|
counterpartLastSeenAt = counterpartLastSeenAt,
|
||||||
|
lastMessageText = lastMessageText,
|
||||||
|
lastMessageType = lastMessageType,
|
||||||
|
lastMessageCreatedAt = lastMessageCreatedAt,
|
||||||
|
myRole = myRole,
|
||||||
|
updatedSortAt = updatedSortAt,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,8 +1,10 @@
|
|||||||
package ru.daemonlord.messenger.data.chat.mapper
|
package ru.daemonlord.messenger.data.chat.mapper
|
||||||
|
|
||||||
import ru.daemonlord.messenger.data.chat.dto.ChatReadDto
|
import ru.daemonlord.messenger.data.chat.dto.ChatReadDto
|
||||||
|
import ru.daemonlord.messenger.data.chat.dto.ChatInviteLinkDto
|
||||||
import ru.daemonlord.messenger.data.chat.local.entity.ChatEntity
|
import ru.daemonlord.messenger.data.chat.local.entity.ChatEntity
|
||||||
import ru.daemonlord.messenger.data.chat.local.entity.UserShortEntity
|
import ru.daemonlord.messenger.data.chat.local.entity.UserShortEntity
|
||||||
|
import ru.daemonlord.messenger.domain.chat.model.ChatInviteLink
|
||||||
|
|
||||||
fun ChatReadDto.toChatEntity(): ChatEntity {
|
fun ChatReadDto.toChatEntity(): ChatEntity {
|
||||||
return ChatEntity(
|
return ChatEntity(
|
||||||
@@ -42,3 +44,11 @@ fun ChatReadDto.toUserShortEntityOrNull(): UserShortEntity? {
|
|||||||
avatarUrl = counterpartAvatarUrl,
|
avatarUrl = counterpartAvatarUrl,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun ChatInviteLinkDto.toDomain(): ChatInviteLink {
|
||||||
|
return ChatInviteLink(
|
||||||
|
chatId = chatId,
|
||||||
|
token = token,
|
||||||
|
inviteUrl = inviteUrl,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|||||||
@@ -10,10 +10,12 @@ import kotlinx.coroutines.launch
|
|||||||
import kotlinx.coroutines.channels.awaitClose
|
import kotlinx.coroutines.channels.awaitClose
|
||||||
import retrofit2.HttpException
|
import retrofit2.HttpException
|
||||||
import ru.daemonlord.messenger.data.chat.api.ChatApiService
|
import ru.daemonlord.messenger.data.chat.api.ChatApiService
|
||||||
|
import ru.daemonlord.messenger.data.chat.dto.ChatJoinByInviteRequestDto
|
||||||
import ru.daemonlord.messenger.data.chat.local.dao.ChatDao
|
import ru.daemonlord.messenger.data.chat.local.dao.ChatDao
|
||||||
import ru.daemonlord.messenger.data.chat.mapper.toChatEntity
|
import ru.daemonlord.messenger.data.chat.mapper.toChatEntity
|
||||||
import ru.daemonlord.messenger.data.chat.mapper.toDomain
|
import ru.daemonlord.messenger.data.chat.mapper.toDomain
|
||||||
import ru.daemonlord.messenger.data.chat.mapper.toUserShortEntityOrNull
|
import ru.daemonlord.messenger.data.chat.mapper.toUserShortEntityOrNull
|
||||||
|
import ru.daemonlord.messenger.domain.chat.model.ChatInviteLink
|
||||||
import ru.daemonlord.messenger.di.IoDispatcher
|
import ru.daemonlord.messenger.di.IoDispatcher
|
||||||
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
|
||||||
@@ -75,6 +77,26 @@ class NetworkChatRepository @Inject constructor(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override suspend fun createInviteLink(chatId: Long): AppResult<ChatInviteLink> = withContext(ioDispatcher) {
|
||||||
|
try {
|
||||||
|
AppResult.Success(chatApiService.createInviteLink(chatId = chatId).toDomain())
|
||||||
|
} catch (error: Throwable) {
|
||||||
|
AppResult.Error(error.toAppError())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun joinByInvite(token: String): AppResult<ChatItem> = withContext(ioDispatcher) {
|
||||||
|
try {
|
||||||
|
val joined = chatApiService.joinByInvite(request = ChatJoinByInviteRequestDto(token = token))
|
||||||
|
chatDao.upsertUsers(joined.toUserShortEntityOrNull()?.let(::listOf).orEmpty())
|
||||||
|
val chatEntity = joined.toChatEntity()
|
||||||
|
chatDao.upsertChats(listOf(chatEntity))
|
||||||
|
AppResult.Success(chatEntity.toDomain())
|
||||||
|
} catch (error: Throwable) {
|
||||||
|
AppResult.Error(error.toAppError())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override suspend fun deleteChat(chatId: Long) {
|
override suspend fun deleteChat(chatId: Long) {
|
||||||
withContext(ioDispatcher) {
|
withContext(ioDispatcher) {
|
||||||
chatDao.deleteChat(chatId = chatId)
|
chatDao.deleteChat(chatId = chatId)
|
||||||
|
|||||||
@@ -0,0 +1,7 @@
|
|||||||
|
package ru.daemonlord.messenger.domain.chat.model
|
||||||
|
|
||||||
|
data class ChatInviteLink(
|
||||||
|
val chatId: Long,
|
||||||
|
val token: String,
|
||||||
|
val inviteUrl: String,
|
||||||
|
)
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
package ru.daemonlord.messenger.domain.chat.repository
|
package ru.daemonlord.messenger.domain.chat.repository
|
||||||
|
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
import ru.daemonlord.messenger.domain.chat.model.ChatInviteLink
|
||||||
import ru.daemonlord.messenger.domain.chat.model.ChatItem
|
import ru.daemonlord.messenger.domain.chat.model.ChatItem
|
||||||
import ru.daemonlord.messenger.domain.common.AppResult
|
import ru.daemonlord.messenger.domain.common.AppResult
|
||||||
|
|
||||||
@@ -9,5 +10,7 @@ interface ChatRepository {
|
|||||||
fun observeChat(chatId: Long): Flow<ChatItem?>
|
fun observeChat(chatId: Long): Flow<ChatItem?>
|
||||||
suspend fun refreshChats(archived: Boolean): AppResult<Unit>
|
suspend fun refreshChats(archived: Boolean): AppResult<Unit>
|
||||||
suspend fun refreshChat(chatId: Long): AppResult<Unit>
|
suspend fun refreshChat(chatId: Long): AppResult<Unit>
|
||||||
|
suspend fun createInviteLink(chatId: Long): AppResult<ChatInviteLink>
|
||||||
|
suspend fun joinByInvite(token: String): AppResult<ChatItem>
|
||||||
suspend fun deleteChat(chatId: Long)
|
suspend fun deleteChat(chatId: Long)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,14 @@
|
|||||||
|
package ru.daemonlord.messenger.domain.chat.usecase
|
||||||
|
|
||||||
|
import ru.daemonlord.messenger.domain.chat.model.ChatInviteLink
|
||||||
|
import ru.daemonlord.messenger.domain.chat.repository.ChatRepository
|
||||||
|
import ru.daemonlord.messenger.domain.common.AppResult
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
class CreateInviteLinkUseCase @Inject constructor(
|
||||||
|
private val repository: ChatRepository,
|
||||||
|
) {
|
||||||
|
suspend operator fun invoke(chatId: Long): AppResult<ChatInviteLink> {
|
||||||
|
return repository.createInviteLink(chatId = chatId)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
package ru.daemonlord.messenger.domain.chat.usecase
|
||||||
|
|
||||||
|
import ru.daemonlord.messenger.domain.chat.model.ChatItem
|
||||||
|
import ru.daemonlord.messenger.domain.chat.repository.ChatRepository
|
||||||
|
import ru.daemonlord.messenger.domain.common.AppResult
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
class JoinByInviteUseCase @Inject constructor(
|
||||||
|
private val repository: ChatRepository,
|
||||||
|
) {
|
||||||
|
suspend operator fun invoke(token: String): AppResult<ChatItem> {
|
||||||
|
return repository.joinByInvite(token = token)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -12,6 +12,7 @@ import androidx.compose.foundation.lazy.LazyColumn
|
|||||||
import androidx.compose.foundation.lazy.items
|
import androidx.compose.foundation.lazy.items
|
||||||
import androidx.compose.material3.AssistChip
|
import androidx.compose.material3.AssistChip
|
||||||
import androidx.compose.material3.AssistChipDefaults
|
import androidx.compose.material3.AssistChipDefaults
|
||||||
|
import androidx.compose.material3.Button
|
||||||
import androidx.compose.material3.CircularProgressIndicator
|
import androidx.compose.material3.CircularProgressIndicator
|
||||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
@@ -21,6 +22,7 @@ import androidx.compose.material3.TabRow
|
|||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.material3.pulltorefresh.PullToRefreshBox
|
import androidx.compose.material3.pulltorefresh.PullToRefreshBox
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
@@ -36,10 +38,17 @@ fun ChatListRoute(
|
|||||||
viewModel: ChatListViewModel = hiltViewModel(),
|
viewModel: ChatListViewModel = hiltViewModel(),
|
||||||
) {
|
) {
|
||||||
val state by viewModel.uiState.collectAsStateWithLifecycle()
|
val state by viewModel.uiState.collectAsStateWithLifecycle()
|
||||||
|
LaunchedEffect(state.pendingOpenChatId) {
|
||||||
|
val chatId = state.pendingOpenChatId ?: return@LaunchedEffect
|
||||||
|
onOpenChat(chatId)
|
||||||
|
viewModel.onNavigatedToPendingChat()
|
||||||
|
}
|
||||||
ChatListScreen(
|
ChatListScreen(
|
||||||
state = state,
|
state = state,
|
||||||
onTabSelected = viewModel::onTabSelected,
|
onTabSelected = viewModel::onTabSelected,
|
||||||
onSearchChanged = viewModel::onSearchChanged,
|
onSearchChanged = viewModel::onSearchChanged,
|
||||||
|
onInviteTokenChanged = viewModel::onInviteTokenChanged,
|
||||||
|
onJoinByInvite = viewModel::onJoinByInvite,
|
||||||
onRefresh = viewModel::onPullToRefresh,
|
onRefresh = viewModel::onPullToRefresh,
|
||||||
onOpenChat = onOpenChat,
|
onOpenChat = onOpenChat,
|
||||||
)
|
)
|
||||||
@@ -51,6 +60,8 @@ fun ChatListScreen(
|
|||||||
state: ChatListUiState,
|
state: ChatListUiState,
|
||||||
onTabSelected: (ChatTab) -> Unit,
|
onTabSelected: (ChatTab) -> Unit,
|
||||||
onSearchChanged: (String) -> Unit,
|
onSearchChanged: (String) -> Unit,
|
||||||
|
onInviteTokenChanged: (String) -> Unit,
|
||||||
|
onJoinByInvite: () -> Unit,
|
||||||
onRefresh: () -> Unit,
|
onRefresh: () -> Unit,
|
||||||
onOpenChat: (Long) -> Unit,
|
onOpenChat: (Long) -> Unit,
|
||||||
) {
|
) {
|
||||||
@@ -82,6 +93,28 @@ fun ChatListScreen(
|
|||||||
.padding(horizontal = 16.dp, vertical = 8.dp),
|
.padding(horizontal = 16.dp, vertical = 8.dp),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
Row(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(horizontal = 16.dp, vertical = 4.dp),
|
||||||
|
horizontalArrangement = Arrangement.spacedBy(8.dp),
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
) {
|
||||||
|
OutlinedTextField(
|
||||||
|
value = state.inviteTokenInput,
|
||||||
|
onValueChange = onInviteTokenChanged,
|
||||||
|
label = { Text("Invite token") },
|
||||||
|
singleLine = true,
|
||||||
|
modifier = Modifier.weight(1f),
|
||||||
|
)
|
||||||
|
Button(
|
||||||
|
onClick = onJoinByInvite,
|
||||||
|
enabled = !state.isJoiningInvite && state.inviteTokenInput.isNotBlank(),
|
||||||
|
) {
|
||||||
|
Text(if (state.isJoiningInvite) "..." else "Join")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
PullToRefreshBox(
|
PullToRefreshBox(
|
||||||
isRefreshing = state.isRefreshing,
|
isRefreshing = state.isRefreshing,
|
||||||
onRefresh = onRefresh,
|
onRefresh = onRefresh,
|
||||||
|
|||||||
@@ -9,4 +9,7 @@ data class ChatListUiState(
|
|||||||
val isRefreshing: Boolean = false,
|
val isRefreshing: Boolean = false,
|
||||||
val errorMessage: String? = null,
|
val errorMessage: String? = null,
|
||||||
val chats: List<ChatItem> = emptyList(),
|
val chats: List<ChatItem> = emptyList(),
|
||||||
|
val inviteTokenInput: String = "",
|
||||||
|
val isJoiningInvite: Boolean = false,
|
||||||
|
val pendingOpenChatId: Long? = null,
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import kotlinx.coroutines.flow.flatMapLatest
|
|||||||
import kotlinx.coroutines.flow.update
|
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.usecase.JoinByInviteUseCase
|
||||||
import ru.daemonlord.messenger.domain.chat.usecase.ObserveChatsUseCase
|
import ru.daemonlord.messenger.domain.chat.usecase.ObserveChatsUseCase
|
||||||
import ru.daemonlord.messenger.domain.chat.usecase.RefreshChatsUseCase
|
import ru.daemonlord.messenger.domain.chat.usecase.RefreshChatsUseCase
|
||||||
import ru.daemonlord.messenger.domain.common.AppError
|
import ru.daemonlord.messenger.domain.common.AppError
|
||||||
@@ -23,6 +24,7 @@ import javax.inject.Inject
|
|||||||
class ChatListViewModel @Inject constructor(
|
class ChatListViewModel @Inject constructor(
|
||||||
private val observeChatsUseCase: ObserveChatsUseCase,
|
private val observeChatsUseCase: ObserveChatsUseCase,
|
||||||
private val refreshChatsUseCase: RefreshChatsUseCase,
|
private val refreshChatsUseCase: RefreshChatsUseCase,
|
||||||
|
private val joinByInviteUseCase: JoinByInviteUseCase,
|
||||||
private val handleRealtimeEventsUseCase: HandleRealtimeEventsUseCase,
|
private val handleRealtimeEventsUseCase: HandleRealtimeEventsUseCase,
|
||||||
) : ViewModel() {
|
) : ViewModel() {
|
||||||
|
|
||||||
@@ -52,6 +54,42 @@ class ChatListViewModel @Inject constructor(
|
|||||||
refreshCurrentTab(forceRefresh = true)
|
refreshCurrentTab(forceRefresh = true)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun onInviteTokenChanged(value: String) {
|
||||||
|
_uiState.update { it.copy(inviteTokenInput = value) }
|
||||||
|
}
|
||||||
|
|
||||||
|
fun onJoinByInvite() {
|
||||||
|
val token = uiState.value.inviteTokenInput.trim()
|
||||||
|
if (token.isBlank()) return
|
||||||
|
viewModelScope.launch {
|
||||||
|
_uiState.update { it.copy(isJoiningInvite = true, errorMessage = null) }
|
||||||
|
when (val result = joinByInviteUseCase(token = token)) {
|
||||||
|
is AppResult.Success -> {
|
||||||
|
_uiState.update {
|
||||||
|
it.copy(
|
||||||
|
isJoiningInvite = false,
|
||||||
|
inviteTokenInput = "",
|
||||||
|
pendingOpenChatId = result.data.id,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
refreshCurrentTab(forceRefresh = true)
|
||||||
|
}
|
||||||
|
is AppResult.Error -> {
|
||||||
|
_uiState.update {
|
||||||
|
it.copy(
|
||||||
|
isJoiningInvite = false,
|
||||||
|
errorMessage = result.reason.toUiMessage(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun onNavigatedToPendingChat() {
|
||||||
|
_uiState.update { it.copy(pendingOpenChatId = null) }
|
||||||
|
}
|
||||||
|
|
||||||
private fun observeChatStream() {
|
private fun observeChatStream() {
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
selectedTab
|
selectedTab
|
||||||
|
|||||||
@@ -14,6 +14,8 @@ import org.junit.Assert.assertEquals
|
|||||||
import org.junit.Assert.assertTrue
|
import org.junit.Assert.assertTrue
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
import ru.daemonlord.messenger.data.chat.api.ChatApiService
|
import ru.daemonlord.messenger.data.chat.api.ChatApiService
|
||||||
|
import ru.daemonlord.messenger.data.chat.dto.ChatInviteLinkDto
|
||||||
|
import ru.daemonlord.messenger.data.chat.dto.ChatJoinByInviteRequestDto
|
||||||
import ru.daemonlord.messenger.data.chat.dto.ChatReadDto
|
import ru.daemonlord.messenger.data.chat.dto.ChatReadDto
|
||||||
import ru.daemonlord.messenger.data.chat.local.dao.ChatDao
|
import ru.daemonlord.messenger.data.chat.local.dao.ChatDao
|
||||||
import ru.daemonlord.messenger.data.chat.local.entity.ChatEntity
|
import ru.daemonlord.messenger.data.chat.local.entity.ChatEntity
|
||||||
@@ -109,6 +111,14 @@ class NetworkChatRepositoryTest {
|
|||||||
override suspend fun getChatById(chatId: Long): ChatReadDto {
|
override suspend fun getChatById(chatId: Long): ChatReadDto {
|
||||||
return chats.first()
|
return chats.first()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override suspend fun createInviteLink(chatId: Long): ChatInviteLinkDto {
|
||||||
|
return ChatInviteLinkDto(chatId = chatId, token = "token", inviteUrl = "https://chat.daemonlord.ru/invite/token")
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun joinByInvite(request: ChatJoinByInviteRequestDto): ChatReadDto {
|
||||||
|
return chats.first()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private class FakeChatDao(
|
private class FakeChatDao(
|
||||||
|
|||||||
Reference in New Issue
Block a user