music_equalizer.dart 2.5 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586
  1. import 'dart:math' as math;
  2. import 'package:flutter/material.dart';
  3. import '../../../core/theme/app_colors.dart';
  4. /// A tiny decorative equalizer shown while media audio is active. It is not a
  5. /// real analyser; the bars are phased sine waves.
  6. class MusicEqualizer extends StatefulWidget {
  7. const MusicEqualizer({super.key, this.compact = false});
  8. final bool compact;
  9. @override
  10. State<MusicEqualizer> createState() => _MusicEqualizerState();
  11. }
  12. class _MusicEqualizerState extends State<MusicEqualizer>
  13. with SingleTickerProviderStateMixin {
  14. late final AnimationController _controller;
  15. @override
  16. void initState() {
  17. super.initState();
  18. _controller = AnimationController(
  19. vsync: this,
  20. duration: const Duration(milliseconds: 900),
  21. )..repeat();
  22. }
  23. @override
  24. void dispose() {
  25. _controller.dispose();
  26. super.dispose();
  27. }
  28. @override
  29. Widget build(BuildContext context) {
  30. final colors = context.colors;
  31. final width = widget.compact ? 24.0 : 30.0;
  32. final height = widget.compact ? 14.0 : 16.0;
  33. final minBar = widget.compact ? 3.0 : 3.5;
  34. final maxBar = widget.compact ? 7.0 : 8.0;
  35. const phases = <double>[0.0, 2.1, 4.2];
  36. return Semantics(
  37. label: 'Music playing',
  38. child: Container(
  39. width: width,
  40. height: height,
  41. padding: const EdgeInsets.symmetric(horizontal: 4, vertical: 2),
  42. decoration: BoxDecoration(
  43. color: colors.surface.withValues(alpha: 0.9),
  44. borderRadius: BorderRadius.circular(6),
  45. border: Border.all(color: colors.border, width: 0.6),
  46. ),
  47. child: AnimatedBuilder(
  48. animation: _controller,
  49. builder: (context, _) {
  50. final phase = _controller.value * 2 * math.pi;
  51. return Row(
  52. mainAxisAlignment: MainAxisAlignment.spaceEvenly,
  53. crossAxisAlignment: CrossAxisAlignment.end,
  54. children: [
  55. for (final offset in phases)
  56. SizedBox(
  57. width: 2.2,
  58. height:
  59. minBar +
  60. (maxBar - minBar) *
  61. ((math.sin(phase + offset) + 1) / 2),
  62. child: DecoratedBox(
  63. decoration: BoxDecoration(
  64. color: colors.accent,
  65. borderRadius: BorderRadius.circular(2),
  66. ),
  67. ),
  68. ),
  69. ],
  70. );
  71. },
  72. ),
  73. ),
  74. );
  75. }
  76. }