android: start material icons migration for chat and list ui
This commit is contained in:
@@ -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.
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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,12 +938,22 @@ private fun MessageBubble(
|
||||
.padding(horizontal = 10.dp, vertical = 7.dp),
|
||||
) {
|
||||
if (isSelected) {
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.spacedBy(4.dp),
|
||||
) {
|
||||
Icon(
|
||||
imageVector = Icons.Filled.Check,
|
||||
contentDescription = "Selected",
|
||||
modifier = Modifier.size(14.dp),
|
||||
)
|
||||
Text(
|
||||
text = "✓ Selected",
|
||||
text = "Selected",
|
||||
style = MaterialTheme.typography.labelSmall,
|
||||
fontWeight = FontWeight.SemiBold,
|
||||
)
|
||||
}
|
||||
}
|
||||
if (!isOutgoing && !message.senderDisplayName.isNullOrBlank()) {
|
||||
Text(
|
||||
text = message.senderDisplayName,
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.
|
||||
|
||||
Reference in New Issue
Block a user