// The status bar's drawn icons, Android-like: a battery whose fill shows level and // colour, WiFi and cellular signal in four steps, and the traffic arrow pair. Drawn // rather than taken from the icon font, which has no four-step cellular icon and no // separately coloured battery fill. import 'dart:math' as math; import 'package:flutter/material.dart'; /// A horizontal battery: [outline] for the case, the inside filled to [level] /// (0–1, null unknown) in [fill]. class BatteryIcon extends StatelessWidget { const BatteryIcon({ super.key, required this.level, required this.fill, required this.outline, }); final double? level; final Color fill; final Color outline; @override Widget build(BuildContext context) => CustomPaint( size: const Size(24, 12), painter: _Battery(level, fill, outline), ); } class _Battery extends CustomPainter { _Battery(this.level, this.fill, this.outline); final double? level; final Color fill; final Color outline; @override void paint(Canvas c, Size s) { const nub = 2.0; final body = Rect.fromLTWH(0.75, 0.75, s.width - nub - 1.5, s.height - 1.5); c.drawRRect( RRect.fromRectAndRadius(body, const Radius.circular(2.5)), Paint() ..style = PaintingStyle.stroke ..strokeWidth = 1.5 ..color = outline, ); c.drawRRect( RRect.fromRectAndRadius( Rect.fromLTWH(s.width - nub, s.height * 0.3, nub, s.height * 0.4), const Radius.circular(1), ), Paint()..color = outline, ); final l = level; if (l == null) return; final inner = body.deflate(2); c.drawRRect( RRect.fromRectAndRadius( Rect.fromLTWH( inner.left, inner.top, inner.width * l.clamp(0, 1), inner.height, ), const Radius.circular(1), ), Paint()..color = fill, ); } @override bool shouldRepaint(_Battery o) => o.level != level || o.fill != fill || o.outline != outline; } /// Signal in [bars] of four (null: off, drawn crossed), WiFi's fan or cellular's /// triangle; the unlit part faint. class SignalIcon extends StatelessWidget { const SignalIcon.wifi({super.key, required this.bars, required this.color}) : wifi = true; const SignalIcon.cellular({ super.key, required this.bars, required this.color, }) : wifi = false; final int? bars; final Color color; final bool wifi; @override Widget build(BuildContext context) => CustomPaint( size: const Size(18, 16), painter: _Signal(bars, color, wifi), ); } class _Signal extends CustomPainter { _Signal(this.bars, this.color, this.wifi); final int? bars; final Color color; final bool wifi; @override void paint(Canvas c, Size s) { final faint = Paint()..color = color.withValues(alpha: 0.3); final lit = Paint()..color = color; final n = bars ?? 0; if (wifi) { // A quarter-circle fan standing on its point; lit from the point outwards. final apex = Offset(s.width / 2, s.height); Path fan(double r) => Path() ..moveTo(apex.dx, apex.dy) ..arcTo( Rect.fromCircle(center: apex, radius: r), -3 * math.pi / 4, math.pi / 2, false, ) ..close(); final full = s.height; c.drawPath(fan(full), faint); if (n > 0) c.drawPath(fan(full * (0.25 + 0.75 * n / 4)), lit); } else { // A right triangle rising to the right; lit from the left. final tri = Path() ..moveTo(0, s.height) ..lineTo(s.width, s.height) ..lineTo(s.width, 0) ..close(); c.drawPath(tri, faint); if (n > 0) { c.save(); c.clipRect( Rect.fromLTWH(0, 0, s.width * (0.2 + 0.8 * n / 4), s.height), ); c.drawPath(tri, lit); c.restore(); } } if (bars == null) { // Off: a stroke through it. c.drawLine( Offset(s.width * 0.15, s.height * 0.15), Offset(s.width * 0.85, s.height * 0.95), Paint() ..color = color ..strokeWidth = 1.5, ); } } @override bool shouldRepaint(_Signal o) => o.bars != bars || o.color != color || o.wifi != wifi; } /// Android's traffic pair beside a signal icon: up lit while sending, down while /// receiving. class TrafficArrows extends StatelessWidget { const TrafficArrows({ super.key, required this.up, required this.down, required this.color, }); final bool up; final bool down; final Color color; @override Widget build(BuildContext context) => CustomPaint(size: const Size(6, 14), painter: _Arrows(up, down, color)); } class _Arrows extends CustomPainter { _Arrows(this.up, this.down, this.color); final bool up; final bool down; final Color color; @override void paint(Canvas c, Size s) { Paint p(bool on) => Paint()..color = on ? color : color.withValues(alpha: 0.25); final h = s.height / 2 - 1; c.drawPath( Path() ..moveTo(0, h) ..lineTo(s.width, h) ..lineTo(s.width / 2, 0) ..close(), p(up), ); c.drawPath( Path() ..moveTo(0, s.height - h) ..lineTo(s.width, s.height - h) ..lineTo(s.width / 2, s.height) ..close(), p(down), ); } @override bool shouldRepaint(_Arrows o) => o.up != up || o.down != down || o.color != color; }