diff --git a/android/app/src/main/java/ru/daemonlord/messenger/ui/chat/ChatScreen.kt b/android/app/src/main/java/ru/daemonlord/messenger/ui/chat/ChatScreen.kt
index bdf0cb3..93af5a0 100644
--- a/android/app/src/main/java/ru/daemonlord/messenger/ui/chat/ChatScreen.kt
+++ b/android/app/src/main/java/ru/daemonlord/messenger/ui/chat/ChatScreen.kt
@@ -386,7 +386,8 @@ fun ChatScreen(
val senderNameByUserId = remember(state.messages, state.chatMembers) {
val fromMembers = state.chatMembers.associate { member ->
val resolved = member.name.ifBlank {
- member.username?.takeIf { it.isNotBlank() }?.let { "@$it" } ?: "User #${member.userId}"
+ member.username?.takeIf { it.isNotBlank() }?.let { "@$it" }
+ ?: context.getString(R.string.chat_user_fallback_with_id, member.userId)
}
member.userId to resolved
}
@@ -694,7 +695,9 @@ fun ChatScreen(
}
Column(modifier = Modifier.weight(1f)) {
Text(
- text = state.chatTitle.ifBlank { "Chat #${state.chatId}" },
+ text = state.chatTitle.ifBlank {
+ stringResource(id = R.string.chat_title_fallback, state.chatId)
+ },
style = MaterialTheme.typography.titleSmall,
fontWeight = FontWeight.SemiBold,
maxLines = 1,
@@ -1287,7 +1290,9 @@ fun ChatScreen(
}
Column(modifier = Modifier.weight(1f)) {
Text(
- text = state.chatTitle.ifBlank { "Chat #${state.chatId}" },
+ text = state.chatTitle.ifBlank {
+ stringResource(id = R.string.chat_title_fallback, state.chatId)
+ },
style = MaterialTheme.typography.titleMedium,
fontWeight = FontWeight.SemiBold,
)
@@ -1977,7 +1982,7 @@ fun ChatScreen(
.clickable { emojiPickerTab = tab },
) {
Text(
- text = tab.title,
+ text = stringResource(id = tab.titleRes),
modifier = Modifier.padding(horizontal = 12.dp, vertical = 8.dp),
style = MaterialTheme.typography.labelLarge,
)
@@ -2170,6 +2175,7 @@ private fun MessageBubble(
onClick: () -> Unit,
onLongPress: () -> Unit,
) {
+ val context = LocalContext.current
val isOutgoing = message.isOutgoing
val renderAsChannelPost = isChannelChat
val alignAsOutgoing = isOutgoing && !renderAsChannelPost
@@ -2360,7 +2366,11 @@ private fun MessageBubble(
if (isLegacyImageUrlMessage) {
val isLegacyStickerMessage = isStickerAsset(fileUrl = textUrl, fileType = "image/url")
- val badgeLabel = if (isLegacyStickerMessage) null else mediaBadgeLabel(fileType = "image/url", url = textUrl)
+ val badgeLabel = if (isLegacyStickerMessage) {
+ null
+ } else {
+ mediaBadgeLabel(fileType = "image/url", url = textUrl, context = context)
+ }
val openable = !isLegacyStickerMessage && badgeLabel == null
Box(
modifier = Modifier
@@ -2434,7 +2444,11 @@ private fun MessageBubble(
if (imageAttachments.size == 1) {
val single = imageAttachments.first()
val isStickerImage = isStickerAsset(fileUrl = single.fileUrl, fileType = single.fileType)
- val badgeLabel = if (isStickerImage) null else mediaBadgeLabel(fileType = single.fileType, url = single.fileUrl)
+ val badgeLabel = if (isStickerImage) {
+ null
+ } else {
+ mediaBadgeLabel(fileType = single.fileType, url = single.fileUrl, context = context)
+ }
val openable = !isStickerImage && badgeLabel == null
Box(
modifier = Modifier
@@ -2475,7 +2489,11 @@ private fun MessageBubble(
horizontalArrangement = Arrangement.spacedBy(4.dp),
) {
rowItems.forEach { image ->
- val badgeLabel = mediaBadgeLabel(fileType = image.fileType, url = image.fileUrl)
+ val badgeLabel = mediaBadgeLabel(
+ fileType = image.fileType,
+ url = image.fileUrl,
+ context = context,
+ )
val openable = badgeLabel == null
Box(
modifier = Modifier
@@ -2533,9 +2551,15 @@ private fun MessageBubble(
playbackTitle = message.senderDisplayName?.ifBlank { null }
?: extractFileName(attachment.fileUrl),
playbackSubtitle = if (message.type.contains("voice", ignoreCase = true)) {
- "Voice message • ${formatMessageTime(message.createdAt)}"
+ stringResource(
+ id = R.string.chat_playback_subtitle_voice,
+ formatMessageTime(message.createdAt),
+ )
} else {
- "Audio • ${formatMessageTime(message.createdAt)}"
+ stringResource(
+ id = R.string.chat_playback_subtitle_audio,
+ formatMessageTime(message.createdAt),
+ )
},
messageId = message.id,
onPlaybackChanged = onAudioPlaybackChanged,
@@ -2741,10 +2765,10 @@ private sealed interface ChatTimelineItem {
) : ChatTimelineItem
}
-private enum class ComposerPickerTab(val title: String) {
- Emoji("Эмодзи"),
- Gif("GIF"),
- Sticker("Стикеры"),
+private enum class ComposerPickerTab(@StringRes val titleRes: Int) {
+ Emoji(R.string.chat_picker_tab_emoji),
+ Gif(R.string.chat_picker_tab_gif),
+ Sticker(R.string.chat_picker_tab_stickers),
}
private data class RemotePickerItem(
@@ -3882,12 +3906,19 @@ private fun ChatMembersTabContent(
) {
Column(modifier = Modifier.weight(1f)) {
Text(
- text = member.name.ifBlank { member.username?.let { "@$it" } ?: "User #${member.userId}" },
+ text = member.name.ifBlank {
+ member.username?.let { "@$it" }
+ ?: stringResource(id = R.string.chat_user_fallback_with_id, member.userId)
+ },
style = MaterialTheme.typography.bodyMedium,
fontWeight = FontWeight.SemiBold,
)
Text(
- text = "${member.role} • id ${member.userId}",
+ text = stringResource(
+ id = R.string.chat_member_role_with_id,
+ member.role,
+ member.userId,
+ ),
style = MaterialTheme.typography.bodySmall,
color = MaterialTheme.colorScheme.onSurfaceVariant,
)
@@ -3993,12 +4024,15 @@ private fun ChatMembersTabContent(
) {
Column(modifier = Modifier.weight(1f)) {
Text(
- text = ban.name.ifBlank { ban.username?.let { "@$it" } ?: "User #${ban.userId}" },
+ text = ban.name.ifBlank {
+ ban.username?.let { "@$it" }
+ ?: stringResource(id = R.string.chat_user_fallback_with_id, ban.userId)
+ },
style = MaterialTheme.typography.bodyMedium,
fontWeight = FontWeight.SemiBold,
)
Text(
- text = "id ${ban.userId}",
+ text = stringResource(id = R.string.chat_member_id, ban.userId),
style = MaterialTheme.typography.bodySmall,
color = MaterialTheme.colorScheme.onSurfaceVariant,
)
@@ -4115,11 +4149,15 @@ private fun isStickerAsset(fileUrl: String, fileType: String): Boolean {
return isStickerLikeUrl(fileUrl)
}
-private fun mediaBadgeLabel(fileType: String, url: String): String? {
+private fun mediaBadgeLabel(
+ fileType: String,
+ url: String,
+ context: Context,
+): String? {
val normalizedType = fileType.lowercase(Locale.getDefault())
return when {
- normalizedType.contains("gif") || isGifLikeUrl(url) -> "GIF"
- normalizedType.contains("webp") || isStickerLikeUrl(url) -> "Sticker"
+ normalizedType.contains("gif") || isGifLikeUrl(url) -> context.getString(R.string.chat_media_badge_gif)
+ normalizedType.contains("webp") || isStickerLikeUrl(url) -> context.getString(R.string.chat_media_badge_sticker)
else -> null
}
}
diff --git a/android/app/src/main/res/values-ru/strings.xml b/android/app/src/main/res/values-ru/strings.xml
index a740944..b4ab89c 100644
--- a/android/app/src/main/res/values-ru/strings.xml
+++ b/android/app/src/main/res/values-ru/strings.xml
@@ -147,6 +147,16 @@
Кикнуть
Разбанить
Видео
+ GIF
+ Стикер
+ Эмодзи
+ GIF
+ Стикеры
+ Голосовое сообщение • %1$s
+ Аудио • %1$s
+ Пользователь #%1$d
+ %1$s • id %2$d
+ id %1$d
Понизить админа
Понизить %1$s до участника?
Передача owner
diff --git a/android/app/src/main/res/values/strings.xml b/android/app/src/main/res/values/strings.xml
index 438d294..4b6071f 100644
--- a/android/app/src/main/res/values/strings.xml
+++ b/android/app/src/main/res/values/strings.xml
@@ -147,6 +147,16 @@
Kick
Unban
Video
+ GIF
+ Sticker
+ Emoji
+ GIF
+ Stickers
+ Voice message • %1$s
+ Audio • %1$s
+ User #%1$d
+ %1$s • id %2$d
+ id %1$d
Demote admin
Demote %1$s to member?
Transfer ownership