Skip to content

redesign NodeCard with cleaner Material 3 styling#420

Merged
Q7DF1 merged 1 commit into
mainfrom
feature/nodecard
Jun 9, 2026
Merged

redesign NodeCard with cleaner Material 3 styling#420
Q7DF1 merged 1 commit into
mainfrom
feature/nodecard

Conversation

@Q7DF1

@Q7DF1 Q7DF1 commented Jun 8, 2026

Copy link
Copy Markdown
Owner

Drop the random ColorMap avatar tint and hardcoded colors in favor of colorScheme tokens. Split into list mode (flat row, leading 3dp primary bar as the sole selection signal) and card mode (ElevatedCard for HomeScreen). Unify icons to outlined family, tighten typography and spacing, and use a softer inset divider.

Drop the random ColorMap avatar tint and hardcoded colors in favor of
colorScheme tokens. Split into list mode (flat row, leading 3dp primary
bar as the sole selection signal) and card mode (ElevatedCard for
HomeScreen). Unify icons to outlined family, tighten typography and
spacing, and use a softer inset divider.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request refactors the NodeCard component to support both standalone card and list modes, extracts NodeCardContent and DelayChip composables, updates icons to outlined variants, and refines dividers in ConfigScreen. Feedback is provided to optimize performance by only running infinite animations when active, and to improve accessibility in dark theme by dynamically adjusting the hardcoded colors in DelayChip for better contrast.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment on lines +238 to +274
if (onTest != null) {
val infiniteTransition = rememberInfiniteTransition(label = "pulse")
val scale by infiniteTransition.animateFloat(
initialValue = 1f,
targetValue = if (testing) 1.2f else 1f,
animationSpec = infiniteRepeatable(
animation = tween(800, easing = LinearEasing),
repeatMode = androidx.compose.animation.core.RepeatMode.Reverse
),
label = "scale"
)
val alpha by infiniteTransition.animateFloat(
initialValue = 1f,
targetValue = if (testing) 0.4f else 1f,
animationSpec = infiniteRepeatable(
animation = tween(800, easing = LinearEasing),
repeatMode = androidx.compose.animation.core.RepeatMode.Reverse
),
label = "alpha"
)
IconButton(
onClick = onTest,
enabled = enableTest,
modifier = Modifier.size(36.dp)
) {
Icon(
imageVector = Icons.Outlined.Speed,
contentDescription = stringResource(R.string.test_url),
modifier = Modifier
.size(20.dp)
.scale(scale),
tint = if (enableTest)
MaterialTheme.colorScheme.primary.copy(alpha = alpha)
else MaterialTheme.colorScheme.onSurfaceVariant.copy(alpha = 0.4f)
)

IconButton(onClick = onTest, enabled = enableTest) {
Icon(
imageVector = Icons.Outlined.Speed,
contentDescription = stringResource(R.string.test_url),
modifier = Modifier.scale(scale),
tint = if (enableTest) {
MaterialTheme.colorScheme.primary.copy(alpha = alpha)
} else Color.Gray
)
}
}
if (onShare != null) {
IconButton(onClick = onShare) {
Icon(Icons.Default.Share, contentDescription = stringResource(R.string.clipboard_export))
}
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

Performance Optimization: Avoid Infinite Animations when Idle

Currently, infiniteTransition is created and run continuously even when testing is false. Because infiniteRepeatable animations run on every frame, this will cause the composable to constantly redraw/recompose at 60fps or 120fps, leading to high CPU usage and rapid battery drain, especially when multiple cards are displayed in a list.

By moving the infinite transition and its animations inside the if (testing) block, we ensure that the animation only runs when active, and is completely removed from the composition when idle.

            if (onTest != null) {
                IconButton(
                    onClick = onTest,
                    enabled = enableTest,
                    modifier = Modifier.size(36.dp)
                ) {
                    if (testing) {
                        val infiniteTransition = rememberInfiniteTransition(label = "pulse")
                        val scale by infiniteTransition.animateFloat(
                            initialValue = 1f,
                            targetValue = 1.2f,
                            animationSpec = infiniteRepeatable(
                                animation = tween(800, easing = LinearEasing),
                                repeatMode = androidx.compose.animation.core.RepeatMode.Reverse
                            ),
                            label = "scale"
                        )
                        val alpha by infiniteTransition.animateFloat(
                            initialValue = 1f,
                            targetValue = 0.4f,
                            animationSpec = infiniteRepeatable(
                                animation = tween(800, easing = LinearEasing),
                                repeatMode = androidx.compose.animation.core.RepeatMode.Reverse
                            ),
                            label = "alpha"
                        )
                        Icon(
                            imageVector = Icons.Outlined.Speed,
                            contentDescription = stringResource(R.string.test_url),
                            modifier = Modifier
                                .size(20.dp)
                                .scale(scale),
                            tint = MaterialTheme.colorScheme.primary.copy(alpha = alpha)
                        )
                    } else {
                        Icon(
                            imageVector = Icons.Outlined.Speed,
                            contentDescription = stringResource(R.string.test_url),
                            modifier = Modifier.size(20.dp),
                            tint = if (enableTest)
                                MaterialTheme.colorScheme.primary
                            else MaterialTheme.colorScheme.onSurfaceVariant.copy(alpha = 0.4f)
                        )
                    }
                }
            }

Comment on lines +319 to +326
private fun DelayChip(delayMs: Long) {
// Delay semantic colors: keep universal green/orange/red semantics, but tone down to Material-friendly saturation
val delayColor = when {
delayMs == -2L -> MaterialTheme.colorScheme.error
delayMs < 300 -> Color(0xFF2E7D32) // green 800
delayMs < 900 -> Color(0xFFE65100) // orange 900
else -> MaterialTheme.colorScheme.error
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Accessibility: Improve Contrast Ratio in Dark Theme

Using hardcoded dark colors like Color(0xFF2E7D32) (Green 800) and Color(0xFFE65100) (Orange 900) on a dark background in dark theme results in extremely low contrast, making the text nearly unreadable and violating WCAG accessibility guidelines.

We should dynamically adjust these colors based on the system theme using isSystemInDarkTheme(), using lighter/softer colors in dark theme and darker/more saturated colors in light theme.

private fun DelayChip(delayMs: Long) {
    // Delay semantic colors: keep universal green/orange/red semantics, but tone down to Material-friendly saturation
    val isDark = androidx.compose.foundation.isSystemInDarkTheme()
    val delayColor = when {
        delayMs == -2L -> MaterialTheme.colorScheme.error
        delayMs < 300 -> if (isDark) Color(0xFF81C784) else Color(0xFF2E7D32) // green 300 in dark, green 800 in light
        delayMs < 900 -> if (isDark) Color(0xFFFFB74D) else Color(0xFFE65100) // orange 300 in dark, orange 900 in light
        else -> MaterialTheme.colorScheme.error
    }

@Q7DF1 Q7DF1 merged commit 293387c into main Jun 9, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant