mirror of
https://github.com/block/buzz.git
synced 2026-10-27 12:40:30 +00:00
## Summary - Open Activity items directly in their target thread while preserving Activity as the Back destination. - Fade in a muted target-message highlight after navigation settles, hold it for three seconds, then fade it away. ## Why Activity deep links hydrated the channel before opening the thread, which left the temporary channel route in the navigation stack. The target highlight also appeared before the route settled and remained indefinitely. ## Validation - `just mobile-check` - Full Flutter test suite (1,357 tests) - Signed Profile build installed and launched on a physical iPhone --------- Signed-off-by: kenny lopez <klopez4212@gmail.com> Signed-off-by: Fast Fizz <2df81cb51f05a9d5387ef24d7b9ecb8fcdfcd1c70ffabc67061c9596e1b5b1c4@buzz.block.builderlab.xyz> Signed-off-by: Princess Donut <b238ea756dee4d98afa5883fc7f1de61eeabe65bf700e3a5a5a80db5e42e2c2b@buzz.block.builderlab.xyz> Signed-off-by: Kenny Lopez <klopez4212@gmail.com> Co-authored-by: Fast Fizz <2df81cb51f05a9d5387ef24d7b9ecb8fcdfcd1c70ffabc67061c9596e1b5b1c4@buzz.block.builderlab.xyz> Co-authored-by: Princess Donut <b238ea756dee4d98afa5883fc7f1de61eeabe65bf700e3a5a5a80db5e42e2c2b@buzz.block.builderlab.xyz> Co-authored-by: Mongo <9cfd347903944d5b85aa6c93d2ab67381b978a92a31914bca69998968752a1d7@buzz.block.builderlab.xyz>
158 lines
4.4 KiB
Dart
158 lines
4.4 KiB
Dart
import 'dart:math' show min;
|
|
|
|
import 'package:flutter/material.dart';
|
|
|
|
/// The Buzz mark at a caller-controlled wing position.
|
|
///
|
|
/// The geometry matches the desktop loading bee. Callers drive the wings
|
|
/// themselves, which lets one painter serve both the tap-to-flutter mark
|
|
/// ([TappableFlappingBee]) and the pull-to-refresh indicator
|
|
/// ([BeeRefreshIndicator]).
|
|
class FlappingBee extends StatelessWidget {
|
|
/// The rendered width of the complete bee mark.
|
|
///
|
|
/// Height follows from the mark's 466:309 aspect ratio.
|
|
final double width;
|
|
|
|
/// The color used for the bee silhouette, wings, and pupils.
|
|
final Color color;
|
|
|
|
/// How far the wings are tucked toward the body, from 0 to 1.
|
|
///
|
|
/// 0 renders the wings fully spread; 1 renders them at their innermost
|
|
/// tuck. Callers animate this to flap the wings.
|
|
final double flapAmount;
|
|
|
|
/// How far the pupils have grown, from 0 to 1, or null for cutout eyes.
|
|
///
|
|
/// Only the pull-to-refresh treatment sets this. Leaving it null preserves
|
|
/// the mark's ordinary cutout eyes; a non-null value fills them in, with 1
|
|
/// drawing the pupils at full size.
|
|
final double? eyeProgress;
|
|
|
|
const FlappingBee({
|
|
required this.width,
|
|
required this.color,
|
|
required this.flapAmount,
|
|
this.eyeProgress,
|
|
super.key,
|
|
});
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return RepaintBoundary(
|
|
child: CustomPaint(
|
|
size: Size(width, width * 309 / 466),
|
|
painter: _FlappingBeePainter(
|
|
color: color,
|
|
flapAmount: flapAmount,
|
|
eyeProgress: eyeProgress,
|
|
),
|
|
),
|
|
);
|
|
}
|
|
}
|
|
|
|
class _FlappingBeePainter extends CustomPainter {
|
|
final Color color;
|
|
final double flapAmount;
|
|
final double? eyeProgress;
|
|
|
|
const _FlappingBeePainter({
|
|
required this.color,
|
|
required this.flapAmount,
|
|
this.eyeProgress,
|
|
});
|
|
|
|
@override
|
|
void paint(Canvas canvas, Size size) {
|
|
final scale = min(size.width / 466, size.height / 309);
|
|
final renderedWidth = 466 * scale;
|
|
final renderedHeight = 309 * scale;
|
|
|
|
canvas
|
|
..save()
|
|
..translate(
|
|
(size.width - renderedWidth) / 2,
|
|
(size.height - renderedHeight) / 2,
|
|
)
|
|
..scale(scale);
|
|
|
|
final wingRadiusX = 91.7 * (1 - (0.38 * flapAmount));
|
|
final wingTranslation = 30 * flapAmount;
|
|
final leftWing = Path()
|
|
..addOval(
|
|
Rect.fromCenter(
|
|
center: Offset(91.7 + wingTranslation, 154.5),
|
|
width: wingRadiusX * 2,
|
|
height: 183.4,
|
|
),
|
|
);
|
|
final rightWing = Path()
|
|
..addOval(
|
|
Rect.fromCenter(
|
|
center: Offset(374.3 - wingTranslation, 154.5),
|
|
width: wingRadiusX * 2,
|
|
height: 183.4,
|
|
),
|
|
);
|
|
final body = Path()
|
|
..addRRect(
|
|
RRect.fromRectAndRadius(
|
|
const Rect.fromLTWH(128, 0, 210, 309),
|
|
const Radius.circular(34),
|
|
),
|
|
);
|
|
final cutouts = Path()
|
|
..addOval(
|
|
Rect.fromCenter(
|
|
center: const Offset(193.3, 84.4),
|
|
width: 54,
|
|
height: 54,
|
|
),
|
|
)
|
|
..addOval(
|
|
Rect.fromCenter(center: const Offset(276, 84.4), width: 54, height: 54),
|
|
)
|
|
..addRRect(
|
|
RRect.fromRectAndRadius(
|
|
const Rect.fromLTWH(166.3, 157.2, 136.9, 38.3),
|
|
const Radius.circular(5),
|
|
),
|
|
)
|
|
..addRRect(
|
|
RRect.fromRectAndRadius(
|
|
const Rect.fromLTWH(166.9, 235.1, 136.2, 37.6),
|
|
const Radius.circular(5),
|
|
),
|
|
);
|
|
final wings = Path.combine(PathOperation.union, leftWing, rightWing);
|
|
final silhouette = Path.combine(PathOperation.union, wings, body);
|
|
final finishedMark = Path.combine(
|
|
PathOperation.difference,
|
|
silhouette,
|
|
cutouts,
|
|
);
|
|
|
|
canvas.drawPath(finishedMark, Paint()..color = color);
|
|
|
|
if (eyeProgress case final progress?) {
|
|
// The eye cutouts are 54px wide. A full pupil must reach their 27px
|
|
// radius so the emoji-eye overlay never exposes the background beneath.
|
|
final pupilRadius = 27 * progress.clamp(0.0, 1.0);
|
|
final pupilPaint = Paint()..color = color;
|
|
canvas
|
|
..drawCircle(const Offset(193.3, 84.4), pupilRadius, pupilPaint)
|
|
..drawCircle(const Offset(276, 84.4), pupilRadius, pupilPaint);
|
|
}
|
|
|
|
canvas.restore();
|
|
}
|
|
|
|
@override
|
|
bool shouldRepaint(_FlappingBeePainter oldDelegate) =>
|
|
color != oldDelegate.color ||
|
|
flapAmount != oldDelegate.flapAmount ||
|
|
eyeProgress != oldDelegate.eyeProgress;
|
|
}
|