188 lines
7.0 KiB
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;
|
|
}
|
|
}
|