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:
@@ -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)
|
||||
|
||||
Binary file not shown.
+12
-16
@@ -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
|
||||
|
||||
+3
-3
@@ -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,
|
||||
)
|
||||
|
||||
+17
-23
@@ -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
|
||||
|
||||
+6
-9
@@ -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) {
|
||||
|
||||
+15
-25
@@ -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,
|
||||
|
||||
+3
-3
@@ -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),
|
||||
)
|
||||
|
||||
+97
@@ -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) }
|
||||
}
|
||||
}
|
||||
}
|
||||
+29
@@ -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,
|
||||
)
|
||||
+79
@@ -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) }
|
||||
}
|
||||
}
|
||||
+249
@@ -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)
|
||||
}
|
||||
}
|
||||
+30
@@ -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
|
||||
}
|
||||
+47
@@ -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) }
|
||||
}
|
||||
+9
-11
@@ -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
|
||||
|
||||
+7
-9
@@ -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,
|
||||
|
||||
+4
-5
@@ -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),
|
||||
|
||||
+4
-4
@@ -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,
|
||||
|
||||
+4
-6
@@ -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,
|
||||
|
||||
+3
-3
@@ -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,
|
||||
)
|
||||
|
||||
+4
-6
@@ -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,
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user