feat(icons): migrate Material Icons → Material Symbols (thin weight)

Replace the `material-icons-extended` library with Google's Material Symbols
variable font, rendered via a new `commons/icons/symbols/` package. Weight is
set to 100 (Thin) via a single tunable default (MaterialSymbolsDefaults.WEIGHT),
so we can re-tune line thickness globally in one line without regenerating
assets.

Key changes:
- commons/composeResources/font/material_symbols_outlined.ttf: Material Symbols
  Outlined variable font (all 3600+ glyphs, 4 design axes: wght/FILL/GRAD/opsz).
- commons/icons/symbols/: MaterialSymbol data class, 219 codepoint constants,
  variable-font FontFamily helper, Canvas-backed Icon composable (overload that
  accepts MaterialSymbol and supports auto-mirror for RTL), and a Painter
  wrapper for use with Image / AsyncImage.
- Bulk-rewrote 250+ call sites across amethyst/, commons/, desktopApp/:
  Icons.{Default,Outlined,Filled,Rounded,AutoMirrored.*}.X → MaterialSymbols.X
  or MaterialSymbols.AutoMirrored.X. `imageVector =` renamed to `symbol =`
  inside Icon() calls; Image(imageVector = MS.X) converted to
  Image(painter = rememberMaterialSymbolPainter(MS.X)).
- Dropped material-icons-extended dependency from commons, desktopApp,
  amethyst build files.

Hand-drawn custom icons (Bookmark, Like, Reply, Zap, chess pieces, robohash)
remain as ImageVector and are untouched.

https://claude.ai/code/session_016gV9gEaSud6kDMdcaqy8ti
This commit is contained in:
Claude
2026-04-23 19:44:17 +00:00
parent dffcd258b7
commit 8ff091ca0e
263 changed files with 2050 additions and 2082 deletions
-1
View File
@@ -53,7 +53,6 @@ kotlin {
implementation(libs.jetbrains.compose.foundation)
implementation(libs.jetbrains.compose.runtime)
implementation(libs.jetbrains.compose.material3)
implementation(libs.jetbrains.compose.material.icons.extended)
implementation(libs.jetbrains.compose.ui.tooling.preview)
// Lifecycle ViewModel (KMP since 2.8.0)
@@ -38,12 +38,6 @@ import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.CheckCircle
import androidx.compose.material.icons.filled.CloudSync
import androidx.compose.material.icons.filled.Error
import androidx.compose.material.icons.filled.HourglassBottom
import androidx.compose.material.icons.filled.Sync
import androidx.compose.material3.Icon
import androidx.compose.material3.LinearProgressIndicator
import androidx.compose.material3.MaterialTheme
@@ -54,9 +48,11 @@ import androidx.compose.runtime.getValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
import com.vitorpamplona.amethyst.commons.icons.symbols.Icon
import com.vitorpamplona.amethyst.commons.icons.symbols.MaterialSymbol
import com.vitorpamplona.amethyst.commons.icons.symbols.MaterialSymbols
/**
* Shared banner showing chess broadcast status - broadcast progress, sync status, etc.
@@ -93,7 +89,7 @@ fun ChessBroadcastBanner(
.animateContentSize(),
) {
Icon(
imageVector = getStatusIcon(status),
symbol = getStatusIcon(status),
contentDescription = null,
tint = getStatusIconColor(status),
modifier = Modifier.size(18.dp),
@@ -162,15 +158,15 @@ private fun getStatusBackgroundColor(status: ChessBroadcastStatus): Color =
}
}
private fun getStatusIcon(status: ChessBroadcastStatus): ImageVector =
private fun getStatusIcon(status: ChessBroadcastStatus): MaterialSymbol =
when (status) {
is ChessBroadcastStatus.Broadcasting -> Icons.Default.Sync
is ChessBroadcastStatus.Success -> Icons.Default.CheckCircle
is ChessBroadcastStatus.Failed -> Icons.Default.Error
is ChessBroadcastStatus.WaitingForOpponent -> Icons.Default.HourglassBottom
is ChessBroadcastStatus.Syncing -> Icons.Default.CloudSync
is ChessBroadcastStatus.Desynced -> Icons.Default.Error
is ChessBroadcastStatus.Idle -> Icons.Default.CheckCircle
is ChessBroadcastStatus.Broadcasting -> MaterialSymbols.Sync
is ChessBroadcastStatus.Success -> MaterialSymbols.CheckCircle
is ChessBroadcastStatus.Failed -> MaterialSymbols.Error
is ChessBroadcastStatus.WaitingForOpponent -> MaterialSymbols.HourglassBottom
is ChessBroadcastStatus.Syncing -> MaterialSymbols.CloudSync
is ChessBroadcastStatus.Desynced -> MaterialSymbols.Error
is ChessBroadcastStatus.Idle -> MaterialSymbols.CheckCircle
}
@Composable
@@ -27,8 +27,6 @@ import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Close
import androidx.compose.material3.Button
import androidx.compose.material3.Card
import androidx.compose.material3.Icon
@@ -43,6 +41,8 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import com.vitorpamplona.amethyst.commons.icons.symbols.Icon
import com.vitorpamplona.amethyst.commons.icons.symbols.MaterialSymbols
import com.vitorpamplona.quartz.nip64Chess.ChessGameNameGenerator
// Color constants for card borders
@@ -348,7 +348,7 @@ fun CompletedGameCard(
if (onDismiss != null) {
IconButton(onClick = onDismiss) {
Icon(
Icons.Default.Close,
MaterialSymbols.Close,
contentDescription = "Dismiss",
tint = MaterialTheme.colorScheme.onSurfaceVariant,
)
@@ -40,14 +40,6 @@ import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.CheckCircle
import androidx.compose.material.icons.filled.CloudDownload
import androidx.compose.material.icons.filled.Error
import androidx.compose.material.icons.filled.ExpandLess
import androidx.compose.material.icons.filled.ExpandMore
import androidx.compose.material.icons.filled.HourglassEmpty
import androidx.compose.material.icons.filled.Warning
import androidx.compose.material3.HorizontalDivider
import androidx.compose.material3.Icon
import androidx.compose.material3.LinearProgressIndicator
@@ -62,9 +54,11 @@ import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
import com.vitorpamplona.amethyst.commons.icons.symbols.Icon
import com.vitorpamplona.amethyst.commons.icons.symbols.MaterialSymbol
import com.vitorpamplona.amethyst.commons.icons.symbols.MaterialSymbols
/**
* Shared banner showing chess sync/subscription status with expandable relay details.
@@ -109,7 +103,7 @@ fun ChessSyncBanner(
modifier = Modifier.padding(horizontal = 16.dp, vertical = 10.dp),
) {
Icon(
imageVector = getSyncStatusIcon(status),
symbol = getSyncStatusIcon(status),
contentDescription = null,
tint = getSyncStatusIconColor(status),
modifier = Modifier.size(18.dp),
@@ -160,7 +154,7 @@ fun ChessSyncBanner(
// Expand/collapse icon
if (status !is ChessSyncStatus.Idle && getRelayStates(status).isNotEmpty()) {
Icon(
imageVector = if (isExpanded) Icons.Default.ExpandLess else Icons.Default.ExpandMore,
symbol = if (isExpanded) MaterialSymbols.ExpandLess else MaterialSymbols.ExpandMore,
contentDescription = if (isExpanded) "Collapse" else "Expand",
tint = MaterialTheme.colorScheme.onSurfaceVariant,
modifier = Modifier.size(20.dp),
@@ -213,7 +207,7 @@ private fun RelayStatusRow(relay: RelaySyncState) {
modifier = Modifier.fillMaxWidth(),
) {
Icon(
imageVector = getRelayStatusIcon(relay.status),
symbol = getRelayStatusIcon(relay.status),
contentDescription = null,
tint = getRelayStatusColor(relay.status),
modifier = Modifier.size(14.dp),
@@ -244,13 +238,13 @@ private fun getRelayStates(status: ChessSyncStatus): List<RelaySyncState> =
is ChessSyncStatus.Idle -> emptyList()
}
private fun getRelayStatusIcon(status: RelaySyncStatus): ImageVector =
private fun getRelayStatusIcon(status: RelaySyncStatus): MaterialSymbol =
when (status) {
RelaySyncStatus.CONNECTING -> Icons.Default.HourglassEmpty
RelaySyncStatus.WAITING -> Icons.Default.HourglassEmpty
RelaySyncStatus.RECEIVING -> Icons.Default.CloudDownload
RelaySyncStatus.EOSE_RECEIVED -> Icons.Default.CheckCircle
RelaySyncStatus.FAILED -> Icons.Default.Error
RelaySyncStatus.CONNECTING -> MaterialSymbols.HourglassEmpty
RelaySyncStatus.WAITING -> MaterialSymbols.HourglassEmpty
RelaySyncStatus.RECEIVING -> MaterialSymbols.CloudDownload
RelaySyncStatus.EOSE_RECEIVED -> MaterialSymbols.CheckCircle
RelaySyncStatus.FAILED -> MaterialSymbols.Error
}
@Composable
@@ -271,12 +265,12 @@ private fun getSyncStatusBackgroundColor(status: ChessSyncStatus): Color =
else -> MaterialTheme.colorScheme.surfaceContainer
}
private fun getSyncStatusIcon(status: ChessSyncStatus): ImageVector =
private fun getSyncStatusIcon(status: ChessSyncStatus): MaterialSymbol =
when (status) {
is ChessSyncStatus.Syncing -> Icons.Default.CloudDownload
is ChessSyncStatus.Synced -> Icons.Default.CheckCircle
is ChessSyncStatus.PartialSync -> Icons.Default.Warning
is ChessSyncStatus.Idle -> Icons.Default.CheckCircle
is ChessSyncStatus.Syncing -> MaterialSymbols.CloudDownload
is ChessSyncStatus.Synced -> MaterialSymbols.CheckCircle
is ChessSyncStatus.PartialSync -> MaterialSymbols.Warning
is ChessSyncStatus.Idle -> MaterialSymbols.CheckCircle
}
@Composable
@@ -24,11 +24,6 @@ import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.filled.KeyboardArrowLeft
import androidx.compose.material.icons.automirrored.filled.KeyboardArrowRight
import androidx.compose.material.icons.filled.SkipNext
import androidx.compose.material.icons.filled.SkipPrevious
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme
@@ -37,6 +32,8 @@ import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import com.vitorpamplona.amethyst.commons.icons.symbols.Icon
import com.vitorpamplona.amethyst.commons.icons.symbols.MaterialSymbols
/**
* Move navigation controls for stepping through chess game positions
@@ -65,7 +62,7 @@ fun MoveNavigator(
enabled = currentMove > 0,
) {
Icon(
Icons.Default.SkipPrevious,
MaterialSymbols.SkipPrevious,
contentDescription = "Start position",
tint =
if (currentMove > 0) {
@@ -82,7 +79,7 @@ fun MoveNavigator(
enabled = currentMove > 0,
) {
Icon(
Icons.AutoMirrored.Filled.KeyboardArrowLeft,
MaterialSymbols.AutoMirrored.KeyboardArrowLeft,
contentDescription = "Previous move",
tint =
if (currentMove > 0) {
@@ -106,7 +103,7 @@ fun MoveNavigator(
enabled = currentMove < totalMoves,
) {
Icon(
Icons.AutoMirrored.Filled.KeyboardArrowRight,
MaterialSymbols.AutoMirrored.KeyboardArrowRight,
contentDescription = "Next move",
tint =
if (currentMove < totalMoves) {
@@ -123,7 +120,7 @@ fun MoveNavigator(
enabled = currentMove < totalMoves,
) {
Icon(
Icons.Default.SkipNext,
MaterialSymbols.SkipNext,
contentDescription = "Final position",
tint =
if (currentMove < totalMoves) {
@@ -25,18 +25,6 @@ import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.filled.FormatListBulleted
import androidx.compose.material.icons.filled.Checklist
import androidx.compose.material.icons.filled.Code
import androidx.compose.material.icons.filled.FormatBold
import androidx.compose.material.icons.filled.FormatItalic
import androidx.compose.material.icons.filled.FormatListNumbered
import androidx.compose.material.icons.filled.FormatQuote
import androidx.compose.material.icons.filled.FormatStrikethrough
import androidx.compose.material.icons.filled.HorizontalRule
import androidx.compose.material.icons.filled.Image
import androidx.compose.material.icons.filled.Link
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.SmallFloatingActionButton
@@ -46,9 +34,11 @@ import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.focus.focusProperties
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import com.vitorpamplona.amethyst.commons.icons.symbols.Icon
import com.vitorpamplona.amethyst.commons.icons.symbols.MaterialSymbol
import com.vitorpamplona.amethyst.commons.icons.symbols.MaterialSymbols
/**
* Markdown toolbar with Material icons, grouped formatting buttons, and active state.
@@ -75,30 +65,30 @@ fun MarkdownToolbar(
Separator()
// --- Inline formatting ---
ToolbarIconButton(Icons.Default.FormatBold, "Bold", state.isBold) { state.toggleBold() }
ToolbarIconButton(Icons.Default.FormatItalic, "Italic", state.isItalic) { state.toggleItalic() }
ToolbarIconButton(Icons.Default.FormatStrikethrough, "Strikethrough", state.isStrikethrough) { state.toggleStrikethrough() }
ToolbarIconButton(Icons.Default.Code, "Inline code", state.isInlineCode) { state.toggleInlineCode() }
ToolbarIconButton(MaterialSymbols.FormatBold, "Bold", state.isBold) { state.toggleBold() }
ToolbarIconButton(MaterialSymbols.FormatItalic, "Italic", state.isItalic) { state.toggleItalic() }
ToolbarIconButton(MaterialSymbols.FormatStrikethrough, "Strikethrough", state.isStrikethrough) { state.toggleStrikethrough() }
ToolbarIconButton(MaterialSymbols.Code, "Inline code", state.isInlineCode) { state.toggleInlineCode() }
Separator()
// --- Lists ---
ToolbarIconButton(Icons.AutoMirrored.Filled.FormatListBulleted, "Bullet list", state.isUnorderedList) { state.toggleUnorderedList() }
ToolbarIconButton(Icons.Default.FormatListNumbered, "Numbered list", state.isOrderedList) { state.toggleOrderedList() }
ToolbarIconButton(Icons.Default.Checklist, "Task list", state.isTaskList) { state.toggleTaskList() }
ToolbarIconButton(MaterialSymbols.AutoMirrored.FormatListBulleted, "Bullet list", state.isUnorderedList) { state.toggleUnorderedList() }
ToolbarIconButton(MaterialSymbols.FormatListNumbered, "Numbered list", state.isOrderedList) { state.toggleOrderedList() }
ToolbarIconButton(MaterialSymbols.Checklist, "Task list", state.isTaskList) { state.toggleTaskList() }
Separator()
// --- Block elements ---
ToolbarIconButton(Icons.Default.FormatQuote, "Blockquote", state.isBlockquote) { state.toggleBlockquote() }
ToolbarIconButton(MaterialSymbols.FormatQuote, "Blockquote", state.isBlockquote) { state.toggleBlockquote() }
ToolbarButton(label = "```", active = false) { state.toggleCodeBlock() }
ToolbarIconButton(Icons.Default.HorizontalRule, "Horizontal rule", false) { state.insertHorizontalRule() }
ToolbarIconButton(MaterialSymbols.HorizontalRule, "Horizontal rule", false) { state.insertHorizontalRule() }
Separator()
// --- Insert ---
ToolbarIconButton(Icons.Default.Link, "Link", false) { state.insertLink() }
ToolbarIconButton(Icons.Default.Image, "Image", false) { state.insertImage() }
ToolbarIconButton(MaterialSymbols.Link, "Link", false) { state.insertLink() }
ToolbarIconButton(MaterialSymbols.Image, "Image", false) { state.insertImage() }
}
}
@@ -112,7 +102,7 @@ private fun Separator() {
@Composable
private fun ToolbarIconButton(
icon: ImageVector,
icon: MaterialSymbol,
contentDescription: String,
active: Boolean,
onClick: () -> Unit,
@@ -30,8 +30,6 @@ import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Close
import androidx.compose.material3.AssistChip
import androidx.compose.material3.Icon
import androidx.compose.material3.OutlinedTextField
@@ -47,6 +45,8 @@ import androidx.compose.ui.input.key.Key
import androidx.compose.ui.input.key.key
import androidx.compose.ui.input.key.onKeyEvent
import androidx.compose.ui.unit.dp
import com.vitorpamplona.amethyst.commons.icons.symbols.Icon
import com.vitorpamplona.amethyst.commons.icons.symbols.MaterialSymbols
/**
* Form fields for article metadata: title, summary, banner, tags, slug.
@@ -137,7 +137,7 @@ fun MetadataPanel(
label = { Text(tag) },
trailingIcon = {
Icon(
Icons.Default.Close,
MaterialSymbols.Close,
contentDescription = "Remove $tag",
modifier = Modifier.size(16.dp),
)
@@ -0,0 +1,97 @@
/*
* Copyright (c) 2025 Vitor Pamplona
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to use,
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
* Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package com.vitorpamplona.amethyst.commons.icons.symbols
import androidx.compose.foundation.Canvas
import androidx.compose.foundation.layout.defaultMinSize
import androidx.compose.material3.LocalContentColor
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.drawscope.scale
import androidx.compose.ui.graphics.drawscope.translate
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.platform.LocalLayoutDirection
import androidx.compose.ui.semantics.Role
import androidx.compose.ui.semantics.contentDescription
import androidx.compose.ui.semantics.role
import androidx.compose.ui.semantics.semantics
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.drawText
import androidx.compose.ui.text.rememberTextMeasurer
import androidx.compose.ui.unit.LayoutDirection
import androidx.compose.ui.unit.dp
private val IconDefaultSize = 24.dp
@Composable
fun Icon(
symbol: MaterialSymbol,
contentDescription: String?,
modifier: Modifier = Modifier,
tint: Color = LocalContentColor.current,
weight: Int = MaterialSymbolsDefaults.WEIGHT,
) {
val fontFamily = rememberMaterialSymbolsFontFamily(weight)
val textMeasurer = rememberTextMeasurer()
val density = LocalDensity.current
val mirror = symbol.autoMirror && LocalLayoutDirection.current == LayoutDirection.Rtl
val semanticsModifier =
if (contentDescription != null) {
Modifier.semantics {
this.contentDescription = contentDescription
this.role = Role.Image
}
} else {
Modifier
}
Canvas(
modifier =
modifier
.defaultMinSize(IconDefaultSize, IconDefaultSize)
.then(semanticsModifier),
) {
val sizePx = size.minDimension
val fontSize = with(density) { sizePx.toSp() }
val layout =
textMeasurer.measure(
text = symbol.glyph,
style =
TextStyle(
fontFamily = fontFamily,
fontSize = fontSize,
color = tint,
),
)
val tx = (size.width - layout.size.width) / 2f
val ty = (size.height - layout.size.height) / 2f
if (mirror) {
scale(scaleX = -1f, scaleY = 1f, pivot = Offset(size.width / 2f, size.height / 2f)) {
translate(tx, ty) { drawText(layout) }
}
} else {
translate(tx, ty) { drawText(layout) }
}
}
}
@@ -0,0 +1,29 @@
/*
* Copyright (c) 2025 Vitor Pamplona
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to use,
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
* Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package com.vitorpamplona.amethyst.commons.icons.symbols
import androidx.compose.runtime.Immutable
@Immutable
data class MaterialSymbol(
val glyph: String,
val autoMirror: Boolean = false,
)
@@ -0,0 +1,79 @@
/*
* Copyright (c) 2025 Vitor Pamplona
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to use,
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
* Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package com.vitorpamplona.amethyst.commons.icons.symbols
import androidx.compose.material3.LocalContentColor
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.ui.geometry.Size
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.drawscope.DrawScope
import androidx.compose.ui.graphics.drawscope.translate
import androidx.compose.ui.graphics.painter.Painter
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.text.TextMeasurer
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.drawText
import androidx.compose.ui.text.font.FontFamily
import androidx.compose.ui.text.rememberTextMeasurer
import androidx.compose.ui.unit.Density
@Composable
fun rememberMaterialSymbolPainter(
symbol: MaterialSymbol,
tint: Color = LocalContentColor.current,
weight: Int = MaterialSymbolsDefaults.WEIGHT,
): Painter {
val fontFamily = rememberMaterialSymbolsFontFamily(weight)
val textMeasurer = rememberTextMeasurer()
val density = LocalDensity.current
return remember(symbol, fontFamily, tint, density) {
MaterialSymbolPainter(symbol, fontFamily, tint, textMeasurer, density)
}
}
private class MaterialSymbolPainter(
private val symbol: MaterialSymbol,
private val fontFamily: FontFamily,
private val tint: Color,
private val textMeasurer: TextMeasurer,
private val density: Density,
) : Painter() {
override val intrinsicSize: Size = Size.Unspecified
override fun DrawScope.onDraw() {
val sizePx = size.minDimension
val fontSize = with(density) { sizePx.toSp() }
val layout =
textMeasurer.measure(
text = symbol.glyph,
style =
TextStyle(
fontFamily = fontFamily,
fontSize = fontSize,
color = tint,
),
)
val tx = (size.width - layout.size.width) / 2f
val ty = (size.height - layout.size.height) / 2f
translate(tx, ty) { drawText(layout) }
}
}
@@ -0,0 +1,249 @@
/*
* Copyright (c) 2025 Vitor Pamplona
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to use,
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
* Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package com.vitorpamplona.amethyst.commons.icons.symbols
// Generated from Material Symbols Outlined codepoints.
// Source: github.com/google/material-design-icons/variablefont
object MaterialSymbols {
val AccountBalanceWallet = MaterialSymbol("\uE850")
val AccountCircle = MaterialSymbol("\uF20B")
val Add = MaterialSymbol("\uE145")
val AddCircle = MaterialSymbol("\uE3BA")
val AddPhotoAlternate = MaterialSymbol("\uE43E")
val AddReaction = MaterialSymbol("\uE1D3")
val AlternateEmail = MaterialSymbol("\uE0E6")
val Apps = MaterialSymbol("\uE5C3")
val ArrowDownward = MaterialSymbol("\uE5DB")
val ArrowUpward = MaterialSymbol("\uE5D8")
val Assistant = MaterialSymbol("\uE39F")
val AttachFile = MaterialSymbol("\uE226")
val AttachMoney = MaterialSymbol("\uE227")
val AudioFile = MaterialSymbol("\uEB82")
val AutoAwesome = MaterialSymbol("\uE65F")
val Block = MaterialSymbol("\uF08C")
val BluetoothAudio = MaterialSymbol("\uE60F")
val Bolt = MaterialSymbol("\uEA0B")
val Bookmark = MaterialSymbol("\uE8E7")
val BookmarkAdd = MaterialSymbol("\uE598")
val BookmarkBorder = MaterialSymbol("\uE8E7")
val BookmarkRemove = MaterialSymbol("\uE59A")
val Forward10 = MaterialSymbol("\uE056")
val Replay10 = MaterialSymbol("\uE059")
val CalendarMonth = MaterialSymbol("\uEBCC")
val Call = MaterialSymbol("\uF0D4")
val CallEnd = MaterialSymbol("\uF0BC")
val CameraAlt = MaterialSymbol("\uE412")
val CameraFront = MaterialSymbol("\uF2C9")
val CameraRear = MaterialSymbol("\uF2C8")
val Cancel = MaterialSymbol("\uE888")
val CellTower = MaterialSymbol("\uEBBA")
val Chat = MaterialSymbol("\uE0C9")
val Check = MaterialSymbol("\uE5CA")
val CheckCircle = MaterialSymbol("\uF0BE")
val Checklist = MaterialSymbol("\uE6B1")
val ChevronRight = MaterialSymbol("\uE5CC")
val Circle = MaterialSymbol("\uEF4A")
val Clear = MaterialSymbol("\uE5CD")
val Close = MaterialSymbol("\uE5CD")
val CloudDownload = MaterialSymbol("\uE2C0")
val CloudSync = MaterialSymbol("\uEB5A")
val CloudUpload = MaterialSymbol("\uE2C3")
val Code = MaterialSymbol("\uE86F")
val Collections = MaterialSymbol("\uE3D3")
val CollectionsBookmark = MaterialSymbol("\uE431")
val ContentCopy = MaterialSymbol("\uE14D")
val ContentPaste = MaterialSymbol("\uE14F")
val CurrencyBitcoin = MaterialSymbol("\uEBC5")
val Dashboard = MaterialSymbol("\uE871")
val DateRange = MaterialSymbol("\uE916")
val Delete = MaterialSymbol("\uE92E")
val DeleteForever = MaterialSymbol("\uE92B")
val DeleteSweep = MaterialSymbol("\uE16C")
val Description = MaterialSymbol("\uE873")
val Dns = MaterialSymbol("\uE875")
val Done = MaterialSymbol("\uE876")
val DoneAll = MaterialSymbol("\uE877")
val Download = MaterialSymbol("\uF090")
val DownloadForOffline = MaterialSymbol("\uF000")
val Downloading = MaterialSymbol("\uF001")
val Drafts = MaterialSymbol("\uE151")
val DragIndicator = MaterialSymbol("\uE945")
val Edit = MaterialSymbol("\uF097")
val EditNote = MaterialSymbol("\uE745")
val EditOff = MaterialSymbol("\uE950")
val Email = MaterialSymbol("\uE159")
val EmojiEmotions = MaterialSymbol("\uEA22")
val Error = MaterialSymbol("\uF8B6")
val ErrorOutline = MaterialSymbol("\uF8B6")
val ExpandLess = MaterialSymbol("\uE5CE")
val ExpandMore = MaterialSymbol("\uE5CF")
val Explore = MaterialSymbol("\uE87A")
val Extension = MaterialSymbol("\uE87B")
val Face = MaterialSymbol("\uF008")
val Favorite = MaterialSymbol("\uE87E")
val FavoriteBorder = MaterialSymbol("\uE87E")
val FileOpen = MaterialSymbol("\uEAF3")
val FilterAlt = MaterialSymbol("\uEF4F")
val FolderZip = MaterialSymbol("\uEB2C")
val FormatBold = MaterialSymbol("\uE238")
val FormatItalic = MaterialSymbol("\uE23F")
val FormatListNumbered = MaterialSymbol("\uE242")
val FormatQuote = MaterialSymbol("\uE244")
val FormatStrikethrough = MaterialSymbol("\uE246")
val Forum = MaterialSymbol("\uE8AF")
val Forward = MaterialSymbol("\uF57A")
val Fullscreen = MaterialSymbol("\uE5D0")
val FullscreenExit = MaterialSymbol("\uE5D1")
val Gavel = MaterialSymbol("\uE90E")
val Group = MaterialSymbol("\uEA21")
val GroupAdd = MaterialSymbol("\uE7F0")
val Groups = MaterialSymbol("\uF233")
val Hearing = MaterialSymbol("\uE023")
val History = MaterialSymbol("\uE8B3")
val Home = MaterialSymbol("\uE9B2")
val HorizontalRule = MaterialSymbol("\uF108")
val HourglassBottom = MaterialSymbol("\uEA5C")
val HourglassEmpty = MaterialSymbol("\uE88B")
val HourglassTop = MaterialSymbol("\uEA5B")
val Image = MaterialSymbol("\uE3F4")
val Key = MaterialSymbol("\uE73C")
val KeyboardArrowDown = MaterialSymbol("\uE313")
val KeyboardArrowUp = MaterialSymbol("\uE316")
val Language = MaterialSymbol("\uE894")
val Link = MaterialSymbol("\uE250")
val LocationOff = MaterialSymbol("\uE0C7")
val LocationOn = MaterialSymbol("\uF1DB")
val Lock = MaterialSymbol("\uE899")
val LockOpen = MaterialSymbol("\uE898")
val Mail = MaterialSymbol("\uE159")
val MenuBook = MaterialSymbol("\uEA19")
val Mic = MaterialSymbol("\uE31D")
val MicOff = MaterialSymbol("\uE02B")
val MilitaryTech = MaterialSymbol("\uEA3F")
val MoreVert = MaterialSymbol("\uE5D4")
val MoveToInbox = MaterialSymbol("\uE168")
val MusicNote = MaterialSymbol("\uE405")
val Notifications = MaterialSymbol("\uE7F5")
val Numbers = MaterialSymbol("\uEAC7")
val OpenInBrowser = MaterialSymbol("\uE89D")
val OpenInFull = MaterialSymbol("\uF1CE")
val Paid = MaterialSymbol("\uF041")
val PanTool = MaterialSymbol("\uE925")
val Pause = MaterialSymbol("\uE034")
val PauseCircleOutline = MaterialSymbol("\uE1A2")
val Payment = MaterialSymbol("\uE8A1")
val People = MaterialSymbol("\uEA21")
val Person = MaterialSymbol("\uF0D3")
val PersonAdd = MaterialSymbol("\uEA4D")
val PersonOff = MaterialSymbol("\uE510")
val PersonRemove = MaterialSymbol("\uEF66")
val Phone = MaterialSymbol("\uF0D4")
val Photo = MaterialSymbol("\uE432")
val PictureAsPdf = MaterialSymbol("\uE415")
val PictureInPicture = MaterialSymbol("\uE8AA")
val PlayArrow = MaterialSymbol("\uE037")
val PlayCircle = MaterialSymbol("\uE1C4")
val PlayCircleOutline = MaterialSymbol("\uE1C4")
val Poll = MaterialSymbol("\uF0CC")
val PrivacyTip = MaterialSymbol("\uF0DC")
val Public = MaterialSymbol("\uE80B")
val PublicOff = MaterialSymbol("\uF1CA")
val PushPin = MaterialSymbol("\uF10D")
val RadioButtonChecked = MaterialSymbol("\uE837")
val RadioButtonUnchecked = MaterialSymbol("\uE836")
val Recommend = MaterialSymbol("\uE9D2")
val Refresh = MaterialSymbol("\uE5D5")
val RemoveCircleOutline = MaterialSymbol("\uF08F")
val RemoveDone = MaterialSymbol("\uE9D3")
val Replay = MaterialSymbol("\uE042")
val Report = MaterialSymbol("\uF052")
val Save = MaterialSymbol("\uE161")
val SaveAlt = MaterialSymbol("\uF090")
val Schedule = MaterialSymbol("\uEFD6")
val Science = MaterialSymbol("\uEA4B")
val Search = MaterialSymbol("\uE8B6")
val Security = MaterialSymbol("\uE32A")
val Sensors = MaterialSymbol("\uE51E")
val Settings = MaterialSymbol("\uE8B8")
val SettingsInputAntenna = MaterialSymbol("\uE8BF")
val Share = MaterialSymbol("\uE80D")
val Shield = MaterialSymbol("\uE9E0")
val SkipNext = MaterialSymbol("\uE044")
val SkipPrevious = MaterialSymbol("\uE045")
val SmartDisplay = MaterialSymbol("\uF06A")
val SportsEsports = MaterialSymbol("\uEA28")
val Star = MaterialSymbol("\uF09A")
val StarBorder = MaterialSymbol("\uF09A")
val Stop = MaterialSymbol("\uE047")
val Storage = MaterialSymbol("\uE1DB")
val Storefront = MaterialSymbol("\uEA12")
val Subscriptions = MaterialSymbol("\uE064")
val SwapHoriz = MaterialSymbol("\uE8D4")
val Sync = MaterialSymbol("\uE627")
val SyncProblem = MaterialSymbol("\uE629")
val Tag = MaterialSymbol("\uE9EF")
val ThumbUp = MaterialSymbol("\uF577")
val Timer = MaterialSymbol("\uE425")
val TimerOff = MaterialSymbol("\uE426")
val Topic = MaterialSymbol("\uF1C8")
val Translate = MaterialSymbol("\uE8E2")
val TravelExplore = MaterialSymbol("\uE2DB")
val Tune = MaterialSymbol("\uE429")
val Upload = MaterialSymbol("\uF09B")
val VideoLibrary = MaterialSymbol("\uE04A")
val VideoSettings = MaterialSymbol("\uEA75")
val Videocam = MaterialSymbol("\uE04B")
val VideocamOff = MaterialSymbol("\uE04C")
val Visibility = MaterialSymbol("\uE8F4")
val VisibilityOff = MaterialSymbol("\uE8F5")
val Warning = MaterialSymbol("\uF083")
val Work = MaterialSymbol("\uE943")
val ZoomOutMap = MaterialSymbol("\uE56B")
object AutoMirrored {
val ArrowBack = MaterialSymbol("\uE5C4", autoMirror = true)
val ArrowForward = MaterialSymbol("\uE5C8", autoMirror = true)
val ArrowForwardIos = MaterialSymbol("\uE5E1", autoMirror = true)
val Article = MaterialSymbol("\uEF42", autoMirror = true)
val Chat = MaterialSymbol("\uE0C9", autoMirror = true)
val ContactSupport = MaterialSymbol("\uE94C", autoMirror = true)
val DriveFileMove = MaterialSymbol("\uE9A1", autoMirror = true)
val ExitToApp = MaterialSymbol("\uE879", autoMirror = true)
val Feed = MaterialSymbol("\uF009", autoMirror = true)
val FormatListBulleted = MaterialSymbol("\uE241", autoMirror = true)
val Forward = MaterialSymbol("\uF57A", autoMirror = true)
val InsertDriveFile = MaterialSymbol("\uE66D", autoMirror = true)
val KeyboardArrowLeft = MaterialSymbol("\uE314", autoMirror = true)
val KeyboardArrowRight = MaterialSymbol("\uE315", autoMirror = true)
val Label = MaterialSymbol("\uE893", autoMirror = true)
val List = MaterialSymbol("\uE896", autoMirror = true)
val Logout = MaterialSymbol("\uE9BA", autoMirror = true)
val Message = MaterialSymbol("\uE0C9", autoMirror = true)
val OpenInNew = MaterialSymbol("\uE89E", autoMirror = true)
val PlaylistAdd = MaterialSymbol("\uE03B", autoMirror = true)
val Send = MaterialSymbol("\uE163", autoMirror = true)
val ShowChart = MaterialSymbol("\uE6E1", autoMirror = true)
val ViewList = MaterialSymbol("\uE8EF", autoMirror = true)
val VolumeOff = MaterialSymbol("\uE04F", autoMirror = true)
val VolumeUp = MaterialSymbol("\uE050", autoMirror = true)
}
}
@@ -0,0 +1,30 @@
/*
* Copyright (c) 2025 Vitor Pamplona
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to use,
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
* Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package com.vitorpamplona.amethyst.commons.icons.symbols
// Single source of truth for Material Symbols visual axes. Change WEIGHT here to globally
// re-tune line thickness (100 = Thin, 200 = ExtraLight, 300 = Light, 400 = Regular, …, 700 = Bold).
object MaterialSymbolsDefaults {
const val WEIGHT: Int = 100
const val FILL: Float = 0f
const val OPTICAL_SIZE: Float = 24f
const val GRADE: Float = 0f
}
@@ -0,0 +1,47 @@
/*
* Copyright (c) 2025 Vitor Pamplona
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to use,
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
* Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package com.vitorpamplona.amethyst.commons.icons.symbols
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.ui.text.font.FontFamily
import androidx.compose.ui.text.font.FontVariation
import androidx.compose.ui.text.font.FontWeight
import com.vitorpamplona.amethyst.commons.resources.Res
import com.vitorpamplona.amethyst.commons.resources.material_symbols_outlined
import org.jetbrains.compose.resources.Font
@Composable
fun rememberMaterialSymbolsFontFamily(weight: Int = MaterialSymbolsDefaults.WEIGHT): FontFamily {
val font =
Font(
resource = Res.font.material_symbols_outlined,
weight = FontWeight(weight),
variationSettings =
FontVariation.Settings(
FontVariation.weight(weight),
FontVariation.Setting("FILL", MaterialSymbolsDefaults.FILL),
FontVariation.Setting("opsz", MaterialSymbolsDefaults.OPTICAL_SIZE),
FontVariation.Setting("GRAD", MaterialSymbolsDefaults.GRADE),
),
)
return remember(font) { FontFamily(font) }
}
@@ -38,10 +38,6 @@ import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.CheckCircle
import androidx.compose.material.icons.filled.Error
import androidx.compose.material.icons.filled.Sync
import androidx.compose.material3.Icon
import androidx.compose.material3.LinearProgressIndicator
import androidx.compose.material3.MaterialTheme
@@ -52,9 +48,11 @@ import androidx.compose.runtime.getValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
import com.vitorpamplona.amethyst.commons.icons.symbols.Icon
import com.vitorpamplona.amethyst.commons.icons.symbols.MaterialSymbol
import com.vitorpamplona.amethyst.commons.icons.symbols.MaterialSymbols
/**
* Shared banner showing profile metadata broadcast progress.
@@ -91,7 +89,7 @@ fun ProfileBroadcastBanner(
.animateContentSize(),
) {
Icon(
imageVector = getStatusIcon(status),
symbol = getStatusIcon(status),
contentDescription = null,
tint = getStatusIconColor(status),
modifier = Modifier.size(18.dp),
@@ -152,12 +150,12 @@ private fun getStatusBackgroundColor(status: ProfileBroadcastStatus): Color =
else -> MaterialTheme.colorScheme.surfaceContainer
}
private fun getStatusIcon(status: ProfileBroadcastStatus): ImageVector =
private fun getStatusIcon(status: ProfileBroadcastStatus): MaterialSymbol =
when (status) {
is ProfileBroadcastStatus.Broadcasting -> Icons.Default.Sync
is ProfileBroadcastStatus.Success -> Icons.Default.CheckCircle
is ProfileBroadcastStatus.Failed -> Icons.Default.Error
is ProfileBroadcastStatus.Idle -> Icons.Default.CheckCircle
is ProfileBroadcastStatus.Broadcasting -> MaterialSymbols.Sync
is ProfileBroadcastStatus.Success -> MaterialSymbols.CheckCircle
is ProfileBroadcastStatus.Failed -> MaterialSymbols.Error
is ProfileBroadcastStatus.Idle -> MaterialSymbols.CheckCircle
}
@Composable
@@ -33,10 +33,6 @@ import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.CheckCircle
import androidx.compose.material.icons.filled.Error
import androidx.compose.material.icons.filled.Sync
import androidx.compose.material3.Icon
import androidx.compose.material3.LinearProgressIndicator
import androidx.compose.material3.MaterialTheme
@@ -46,6 +42,8 @@ import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import com.vitorpamplona.amethyst.commons.icons.symbols.Icon
import com.vitorpamplona.amethyst.commons.icons.symbols.MaterialSymbols
@Composable
fun DmBroadcastBanner(
@@ -87,17 +85,17 @@ fun DmBroadcastBanner(
when (status) {
is DmBroadcastStatus.Subscribing,
is DmBroadcastStatus.Sending,
-> Icons.Default.Sync
-> MaterialSymbols.Sync
is DmBroadcastStatus.Sent -> Icons.Default.CheckCircle
is DmBroadcastStatus.Sent -> MaterialSymbols.CheckCircle
is DmBroadcastStatus.Failed -> Icons.Default.Error
is DmBroadcastStatus.Failed -> MaterialSymbols.Error
is DmBroadcastStatus.Idle -> Icons.Default.Sync
is DmBroadcastStatus.Idle -> MaterialSymbols.Sync
}
Icon(
imageVector = icon,
symbol = icon,
contentDescription = null,
modifier = Modifier.size(16.dp),
tint = contentColor,
@@ -26,9 +26,6 @@ import androidx.compose.animation.core.infiniteRepeatable
import androidx.compose.animation.core.rememberInfiniteTransition
import androidx.compose.animation.core.tween
import androidx.compose.foundation.layout.size
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Favorite
import androidx.compose.material.icons.filled.FavoriteBorder
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon
import androidx.compose.material3.PlainTooltip
@@ -44,6 +41,8 @@ import androidx.compose.ui.draw.scale
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.dp
import com.vitorpamplona.amethyst.commons.domain.nip46.SignerConnectionState
import com.vitorpamplona.amethyst.commons.icons.symbols.Icon
import com.vitorpamplona.amethyst.commons.icons.symbols.MaterialSymbols
import com.vitorpamplona.quartz.utils.TimeUtils
@OptIn(ExperimentalMaterial3Api::class)
@@ -92,7 +91,7 @@ fun BunkerHeartbeatIndicator(
label = "heartbeatScale",
)
Icon(
Icons.Default.Favorite,
MaterialSymbols.Favorite,
contentDescription = "Bunker connected",
tint = Color(0xFF4CAF50),
modifier = Modifier.size(20.dp).scale(scale),
@@ -101,7 +100,7 @@ fun BunkerHeartbeatIndicator(
is SignerConnectionState.Disconnected -> {
Icon(
Icons.Default.FavoriteBorder,
MaterialSymbols.FavoriteBorder,
contentDescription = "Bunker disconnected",
tint = Color(0xFFF44336),
modifier = Modifier.size(20.dp),
@@ -21,8 +21,6 @@
package com.vitorpamplona.amethyst.commons.ui.components
import androidx.compose.foundation.Image
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Face
import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
@@ -30,6 +28,8 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.ColorFilter
import androidx.compose.ui.graphics.vector.rememberVectorPainter
import androidx.compose.ui.layout.ContentScale
import com.vitorpamplona.amethyst.commons.icons.symbols.MaterialSymbols
import com.vitorpamplona.amethyst.commons.icons.symbols.rememberMaterialSymbolPainter
import com.vitorpamplona.amethyst.commons.robohash.CachedRobohash
/**
@@ -67,7 +67,7 @@ fun RobohashImage(
)
} else {
Image(
imageVector = Icons.Default.Face,
painter = rememberMaterialSymbolPainter(MaterialSymbols.Face),
contentDescription = contentDescription,
colorFilter = ColorFilter.tint(MaterialTheme.colorScheme.onBackground),
modifier = modifier,
@@ -99,7 +99,7 @@ fun RobohashImage(
)
} else {
Image(
imageVector = Icons.Default.Face,
painter = rememberMaterialSymbolPainter(MaterialSymbols.Face),
contentDescription = contentDescription,
colorFilter = colorFilter ?: ColorFilter.tint(MaterialTheme.colorScheme.onBackground),
modifier = modifier,
@@ -23,8 +23,6 @@ package com.vitorpamplona.amethyst.commons.ui.components
import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Face
import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
@@ -38,6 +36,8 @@ import androidx.compose.ui.graphics.vector.rememberVectorPainter
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.unit.Dp
import coil3.compose.AsyncImage
import com.vitorpamplona.amethyst.commons.icons.symbols.MaterialSymbols
import com.vitorpamplona.amethyst.commons.icons.symbols.rememberMaterialSymbolPainter
import com.vitorpamplona.amethyst.commons.robohash.CachedRobohash
import com.vitorpamplona.amethyst.commons.ui.theme.isLight
@@ -96,9 +96,7 @@ fun UserAvatar(
image = CachedRobohash.get(userHex, MaterialTheme.colorScheme.isLight),
)
} else {
rememberVectorPainter(
image = Icons.Default.Face,
)
rememberMaterialSymbolPainter(MaterialSymbols.Face)
}
AsyncImage(
@@ -125,7 +123,7 @@ fun UserAvatar(
} else {
// Show generic icon
Image(
imageVector = Icons.Default.Face,
painter = rememberMaterialSymbolPainter(MaterialSymbols.Face),
contentDescription = contentDescription,
colorFilter = ColorFilter.tint(MaterialTheme.colorScheme.onBackground),
modifier = avatarModifier,
@@ -26,8 +26,6 @@ import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.filled.ArrowForward
import androidx.compose.material3.Card
import androidx.compose.material3.CardDefaults
import androidx.compose.material3.Icon
@@ -38,6 +36,8 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.text.font.FontFamily
import androidx.compose.ui.unit.dp
import com.vitorpamplona.amethyst.commons.icons.symbols.Icon
import com.vitorpamplona.amethyst.commons.icons.symbols.MaterialSymbols
import com.vitorpamplona.amethyst.commons.model.User
import com.vitorpamplona.amethyst.commons.resources.Res
import com.vitorpamplona.amethyst.commons.resources.accessibility_navigate
@@ -100,7 +100,7 @@ fun UserSearchCard(
}
Icon(
Icons.AutoMirrored.Filled.ArrowForward,
MaterialSymbols.AutoMirrored.ArrowForward,
contentDescription = stringResource(Res.string.accessibility_navigate),
tint = MaterialTheme.colorScheme.onSurfaceVariant,
)
@@ -24,10 +24,6 @@ import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.size
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Check
import androidx.compose.material.icons.filled.Close
import androidx.compose.material.icons.filled.Refresh
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme
@@ -36,6 +32,8 @@ import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import com.vitorpamplona.amethyst.commons.icons.symbols.Icon
import com.vitorpamplona.amethyst.commons.icons.symbols.MaterialSymbols
import com.vitorpamplona.amethyst.commons.ui.theme.RelayStatusColors
/**
@@ -93,7 +91,7 @@ fun RelayStatusIndicator(
}
Icon(
imageVector = if (connectedCount > 0) Icons.Default.Check else Icons.Default.Close,
symbol = if (connectedCount > 0) MaterialSymbols.Check else MaterialSymbols.Close,
contentDescription = null,
tint = statusColor,
modifier = Modifier.size(16.dp),
@@ -107,7 +105,7 @@ fun RelayStatusIndicator(
IconButton(onClick = onRefresh) {
Icon(
Icons.Default.Refresh,
MaterialSymbols.Refresh,
contentDescription = "Reconnect",
tint = MaterialTheme.colorScheme.primary,
)