Rebrand as TPSchedule and refine clear liquid glass dock

This commit is contained in:
Codex
2026-10-13 22:08:20 +08:00
parent 50fcc70e8e
commit 204e945b2e
45 changed files with 1155 additions and 103 deletions
+30 -13
View File
@@ -126,11 +126,11 @@ class _GlassDockState extends State<GlassDock> with TickerProviderStateMixin {
colors: lens
? [
const Color(0xFFD8DBE0)
.withValues(alpha: 0.72 * (1 - pressure * 0.91)),
.withValues(alpha: 0.14 * (1 - pressure * 0.82)),
const Color(0xFFC3C7CE)
.withValues(alpha: 0.60 * (1 - pressure * 0.93)),
.withValues(alpha: 0.08 * (1 - pressure * 0.82)),
const Color(0xFFE5E7EB)
.withValues(alpha: 0.72 * (1 - pressure * 0.90)),
.withValues(alpha: 0.12 * (1 - pressure * 0.82)),
]
: [
Colors.white.withValues(alpha: 0.028),
@@ -145,7 +145,8 @@ class _GlassDockState extends State<GlassDock> with TickerProviderStateMixin {
return ClipRRect(
borderRadius: BorderRadius.circular(40),
child: GlassRefraction(
strength: lens ? 0.25 + pressure * 0.95 : 0.32,
strength: lens ? 0.70 + pressure * 0.70 : 0.55,
blurSigma: lens ? 1.6 - pressure * 0.6 : 2,
child: content,
),
);
@@ -177,8 +178,8 @@ class _GlassDockState extends State<GlassDock> with TickerProviderStateMixin {
borderRadius: BorderRadius.circular(40),
boxShadow: [
BoxShadow(
color: _blue.withValues(alpha: 0.035),
blurRadius: 12,
color: const Color(0xFF4C6078).withValues(alpha: 0.075),
blurRadius: 16,
offset: const Offset(0, 5),
),
],
@@ -288,15 +289,14 @@ class _GlassDockState extends State<GlassDock> with TickerProviderStateMixin {
);
}
/// Thin paired rims and directional glints suggest rounded optical thickness.
/// All strong highlights stay on the edge; the centre remains clear.
/// A diffuse silver bevel and thin directional glints show optical thickness.
/// The painted highlights stay on the edge so background colour passes through.
class _GlassRimPainter extends CustomPainter {
const _GlassRimPainter({required this.lens});
final bool lens;
static const _blue = Color(0xFF007AFF);
@override
void paint(Canvas canvas, Size size) {
final edge = lens ? const Color(0xFF7B838F) : _blue;
final edge = lens ? const Color(0xFF7B838F) : const Color(0xFFAFBECF);
final bounds = Offset.zero & size;
final outer = RRect.fromRectAndRadius(
bounds.deflate(0.7),
@@ -306,6 +306,23 @@ class _GlassRimPainter extends CustomPainter {
bounds.deflate(2.2),
Radius.circular(size.height / 2 - 1.5),
);
canvas.drawRRect(
outer,
Paint()
..style = PaintingStyle.stroke
..strokeWidth = lens ? 1.8 : 2.2
..maskFilter = const MaskFilter.blur(BlurStyle.normal, 0.9)
..shader = ui.Gradient.linear(
bounds.topLeft,
bounds.bottomRight,
[
Colors.white.withValues(alpha: 0.24),
edge.withValues(alpha: 0.04),
Colors.white.withValues(alpha: 0.14),
],
[0.0, 0.55, 1.0],
),
);
canvas.drawRRect(
outer,
Paint()
@@ -336,9 +353,9 @@ class _GlassRimPainter extends CustomPainter {
bounds.topLeft,
bounds.bottomRight,
[
Colors.white.withValues(alpha: 0.30),
Colors.white.withValues(alpha: 0.20),
edge.withValues(alpha: lens ? 0.13 : 0.07),
Colors.white.withValues(alpha: 0.40),
Colors.white.withValues(alpha: 0.28),
],
[0.0, 0.5, 1.0],
),
@@ -362,7 +379,7 @@ class _GlassRimPainter extends CustomPainter {
Offset(size.width, 0),
[
Colors.white.withValues(alpha: 0),
Colors.white.withValues(alpha: 0.65),
Colors.white.withValues(alpha: 0.50),
Colors.white.withValues(alpha: 0),
],
[0.0, 0.5, 1.0],
+43 -11
View File
@@ -8,9 +8,13 @@ class GlassRefraction extends StatefulWidget {
const GlassRefraction({
super.key,
required this.strength,
this.blurSigma = 2,
required this.child,
});
final double strength;
/// Optical softness in logical pixels, independent of display density.
final double blurSigma;
final Widget child;
@override
State<GlassRefraction> createState() => _GlassRefractionState();
@@ -18,6 +22,7 @@ class GlassRefraction extends StatefulWidget {
class _GlassRefractionState extends State<GlassRefraction> {
static Future<ui.FragmentProgram>? _program;
static ui.FragmentProgram? _loadedProgram;
ui.FragmentShader? _shader;
@override
void initState() {
@@ -27,12 +32,12 @@ class _GlassRefractionState extends State<GlassRefraction> {
Future<void> _load() async {
try {
final program = await (_program ??= ui.FragmentProgram.fromAsset(
'shaders/liquid_glass.frag',
));
final program = _loadedProgram ??= await (_program ??=
ui.FragmentProgram.fromAsset('shaders/liquid_glass.frag'));
if (mounted) setState(() => _shader = program.fragmentShader());
} catch (error) {
_program = null;
_loadedProgram = null;
debugPrint('Glass refraction unavailable: $error');
}
}
@@ -46,12 +51,17 @@ class _GlassRefractionState extends State<GlassRefraction> {
@override
Widget build(BuildContext context) => _shader == null
? BackdropFilter(
filter: ui.ImageFilter.blur(sigmaX: 0.6, sigmaY: 0.6),
filter: ui.ImageFilter.blur(
sigmaX: widget.blurSigma,
sigmaY: widget.blurSigma,
tileMode: TileMode.clamp,
),
child: widget.child,
)
: _LiveGlassFilter(
shader: _shader!,
strength: widget.strength,
blurSigma: widget.blurSigma,
pixelRatio: MediaQuery.devicePixelRatioOf(context),
child: widget.child,
);
@@ -61,29 +71,41 @@ class _LiveGlassFilter extends SingleChildRenderObjectWidget {
const _LiveGlassFilter({
required this.shader,
required this.strength,
required this.blurSigma,
required this.pixelRatio,
required super.child,
});
final ui.FragmentShader shader;
final double strength, pixelRatio;
final double strength, blurSigma, pixelRatio;
@override
_RenderLiveGlass createRenderObject(BuildContext context) =>
_RenderLiveGlass(shader, strength, pixelRatio);
_RenderLiveGlass(shader, strength, blurSigma, pixelRatio);
@override
void updateRenderObject(
BuildContext context,
_RenderLiveGlass renderObject,
) => renderObject.update(shader, strength, pixelRatio);
) => renderObject.update(shader, strength, blurSigma, pixelRatio);
}
class _RenderLiveGlass extends RenderProxyBox {
_RenderLiveGlass(this._shader, this._strength, this._pixelRatio);
_RenderLiveGlass(
this._shader,
this._strength,
this._blurSigma,
this._pixelRatio,
);
ui.FragmentShader _shader;
double _strength, _pixelRatio;
double _strength, _blurSigma, _pixelRatio;
final _layer = LayerHandle<BackdropFilterLayer>();
void update(ui.FragmentShader shader, double strength, double pixelRatio) {
void update(
ui.FragmentShader shader,
double strength,
double blurSigma,
double pixelRatio,
) {
_shader = shader;
_strength = strength;
_blurSigma = blurSigma;
_pixelRatio = pixelRatio;
markNeedsPaint();
}
@@ -103,7 +125,17 @@ class _RenderLiveGlass extends RenderProxyBox {
..setFloat(5, size.height * _pixelRatio)
..setFloat(6, _strength);
_layer.layer ??= BackdropFilterLayer();
_layer.layer!.filter = ui.ImageFilter.shader(_shader);
// Refract the original backdrop first, then diffuse it with the engine's
// separable Gaussian blur. Keeping the shader inside the composition also
// keeps its source texture coordinates independent of blur padding.
_layer.layer!.filter = ui.ImageFilter.compose(
outer: ui.ImageFilter.blur(
sigmaX: _blurSigma,
sigmaY: _blurSigma,
tileMode: TileMode.clamp,
),
inner: ui.ImageFilter.shader(_shader),
);
context.pushLayer(_layer.layer!, super.paint, offset);
}
+93
View File
@@ -0,0 +1,93 @@
import 'package:flutter/material.dart';
/// The original APP.svg gradient, expressed in its view-box coordinates.
const tpBrandGradient = LinearGradient(
begin: Alignment(2 * 324.09 / 2008.27 - 1, 2 * 1572.05 / 2009.62 - 1),
end: Alignment(2 * 1871.99 / 2008.27 - 1, 2 * 280.93 / 2009.62 - 1),
colors: [Color(0xFF3FD1FF), Color(0xFF38B3FF), Color(0xFF2A6CFF)],
stops: [0, 0.28, 1],
);
/// Vector rendering of assets/branding/APP.svg; no raster scaling or redesign.
class TpLogo extends StatelessWidget {
const TpLogo({super.key, this.size = 128, this.background = true});
final double size;
final bool background;
@override
Widget build(BuildContext context) => Semantics(
label: 'TPSchedule 标志',
image: true,
child: SizedBox.square(
dimension: size,
child: CustomPaint(painter: _TpLogoPainter(background: background)),
),
);
}
class _TpLogoPainter extends CustomPainter {
const _TpLogoPainter({required this.background});
final bool background;
@override
void paint(Canvas canvas, Size size) {
canvas.save();
canvas.scale(size.width / 2008.27, size.height / 2009.62);
if (background) {
const bounds = Rect.fromLTWH(0.5, 0.5, 2007.27, 2008.62);
canvas.drawRect(
bounds,
Paint()
..shader = tpBrandGradient.createShader(
const Rect.fromLTWH(0, 0, 2008.27, 2009.62),
),
);
canvas.drawRect(
bounds,
Paint()
..style = PaintingStyle.stroke
..strokeWidth = 1
..color = Colors.black,
);
}
final stroke = Paint()
..style = PaintingStyle.stroke
..strokeJoin = StrokeJoin.miter
..strokeMiterLimit = 10
..strokeCap = StrokeCap.butt;
canvas.drawRRect(
RRect.fromRectAndRadius(
const Rect.fromLTWH(405.1, 514.64, 1198.08, 1071.89),
const Radius.circular(20),
),
stroke
..color = const Color(0xFFFFD22A)
..strokeWidth = 217,
);
stroke
..color = Colors.white
..strokeWidth = 150;
for (final x in [704.07, 1249.91]) {
canvas.drawRRect(
RRect.fromRectAndRadius(
Rect.fromLTWH(x, 339.92, 64.51, 349.44),
const Radius.circular(27.65),
),
stroke,
);
}
canvas.drawPath(
Path()
..moveTo(729.01, 1004.81)
..relativeCubicTo(70.21, 112.08, 200.6, 174.7, 327.98, 152.69)
..relativeCubicTo(122.48, -18.51, 206.2, -123.19, 222.28, -152.69),
stroke..strokeWidth = 185,
);
canvas.restore();
}
@override
bool shouldRepaint(_TpLogoPainter oldDelegate) =>
background != oldDelegate.background;
}