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 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.
|
- Added explicit icon policy: no emoji icons in production UI components, Material Icons/vector icons only.
|
||||||
- Updated UI checklist index with Batch 4 entry.
|
- 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:1.7.6")
|
||||||
implementation("androidx.compose.ui:ui-tooling-preview:1.7.6")
|
implementation("androidx.compose.ui:ui-tooling-preview:1.7.6")
|
||||||
implementation("androidx.compose.material3:material3:1.3.1")
|
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:2.7.0")
|
||||||
implementation("io.coil-kt:coil-compose:2.7.0")
|
implementation("io.coil-kt:coil-compose:2.7.0")
|
||||||
implementation("androidx.media3:media3-exoplayer:1.4.1")
|
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.CircularProgressIndicator
|
||||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||||
import androidx.compose.material3.AlertDialog
|
import androidx.compose.material3.AlertDialog
|
||||||
|
import androidx.compose.material3.Icon
|
||||||
|
import androidx.compose.material3.IconButton
|
||||||
import androidx.compose.material3.LinearProgressIndicator
|
import androidx.compose.material3.LinearProgressIndicator
|
||||||
import androidx.compose.material3.ModalBottomSheet
|
import androidx.compose.material3.ModalBottomSheet
|
||||||
import androidx.compose.material3.Surface
|
import androidx.compose.material3.Surface
|
||||||
@@ -75,6 +77,18 @@ import androidx.compose.ui.semantics.semantics
|
|||||||
import androidx.hilt.navigation.compose.hiltViewModel
|
import androidx.hilt.navigation.compose.hiltViewModel
|
||||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||||
import androidx.core.content.ContextCompat
|
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 coil.compose.AsyncImage
|
||||||
import kotlinx.coroutines.flow.collectLatest
|
import kotlinx.coroutines.flow.collectLatest
|
||||||
import ru.daemonlord.messenger.core.audio.AppAudioFocusCoordinator
|
import ru.daemonlord.messenger.core.audio.AppAudioFocusCoordinator
|
||||||
@@ -316,8 +330,8 @@ fun ChatScreen(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Button(onClick = onLoadMore, enabled = !state.isLoadingMore) {
|
IconButton(onClick = onLoadMore, enabled = !state.isLoadingMore) {
|
||||||
Text("⋮")
|
Icon(imageVector = Icons.Filled.MoreVert, contentDescription = "More")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Row(
|
Row(
|
||||||
@@ -335,14 +349,14 @@ fun ChatScreen(
|
|||||||
label = { Text("Search in chat") },
|
label = { Text("Search in chat") },
|
||||||
singleLine = true,
|
singleLine = true,
|
||||||
)
|
)
|
||||||
Button(
|
IconButton(
|
||||||
onClick = { onJumpInlineSearch(false) },
|
onClick = { onJumpInlineSearch(false) },
|
||||||
enabled = state.inlineSearchMatches.isNotEmpty(),
|
enabled = state.inlineSearchMatches.isNotEmpty(),
|
||||||
) { Text("↑") }
|
) { Icon(imageVector = Icons.Filled.ArrowUpward, contentDescription = "Previous match") }
|
||||||
Button(
|
IconButton(
|
||||||
onClick = { onJumpInlineSearch(true) },
|
onClick = { onJumpInlineSearch(true) },
|
||||||
enabled = state.inlineSearchMatches.isNotEmpty(),
|
enabled = state.inlineSearchMatches.isNotEmpty(),
|
||||||
) { Text("↓") }
|
) { Icon(imageVector = Icons.Filled.ArrowDownward, contentDescription = "Next match") }
|
||||||
}
|
}
|
||||||
if (state.inlineSearchMatches.isNotEmpty()) {
|
if (state.inlineSearchMatches.isNotEmpty()) {
|
||||||
Text(
|
Text(
|
||||||
@@ -781,14 +795,20 @@ fun ChatScreen(
|
|||||||
horizontalArrangement = Arrangement.SpaceBetween,
|
horizontalArrangement = Arrangement.SpaceBetween,
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
) {
|
) {
|
||||||
Button(onClick = { viewerImageIndex = null }) { Text("✕") }
|
IconButton(onClick = { viewerImageIndex = null }) {
|
||||||
|
Icon(imageVector = Icons.Filled.Close, contentDescription = "Close viewer")
|
||||||
|
}
|
||||||
Text(
|
Text(
|
||||||
text = "${currentIndex + 1}/${allImageUrls.size.coerceAtLeast(1)}",
|
text = "${currentIndex + 1}/${allImageUrls.size.coerceAtLeast(1)}",
|
||||||
style = MaterialTheme.typography.titleSmall,
|
style = MaterialTheme.typography.titleSmall,
|
||||||
)
|
)
|
||||||
Row(horizontalArrangement = Arrangement.spacedBy(6.dp)) {
|
Row(horizontalArrangement = Arrangement.spacedBy(6.dp)) {
|
||||||
Button(onClick = {}, enabled = false) { Text("↗") }
|
IconButton(onClick = {}, enabled = false) {
|
||||||
Button(onClick = {}, enabled = false) { Text("🗑") }
|
Icon(imageVector = Icons.AutoMirrored.Filled.Forward, contentDescription = "Forward image")
|
||||||
|
}
|
||||||
|
IconButton(onClick = {}, enabled = false) {
|
||||||
|
Icon(imageVector = Icons.Filled.DeleteOutline, contentDescription = "Delete image")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Box(
|
Box(
|
||||||
@@ -894,9 +914,9 @@ private fun MessageBubble(
|
|||||||
horizontalArrangement = if (isOutgoing) Arrangement.End else Arrangement.Start,
|
horizontalArrangement = if (isOutgoing) Arrangement.End else Arrangement.Start,
|
||||||
) {
|
) {
|
||||||
if (isMultiSelecting && !isOutgoing) {
|
if (isMultiSelecting && !isOutgoing) {
|
||||||
Text(
|
Icon(
|
||||||
text = if (isSelected) "◉" else "○",
|
imageVector = if (isSelected) Icons.Filled.RadioButtonChecked else Icons.Filled.RadioButtonUnchecked,
|
||||||
style = MaterialTheme.typography.titleMedium,
|
contentDescription = if (isSelected) "Selected" else "Not selected",
|
||||||
modifier = Modifier.padding(end = 6.dp),
|
modifier = Modifier.padding(end = 6.dp),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -918,11 +938,21 @@ private fun MessageBubble(
|
|||||||
.padding(horizontal = 10.dp, vertical = 7.dp),
|
.padding(horizontal = 10.dp, vertical = 7.dp),
|
||||||
) {
|
) {
|
||||||
if (isSelected) {
|
if (isSelected) {
|
||||||
Text(
|
Row(
|
||||||
text = "✓ Selected",
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
style = MaterialTheme.typography.labelSmall,
|
horizontalArrangement = Arrangement.spacedBy(4.dp),
|
||||||
fontWeight = FontWeight.SemiBold,
|
) {
|
||||||
)
|
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()) {
|
if (!isOutgoing && !message.senderDisplayName.isNullOrBlank()) {
|
||||||
Text(
|
Text(
|
||||||
@@ -1087,9 +1117,9 @@ private fun MessageBubble(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (isMultiSelecting && isOutgoing) {
|
if (isMultiSelecting && isOutgoing) {
|
||||||
Text(
|
Icon(
|
||||||
text = if (isSelected) "◉" else "○",
|
imageVector = if (isSelected) Icons.Filled.RadioButtonChecked else Icons.Filled.RadioButtonUnchecked,
|
||||||
style = MaterialTheme.typography.titleMedium,
|
contentDescription = if (isSelected) "Selected" else "Not selected",
|
||||||
modifier = Modifier.padding(start = 6.dp),
|
modifier = Modifier.padding(start = 6.dp),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -1108,7 +1138,13 @@ private fun VideoAttachmentCard(
|
|||||||
.background(MaterialTheme.colorScheme.surface.copy(alpha = 0.45f), RoundedCornerShape(10.dp))
|
.background(MaterialTheme.colorScheme.surface.copy(alpha = 0.45f), RoundedCornerShape(10.dp))
|
||||||
.padding(8.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 = extractFileName(url), style = MaterialTheme.typography.bodySmall, maxLines = 1)
|
||||||
Text(text = fileType, style = MaterialTheme.typography.labelSmall)
|
Text(text = fileType, style = MaterialTheme.typography.labelSmall)
|
||||||
}
|
}
|
||||||
@@ -1170,7 +1206,7 @@ private fun FileAttachmentRow(
|
|||||||
.background(MaterialTheme.colorScheme.primaryContainer),
|
.background(MaterialTheme.colorScheme.primaryContainer),
|
||||||
contentAlignment = Alignment.Center,
|
contentAlignment = Alignment.Center,
|
||||||
) {
|
) {
|
||||||
Text("📎")
|
Icon(imageVector = Icons.Filled.AttachFile, contentDescription = "Attachment")
|
||||||
}
|
}
|
||||||
Column(modifier = Modifier.weight(1f)) {
|
Column(modifier = Modifier.weight(1f)) {
|
||||||
Text(text = extractFileName(fileUrl), style = MaterialTheme.typography.bodySmall, maxLines = 1)
|
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.Button
|
||||||
import androidx.compose.material3.CircularProgressIndicator
|
import androidx.compose.material3.CircularProgressIndicator
|
||||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||||
|
import androidx.compose.material3.Icon
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material3.OutlinedTextField
|
import androidx.compose.material3.OutlinedTextField
|
||||||
import androidx.compose.material3.Surface
|
import androidx.compose.material3.Surface
|
||||||
@@ -49,6 +50,9 @@ import androidx.compose.ui.text.font.FontWeight
|
|||||||
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 androidx.compose.material.icons.Icons
|
||||||
|
import androidx.compose.material.icons.filled.Add
|
||||||
|
import androidx.compose.material.icons.filled.Close
|
||||||
import coil.compose.AsyncImage
|
import coil.compose.AsyncImage
|
||||||
import ru.daemonlord.messenger.domain.chat.model.ChatItem
|
import ru.daemonlord.messenger.domain.chat.model.ChatItem
|
||||||
import java.time.Instant
|
import java.time.Instant
|
||||||
@@ -296,7 +300,10 @@ fun ChatListScreen(
|
|||||||
.align(Alignment.BottomEnd)
|
.align(Alignment.BottomEnd)
|
||||||
.padding(end = 16.dp, bottom = 88.dp),
|
.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(
|
Surface(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
|
|||||||
@@ -8,6 +8,9 @@
|
|||||||
- [ ] Использовать только `Material Icons` (Outlined/Rounded) или векторные ассеты в том же стиле.
|
- [ ] Использовать только `Material Icons` (Outlined/Rounded) или векторные ассеты в том же стиле.
|
||||||
- [ ] Все action-кнопки (`send`, `attach`, `mic`, `play`, `pause`, `delete`, `reply`, `forward`) перевести на иконки из `androidx.compose.material:material-icons-extended`.
|
- [ ] Все action-кнопки (`send`, `attach`, `mic`, `play`, `pause`, `delete`, `reply`, `forward`) перевести на иконки из `androidx.compose.material:material-icons-extended`.
|
||||||
|
|
||||||
|
Прогресс:
|
||||||
|
- [x] Chat/List базовые action-иконки переведены на `Material Icons` (Step 75).
|
||||||
|
|
||||||
## Карта скриншотов (что на каждом)
|
## Карта скриншотов (что на каждом)
|
||||||
1. Энергосбережение: секционные карточки + тумблеры.
|
1. Энергосбережение: секционные карточки + тумблеры.
|
||||||
2. Уведомления и звуки: длинный список секций и switch-row.
|
2. Уведомления и звуки: длинный список секций и switch-row.
|
||||||
|
|||||||
Reference in New Issue
Block a user