259 lines
7.2 KiB
Dart
259 lines
7.2 KiB
Dart
import 'dart:async';
|
|
import 'package:flutter/material.dart';
|
|
import 'package:lba/res/colors.dart';
|
|
|
|
class HuntTimerWidget extends StatefulWidget {
|
|
final Duration timeRemaining;
|
|
final bool isActive;
|
|
final bool showMinutesSeconds;
|
|
final TextStyle? textStyle;
|
|
final double? fontSize;
|
|
|
|
const HuntTimerWidget({
|
|
super.key,
|
|
required this.timeRemaining,
|
|
this.isActive = false,
|
|
this.showMinutesSeconds = false,
|
|
this.textStyle,
|
|
this.fontSize,
|
|
});
|
|
|
|
@override
|
|
State<HuntTimerWidget> createState() => _HuntTimerWidgetState();
|
|
}
|
|
|
|
class _HuntTimerWidgetState extends State<HuntTimerWidget>
|
|
with TickerProviderStateMixin {
|
|
Timer? _timer;
|
|
Duration _currentTime = Duration.zero;
|
|
late AnimationController _pulseController;
|
|
late Animation<double> _pulseAnimation;
|
|
|
|
@override
|
|
void initState() {
|
|
super.initState();
|
|
_currentTime = widget.timeRemaining;
|
|
_pulseController = AnimationController(
|
|
duration: const Duration(seconds: 1),
|
|
vsync: this,
|
|
);
|
|
_pulseAnimation = Tween<double>(
|
|
begin: 1.0,
|
|
end: 1.1,
|
|
).animate(CurvedAnimation(
|
|
parent: _pulseController,
|
|
curve: Curves.easeInOut,
|
|
));
|
|
|
|
if (widget.isActive) {
|
|
_startTimer();
|
|
}
|
|
}
|
|
|
|
@override
|
|
void didUpdateWidget(HuntTimerWidget oldWidget) {
|
|
super.didUpdateWidget(oldWidget);
|
|
|
|
if (widget.timeRemaining != oldWidget.timeRemaining) {
|
|
_currentTime = widget.timeRemaining;
|
|
}
|
|
|
|
if (widget.isActive != oldWidget.isActive) {
|
|
if (widget.isActive) {
|
|
_startTimer();
|
|
} else {
|
|
_stopTimer();
|
|
}
|
|
}
|
|
}
|
|
|
|
void _startTimer() {
|
|
_timer?.cancel();
|
|
_timer = Timer.periodic(const Duration(seconds: 1), (timer) {
|
|
if (mounted) {
|
|
setState(() {
|
|
if (_currentTime.inSeconds > 0) {
|
|
_currentTime = Duration(seconds: _currentTime.inSeconds - 1);
|
|
|
|
// Pulse animation when time is running low
|
|
if (_currentTime.inMinutes < 5) {
|
|
_pulseController.forward().then((_) {
|
|
_pulseController.reverse();
|
|
});
|
|
}
|
|
} else {
|
|
_stopTimer();
|
|
}
|
|
});
|
|
}
|
|
});
|
|
}
|
|
|
|
void _stopTimer() {
|
|
_timer?.cancel();
|
|
_timer = null;
|
|
}
|
|
|
|
@override
|
|
void dispose() {
|
|
_timer?.cancel();
|
|
_pulseController.dispose();
|
|
super.dispose();
|
|
}
|
|
|
|
String _formatTime() {
|
|
if (widget.showMinutesSeconds) {
|
|
final minutes = _currentTime.inMinutes;
|
|
final seconds = _currentTime.inSeconds % 60;
|
|
return '${minutes.toString().padLeft(2, '0')}:${seconds.toString().padLeft(2, '0')}';
|
|
} else {
|
|
final hours = _currentTime.inHours;
|
|
final minutes = _currentTime.inMinutes % 60;
|
|
final seconds = _currentTime.inSeconds % 60;
|
|
return '${hours.toString().padLeft(2, '0')}:${minutes.toString().padLeft(2, '0')}:${seconds.toString().padLeft(2, '0')}';
|
|
}
|
|
}
|
|
|
|
Color _getTimerColor() {
|
|
if (_currentTime.inMinutes < 2) {
|
|
return Colors.red;
|
|
} else if (_currentTime.inMinutes < 5) {
|
|
return Colors.orange;
|
|
} else if (_currentTime.inHours < 2) {
|
|
return AppColors.offerTimer;
|
|
} else {
|
|
return AppColors.confirmButton;
|
|
}
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
final timerColor = _getTimerColor();
|
|
final isExpired = _currentTime.inSeconds <= 0;
|
|
|
|
return AnimatedBuilder(
|
|
animation: _pulseAnimation,
|
|
builder: (context, child) {
|
|
return Transform.scale(
|
|
scale: _currentTime.inMinutes < 5 && widget.isActive ? _pulseAnimation.value : 1.0,
|
|
child: Container(
|
|
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 6),
|
|
decoration: BoxDecoration(
|
|
color: timerColor.withOpacity(0.15),
|
|
borderRadius: BorderRadius.circular(12),
|
|
border: Border.all(
|
|
color: timerColor.withOpacity(0.4),
|
|
width: 1,
|
|
),
|
|
boxShadow: _currentTime.inMinutes < 5 && widget.isActive
|
|
? [
|
|
BoxShadow(
|
|
color: timerColor.withOpacity(0.3),
|
|
blurRadius: 8,
|
|
spreadRadius: 1,
|
|
),
|
|
]
|
|
: null,
|
|
),
|
|
child: Row(
|
|
mainAxisSize: MainAxisSize.min,
|
|
children: [
|
|
Icon(
|
|
isExpired
|
|
? Icons.timer_off_outlined
|
|
: _currentTime.inMinutes < 2
|
|
? Icons.timer_outlined
|
|
: Icons.timer_rounded,
|
|
color: timerColor,
|
|
size: widget.fontSize != null ? widget.fontSize! + 2 : 14,
|
|
),
|
|
const SizedBox(width: 4),
|
|
Text(
|
|
isExpired ? 'Expired' : _formatTime(),
|
|
style: widget.textStyle?.copyWith(
|
|
color: timerColor,
|
|
fontWeight: FontWeight.bold,
|
|
fontFamily: 'monospace',
|
|
) ??
|
|
TextStyle(
|
|
color: timerColor,
|
|
fontWeight: FontWeight.bold,
|
|
fontSize: widget.fontSize ?? 10,
|
|
fontFamily: 'monospace',
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
},
|
|
);
|
|
}
|
|
}
|
|
|
|
class CountdownProgressWidget extends StatefulWidget {
|
|
final Duration timeRemaining;
|
|
final Duration totalTime;
|
|
|
|
const CountdownProgressWidget({
|
|
super.key,
|
|
required this.timeRemaining,
|
|
this.totalTime = const Duration(hours: 12),
|
|
});
|
|
|
|
@override
|
|
State<CountdownProgressWidget> createState() => _CountdownProgressWidgetState();
|
|
}
|
|
|
|
class _CountdownProgressWidgetState extends State<CountdownProgressWidget>
|
|
with SingleTickerProviderStateMixin {
|
|
late AnimationController _animationController;
|
|
|
|
@override
|
|
void initState() {
|
|
super.initState();
|
|
_animationController = AnimationController(
|
|
duration: const Duration(milliseconds: 500),
|
|
vsync: this,
|
|
);
|
|
_animationController.forward();
|
|
}
|
|
|
|
@override
|
|
void dispose() {
|
|
_animationController.dispose();
|
|
super.dispose();
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
final progress = widget.timeRemaining.inSeconds / widget.totalTime.inSeconds;
|
|
final clampedProgress = progress.clamp(0.0, 1.0);
|
|
|
|
return AnimatedBuilder(
|
|
animation: _animationController,
|
|
builder: (context, child) {
|
|
return Container(
|
|
height: 8,
|
|
decoration: BoxDecoration(
|
|
color: AppColors.divider.withOpacity(0.3),
|
|
borderRadius: BorderRadius.circular(4),
|
|
),
|
|
child: FractionallySizedBox(
|
|
alignment: Alignment.centerLeft,
|
|
widthFactor: clampedProgress * _animationController.value,
|
|
child: Container(
|
|
decoration: BoxDecoration(
|
|
color: clampedProgress > 0.2
|
|
? AppColors.confirmButton
|
|
: AppColors.offerTimer,
|
|
borderRadius: BorderRadius.circular(4),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
},
|
|
);
|
|
}
|
|
}
|