|
| 1 | +/* |
| 2 | + * // Software Name: OUDS Flutter |
| 3 | + * // SPDX-FileCopyrightText: Copyright (c) Orange SA |
| 4 | + * // SPDX-License-Identifier: MIT |
| 5 | + * // |
| 6 | + * // This software is distributed under the MIT license, |
| 7 | + * // the text of which is available at https://opensource.org/license/MIT/ |
| 8 | + * // or see the "LICENSE" file for more details. |
| 9 | + * // |
| 10 | + * // Software description: Flutter library of reusable graphical components |
| 11 | + * // |
| 12 | + */ |
| 13 | + |
| 14 | +/// @nodoc |
| 15 | +library; |
| 16 | + |
| 17 | +import 'package:flutter/material.dart'; |
| 18 | +import 'package:ouds_theme_contract/ouds_theme.dart'; |
| 19 | + |
| 20 | +/// Wraps [child] in a [Stack] and draws a 1-px circular border ring around |
| 21 | +/// the [OudsBadge] indicator. |
| 22 | +/// |
| 23 | +/// The ring is rendered via [CustomPaint] — the badge itself is never altered. |
| 24 | +/// Its position and radius adapt automatically to any icon size or |
| 25 | +/// accessibility text-scale factor. |
| 26 | +/// |
| 27 | +/// **Parameters** |
| 28 | +/// |
| 29 | +/// - [context] — resolves badge-size tokens and the border colour from the |
| 30 | +/// active OUDS theme. |
| 31 | +/// - [child] — the badge widget to wrap (typically an [OudsBadge]). |
| 32 | +/// - [hasCount] — `true` for a numeric count badge (medium, 16 dp), |
| 33 | +/// `false` for a plain dot badge (xsmall, 8 dp). |
| 34 | +/// |
| 35 | +/// **Example — dot badge (xsmall)** |
| 36 | +/// ```dart |
| 37 | +/// buildBadgeWithBorder( |
| 38 | +/// context: context, |
| 39 | +/// hasCount: false, |
| 40 | +/// child: myBadgeWidget, |
| 41 | +/// ); |
| 42 | +/// ``` |
| 43 | +/// |
| 44 | +/// **Example — count badge (medium)** |
| 45 | +/// ```dart |
| 46 | +/// buildBadgeWithBorder( |
| 47 | +/// context: context, |
| 48 | +/// hasCount: true, |
| 49 | +/// child: myBadgeWidget, |
| 50 | +/// ); |
| 51 | +/// ``` |
| 52 | +Widget buildBadgeWithBorder({ |
| 53 | + required BuildContext context, |
| 54 | + required Widget child, |
| 55 | + required bool hasCount, |
| 56 | +}) { |
| 57 | + final bar = OudsTheme.of(context).componentsTokens(context).bar; |
| 58 | + final badgeTokens = OudsTheme.of(context).componentsTokens(context).badge; |
| 59 | + |
| 60 | + final badgeRadius = |
| 61 | + MediaQuery.textScalerOf( |
| 62 | + context, |
| 63 | + ).scale(hasCount ? badgeTokens.sizeMedium : badgeTokens.sizeXsmall) / |
| 64 | + 2; |
| 65 | + |
| 66 | + return Stack( |
| 67 | + clipBehavior: Clip.none, |
| 68 | + children: [ |
| 69 | + child, |
| 70 | + Positioned.fill( |
| 71 | + child: IgnorePointer( |
| 72 | + child: CustomPaint( |
| 73 | + painter: _BadgeBorderPainter( |
| 74 | + badgeRadius: badgeRadius, |
| 75 | + hasCount: hasCount, |
| 76 | + borderColor: bar.colorBorderBadge, |
| 77 | + ), |
| 78 | + ), |
| 79 | + ), |
| 80 | + ), |
| 81 | + ], |
| 82 | + ); |
| 83 | +} |
| 84 | + |
| 85 | +/// Paints the circular border ring around the badge indicator. |
| 86 | +/// |
| 87 | +/// The ring centre is derived from Flutter Badge's layout algorithm |
| 88 | +/// ([_RenderBadge.performLayout]): |
| 89 | +/// |
| 90 | +/// - **Dot** (`!hasCount`): `centre = (width − r, r)` |
| 91 | +/// - **Count** (`hasCount`): `centre = (width − r + 4, 4)` |
| 92 | +/// where `4` is the LTR effective offset applied to labelled badges. |
| 93 | +/// |
| 94 | +/// The draw radius is set to `badgeRadius + strokeWidth / 2` so that the |
| 95 | +/// inner edge of the stroke touches the badge boundary with no gap. |
| 96 | +class _BadgeBorderPainter extends CustomPainter { |
| 97 | + const _BadgeBorderPainter({ |
| 98 | + required this.badgeRadius, |
| 99 | + required this.hasCount, |
| 100 | + required this.borderColor, |
| 101 | + }); |
| 102 | + |
| 103 | + final double badgeRadius; |
| 104 | + final bool hasCount; |
| 105 | + final Color borderColor; |
| 106 | + |
| 107 | + @override |
| 108 | + void paint(Canvas canvas, Size size) { |
| 109 | + final double cx = hasCount |
| 110 | + ? size.width - badgeRadius + 4 |
| 111 | + : size.width - badgeRadius; |
| 112 | + final double cy = hasCount ? 4.0 : badgeRadius; |
| 113 | + |
| 114 | + const double strokeWidth = 1.0; |
| 115 | + canvas.drawCircle( |
| 116 | + Offset(cx, cy), |
| 117 | + badgeRadius + strokeWidth / 2, |
| 118 | + Paint() |
| 119 | + ..color = borderColor |
| 120 | + ..style = PaintingStyle.stroke |
| 121 | + ..strokeWidth = strokeWidth, |
| 122 | + ); |
| 123 | + } |
| 124 | + |
| 125 | + @override |
| 126 | + bool shouldRepaint(covariant _BadgeBorderPainter old) => |
| 127 | + old.badgeRadius != badgeRadius || |
| 128 | + old.hasCount != hasCount || |
| 129 | + old.borderColor != borderColor; |
| 130 | +} |
0 commit comments