feat: working connection, conn setting, and gear ratio setting for universal shifters
This commit is contained in:
207
lib/widgets/horizontal_scanning_animation.dart
Normal file
207
lib/widgets/horizontal_scanning_animation.dart
Normal file
@ -0,0 +1,207 @@
|
||||
import 'dart:math';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class HorizontalScanningAnimation extends StatefulWidget {
|
||||
final bool isScanning; // Add this to control the animation
|
||||
final Color waveColor;
|
||||
final double height;
|
||||
const HorizontalScanningAnimation({
|
||||
super.key,
|
||||
required this.isScanning, // Make it required
|
||||
this.waveColor = Colors.lightBlueAccent,
|
||||
this.height = 50.0,
|
||||
});
|
||||
@override
|
||||
_HorizontalScanningAnimationState createState() =>
|
||||
_HorizontalScanningAnimationState();
|
||||
}
|
||||
|
||||
class _HorizontalScanningAnimationState
|
||||
extends State<HorizontalScanningAnimation>
|
||||
with SingleTickerProviderStateMixin {
|
||||
late AnimationController _controller;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_controller = AnimationController(
|
||||
vsync: this,
|
||||
duration: const Duration(seconds: 2),
|
||||
);
|
||||
// Start repeating only if initially scanning
|
||||
if (widget.isScanning) {
|
||||
_controller.repeat();
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_controller.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
void didUpdateWidget(covariant HorizontalScanningAnimation oldWidget) {
|
||||
super.didUpdateWidget(oldWidget);
|
||||
if (widget.isScanning != oldWidget.isScanning) {
|
||||
if (widget.isScanning) {
|
||||
// Start or resume repeating
|
||||
if (!_controller.isAnimating) {
|
||||
// If stopped previously, reset before repeating for a clean start
|
||||
// Though, repeat() should handle restarting if stopped. Testing needed.
|
||||
// _controller.reset(); // Optional: uncomment if repeat doesn't restart smoothly
|
||||
_controller.repeat();
|
||||
}
|
||||
} else {
|
||||
// Stop repeating, but let the current animation cycle finish visually
|
||||
if (_controller.isAnimating) {
|
||||
_controller.stop(
|
||||
canceled:
|
||||
false); // Use canceled: false to let it finish the current tick
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
// Only build the painter if the controller is active or was recently stopped
|
||||
// This prevents drawing when completely idle. Check if value is changing or non-zero.
|
||||
// Or simply rely on the AnimatedBuilder which won't rebuild if controller is idle at 0.0
|
||||
return SizedBox(
|
||||
height: widget.height,
|
||||
width: double.infinity,
|
||||
child: AnimatedBuilder(
|
||||
animation: _controller,
|
||||
builder: (context, child) {
|
||||
return CustomPaint(
|
||||
painter: _HorizontalWavePainter(
|
||||
progress: _controller.value,
|
||||
waveColor: widget.waveColor,
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _HorizontalWavePainter extends CustomPainter {
|
||||
final double progress; // Animation value from 0.0 to 1.0
|
||||
final Color waveColor;
|
||||
final int waveCount = 2; // Number of waves visible at once
|
||||
final double waveAmplitude = 10.0; // Max height deviation of the wave
|
||||
|
||||
_HorizontalWavePainter({required this.progress, required this.waveColor});
|
||||
|
||||
@override
|
||||
void paint(Canvas canvas, Size size) {
|
||||
final paint = Paint()
|
||||
..color = waveColor.withValues(alpha: 0.6) // Semi-transparent waves
|
||||
..style = PaintingStyle.fill;
|
||||
|
||||
final centerY = size.height / 2;
|
||||
final width = size.width;
|
||||
|
||||
// Draw multiple waves propagating outwards
|
||||
for (int i = 0; i < waveCount; i++) {
|
||||
// Calculate the phase offset for each wave based on progress and index
|
||||
// This creates the effect of waves moving outwards
|
||||
double waveProgress = (progress + i / waveCount) % 1.0;
|
||||
|
||||
// Use an easing curve for smoother expansion
|
||||
double easedProgress = Curves.easeInOutSine.transform(waveProgress);
|
||||
|
||||
// Calculate the current horizontal position (expanding from center)
|
||||
// The wave starts narrow and expands outwards
|
||||
double currentWidth =
|
||||
width * easedProgress * 0.8; // Max 80% width expansion
|
||||
double startX = (width / 2) - (currentWidth / 2);
|
||||
double endX = (width / 2) + (currentWidth / 2);
|
||||
|
||||
// Calculate opacity based on progress (fade in and out)
|
||||
double opacity;
|
||||
if (waveProgress < 0.1) {
|
||||
opacity = waveProgress / 0.1; // Fade in
|
||||
} else if (waveProgress > 0.8) {
|
||||
opacity = (1.0 - waveProgress) / 0.2; // Fade out
|
||||
} else {
|
||||
opacity = 1.0;
|
||||
}
|
||||
opacity = max(0.0, opacity); // Clamp opacity
|
||||
|
||||
if (opacity <= 0.0 || currentWidth < 5)
|
||||
continue; // Skip drawing if invisible or too small
|
||||
|
||||
// Create the wave path
|
||||
final path = Path();
|
||||
path.moveTo(startX, centerY);
|
||||
|
||||
// Calculate points for the sine wave shape within the current width
|
||||
const int segments = 50; // Number of segments for the curve
|
||||
for (int j = 0; j <= segments; j++) {
|
||||
double segmentProgress = j / segments;
|
||||
double x = startX + currentWidth * segmentProgress;
|
||||
// Apply sine wave based on segment progress and overall animation progress
|
||||
// Multiply by (1 - easedProgress) to reduce amplitude as it expands
|
||||
double yOffset = waveAmplitude *
|
||||
sin(segmentProgress * 2 * pi + progress * 4 * pi) *
|
||||
(1 - easedProgress * 0.8) * // Reduce amplitude as it expands
|
||||
opacity; // Apply opacity effect to amplitude too
|
||||
path.lineTo(x, centerY + yOffset);
|
||||
}
|
||||
|
||||
// Draw a filled shape (like a lens flare or horizontal bar)
|
||||
// Adjust thickness based on easedProgress (thicker in the middle, thinner at ends)
|
||||
double thickness =
|
||||
waveAmplitude * (1 - easedProgress * 0.9) * opacity * 0.5;
|
||||
paint.color = waveColor.withValues(
|
||||
alpha: opacity * 0.5); // Update paint color with opacity
|
||||
|
||||
// Simplified: Draw a rectangle that pulses
|
||||
// More complex shapes could be drawn here using path.arcTo or path.quadraticBezierTo
|
||||
// For simplicity, let's use a slightly blurred rectangle effect
|
||||
|
||||
final rectPath = Path()
|
||||
..addRRect(RRect.fromRectAndRadius(
|
||||
Rect.fromCenter(
|
||||
center: Offset(width / 2, centerY),
|
||||
width: currentWidth,
|
||||
height: thickness * 2),
|
||||
Radius.circular(thickness)));
|
||||
|
||||
// Apply a blur effect
|
||||
final blurPaint = Paint()
|
||||
..color = waveColor.withValues(alpha: opacity * 0.4)
|
||||
..maskFilter = MaskFilter.blur(
|
||||
BlurStyle.normal, thickness * 1.5); // Blur based on thickness
|
||||
|
||||
// Draw the blurred shape
|
||||
canvas.drawPath(rectPath, blurPaint);
|
||||
|
||||
// Draw a slightly smaller, less opaque shape on top for highlight
|
||||
final highlightPaint = Paint()
|
||||
..color = waveColor.withValues(alpha: opacity * 0.7)
|
||||
..style = PaintingStyle.fill;
|
||||
final highlightRectPath = Path()
|
||||
..addRRect(RRect.fromRectAndRadius(
|
||||
Rect.fromCenter(
|
||||
center: Offset(width / 2, centerY),
|
||||
width: currentWidth * 0.95,
|
||||
height: thickness * 1.5),
|
||||
Radius.circular(thickness * 0.8)));
|
||||
canvas.drawPath(highlightRectPath, highlightPaint);
|
||||
|
||||
// Old Path drawing - keep if rectangle isn't desired
|
||||
// paint.color = waveColor.withValues(alpha: opacity * 0.5); // Apply opacity
|
||||
// canvas.drawPath(path, paint);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
bool shouldRepaint(covariant _HorizontalWavePainter oldDelegate) {
|
||||
// Repaint whenever the animation progress or color changes
|
||||
return oldDelegate.progress != progress ||
|
||||
oldDelegate.waveColor != waveColor;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user