diff --git a/android/CHANGELOG.md b/android/CHANGELOG.md index 2dc23ec..e19a812 100644 --- a/android/CHANGELOG.md +++ b/android/CHANGELOG.md @@ -465,3 +465,12 @@ - Added full Telegram reference mapping checklist (`docs/android-ui-batch-4-checklist.md`) with screenshot-by-screenshot description. - Added explicit icon policy: no emoji icons in production UI components, Material Icons/vector icons only. - Updated UI checklist index with Batch 4 entry. + +### Step 75 - Material Icons migration (Batch 1 start) +- Replaced symbol/emoji-based UI controls in chat surfaces with Material Icons: + - chat header/menu/search controls (`more`, `up/down`), + - image viewer actions (`close`, `forward`, `delete`), + - multi-select markers (`radio checked/unchecked`, `selected` check), + - attachment/media markers (`movie`, `attach file`). +- Replaced chat list management FAB glyph toggle (`+`/`×`) with Material `Add`/`Close` icons. +- Added `androidx.compose.material:material-icons-extended` dependency for consistent icon usage. diff --git a/android/app/build.gradle.kts b/android/app/build.gradle.kts index e6af27a..1846dad 100644 --- a/android/app/build.gradle.kts +++ b/android/app/build.gradle.kts @@ -81,6 +81,7 @@ dependencies { implementation("androidx.compose.ui:ui:1.7.6") implementation("androidx.compose.ui:ui-tooling-preview:1.7.6") implementation("androidx.compose.material3:material3:1.3.1") + implementation("androidx.compose.material:material-icons-extended:1.7.6") implementation("io.coil-kt:coil:2.7.0") implementation("io.coil-kt:coil-compose:2.7.0") implementation("androidx.media3:media3-exoplayer:1.4.1") 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 60502eb..ef05aee 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 @@ -40,6 +40,8 @@ import androidx.compose.material3.Button import androidx.compose.material3.CircularProgressIndicator import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.AlertDialog +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton import androidx.compose.material3.LinearProgressIndicator import androidx.compose.material3.ModalBottomSheet import androidx.compose.material3.Surface @@ -75,6 +77,18 @@ import androidx.compose.ui.semantics.semantics import androidx.hilt.navigation.compose.hiltViewModel import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.core.content.ContextCompat +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.automirrored.filled.Forward +import androidx.compose.material.icons.filled.ArrowDownward +import androidx.compose.material.icons.filled.ArrowUpward +import androidx.compose.material.icons.filled.AttachFile +import androidx.compose.material.icons.filled.Check +import androidx.compose.material.icons.filled.Close +import androidx.compose.material.icons.filled.DeleteOutline +import androidx.compose.material.icons.filled.MoreVert +import androidx.compose.material.icons.filled.Movie +import androidx.compose.material.icons.filled.RadioButtonChecked +import androidx.compose.material.icons.filled.RadioButtonUnchecked import coil.compose.AsyncImage import kotlinx.coroutines.flow.collectLatest import ru.daemonlord.messenger.core.audio.AppAudioFocusCoordinator @@ -316,8 +330,8 @@ fun ChatScreen( ) } } - Button(onClick = onLoadMore, enabled = !state.isLoadingMore) { - Text("⋮") + IconButton(onClick = onLoadMore, enabled = !state.isLoadingMore) { + Icon(imageVector = Icons.Filled.MoreVert, contentDescription = "More") } } Row( @@ -335,14 +349,14 @@ fun ChatScreen( label = { Text("Search in chat") }, singleLine = true, ) - Button( + IconButton( onClick = { onJumpInlineSearch(false) }, enabled = state.inlineSearchMatches.isNotEmpty(), - ) { Text("↑") } - Button( + ) { Icon(imageVector = Icons.Filled.ArrowUpward, contentDescription = "Previous match") } + IconButton( onClick = { onJumpInlineSearch(true) }, enabled = state.inlineSearchMatches.isNotEmpty(), - ) { Text("↓") } + ) { Icon(imageVector = Icons.Filled.ArrowDownward, contentDescription = "Next match") } } if (state.inlineSearchMatches.isNotEmpty()) { Text( @@ -781,14 +795,20 @@ fun ChatScreen( horizontalArrangement = Arrangement.SpaceBetween, verticalAlignment = Alignment.CenterVertically, ) { - Button(onClick = { viewerImageIndex = null }) { Text("✕") } + IconButton(onClick = { viewerImageIndex = null }) { + Icon(imageVector = Icons.Filled.Close, contentDescription = "Close viewer") + } Text( text = "${currentIndex + 1}/${allImageUrls.size.coerceAtLeast(1)}", style = MaterialTheme.typography.titleSmall, ) Row(horizontalArrangement = Arrangement.spacedBy(6.dp)) { - Button(onClick = {}, enabled = false) { Text("↗") } - Button(onClick = {}, enabled = false) { Text("🗑") } + IconButton(onClick = {}, enabled = false) { + Icon(imageVector = Icons.AutoMirrored.Filled.Forward, contentDescription = "Forward image") + } + IconButton(onClick = {}, enabled = false) { + Icon(imageVector = Icons.Filled.DeleteOutline, contentDescription = "Delete image") + } } } Box( @@ -894,9 +914,9 @@ private fun MessageBubble( horizontalArrangement = if (isOutgoing) Arrangement.End else Arrangement.Start, ) { if (isMultiSelecting && !isOutgoing) { - Text( - text = if (isSelected) "◉" else "○", - style = MaterialTheme.typography.titleMedium, + Icon( + imageVector = if (isSelected) Icons.Filled.RadioButtonChecked else Icons.Filled.RadioButtonUnchecked, + contentDescription = if (isSelected) "Selected" else "Not selected", modifier = Modifier.padding(end = 6.dp), ) } @@ -918,11 +938,21 @@ private fun MessageBubble( .padding(horizontal = 10.dp, vertical = 7.dp), ) { if (isSelected) { - Text( - text = "✓ Selected", - style = MaterialTheme.typography.labelSmall, - fontWeight = FontWeight.SemiBold, - ) + Row( + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(4.dp), + ) { + Icon( + imageVector = Icons.Filled.Check, + contentDescription = "Selected", + modifier = Modifier.size(14.dp), + ) + Text( + text = "Selected", + style = MaterialTheme.typography.labelSmall, + fontWeight = FontWeight.SemiBold, + ) + } } if (!isOutgoing && !message.senderDisplayName.isNullOrBlank()) { Text( @@ -1087,9 +1117,9 @@ private fun MessageBubble( } } if (isMultiSelecting && isOutgoing) { - Text( - text = if (isSelected) "◉" else "○", - style = MaterialTheme.typography.titleMedium, + Icon( + imageVector = if (isSelected) Icons.Filled.RadioButtonChecked else Icons.Filled.RadioButtonUnchecked, + contentDescription = if (isSelected) "Selected" else "Not selected", modifier = Modifier.padding(start = 6.dp), ) } @@ -1108,7 +1138,13 @@ private fun VideoAttachmentCard( .background(MaterialTheme.colorScheme.surface.copy(alpha = 0.45f), RoundedCornerShape(10.dp)) .padding(8.dp), ) { - Text(text = "🎬 Video", style = MaterialTheme.typography.labelSmall, fontWeight = FontWeight.SemiBold) + Row( + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(6.dp), + ) { + Icon(imageVector = Icons.Filled.Movie, contentDescription = "Video") + Text(text = "Video", style = MaterialTheme.typography.labelSmall, fontWeight = FontWeight.SemiBold) + } Text(text = extractFileName(url), style = MaterialTheme.typography.bodySmall, maxLines = 1) Text(text = fileType, style = MaterialTheme.typography.labelSmall) } @@ -1170,7 +1206,7 @@ private fun FileAttachmentRow( .background(MaterialTheme.colorScheme.primaryContainer), contentAlignment = Alignment.Center, ) { - Text("📎") + Icon(imageVector = Icons.Filled.AttachFile, contentDescription = "Attachment") } Column(modifier = Modifier.weight(1f)) { Text(text = extractFileName(fileUrl), style = MaterialTheme.typography.bodySmall, maxLines = 1) diff --git a/android/app/src/main/java/ru/daemonlord/messenger/ui/chats/ChatListScreen.kt b/android/app/src/main/java/ru/daemonlord/messenger/ui/chats/ChatListScreen.kt index 933dc7e..c81f79b 100644 --- a/android/app/src/main/java/ru/daemonlord/messenger/ui/chats/ChatListScreen.kt +++ b/android/app/src/main/java/ru/daemonlord/messenger/ui/chats/ChatListScreen.kt @@ -24,6 +24,7 @@ import androidx.compose.material3.AssistChipDefaults import androidx.compose.material3.Button import androidx.compose.material3.CircularProgressIndicator import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme import androidx.compose.material3.OutlinedTextField import androidx.compose.material3.Surface @@ -49,6 +50,9 @@ import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.dp import androidx.hilt.navigation.compose.hiltViewModel import androidx.lifecycle.compose.collectAsStateWithLifecycle +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Add +import androidx.compose.material.icons.filled.Close import coil.compose.AsyncImage import ru.daemonlord.messenger.domain.chat.model.ChatItem import java.time.Instant @@ -296,7 +300,10 @@ fun ChatListScreen( .align(Alignment.BottomEnd) .padding(end = 16.dp, bottom = 88.dp), ) { - Text(if (managementExpanded) "×" else "+") + Icon( + imageVector = if (managementExpanded) Icons.Filled.Close else Icons.Filled.Add, + contentDescription = if (managementExpanded) "Close management" else "Open management", + ) } Surface( modifier = Modifier diff --git a/docs/android-ui-batch-4-checklist.md b/docs/android-ui-batch-4-checklist.md index dd61db0..7af2c83 100644 --- a/docs/android-ui-batch-4-checklist.md +++ b/docs/android-ui-batch-4-checklist.md @@ -8,6 +8,9 @@ - [ ] Использовать только `Material Icons` (Outlined/Rounded) или векторные ассеты в том же стиле. - [ ] Все action-кнопки (`send`, `attach`, `mic`, `play`, `pause`, `delete`, `reply`, `forward`) перевести на иконки из `androidx.compose.material:material-icons-extended`. +Прогресс: +- [x] Chat/List базовые action-иконки переведены на `Material Icons` (Step 75). + ## Карта скриншотов (что на каждом) 1. Энергосбережение: секционные карточки + тумблеры. 2. Уведомления и звуки: длинный список секций и switch-row.