diff --git a/core/ui/src/main/res/values-ru/strings.xml b/core/ui/src/main/res/values-ru/strings.xml index 9a99ec89..37cf84d2 100644 --- a/core/ui/src/main/res/values-ru/strings.xml +++ b/core/ui/src/main/res/values-ru/strings.xml @@ -286,4 +286,7 @@ Н Д Сейчас + + Вы действительно хотите создать чат «%s»? + Вы действительно хотите создать чат «%s» только с собой? diff --git a/core/ui/src/main/res/values/strings.xml b/core/ui/src/main/res/values/strings.xml index 6b824562..c25e95db 100644 --- a/core/ui/src/main/res/values/strings.xml +++ b/core/ui/src/main/res/values/strings.xml @@ -363,4 +363,7 @@ W D Now + + Are you sure you want to create chat «%s»? + Are you sure you want to create chat «%s» only with yourself? diff --git a/feature/createchat/src/main/kotlin/dev/meloda/fast/conversations/CreateChatViewModel.kt b/feature/createchat/src/main/kotlin/dev/meloda/fast/conversations/CreateChatViewModel.kt index b4a43fde..6692cc60 100644 --- a/feature/createchat/src/main/kotlin/dev/meloda/fast/conversations/CreateChatViewModel.kt +++ b/feature/createchat/src/main/kotlin/dev/meloda/fast/conversations/CreateChatViewModel.kt @@ -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 - val baseError: StateFlow - val currentOffset: StateFlow - val canPaginate: StateFlow - - val isChatCreated: StateFlow - - 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(null) - override val currentOffset = MutableStateFlow(0) - override val canPaginate = MutableStateFlow(false) + private val _screenState = MutableStateFlow(CreateChatScreenState.EMPTY) + val screenState: StateFlow = _screenState.asStateFlow() - override val isChatCreated = MutableStateFlow(null) + private val _baseError = MutableStateFlow(null) + val baseError: StateFlow = _baseError.asStateFlow() + + private val currentOffset = MutableStateFlow(0) + + private val _canPaginate = MutableStateFlow(false) + val canPaginate: StateFlow = _canPaginate.asStateFlow() + + private val _isChatCreated = MutableStateFlow(null) + val isChatCreated: StateFlow = _isChatCreated.asStateFlow() + + private val _finalChatTitle = MutableStateFlow("") + val finalChatTitle: StateFlow = _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") } } diff --git a/feature/createchat/src/main/kotlin/dev/meloda/fast/conversations/di/CreateChatModule.kt b/feature/createchat/src/main/kotlin/dev/meloda/fast/conversations/di/CreateChatModule.kt index 2b9ec1b5..e57b7382 100644 --- a/feature/createchat/src/main/kotlin/dev/meloda/fast/conversations/di/CreateChatModule.kt +++ b/feature/createchat/src/main/kotlin/dev/meloda/fast/conversations/di/CreateChatModule.kt @@ -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) } diff --git a/feature/createchat/src/main/kotlin/dev/meloda/fast/conversations/model/CreateChatScreenState.kt b/feature/createchat/src/main/kotlin/dev/meloda/fast/conversations/model/CreateChatScreenState.kt index 10a6591c..878ef734 100644 --- a/feature/createchat/src/main/kotlin/dev/meloda/fast/conversations/model/CreateChatScreenState.kt +++ b/feature/createchat/src/main/kotlin/dev/meloda/fast/conversations/model/CreateChatScreenState.kt @@ -10,7 +10,8 @@ data class CreateChatScreenState( val isPaginationExhausted: Boolean, val friends: List, val selectedFriendsIds: List, - 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 ) } } diff --git a/feature/createchat/src/main/kotlin/dev/meloda/fast/conversations/navigation/CreateChatNavigation.kt b/feature/createchat/src/main/kotlin/dev/meloda/fast/conversations/navigation/CreateChatNavigation.kt index 81ef6ef0..2ea96c69 100644 --- a/feature/createchat/src/main/kotlin/dev/meloda/fast/conversations/navigation/CreateChatNavigation.kt +++ b/feature/createchat/src/main/kotlin/dev/meloda/fast/conversations/navigation/CreateChatNavigation.kt @@ -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 { val context = LocalContext.current - val viewModel: CreateChatViewModel = koinViewModel( + val viewModel: CreateChatViewModel = koinViewModel( viewModelStoreOwner = context as AppCompatActivity ) diff --git a/feature/createchat/src/main/kotlin/dev/meloda/fast/conversations/presentation/CreateChatScreen.kt b/feature/createchat/src/main/kotlin/dev/meloda/fast/conversations/presentation/CreateChatScreen.kt index 9906e058..931dc713 100644 --- a/feature/createchat/src/main/kotlin/dev/meloda/fast/conversations/presentation/CreateChatScreen.kt +++ b/feature/createchat/src/main/kotlin/dev/meloda/fast/conversations/presentation/CreateChatScreen.kt @@ -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,