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:
@@ -286,4 +286,7 @@
|
|||||||
<string name="week_short">Н</string>
|
<string name="week_short">Н</string>
|
||||||
<string name="day_short">Д</string>
|
<string name="day_short">Д</string>
|
||||||
<string name="time_now">Сейчас</string>
|
<string name="time_now">Сейчас</string>
|
||||||
|
|
||||||
|
<string name="confirm_chat_create_with_title">Вы действительно хотите создать чат «%s»?</string>
|
||||||
|
<string name="confirm_chat_create_empty_with_title">Вы действительно хотите создать чат «%s» только с собой?</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
@@ -363,4 +363,7 @@
|
|||||||
<string name="week_short">W</string>
|
<string name="week_short">W</string>
|
||||||
<string name="day_short">D</string>
|
<string name="day_short">D</string>
|
||||||
<string name="time_now">Now</string>
|
<string name="time_now">Now</string>
|
||||||
|
|
||||||
|
<string name="confirm_chat_create_with_title">Are you sure you want to create chat «%s»?</string>
|
||||||
|
<string name="confirm_chat_create_empty_with_title">Are you sure you want to create chat «%s» only with yourself?</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
+93
-65
@@ -17,74 +17,67 @@ import dev.meloda.fast.domain.GetLocalUserByIdUseCase
|
|||||||
import dev.meloda.fast.domain.MessagesUseCase
|
import dev.meloda.fast.domain.MessagesUseCase
|
||||||
import dev.meloda.fast.domain.util.asPresentation
|
import dev.meloda.fast.domain.util.asPresentation
|
||||||
import dev.meloda.fast.model.BaseError
|
import dev.meloda.fast.model.BaseError
|
||||||
|
import dev.meloda.fast.model.api.domain.VkUser
|
||||||
import dev.meloda.fast.network.VkErrorCode
|
import dev.meloda.fast.network.VkErrorCode
|
||||||
import dev.meloda.fast.ui.model.api.UiFriend
|
import dev.meloda.fast.ui.model.api.UiFriend
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
import kotlinx.coroutines.flow.StateFlow
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
|
import kotlinx.coroutines.flow.asStateFlow
|
||||||
import kotlinx.coroutines.flow.update
|
import kotlinx.coroutines.flow.update
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
|
|
||||||
interface CreateChatViewModel {
|
class 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(
|
|
||||||
private val friendsUseCase: FriendsUseCase,
|
private val friendsUseCase: FriendsUseCase,
|
||||||
private val messagesUseCase: MessagesUseCase,
|
private val messagesUseCase: MessagesUseCase,
|
||||||
private val imageLoader: ImageLoader,
|
private val imageLoader: ImageLoader,
|
||||||
private val applicationContext: Context,
|
private val applicationContext: Context,
|
||||||
private val getLocalUserByIdUseCase: GetLocalUserByIdUseCase,
|
private val getLocalUserByIdUseCase: GetLocalUserByIdUseCase,
|
||||||
private val userSettings: UserSettings
|
private val userSettings: UserSettings
|
||||||
) : CreateChatViewModel, ViewModel() {
|
) : ViewModel() {
|
||||||
|
|
||||||
override val screenState = MutableStateFlow(CreateChatScreenState.EMPTY)
|
private val _screenState = MutableStateFlow(CreateChatScreenState.EMPTY)
|
||||||
override val baseError = MutableStateFlow<BaseError?>(null)
|
val screenState: StateFlow<CreateChatScreenState> = _screenState.asStateFlow()
|
||||||
override val currentOffset = MutableStateFlow(0)
|
|
||||||
override val canPaginate = MutableStateFlow(false)
|
|
||||||
|
|
||||||
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 val useContactNames: Boolean = userSettings.useContactNames.value
|
||||||
|
|
||||||
|
private var accountUser: VkUser? = null
|
||||||
|
|
||||||
init {
|
init {
|
||||||
loadFriends()
|
fetchAccountUser()
|
||||||
|
fetchUsers()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onPaginationConditionsMet() {
|
fun onPaginationConditionsMet() {
|
||||||
currentOffset.update { screenState.value.friends.size }
|
currentOffset.update { screenState.value.friends.size }
|
||||||
loadFriends()
|
fetchUsers()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onRefresh() {
|
fun onRefresh() {
|
||||||
onErrorConsumed()
|
onErrorConsumed()
|
||||||
loadFriends(offset = 0)
|
fetchUsers(offset = 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onErrorConsumed() {
|
fun onErrorConsumed() {
|
||||||
baseError.setValue { null }
|
_baseError.setValue { null }
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun toggleFriendSelection(userId: Long) {
|
fun toggleFriendSelection(userId: Long) {
|
||||||
val newSelectionList = screenState.value.selectedFriendsIds.toMutableList()
|
val newSelectionList = screenState.value.selectedFriendsIds.toMutableList()
|
||||||
|
|
||||||
if (newSelectionList.contains(userId)) {
|
if (newSelectionList.contains(userId)) {
|
||||||
@@ -93,26 +86,69 @@ class CreateChatViewModelImpl(
|
|||||||
newSelectionList.add(userId)
|
newSelectionList.add(userId)
|
||||||
}
|
}
|
||||||
|
|
||||||
screenState.setValue { old ->
|
_screenState.setValue { old ->
|
||||||
old.copy(selectedFriendsIds = newSelectionList)
|
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) {
|
fun onConfirmDialogDismissed() {
|
||||||
screenState.setValue { old -> old.copy(chatTitle = newTitle) }
|
_screenState.setValue { old -> old.copy(showConfirmDialog = false) }
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onCreateChatButtonClicked() {
|
fun onConfirmDialogConfirmed() {
|
||||||
|
_screenState.setValue { old -> old.copy(showConfirmDialog = false) }
|
||||||
createChat()
|
createChat()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onNavigatedBack() {
|
private fun fetchAccountUser() {
|
||||||
viewModelScope.launch(Dispatchers.Main) {
|
viewModelScope.launch {
|
||||||
isChatCreated.emit(null)
|
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
|
offset: Int = currentOffset.value
|
||||||
) {
|
) {
|
||||||
friendsUseCase.getFriends(count = LOAD_COUNT, offset = offset)
|
friendsUseCase.getFriends(count = LOAD_COUNT, offset = offset)
|
||||||
@@ -121,13 +157,13 @@ class CreateChatViewModelImpl(
|
|||||||
error = ::handleError,
|
error = ::handleError,
|
||||||
success = { response ->
|
success = { response ->
|
||||||
val itemsCountSufficient = response.size == LOAD_COUNT
|
val itemsCountSufficient = response.size == LOAD_COUNT
|
||||||
canPaginate.setValue { itemsCountSufficient }
|
_canPaginate.setValue { itemsCountSufficient }
|
||||||
|
|
||||||
val paginationExhausted = !itemsCountSufficient &&
|
val paginationExhausted = !itemsCountSufficient &&
|
||||||
screenState.value.friends.isNotEmpty()
|
screenState.value.friends.isNotEmpty()
|
||||||
|
|
||||||
val imagesToPreload =
|
val imagesToPreload =
|
||||||
response.mapNotNull { it.photo100.takeIf { !it.isNullOrEmpty() } }
|
response.mapNotNull { it.photo100.takeIf { p -> !p.isNullOrEmpty() } }
|
||||||
|
|
||||||
imagesToPreload.forEach { url ->
|
imagesToPreload.forEach { url ->
|
||||||
imageLoader.enqueue(
|
imageLoader.enqueue(
|
||||||
@@ -147,11 +183,11 @@ class CreateChatViewModelImpl(
|
|||||||
isPaginationExhausted = paginationExhausted
|
isPaginationExhausted = paginationExhausted
|
||||||
)
|
)
|
||||||
if (offset == 0) {
|
if (offset == 0) {
|
||||||
screenState.setValue {
|
_screenState.setValue {
|
||||||
newState.copy(friends = loadedFriends)
|
newState.copy(friends = loadedFriends)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
screenState.setValue {
|
_screenState.setValue {
|
||||||
newState.copy(
|
newState.copy(
|
||||||
friends = newState.friends.plus(loadedFriends)
|
friends = newState.friends.plus(loadedFriends)
|
||||||
)
|
)
|
||||||
@@ -160,7 +196,7 @@ class CreateChatViewModelImpl(
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
screenState.setValue { old ->
|
_screenState.setValue { old ->
|
||||||
old.copy(
|
old.copy(
|
||||||
isLoading = offset == 0 && state.isLoading(),
|
isLoading = offset == 0 && state.isLoading(),
|
||||||
isPaginating = offset > 0 && state.isLoading()
|
isPaginating = offset > 0 && state.isLoading()
|
||||||
@@ -171,27 +207,19 @@ class CreateChatViewModelImpl(
|
|||||||
|
|
||||||
private fun createChat() {
|
private fun createChat() {
|
||||||
viewModelScope.launch {
|
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
|
val selectedFriends = screenState.value.selectedFriendsIds
|
||||||
.takeIf { it.isNotEmpty() }
|
.takeIf { it.isNotEmpty() }
|
||||||
?.mapNotNull { userId -> screenState.value.friends.find { it.userId == userId } }
|
?.mapNotNull { userId -> screenState.value.friends.find { it.userId == userId } }
|
||||||
|
|
||||||
messagesUseCase.createChat(
|
messagesUseCase.createChat(
|
||||||
userIds = selectedFriends?.map { it.userId },
|
userIds = selectedFriends?.map { it.userId },
|
||||||
title = title
|
title = finalChatTitle.value
|
||||||
?: (accountList + selectedFriends.orEmpty()).joinToString(transform = UiFriend::firstName)
|
|
||||||
).listenValue(viewModelScope) { state ->
|
).listenValue(viewModelScope) { state ->
|
||||||
state.processState(
|
state.processState(
|
||||||
error = ::handleError,
|
error = ::handleError,
|
||||||
success = { response ->
|
success = { response ->
|
||||||
withContext(Dispatchers.Main) {
|
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 -> {
|
is State.Error.ApiError -> {
|
||||||
when (error.errorCode) {
|
when (error.errorCode) {
|
||||||
VkErrorCode.USER_AUTHORIZATION_FAILED -> {
|
VkErrorCode.USER_AUTHORIZATION_FAILED -> {
|
||||||
baseError.setValue { BaseError.SessionExpired }
|
_baseError.setValue { BaseError.SessionExpired }
|
||||||
}
|
}
|
||||||
|
|
||||||
else -> {
|
else -> {
|
||||||
baseError.setValue {
|
_baseError.setValue {
|
||||||
BaseError.SimpleError(message = error.errorMessage)
|
BaseError.SimpleError(message = error.errorMessage)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -216,19 +244,19 @@ class CreateChatViewModelImpl(
|
|||||||
}
|
}
|
||||||
|
|
||||||
State.Error.ConnectionError -> {
|
State.Error.ConnectionError -> {
|
||||||
baseError.setValue {
|
_baseError.setValue {
|
||||||
BaseError.SimpleError(message = "Connection error")
|
BaseError.SimpleError(message = "Connection error")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
State.Error.InternalError -> {
|
State.Error.InternalError -> {
|
||||||
baseError.setValue {
|
_baseError.setValue {
|
||||||
BaseError.SimpleError(message = "Internal error")
|
BaseError.SimpleError(message = "Internal error")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
State.Error.UnknownError -> {
|
State.Error.UnknownError -> {
|
||||||
baseError.setValue {
|
_baseError.setValue {
|
||||||
BaseError.SimpleError(message = "Unknown error")
|
BaseError.SimpleError(message = "Unknown error")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-2
@@ -1,9 +1,9 @@
|
|||||||
package dev.meloda.fast.conversations.di
|
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.core.module.dsl.viewModelOf
|
||||||
import org.koin.dsl.module
|
import org.koin.dsl.module
|
||||||
|
|
||||||
val createChatModule = module {
|
val createChatModule = module {
|
||||||
viewModelOf(::CreateChatViewModelImpl)
|
viewModelOf(::CreateChatViewModel)
|
||||||
}
|
}
|
||||||
|
|||||||
+4
-2
@@ -10,7 +10,8 @@ data class CreateChatScreenState(
|
|||||||
val isPaginationExhausted: Boolean,
|
val isPaginationExhausted: Boolean,
|
||||||
val friends: List<UiFriend>,
|
val friends: List<UiFriend>,
|
||||||
val selectedFriendsIds: List<Long>,
|
val selectedFriendsIds: List<Long>,
|
||||||
val chatTitle: String
|
val chatTitle: String,
|
||||||
|
val showConfirmDialog: Boolean
|
||||||
) {
|
) {
|
||||||
companion object {
|
companion object {
|
||||||
val EMPTY: CreateChatScreenState = CreateChatScreenState(
|
val EMPTY: CreateChatScreenState = CreateChatScreenState(
|
||||||
@@ -19,7 +20,8 @@ data class CreateChatScreenState(
|
|||||||
isPaginationExhausted = false,
|
isPaginationExhausted = false,
|
||||||
friends = emptyList(),
|
friends = emptyList(),
|
||||||
selectedFriendsIds = emptyList(),
|
selectedFriendsIds = emptyList(),
|
||||||
chatTitle = ""
|
chatTitle = "",
|
||||||
|
showConfirmDialog = false
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-2
@@ -6,7 +6,6 @@ import androidx.navigation.NavController
|
|||||||
import androidx.navigation.NavGraphBuilder
|
import androidx.navigation.NavGraphBuilder
|
||||||
import androidx.navigation.compose.composable
|
import androidx.navigation.compose.composable
|
||||||
import dev.meloda.fast.conversations.CreateChatViewModel
|
import dev.meloda.fast.conversations.CreateChatViewModel
|
||||||
import dev.meloda.fast.conversations.CreateChatViewModelImpl
|
|
||||||
import dev.meloda.fast.conversations.presentation.CreateChatRoute
|
import dev.meloda.fast.conversations.presentation.CreateChatRoute
|
||||||
import kotlinx.serialization.Serializable
|
import kotlinx.serialization.Serializable
|
||||||
import org.koin.compose.viewmodel.koinViewModel
|
import org.koin.compose.viewmodel.koinViewModel
|
||||||
@@ -20,7 +19,7 @@ fun NavGraphBuilder.createChatScreen(
|
|||||||
) {
|
) {
|
||||||
composable<CreateChat> {
|
composable<CreateChat> {
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
val viewModel: CreateChatViewModel = koinViewModel<CreateChatViewModelImpl>(
|
val viewModel: CreateChatViewModel = koinViewModel(
|
||||||
viewModelStoreOwner = context as AppCompatActivity
|
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.R
|
||||||
import dev.meloda.fast.ui.components.FullScreenContainedLoader
|
import dev.meloda.fast.ui.components.FullScreenContainedLoader
|
||||||
import dev.meloda.fast.ui.components.IconButton
|
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.NoItemsView
|
||||||
import dev.meloda.fast.ui.components.VkErrorView
|
import dev.meloda.fast.ui.components.VkErrorView
|
||||||
import dev.meloda.fast.ui.theme.LocalHazeState
|
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(
|
CreateChatScreen(
|
||||||
screenState = screenState,
|
screenState = screenState,
|
||||||
baseError = baseError,
|
baseError = baseError,
|
||||||
|
|||||||
Reference in New Issue
Block a user