Localize contacts screen and contact errors/messages (EN/RU)
Some checks failed
Android CI / android (push) Failing after 5m28s
Android Release / release (push) Failing after 5m34s
CI / test (push) Failing after 2m37s

This commit is contained in:
2026-03-11 06:16:37 +03:00
parent 2ffc4cce09
commit 3f9aa83110
4 changed files with 61 additions and 19 deletions

View File

@@ -30,12 +30,14 @@ import androidx.compose.runtime.snapshotFlow
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalConfiguration import androidx.compose.ui.platform.LocalConfiguration
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.hilt.navigation.compose.hiltViewModel import androidx.hilt.navigation.compose.hiltViewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.lifecycle.compose.collectAsStateWithLifecycle
import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.flow.collectLatest
import ru.daemonlord.messenger.R
@Composable @Composable
fun ContactsRoute( fun ContactsRoute(
@@ -104,7 +106,7 @@ private fun ContactsScreen(
verticalArrangement = Arrangement.spacedBy(12.dp), verticalArrangement = Arrangement.spacedBy(12.dp),
) { ) {
TopAppBar( TopAppBar(
title = { Text("Contacts") }, title = { Text(stringResource(id = R.string.contacts_title)) },
) )
PullToRefreshBox( PullToRefreshBox(
isRefreshing = state.isRefreshing, isRefreshing = state.isRefreshing,
@@ -122,7 +124,7 @@ private fun ContactsScreen(
onValueChange = onQueryChanged, onValueChange = onQueryChanged,
modifier = Modifier.fillMaxWidth(), modifier = Modifier.fillMaxWidth(),
singleLine = true, singleLine = true,
label = { Text("Search contacts/users") }, label = { Text(stringResource(id = R.string.contacts_search_label)) },
) )
Row( Row(
modifier = Modifier.fillMaxWidth(), modifier = Modifier.fillMaxWidth(),
@@ -134,10 +136,10 @@ private fun ContactsScreen(
onValueChange = onAddByEmailChanged, onValueChange = onAddByEmailChanged,
modifier = Modifier.weight(1f), modifier = Modifier.weight(1f),
singleLine = true, singleLine = true,
label = { Text("Add by email") }, label = { Text(stringResource(id = R.string.contacts_add_by_email_label)) },
) )
Button(onClick = onAddContactByEmail) { Button(onClick = onAddContactByEmail) {
Text("Add") Text(stringResource(id = R.string.common_create))
} }
} }
@@ -172,7 +174,7 @@ private fun ContactsScreen(
if (state.isSearchingUsers || state.query.trim().length >= 2) { if (state.isSearchingUsers || state.query.trim().length >= 2) {
item(key = "search_header") { item(key = "search_header") {
Text( Text(
text = "Search results", text = stringResource(id = R.string.contacts_search_results),
style = MaterialTheme.typography.titleSmall, style = MaterialTheme.typography.titleSmall,
fontWeight = FontWeight.SemiBold, fontWeight = FontWeight.SemiBold,
) )
@@ -197,7 +199,7 @@ private fun ContactsScreen(
) )
} }
OutlinedButton(onClick = { onAddContact(user.id) }) { OutlinedButton(onClick = { onAddContact(user.id) }) {
Text("Add") Text(stringResource(id = R.string.common_create))
} }
} }
} }
@@ -205,7 +207,7 @@ private fun ContactsScreen(
item(key = "contacts_header") { item(key = "contacts_header") {
Text( Text(
text = "My contacts", text = stringResource(id = R.string.contacts_my_contacts),
style = MaterialTheme.typography.titleSmall, style = MaterialTheme.typography.titleSmall,
fontWeight = FontWeight.SemiBold, fontWeight = FontWeight.SemiBold,
modifier = Modifier.padding(top = 4.dp), modifier = Modifier.padding(top = 4.dp),
@@ -226,13 +228,13 @@ private fun ContactsScreen(
overflow = TextOverflow.Ellipsis, overflow = TextOverflow.Ellipsis,
) )
Text( Text(
text = contact.username?.let { "@$it" } ?: "last seen recently", text = contact.username?.let { "@$it" } ?: stringResource(id = R.string.contacts_last_seen_recently),
style = MaterialTheme.typography.bodySmall, style = MaterialTheme.typography.bodySmall,
color = MaterialTheme.colorScheme.onSurfaceVariant, color = MaterialTheme.colorScheme.onSurfaceVariant,
) )
} }
OutlinedButton(onClick = { onRemoveContact(contact.id) }) { OutlinedButton(onClick = { onRemoveContact(contact.id) }) {
Text("Remove") Text(stringResource(id = R.string.contacts_remove))
} }
} }
} }
@@ -240,7 +242,7 @@ private fun ContactsScreen(
if (state.contacts.isEmpty()) { if (state.contacts.isEmpty()) {
item(key = "empty_contacts") { item(key = "empty_contacts") {
Text( Text(
text = "No contacts yet.", text = stringResource(id = R.string.contacts_empty),
style = MaterialTheme.typography.bodyMedium, style = MaterialTheme.typography.bodyMedium,
color = MaterialTheme.colorScheme.onSurfaceVariant, color = MaterialTheme.colorScheme.onSurfaceVariant,
) )

View File

@@ -2,7 +2,9 @@ package ru.daemonlord.messenger.ui.contacts
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import dagger.hilt.android.qualifiers.ApplicationContext
import dagger.hilt.android.lifecycle.HiltViewModel import dagger.hilt.android.lifecycle.HiltViewModel
import android.content.Context
import kotlinx.coroutines.Job import kotlinx.coroutines.Job
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
@@ -10,6 +12,7 @@ import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.update import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import ru.daemonlord.messenger.R
import ru.daemonlord.messenger.domain.account.repository.AccountRepository import ru.daemonlord.messenger.domain.account.repository.AccountRepository
import ru.daemonlord.messenger.domain.common.AppError import ru.daemonlord.messenger.domain.common.AppError
import ru.daemonlord.messenger.domain.common.AppResult import ru.daemonlord.messenger.domain.common.AppResult
@@ -18,6 +21,7 @@ import javax.inject.Inject
@HiltViewModel @HiltViewModel
class ContactsViewModel @Inject constructor( class ContactsViewModel @Inject constructor(
private val accountRepository: AccountRepository, private val accountRepository: AccountRepository,
@ApplicationContext private val context: Context,
) : ViewModel() { ) : ViewModel() {
private val _uiState = MutableStateFlow(ContactsUiState()) private val _uiState = MutableStateFlow(ContactsUiState())
@@ -76,7 +80,7 @@ class ContactsViewModel @Inject constructor(
viewModelScope.launch { viewModelScope.launch {
when (val result = accountRepository.addContact(userId = userId)) { when (val result = accountRepository.addContact(userId = userId)) {
is AppResult.Success -> { is AppResult.Success -> {
_uiState.update { it.copy(infoMessage = "Contact added.") } _uiState.update { it.copy(infoMessage = context.getString(R.string.contacts_info_added)) }
loadContacts(forceRefresh = true) loadContacts(forceRefresh = true)
} }
is AppResult.Error -> _uiState.update { it.copy(errorMessage = result.reason.toUiMessage()) } is AppResult.Error -> _uiState.update { it.copy(errorMessage = result.reason.toUiMessage()) }
@@ -87,7 +91,7 @@ class ContactsViewModel @Inject constructor(
fun addContactByEmail() { fun addContactByEmail() {
val email = uiState.value.addByEmail.trim() val email = uiState.value.addByEmail.trim()
if (email.isBlank()) { if (email.isBlank()) {
_uiState.update { it.copy(errorMessage = "Email is required.") } _uiState.update { it.copy(errorMessage = context.getString(R.string.contacts_error_email_required)) }
return return
} }
viewModelScope.launch { viewModelScope.launch {
@@ -96,7 +100,7 @@ class ContactsViewModel @Inject constructor(
_uiState.update { _uiState.update {
it.copy( it.copy(
addByEmail = "", addByEmail = "",
infoMessage = "Contact added by email.", infoMessage = context.getString(R.string.contacts_info_added_by_email),
) )
} }
loadContacts(forceRefresh = true) loadContacts(forceRefresh = true)
@@ -110,7 +114,7 @@ class ContactsViewModel @Inject constructor(
viewModelScope.launch { viewModelScope.launch {
when (val result = accountRepository.removeContact(userId = userId)) { when (val result = accountRepository.removeContact(userId = userId)) {
is AppResult.Success -> { is AppResult.Success -> {
_uiState.update { it.copy(infoMessage = "Contact removed.") } _uiState.update { it.copy(infoMessage = context.getString(R.string.contacts_info_removed)) }
loadContacts(forceRefresh = true) loadContacts(forceRefresh = true)
} }
is AppResult.Error -> _uiState.update { it.copy(errorMessage = result.reason.toUiMessage()) } is AppResult.Error -> _uiState.update { it.copy(errorMessage = result.reason.toUiMessage()) }
@@ -148,11 +152,11 @@ class ContactsViewModel @Inject constructor(
private fun AppError.toUiMessage(): String { private fun AppError.toUiMessage(): String {
return when (this) { return when (this) {
AppError.Network -> "Network error." AppError.Network -> context.getString(R.string.error_network)
AppError.Unauthorized -> "Session expired." AppError.Unauthorized -> context.getString(R.string.error_session_expired)
AppError.InvalidCredentials -> "Authorization error." AppError.InvalidCredentials -> context.getString(R.string.error_authorization)
is AppError.Server -> this.message ?: "Server error." is AppError.Server -> this.message ?: context.getString(R.string.error_server)
is AppError.Unknown -> this.cause?.message ?: "Unknown error." is AppError.Unknown -> this.cause?.message ?: context.getString(R.string.error_unknown)
} }
} }
} }

View File

@@ -208,4 +208,22 @@
<string name="auth_send_reset_link">Отправить ссылку сброса</string> <string name="auth_send_reset_link">Отправить ссылку сброса</string>
<string name="auth_new_password">Новый пароль</string> <string name="auth_new_password">Новый пароль</string>
<string name="auth_reset_with_token">Сбросить по токену</string> <string name="auth_reset_with_token">Сбросить по токену</string>
<string name="contacts_title">Контакты</string>
<string name="contacts_search_label">Поиск контактов/пользователей</string>
<string name="contacts_add_by_email_label">Добавить по email</string>
<string name="contacts_search_results">Результаты поиска</string>
<string name="contacts_my_contacts">Мои контакты</string>
<string name="contacts_last_seen_recently">был(а) недавно</string>
<string name="contacts_remove">Удалить</string>
<string name="contacts_empty">Контактов пока нет.</string>
<string name="contacts_info_added">Контакт добавлен.</string>
<string name="contacts_info_added_by_email">Контакт добавлен по email.</string>
<string name="contacts_info_removed">Контакт удален.</string>
<string name="contacts_error_email_required">Требуется email.</string>
<string name="error_network">Ошибка сети.</string>
<string name="error_session_expired">Сессия истекла.</string>
<string name="error_authorization">Ошибка авторизации.</string>
<string name="error_server">Ошибка сервера.</string>
<string name="error_unknown">Неизвестная ошибка.</string>
</resources> </resources>

View File

@@ -208,4 +208,22 @@
<string name="auth_send_reset_link">Send reset link</string> <string name="auth_send_reset_link">Send reset link</string>
<string name="auth_new_password">New password</string> <string name="auth_new_password">New password</string>
<string name="auth_reset_with_token">Reset with token</string> <string name="auth_reset_with_token">Reset with token</string>
<string name="contacts_title">Contacts</string>
<string name="contacts_search_label">Search contacts/users</string>
<string name="contacts_add_by_email_label">Add by email</string>
<string name="contacts_search_results">Search results</string>
<string name="contacts_my_contacts">My contacts</string>
<string name="contacts_last_seen_recently">last seen recently</string>
<string name="contacts_remove">Remove</string>
<string name="contacts_empty">No contacts yet.</string>
<string name="contacts_info_added">Contact added.</string>
<string name="contacts_info_added_by_email">Contact added by email.</string>
<string name="contacts_info_removed">Contact removed.</string>
<string name="contacts_error_email_required">Email is required.</string>
<string name="error_network">Network error.</string>
<string name="error_session_expired">Session expired.</string>
<string name="error_authorization">Authorization error.</string>
<string name="error_server">Server error.</string>
<string name="error_unknown">Unknown error.</string>
</resources> </resources>