abawo-bt-app/lib/widgets/scanning_animation.dart

188 lines
7.0 KiB
Dart

import 'dart:math';
import 'package:flutter/material.dart';
class ScanningWaveAnimation extends StatefulWidget {
final Animation<double>
animation; // For the wave effect (0.0 to 1.0 over 1.5s)
final double
progressValue; // For the CircularProgressIndicator (0.0 to 1.0 over scan duration)
final Color waveColor; // Color for the scanning wave
const ScanningWaveAnimation({
super.key,
required this.animation,
required this.progressValue,
this.waveColor = Colors.lightBlueAccent, // Default color using constant
});
@override
_ScanningWaveAnimationState createState() => _ScanningWaveAnimationState();
}
class _ScanningWaveAnimationState extends State<ScanningWaveAnimation> {
@override
Widget build(BuildContext context) {
// Stack to layer the painter, progress indicator, and text
return Stack(
alignment: Alignment.center,
children: [
// The wave animation painter rebuilds based on the wave animation
AnimatedBuilder(
animation: widget.animation,
builder: (context, child) {
return CustomPaint(
// Use full available size for the painter
size: Size.infinite,
painter: _WavePainter(
progress: widget.animation.value, // Pass wave progress
waveColor: widget.waveColor), // Pass wave color
);
},
),
// The progress indicator and text in the center
Column(
mainAxisSize: MainAxisSize.min, // Keep column compact
children: [
CircularProgressIndicator(
value: widget.progressValue, // Use the scan duration progress
backgroundColor:
Colors.white.withValues(alpha: 0.1), // Subtle background
),
const SizedBox(height: 16), // Consistent spacing
const Text(
'Scanning for devices...',
style: TextStyle(
fontSize: 16, color: Colors.white70), // Lighter text
),
], // Close children[] of Column
), // Close Column
], // Close children[] of Stack
); // Close Stack
}
}
// --- New Wave Painter Implementation ---
class _WavePainter extends CustomPainter {
final double
progress; // Animation value from 0.0 to 1.0, drives the single wave
final Color waveColor; // The color of the wave
final double startRadius = 50.0; // Start wave ~175px from center
final double waveThickness = 10.0; // Thickness of the wave
final double waveExpansion = 50.0; // Amount of thickness increase at the end
_WavePainter({required this.progress, required this.waveColor});
@override
void paint(Canvas canvas, Size size) {
final center = Offset(size.width / 2, size.height / 2);
// Max radius should reach significantly beyond the screen edge
final maxRadius = min(size.width, size.height) * 0.8; // Extend further
// Ensure endRadius is always larger than startRadius
final endRadius = max(startRadius + 1.0, maxRadius);
// Apply deceleration curve to the progress
final easedProgress = Curves.easeOut.transform(progress);
final realWaveThickness = waveThickness + waveExpansion * easedProgress;
// --- Opacity Calculation (Fade-in / Fade-out) ---
const double fadeInEnd = 0.20; // Faster fade-in (0% to 20% of animation)
const double fadeOutStart = 0.45; // Start fade-out earlier (45% to 100%)
final double fadeInOpacity =
(progress < fadeInEnd) ? progress / fadeInEnd : 1.0;
final double fadeOutProgress = (progress < fadeOutStart)
? 0.0 // Not fading out yet
: (progress - fadeOutStart) /
(1.0 - fadeOutStart); // Map fade-out phase to 0.0-1.0
final double fadeOutOpacity =
max(0.0, 1.0 - fadeOutProgress); // Linear fade-out
// Combine fade-in and fade-out using minimum
final opacity = max(0.0, min(fadeInOpacity, fadeOutOpacity));
// Skip drawing if fully faded out
if (opacity <= 0.001) {
// Use a small threshold to avoid floating point issues
return;
}
// --- Base Radius Calculation ---
// Calculate the base radius for this frame based on decelerated progress
final baseRadius = startRadius + (endRadius - startRadius) * easedProgress;
// Skip drawing if radius hasn't reached startRadius yet
// (Needed because easedProgress might be 0 at the very start)
if (baseRadius < startRadius) {
return;
}
// --- Path Generation with Deformation ---
final path = Path();
final int steps = 200; // Increase steps for smoother deformation
final double angleStep = (2 * pi) / steps;
// Noise parameters (tune these for desired 'waviness' and 'shimmer')
final double noiseMaxAmplitude =
realWaveThickness * 0.4; // Max radius deviation
final double noiseAmplitude = noiseMaxAmplitude *
min(easedProgress * 4.0,
1.0 - easedProgress * 0.8); // Amplitude decreases as it expands
final double noiseFreq1 = 6.0; // Controls number of 'bumps'
final double noiseFreq2 = 12.0; // Another layer of bumps
final double phaseShift = progress * pi * 6; // Controls 'shimmer' speed
for (int i = 0; i <= steps; i++) {
final double angle = i * angleStep;
// Calculate noise perturbation using layered sine waves
// Different frequencies and phase shifts create complex patterns
double perturbation = noiseAmplitude *
(0.6 * sin(noiseFreq1 * angle + phaseShift) +
0.4 *
sin(noiseFreq2 * angle -
phaseShift * 0.6) // Opposite phase shift adds complexity
);
final double currentRadius = max(
0.0, baseRadius + perturbation); // Ensure radius doesn't go negative
// Convert polar (angle, radius) to cartesian (x, y)
final double x = center.dx + currentRadius * cos(angle);
final double y = center.dy + currentRadius * sin(angle);
if (i == 0) {
path.moveTo(x, y); // Start the path
} else {
path.lineTo(x, y); // Draw line to next point
}
}
path.close(); // Connect the last point back to the first
// --- Painting the Path ---
// Use the provided wave color with calculated opacity
final paintColor = waveColor.withValues(alpha: opacity * 0.75);
final paint = Paint()
..color = paintColor
..style = PaintingStyle.stroke
..strokeWidth = realWaveThickness
..strokeCap =
StrokeCap.round // Soften line endings (though path is closed)
..strokeJoin = StrokeJoin.round // Soften corners in the deformation
// Apply blur for feathered/soft edges, sigma related to thickness
..maskFilter = MaskFilter.blur(BlurStyle.normal, realWaveThickness * 0.5);
// Draw the deformed, blurred path
canvas.drawPath(path, paint);
}
@override
bool shouldRepaint(covariant _WavePainter oldDelegate) {
// Repaint whenever the animation progress changes
return oldDelegate.progress != progress ||
oldDelegate.waveColor != waveColor;
}
}