add confirmation dialog to chat creation
This commit introduces a confirmation dialog before creating a new chat. The dialog displays the final chat title, which is now dynamically generated based on the user's input or the names of the selected participants. Key changes: - Added a confirmation dialog that appears when the user clicks the "create chat" button. - Implemented logic to generate a provisional chat title from participants' names if no title is explicitly set. - Refactored `CreateChatViewModel` by removing the interface and simplifying the implementation. - Added new string resources for the confirmation dialog.
This commit is contained in:
+93
-65
@@ -17,74 +17,67 @@ import dev.meloda.fast.domain.GetLocalUserByIdUseCase
|
||||
import dev.meloda.fast.domain.MessagesUseCase
|
||||
import dev.meloda.fast.domain.util.asPresentation
|
||||
import dev.meloda.fast.model.BaseError
|
||||
import dev.meloda.fast.model.api.domain.VkUser
|
||||
import dev.meloda.fast.network.VkErrorCode
|
||||
import dev.meloda.fast.ui.model.api.UiFriend
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.asStateFlow
|
||||
import kotlinx.coroutines.flow.update
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
|
||||
interface CreateChatViewModel {
|
||||
|
||||
val screenState: StateFlow<CreateChatScreenState>
|
||||
val baseError: StateFlow<BaseError?>
|
||||
val currentOffset: StateFlow<Int>
|
||||
val canPaginate: StateFlow<Boolean>
|
||||
|
||||
val isChatCreated: StateFlow<Long?>
|
||||
|
||||
fun onPaginationConditionsMet()
|
||||
fun onRefresh()
|
||||
fun onErrorConsumed()
|
||||
|
||||
fun toggleFriendSelection(userId: Long)
|
||||
|
||||
fun onTitleTextInputChanged(newTitle: String)
|
||||
|
||||
fun onCreateChatButtonClicked()
|
||||
|
||||
fun onNavigatedBack()
|
||||
}
|
||||
|
||||
class CreateChatViewModelImpl(
|
||||
class CreateChatViewModel(
|
||||
private val friendsUseCase: FriendsUseCase,
|
||||
private val messagesUseCase: MessagesUseCase,
|
||||
private val imageLoader: ImageLoader,
|
||||
private val applicationContext: Context,
|
||||
private val getLocalUserByIdUseCase: GetLocalUserByIdUseCase,
|
||||
private val userSettings: UserSettings
|
||||
) : CreateChatViewModel, ViewModel() {
|
||||
) : ViewModel() {
|
||||
|
||||
override val screenState = MutableStateFlow(CreateChatScreenState.EMPTY)
|
||||
override val baseError = MutableStateFlow<BaseError?>(null)
|
||||
override val currentOffset = MutableStateFlow(0)
|
||||
override val canPaginate = MutableStateFlow(false)
|
||||
private val _screenState = MutableStateFlow(CreateChatScreenState.EMPTY)
|
||||
val screenState: StateFlow<CreateChatScreenState> = _screenState.asStateFlow()
|
||||
|
||||
override val isChatCreated = MutableStateFlow<Long?>(null)
|
||||
private val _baseError = MutableStateFlow<BaseError?>(null)
|
||||
val baseError: StateFlow<BaseError?> = _baseError.asStateFlow()
|
||||
|
||||
private val currentOffset = MutableStateFlow(0)
|
||||
|
||||
private val _canPaginate = MutableStateFlow(false)
|
||||
val canPaginate: StateFlow<Boolean> = _canPaginate.asStateFlow()
|
||||
|
||||
private val _isChatCreated = MutableStateFlow<Long?>(null)
|
||||
val isChatCreated: StateFlow<Long?> = _isChatCreated.asStateFlow()
|
||||
|
||||
private val _finalChatTitle = MutableStateFlow("")
|
||||
val finalChatTitle: StateFlow<String> = _finalChatTitle.asStateFlow()
|
||||
|
||||
private val useContactNames: Boolean = userSettings.useContactNames.value
|
||||
|
||||
private var accountUser: VkUser? = null
|
||||
|
||||
init {
|
||||
loadFriends()
|
||||
fetchAccountUser()
|
||||
fetchUsers()
|
||||
}
|
||||
|
||||
override fun onPaginationConditionsMet() {
|
||||
fun onPaginationConditionsMet() {
|
||||
currentOffset.update { screenState.value.friends.size }
|
||||
loadFriends()
|
||||
fetchUsers()
|
||||
}
|
||||
|
||||
override fun onRefresh() {
|
||||
fun onRefresh() {
|
||||
onErrorConsumed()
|
||||
loadFriends(offset = 0)
|
||||
fetchUsers(offset = 0)
|
||||
}
|
||||
|
||||
override fun onErrorConsumed() {
|
||||
baseError.setValue { null }
|
||||
fun onErrorConsumed() {
|
||||
_baseError.setValue { null }
|
||||
}
|
||||
|
||||
override fun toggleFriendSelection(userId: Long) {
|
||||
fun toggleFriendSelection(userId: Long) {
|
||||
val newSelectionList = screenState.value.selectedFriendsIds.toMutableList()
|
||||
|
||||
if (newSelectionList.contains(userId)) {
|
||||
@@ -93,26 +86,69 @@ class CreateChatViewModelImpl(
|
||||
newSelectionList.add(userId)
|
||||
}
|
||||
|
||||
screenState.setValue { old ->
|
||||
_screenState.setValue { old ->
|
||||
old.copy(selectedFriendsIds = newSelectionList)
|
||||
}
|
||||
|
||||
refreshFinalTitle()
|
||||
}
|
||||
|
||||
fun onTitleTextInputChanged(newTitle: String) {
|
||||
_screenState.setValue { old -> old.copy(chatTitle = newTitle) }
|
||||
|
||||
refreshFinalTitle()
|
||||
}
|
||||
|
||||
fun onCreateChatButtonClicked() {
|
||||
_screenState.setValue { old -> old.copy(showConfirmDialog = true) }
|
||||
}
|
||||
|
||||
fun onNavigatedBack() {
|
||||
viewModelScope.launch(Dispatchers.Main) {
|
||||
_isChatCreated.emit(null)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onTitleTextInputChanged(newTitle: String) {
|
||||
screenState.setValue { old -> old.copy(chatTitle = newTitle) }
|
||||
fun onConfirmDialogDismissed() {
|
||||
_screenState.setValue { old -> old.copy(showConfirmDialog = false) }
|
||||
}
|
||||
|
||||
override fun onCreateChatButtonClicked() {
|
||||
fun onConfirmDialogConfirmed() {
|
||||
_screenState.setValue { old -> old.copy(showConfirmDialog = false) }
|
||||
createChat()
|
||||
}
|
||||
|
||||
override fun onNavigatedBack() {
|
||||
viewModelScope.launch(Dispatchers.Main) {
|
||||
isChatCreated.emit(null)
|
||||
private fun fetchAccountUser() {
|
||||
viewModelScope.launch {
|
||||
accountUser = getLocalUserByIdUseCase.proceed(UserConfig.userId)
|
||||
if (accountUser != null) {
|
||||
_finalChatTitle.setValue { accountUser?.firstName.orEmpty() }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun loadFriends(
|
||||
private fun refreshFinalTitle() {
|
||||
if (screenState.value.chatTitle.trim().isNotEmpty()) {
|
||||
_finalChatTitle.setValue { screenState.value.chatTitle.trim() }
|
||||
} else {
|
||||
val accountAsFriend = accountUser?.asPresentation(useContactNames)
|
||||
|
||||
val accountList = accountAsFriend?.let(::listOf) ?: emptyList()
|
||||
|
||||
val selectedFriends = screenState.value.selectedFriendsIds
|
||||
.take(3)
|
||||
.takeIf { it.isNotEmpty() }
|
||||
?.mapNotNull { userId -> screenState.value.friends.find { it.userId == userId } }
|
||||
|
||||
val finalTitle =
|
||||
(accountList + selectedFriends.orEmpty()).joinToString(transform = UiFriend::firstName)
|
||||
.plus(if (screenState.value.selectedFriendsIds.size > 3) ", ..." else "")
|
||||
|
||||
_finalChatTitle.setValue { finalTitle }
|
||||
}
|
||||
}
|
||||
|
||||
private fun fetchUsers(
|
||||
offset: Int = currentOffset.value
|
||||
) {
|
||||
friendsUseCase.getFriends(count = LOAD_COUNT, offset = offset)
|
||||
@@ -121,13 +157,13 @@ class CreateChatViewModelImpl(
|
||||
error = ::handleError,
|
||||
success = { response ->
|
||||
val itemsCountSufficient = response.size == LOAD_COUNT
|
||||
canPaginate.setValue { itemsCountSufficient }
|
||||
_canPaginate.setValue { itemsCountSufficient }
|
||||
|
||||
val paginationExhausted = !itemsCountSufficient &&
|
||||
screenState.value.friends.isNotEmpty()
|
||||
|
||||
val imagesToPreload =
|
||||
response.mapNotNull { it.photo100.takeIf { !it.isNullOrEmpty() } }
|
||||
response.mapNotNull { it.photo100.takeIf { p -> !p.isNullOrEmpty() } }
|
||||
|
||||
imagesToPreload.forEach { url ->
|
||||
imageLoader.enqueue(
|
||||
@@ -147,11 +183,11 @@ class CreateChatViewModelImpl(
|
||||
isPaginationExhausted = paginationExhausted
|
||||
)
|
||||
if (offset == 0) {
|
||||
screenState.setValue {
|
||||
_screenState.setValue {
|
||||
newState.copy(friends = loadedFriends)
|
||||
}
|
||||
} else {
|
||||
screenState.setValue {
|
||||
_screenState.setValue {
|
||||
newState.copy(
|
||||
friends = newState.friends.plus(loadedFriends)
|
||||
)
|
||||
@@ -160,7 +196,7 @@ class CreateChatViewModelImpl(
|
||||
}
|
||||
)
|
||||
|
||||
screenState.setValue { old ->
|
||||
_screenState.setValue { old ->
|
||||
old.copy(
|
||||
isLoading = offset == 0 && state.isLoading(),
|
||||
isPaginating = offset > 0 && state.isLoading()
|
||||
@@ -171,27 +207,19 @@ class CreateChatViewModelImpl(
|
||||
|
||||
private fun createChat() {
|
||||
viewModelScope.launch {
|
||||
val title = screenState.value.chatTitle.takeUnless(String::isBlank)
|
||||
|
||||
val accountAsFriend =
|
||||
getLocalUserByIdUseCase.proceed(UserConfig.userId)?.asPresentation(useContactNames)
|
||||
|
||||
val accountList = accountAsFriend?.let(::listOf) ?: emptyList()
|
||||
|
||||
val selectedFriends = screenState.value.selectedFriendsIds
|
||||
.takeIf { it.isNotEmpty() }
|
||||
?.mapNotNull { userId -> screenState.value.friends.find { it.userId == userId } }
|
||||
|
||||
messagesUseCase.createChat(
|
||||
userIds = selectedFriends?.map { it.userId },
|
||||
title = title
|
||||
?: (accountList + selectedFriends.orEmpty()).joinToString(transform = UiFriend::firstName)
|
||||
title = finalChatTitle.value
|
||||
).listenValue(viewModelScope) { state ->
|
||||
state.processState(
|
||||
error = ::handleError,
|
||||
success = { response ->
|
||||
withContext(Dispatchers.Main) {
|
||||
isChatCreated.emit(2_000_000_000 + response)
|
||||
_isChatCreated.emit(2_000_000_000 + response)
|
||||
}
|
||||
}
|
||||
)
|
||||
@@ -204,11 +232,11 @@ class CreateChatViewModelImpl(
|
||||
is State.Error.ApiError -> {
|
||||
when (error.errorCode) {
|
||||
VkErrorCode.USER_AUTHORIZATION_FAILED -> {
|
||||
baseError.setValue { BaseError.SessionExpired }
|
||||
_baseError.setValue { BaseError.SessionExpired }
|
||||
}
|
||||
|
||||
else -> {
|
||||
baseError.setValue {
|
||||
_baseError.setValue {
|
||||
BaseError.SimpleError(message = error.errorMessage)
|
||||
}
|
||||
}
|
||||
@@ -216,19 +244,19 @@ class CreateChatViewModelImpl(
|
||||
}
|
||||
|
||||
State.Error.ConnectionError -> {
|
||||
baseError.setValue {
|
||||
_baseError.setValue {
|
||||
BaseError.SimpleError(message = "Connection error")
|
||||
}
|
||||
}
|
||||
|
||||
State.Error.InternalError -> {
|
||||
baseError.setValue {
|
||||
_baseError.setValue {
|
||||
BaseError.SimpleError(message = "Internal error")
|
||||
}
|
||||
}
|
||||
|
||||
State.Error.UnknownError -> {
|
||||
baseError.setValue {
|
||||
_baseError.setValue {
|
||||
BaseError.SimpleError(message = "Unknown error")
|
||||
}
|
||||
}
|
||||
|
||||
+2
-2
@@ -1,9 +1,9 @@
|
||||
package dev.meloda.fast.conversations.di
|
||||
|
||||
import dev.meloda.fast.conversations.CreateChatViewModelImpl
|
||||
import dev.meloda.fast.conversations.CreateChatViewModel
|
||||
import org.koin.core.module.dsl.viewModelOf
|
||||
import org.koin.dsl.module
|
||||
|
||||
val createChatModule = module {
|
||||
viewModelOf(::CreateChatViewModelImpl)
|
||||
viewModelOf(::CreateChatViewModel)
|
||||
}
|
||||
|
||||
+4
-2
@@ -10,7 +10,8 @@ data class CreateChatScreenState(
|
||||
val isPaginationExhausted: Boolean,
|
||||
val friends: List<UiFriend>,
|
||||
val selectedFriendsIds: List<Long>,
|
||||
val chatTitle: String
|
||||
val chatTitle: String,
|
||||
val showConfirmDialog: Boolean
|
||||
) {
|
||||
companion object {
|
||||
val EMPTY: CreateChatScreenState = CreateChatScreenState(
|
||||
@@ -19,7 +20,8 @@ data class CreateChatScreenState(
|
||||
isPaginationExhausted = false,
|
||||
friends = emptyList(),
|
||||
selectedFriendsIds = emptyList(),
|
||||
chatTitle = ""
|
||||
chatTitle = "",
|
||||
showConfirmDialog = false
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
+1
-2
@@ -6,7 +6,6 @@ import androidx.navigation.NavController
|
||||
import androidx.navigation.NavGraphBuilder
|
||||
import androidx.navigation.compose.composable
|
||||
import dev.meloda.fast.conversations.CreateChatViewModel
|
||||
import dev.meloda.fast.conversations.CreateChatViewModelImpl
|
||||
import dev.meloda.fast.conversations.presentation.CreateChatRoute
|
||||
import kotlinx.serialization.Serializable
|
||||
import org.koin.compose.viewmodel.koinViewModel
|
||||
@@ -20,7 +19,7 @@ fun NavGraphBuilder.createChatScreen(
|
||||
) {
|
||||
composable<CreateChat> {
|
||||
val context = LocalContext.current
|
||||
val viewModel: CreateChatViewModel = koinViewModel<CreateChatViewModelImpl>(
|
||||
val viewModel: CreateChatViewModel = koinViewModel(
|
||||
viewModelStoreOwner = context as AppCompatActivity
|
||||
)
|
||||
|
||||
|
||||
+22
@@ -64,6 +64,7 @@ import dev.meloda.fast.model.BaseError
|
||||
import dev.meloda.fast.ui.R
|
||||
import dev.meloda.fast.ui.components.FullScreenContainedLoader
|
||||
import dev.meloda.fast.ui.components.IconButton
|
||||
import dev.meloda.fast.ui.components.MaterialDialog
|
||||
import dev.meloda.fast.ui.components.NoItemsView
|
||||
import dev.meloda.fast.ui.components.VkErrorView
|
||||
import dev.meloda.fast.ui.theme.LocalHazeState
|
||||
@@ -89,6 +90,27 @@ fun CreateChatRoute(
|
||||
}
|
||||
}
|
||||
|
||||
if (screenState.showConfirmDialog) {
|
||||
MaterialDialog(
|
||||
onDismissRequest = viewModel::onConfirmDialogDismissed,
|
||||
title = stringResource(R.string.confirm),
|
||||
text = when {
|
||||
screenState.selectedFriendsIds.isEmpty() -> stringResource(
|
||||
R.string.confirm_chat_create_empty_with_title,
|
||||
viewModel.finalChatTitle.value
|
||||
)
|
||||
|
||||
else -> stringResource(
|
||||
R.string.confirm_chat_create_with_title,
|
||||
viewModel.finalChatTitle.value
|
||||
)
|
||||
},
|
||||
confirmAction = viewModel::onConfirmDialogConfirmed,
|
||||
confirmText = stringResource(R.string.action_create),
|
||||
cancelText = stringResource(R.string.cancel)
|
||||
)
|
||||
}
|
||||
|
||||
CreateChatScreen(
|
||||
screenState = screenState,
|
||||
baseError = baseError,
|
||||
|
||||
Reference in New Issue
Block a user