chore: Update usages of Dart UI Color to not use deprecated fields and methods [flame_3d] (#3297)

Update usages of Dart UI Color to not use deprecated fields and methods.
On latest main of Flutter these will cause lint errors, [as can be seen
here](https://github.com/flame-engine/flame/actions/runs/10814047130/job/29999530293?pr=3282).

Note: I will update the `flame_3d`-specific violations on a followup.
This commit is contained in:
Luan Nico
2024-09-11 23:31:28 +02:00
parent 5f25423f68
commit 27e4dcb65a
30 changed files with 138 additions and 129 deletions

View File

@ -127,7 +127,7 @@ class Trail extends Component {
final path = _paths[i];
final opacity = _opacities[i];
if (opacity > 0) {
_linePaint.color = _color.withOpacity(opacity);
_linePaint.color = _color.withValues(alpha: opacity);
_linePaint.strokeWidth = lineWidth * opacity;
canvas.drawPath(path, _linePaint);
}
@ -216,11 +216,11 @@ class Star extends PositionComponent with DragCallbacks {
@override
void render(Canvas canvas) {
if (isDragged) {
_paint.color = color.withOpacity(0.5);
_paint.color = color.withValues(alpha: 0.5);
canvas.drawPath(_path, _paint);
canvas.drawPath(_path, _borderPaint);
} else {
_paint.color = color.withOpacity(1);
_paint.color = color.withValues(alpha: 1);
canvas.drawPath(_path, _shadowPaint);
canvas.drawPath(_path, _paint);
}

View File

@ -32,7 +32,7 @@ class HoverTarget extends PositionComponent with HoverCallbacks {
final _paint = Paint()
..color = HSLColor.fromAHSL(1, _random.nextDouble() * 360, 1, 0.8)
.toColor()
.withOpacity(0.5);
.withValues(alpha: 0.5);
@override
void render(Canvas canvas) {
@ -41,11 +41,11 @@ class HoverTarget extends PositionComponent with HoverCallbacks {
@override
void onHoverEnter() {
_paint.color = _paint.color.withOpacity(1);
_paint.color = _paint.color.withValues(alpha: 1);
}
@override
void onHoverExit() {
_paint.color = _paint.color.withOpacity(0.5);
_paint.color = _paint.color.withValues(alpha: 0.5);
}
}

View File

@ -13,7 +13,7 @@ class RayCastExample extends FlameGame with HasCollisionDetection {
final velocity = 60;
double get resetPosition => -canvasSize.y;
Paint paint = Paint()..color = Colors.red.withOpacity(0.6);
Paint paint = BasicPalette.red.withOpacity(0.6).paint();
RaycastResult<ShapeHitbox>? result;

View File

@ -10,7 +10,7 @@ import 'package:flutter/material.dart';
class RayTraceExample extends FlameGame
with HasCollisionDetection, TapDetector {
Paint paint = Paint()..color = Colors.red.withOpacity(0.6);
Paint paint = BasicPalette.red.withOpacity(0.6).paint();
bool isClicked = false;
Vector2 get origin => canvasSize / 2;

View File

@ -115,7 +115,7 @@ class ExpandingCircle extends Component {
removeFromParent();
} else {
final opacity = 1 - radius / maxRadius;
_paint.color = _baseColor.withOpacity(opacity);
_paint.color = _baseColor.withValues(alpha: opacity);
_paint.strokeWidth = _outerRadius - _innerRadius;
}
}

View File

@ -250,7 +250,7 @@ game.add(
ParticleSystemComponent(
particle: CircleParticle(
radius: game.size.x / 2,
paint: Paint()..color = Colors.red.withOpacity(.5),
paint: Paint()..color = Colors.red.withValues(alpha: .5),
),
),
);

View File

@ -31,7 +31,7 @@ class LapLine extends BodyComponent with ContactCallbacks {
@override
Body createBody() {
paint.color = (isFinish ? GameColors.green.color : GameColors.green.color)
..withOpacity(0.5);
..withValues(alpha: 0.5);
paint
..style = PaintingStyle.fill
..shader = Gradient.radial(

View File

@ -20,7 +20,7 @@ class Trail extends Component with HasPaint {
@override
Future<void> onLoad() async {
paint
..color = (tire.paint.color.withOpacity(0.9))
..color = (tire.paint.color.withValues(alpha: 0.9))
..strokeWidth = 1.0;
}

View File

@ -147,7 +147,7 @@ class ExpandingCircle extends CircleComponent {
removeFromParent();
} else {
final opacity = 1 - radius / maxRadius;
paint.color = const Color(0xffffffff).withOpacity(opacity);
paint.color = const Color(0xffffffff).withValues(alpha: opacity);
}
}
}

View File

@ -127,7 +127,7 @@ class TextButton extends ButtonComponent {
),
buttonDown: RectangleComponent(
size: Vector2(200, 100),
paint: Paint()..color = BasicPalette.orange.color.withOpacity(0.5),
paint: BasicPalette.orange.withOpacity(0.5).paint(),
),
children: [
TextComponent(

View File

@ -94,8 +94,8 @@ abstract class MyCollidable extends PositionComponent
final Vector2 velocity;
final delta = Vector2.zero();
double angleDelta = 0;
final Color _defaultColor = Colors.blue.withOpacity(0.8);
final Color _collisionColor = Colors.green.withOpacity(0.8);
final Color _defaultColor = Colors.blue.withValues(alpha: 0.8);
final Color _collisionColor = Colors.green.withValues(alpha: 0.8);
late final Paint _dragIndicatorPaint;
final ScreenHitbox screenHitbox;
ShapeHitbox? hitbox;
@ -219,7 +219,7 @@ class CollidableCircle extends MyCollidable {
class SnowmanPart extends CircleHitbox {
@override
final renderShape = true;
final startColor = Colors.white.withOpacity(0.8);
final startColor = Colors.white.withValues(alpha: 0.8);
final Color hitColor;
SnowmanPart(double radius, Vector2 position, this.hitColor)
@ -234,7 +234,7 @@ class SnowmanPart extends CircleHitbox {
if (other.hitboxParent is ScreenHitbox) {
paint.color = startColor;
} else {
paint.color = hitColor.withOpacity(0.8);
paint.color = hitColor.withValues(alpha: 0.8);
}
}

View File

@ -17,7 +17,7 @@ around trying not to hit them.
Ray2? ray;
Ray2? reflection;
Vector2 origin = Vector2(250, 100);
Paint paint = Paint()..color = Colors.amber.withOpacity(0.6);
Paint paint = Paint()..color = Colors.amber.withValues(alpha: 0.6);
final speed = 100;
final inertia = 3.0;
final safetyDistance = 50;

View File

@ -28,8 +28,8 @@ with with mouse.
Paint tapPaint = Paint();
final _colorTween = ColorTween(
begin: Colors.blue.withOpacity(0.2),
end: Colors.red.withOpacity(0.2),
begin: Colors.blue.withValues(alpha: 0.2),
end: Colors.red.withValues(alpha: 0.2),
);
static const numberOfRays = 2000;

View File

@ -23,8 +23,8 @@ bounce on will appear.
''';
final _colorTween = ColorTween(
begin: Colors.amber.withOpacity(1.0),
end: Colors.lightBlueAccent.withOpacity(1.0),
begin: Colors.amber.withValues(alpha: 1.0),
end: Colors.lightBlueAccent.withValues(alpha: 1.0),
);
final random = Random();
Ray2? ray;

View File

@ -106,13 +106,13 @@ class SelectableClass extends SpriteComponent {
super.size,
super.key,
super.sprite,
}) : super(paint: Paint()..color = Colors.white.withOpacity(0.5));
}) : super(paint: Paint()..color = Colors.white.withValues(alpha: 0.5));
bool _selected = false;
bool get selected => _selected;
set selected(bool value) {
_selected = value;
paint = Paint()
..color = value ? Colors.white : Colors.white.withOpacity(0.5);
..color = value ? Colors.white : Colors.white.withValues(alpha: 0.5);
}
}

View File

@ -42,7 +42,7 @@ class OpacityEffectExample extends FlameGame with TapDetector {
@override
void onTap() {
final opacity = sprite.paint.color.opacity;
final opacity = sprite.paint.color.a;
if (opacity >= 0.5) {
sprite.add(OpacityEffect.fadeOut(EffectController(duration: 1)));
} else {

View File

@ -30,7 +30,7 @@ class TapCallbacksSquare extends RectangleComponent with TapCallbacks {
position: position ?? Vector2.all(100),
size: Vector2.all(100),
paint: continuePropagation
? (Paint()..color = Colors.green.withOpacity(0.9))
? (Paint()..color = Colors.green.withValues(alpha: 0.9))
: PaintExtension.random(withAlpha: 0.9, base: 100),
);

View File

@ -432,7 +432,9 @@ class ParticlesExample extends FlameGame {
renderer: (canvas, particle) {
final paint = randomElement(paints);
// Override the color to dynamically update opacity
paint.color = paint.color.withOpacity(1 - particle.progress);
paint.color = paint.color.withValues(
alpha: 1 - particle.progress,
);
canvas.drawCircle(
Offset.zero,

View File

@ -83,12 +83,15 @@ mixin HasPaint<T extends Object> on Component
throw ArgumentError('Opacity needs to be between 0 and 1');
}
setColor(getPaint(paintId).color.withOpacity(opacity), paintId: paintId);
setColor(
getPaint(paintId).color.withValues(alpha: opacity),
paintId: paintId,
);
}
/// Returns the current opacity.
double getOpacity({T? paintId}) {
return getPaint(paintId).color.opacity;
return getPaint(paintId).color.a;
}
/// Changes the opacity of the paint.
@ -100,11 +103,6 @@ mixin HasPaint<T extends Object> on Component
setColor(getPaint(paintId).color.withAlpha(alpha), paintId: paintId);
}
/// Returns the current opacity.
int getAlpha({T? paintId}) {
return getPaint(paintId).color.alpha;
}
/// Shortcut for changing the color of the paint.
void setColor(Color color, {T? paintId}) {
getPaint(paintId).color = color;
@ -118,13 +116,13 @@ mixin HasPaint<T extends Object> on Component
}
@override
double get opacity => paint.color.opacity;
double get opacity => paint.color.a;
@override
set opacity(double value) {
paint.color = paint.color.withOpacity(value);
paint.color = paint.color.withValues(alpha: value);
for (final paint in _paints.values) {
paint.color = paint.color.withOpacity(value);
paint.color = paint.color.withValues(alpha: value);
}
}
@ -186,7 +184,7 @@ class _MultiPaintOpacityProvider<T extends Object> implements OpacityProvider {
];
_layerOpacityRatios = target.paintLayersInternal
?.map(
(paint) => paint.color.opacity / maxOpacity,
(paint) => paint.color.a / maxOpacity,
)
.toList(growable: false);
}
@ -206,7 +204,7 @@ class _MultiPaintOpacityProvider<T extends Object> implements OpacityProvider {
}
if (includeLayers) {
target.paintLayersInternal?.forEach(
(paint) => maxOpacity = max(paint.color.opacity, maxOpacity),
(paint) => maxOpacity = max(paint.color.a, maxOpacity),
);
}
@ -226,7 +224,7 @@ class _MultiPaintOpacityProvider<T extends Object> implements OpacityProvider {
for (var i = 0; i < (paintLayersInternal?.length ?? 0); ++i) {
paintLayersInternal![i].color = paintLayersInternal[i]
.color
.withOpacity(value * _layerOpacityRatios![i]);
.withValues(alpha: value * _layerOpacityRatios![i]);
}
}
}

View File

@ -43,8 +43,8 @@ class ColorEffect extends ComponentEffect<HasPaint> {
@override
void apply(double progress) {
final currentColor = color.withOpacity(
min(max(_tween.transform(progress), 0), 1),
final currentColor = color.withValues(
alpha: min(max(_tween.transform(progress), 0), 1),
);
target.tint(currentColor, paintId: paintId);
}

View File

@ -13,11 +13,11 @@ extension ColorExtension on Color {
assert(amount >= 0 && amount <= 1);
final f = 1 - amount;
return Color.fromARGB(
alpha,
(red * f).round(),
(green * f).round(),
(blue * f).round(),
return Color.from(
alpha: a,
red: r * f,
green: g * f,
blue: b * f,
);
}
@ -29,11 +29,11 @@ extension ColorExtension on Color {
Color brighten(double amount) {
assert(amount >= 0 && amount <= 1);
return Color.fromARGB(
alpha,
red + ((255 - red) * amount).round(),
green + ((255 - green) * amount).round(),
blue + ((255 - blue) * amount).round(),
return Color.from(
alpha: a,
red: r + (1.0 - r) * amount,
blue: b + (1.0 - b) * amount,
green: g + (1.0 - g) * amount,
);
}

View File

@ -47,48 +47,45 @@ extension ImageExtension on Image {
///
/// The [amount] is a double value between 0 and 1.
Future<Image> darken(double amount) async {
assert(amount >= 0 && amount <= 1);
final pixelData = await pixelsInUint8();
final newPixelData = Uint8List(pixelData.length);
for (var i = 0; i < pixelData.length; i += 4) {
final color = Color.fromARGB(
pixelData[i + 3],
pixelData[i + 0],
pixelData[i + 1],
pixelData[i + 2],
).darken(amount);
newPixelData[i] = color.red;
newPixelData[i + 1] = color.green;
newPixelData[i + 2] = color.blue;
newPixelData[i + 3] = color.alpha;
}
return fromPixels(newPixelData, width, height);
return _transformColorsByAmount(
amount,
(color, amount) => color.darken(amount),
);
}
/// Change each pixel's color to be brighter and return a new [Image].
///
/// The [amount] is a double value between 0 and 1.
Future<Image> brighten(double amount) async {
return _transformColorsByAmount(
amount,
(color, amount) => color.brighten(amount),
);
}
Future<Image> _transformColorsByAmount(
double amount,
Color Function(Color, double) process,
) async {
assert(amount >= 0 && amount <= 1);
final pixelData = await pixelsInUint8();
final newPixelData = Uint8List(pixelData.length);
for (var i = 0; i < pixelData.length; i += 4) {
final color = Color.fromARGB(
final originalColor = Color.fromARGB(
pixelData[i + 3],
pixelData[i + 0],
pixelData[i + 1],
pixelData[i + 2],
).brighten(amount);
);
newPixelData[i] = color.red;
newPixelData[i + 1] = color.green;
newPixelData[i + 2] = color.blue;
newPixelData[i + 3] = color.alpha;
final color = process(originalColor, amount);
newPixelData[i] = (255 * color.r).round();
newPixelData[i + 1] = (255 * color.g).round();
newPixelData[i + 2] = (255 * color.b).round();
newPixelData[i + 3] = (255 * color.a).round();
}
return fromPixels(newPixelData, width, height);
}

View File

@ -14,8 +14,10 @@ class ShadowProcessor extends LayerProcessor {
double opacity = 0.9,
Color color = const Color(0xFF000000),
}) : _shadowPaint = Paint()
..colorFilter =
ColorFilter.mode(color.withOpacity(opacity), BlendMode.srcATop);
..colorFilter = ColorFilter.mode(
color.withValues(alpha: opacity),
BlendMode.srcATop,
);
@override
void process(Picture pic, Canvas canvas) {

View File

@ -7,6 +7,10 @@ class PaletteEntry {
const PaletteEntry(this.color);
PaletteEntry withOpacity(double opacity) {
return PaletteEntry(color.withValues(alpha: opacity));
}
PaletteEntry withAlpha(int alpha) {
return PaletteEntry(color.withAlpha(alpha));
}

View File

@ -170,7 +170,7 @@ void main() {
final component = _MyComponent();
component.makeTransparent();
expect(component.paint.color.opacity, 0);
expect(component.paint.color.a, 0);
},
);
@ -182,7 +182,7 @@ void main() {
component.makeTransparent(paintId: _MyComponentKeys.background);
expect(
component.getPaint(_MyComponentKeys.background).color.opacity,
component.getPaint(_MyComponentKeys.background).color.a,
0,
);
},
@ -195,7 +195,7 @@ void main() {
component.makeTransparent();
component.makeOpaque();
expect(component.paint.color.opacity, 1);
expect(component.paint.color.a, 1);
},
);
@ -210,7 +210,7 @@ void main() {
component.makeOpaque(paintId: _MyComponentKeys.background);
expect(
component.getPaint(_MyComponentKeys.background).color.opacity,
component.getPaint(_MyComponentKeys.background).color.a,
1,
);
},
@ -222,7 +222,7 @@ void main() {
final component = _MyComponent();
component.setOpacity(0.2);
expect(component.paint.color.opacity, 0.2);
expect(component.paint.color.a, 0.2);
},
);
@ -234,7 +234,7 @@ void main() {
component.setOpacity(0.2, paintId: _MyComponentKeys.background);
expect(
component.getPaint(_MyComponentKeys.background).color.opacity,
component.getPaint(_MyComponentKeys.background).color.a,
0.2,
);
},

View File

@ -215,12 +215,12 @@ void main() {
game.update(1);
expect(component.getPaint('bluePaint').color.opacity, isZero);
expect(component.getPaint('bluePaint').color.a, isZero);
// RGB components shouldn't be affected after opacity effect.
expect(component.getPaint('bluePaint').color.blue, 255);
expect(component.getPaint('bluePaint').color.red, isZero);
expect(component.getPaint('bluePaint').color.green, isZero);
expect(component.getPaint('bluePaint').color.b, 1.0);
expect(component.getPaint('bluePaint').color.r, isZero);
expect(component.getPaint('bluePaint').color.g, isZero);
},
);
@ -242,10 +242,10 @@ void main() {
game.update(1);
// All paints should have the same opacity after the effect completes.
expect(component.getPaint().color.opacity, isZero);
expect(component.getPaint(_PaintTypes.paint1).color.opacity, isZero);
expect(component.getPaint(_PaintTypes.paint2).color.opacity, isZero);
expect(component.getPaint(_PaintTypes.paint3).color.opacity, isZero);
expect(component.getPaint().color.a, isZero);
expect(component.getPaint(_PaintTypes.paint1).color.a, isZero);
expect(component.getPaint(_PaintTypes.paint2).color.a, isZero);
expect(component.getPaint(_PaintTypes.paint3).color.a, isZero);
},
);
@ -263,17 +263,17 @@ void main() {
..color = BasicPalette.green
.paint()
.color
.withOpacity(redInitialOpacity),
.withValues(alpha: redInitialOpacity),
_PaintTypes.paint2: BasicPalette.green.paint()
..color = BasicPalette.green
.paint()
.color
.withOpacity(greenInitialOpacity),
.withValues(alpha: greenInitialOpacity),
_PaintTypes.paint3: BasicPalette.blue.paint()
..color = BasicPalette.blue
.paint()
.color
.withOpacity(blueInitialOpacity),
.withValues(alpha: blueInitialOpacity),
},
);
await game.ensureAdd(component);
@ -289,15 +289,15 @@ void main() {
game.update(1);
expectDouble(
component.getPaint(_PaintTypes.paint1).color.opacity,
component.getPaint(_PaintTypes.paint1).color.a,
redInitialOpacity * targetOpacity,
);
expectDouble(
component.getPaint(_PaintTypes.paint2).color.opacity,
component.getPaint(_PaintTypes.paint2).color.a,
greenInitialOpacity * targetOpacity,
);
expectDouble(
component.getPaint(_PaintTypes.paint3).color.opacity,
component.getPaint(_PaintTypes.paint3).color.a,
blueInitialOpacity * targetOpacity,
);
},
@ -317,17 +317,17 @@ void main() {
..color = BasicPalette.green
.paint()
.color
.withOpacity(redInitialOpacity),
.withValues(alpha: redInitialOpacity),
_PaintTypes.paint2: BasicPalette.green.paint()
..color = BasicPalette.green
.paint()
.color
.withOpacity(greenInitialOpacity),
.withValues(alpha: greenInitialOpacity),
_PaintTypes.paint3: BasicPalette.blue.paint()
..color = BasicPalette.blue
.paint()
.color
.withOpacity(blueInitialOpacity),
.withValues(alpha: blueInitialOpacity),
},
);
await game.ensureAdd(component);
@ -344,17 +344,17 @@ void main() {
game.update(1);
expectDouble(
component.getPaint(_PaintTypes.paint1).color.opacity,
component.getPaint(_PaintTypes.paint1).color.a,
targetOpacity,
);
expectDouble(
component.getPaint(_PaintTypes.paint2).color.opacity,
component.getPaint(_PaintTypes.paint2).color.a,
(greenInitialOpacity / redInitialOpacity) * targetOpacity,
);
// Opacity of this paint shouldn't be changed.
expectDouble(
component.getPaint(_PaintTypes.paint3).color.opacity,
component.getPaint(_PaintTypes.paint3).color.a,
blueInitialOpacity,
);
},

View File

@ -120,11 +120,12 @@ void main() {
}
int _colorBit(int index, Color color) {
return switch (index % 4) {
0 => color.red,
1 => color.green,
2 => color.blue,
3 => color.alpha,
final value = switch (index % 4) {
0 => color.r,
1 => color.g,
2 => color.b,
3 => color.a,
_ => throw UnimplementedError(),
};
return (255 * value).round();
}

View File

@ -46,9 +46,8 @@ void main() {
// valid inputs are : ccc, CCC, #ccc, #CCC, #c1c1c1, #C1C1C1, c1c1c1,
// C1C1C1
final color = ColorExtension.random();
final sixHexColor = color.red.toRadixString(16).padLeft(2, '0') +
color.green.toRadixString(16).padLeft(2, '0') +
color.blue.toRadixString(16).padLeft(2, '0');
final sixHexColor = '${_hex2(color.r)}${_hex2(color.g)}${_hex2(color.b)}';
// C1C1C1
final sixUpperCaseColor = sixHexColor.toUpperCase();
@ -81,9 +80,8 @@ void main() {
);
// Let's generate a new color from only 3 digits
final threeHexColor = color.red.toRadixString(16).padLeft(1, '0')[0] +
color.green.toRadixString(16).padLeft(1, '0')[0] +
color.blue.toRadixString(16).padLeft(1, '0')[0];
final threeHexColor =
'${_hex1(color.r)}${_hex1(color.g)}${_hex1(color.b)}';
final threeDigitsColor = ColorExtension.fromRGBHexString(threeHexColor);
// CCC
@ -127,10 +125,8 @@ void main() {
// valid inputs are : fccc, FCCC, #fccc, #FCCC, #ffc1c1c1, #FFC1C1C1,
// ffc1c1c1, FFC1C1C1
var color = ColorExtension.random(rng: r);
final sixHexColor = color.alpha.toRadixString(16).padLeft(2, '0') +
color.red.toRadixString(16).padLeft(2, '0') +
color.green.toRadixString(16).padLeft(2, '0') +
color.blue.toRadixString(16).padLeft(2, '0');
final sixHexColor = '${_hex2(color.a)}${_hex2(color.r)}'
'${_hex2(color.g)}${_hex2(color.b)}';
// FFC1C1C1
final sixUpperCaseColor = sixHexColor.toUpperCase();
@ -163,10 +159,8 @@ void main() {
);
// Let's generate a new color from only 3 digits
final threeHexColor = color.alpha.toRadixString(16).padLeft(1, '0')[0] +
color.red.toRadixString(16).padLeft(1, '0')[0] +
color.green.toRadixString(16).padLeft(1, '0')[0] +
color.blue.toRadixString(16).padLeft(1, '0')[0];
final threeHexColor = '${_hex1(color.a)}${_hex1(color.r)}'
'${_hex1(color.g)}${_hex1(color.b)}';
color = ColorExtension.fromARGBHexString(threeHexColor);
// FCCC
@ -213,10 +207,18 @@ void main() {
// As explained in the documentation
// object with the set alpha as [withAlpha]
expect(
paint.color.alpha,
color.alpha,
paint.color.a,
closeTo(color.a, 10e-6),
reason: 'alpha does not have the right value',
);
});
});
}
String _hex2(double v) {
return (255 * v).round().toRadixString(16).padLeft(2, '0');
}
String _hex1(double v) {
return (255 * v).round().toRadixString(16).padLeft(1, '0')[0];
}

View File

@ -1,10 +1,11 @@
import 'package:flame/components.dart';
import 'package:flame/palette.dart';
import 'package:flame_isolate_example/terrain/terrain.dart';
import 'package:flutter/material.dart';
class Grass extends PositionComponent with Terrain {
static final _color = Paint()..color = const Color(0xff567d46);
static final _debugColor = Paint()..color = Colors.black.withOpacity(0.5);
static final _debugColor = BasicPalette.black.withOpacity(0.5).paint();
late final _rect = size.toRect();
late final _rect2 = Rect.fromCenter(

View File

@ -16,7 +16,9 @@ class KawabungaSystem extends BaseSystem with UpdateSystem {
final textComponent = entity.get<TextComponent>()!;
final textRenderer = TextPaint(
style: textComponent.style.copyWith(
color: textComponent.style.color!.withOpacity(1 - timer.percentage),
color: textComponent.style.color!.withValues(
alpha: 1 - timer.percentage,
),
),
);