OnboardingPozoAppv1/flutter_app/lib/route_transitions.dart

305 lines
8.1 KiB
Dart

import 'dart:async';
import 'package:flutter/material.dart';
class FadeSlidePageRoute<T> extends PageRouteBuilder<T> {
final Widget child;
FadeSlidePageRoute({required this.child})
: super(
pageBuilder: (context, animation, secondaryAnimation) => child,
transitionsBuilder: (context, animation, secondaryAnimation, child) {
final fadeAnimation = CurvedAnimation(
parent: animation,
curve: Curves.easeOut,
);
final slideAnimation = Tween<Offset>(
begin: const Offset(0.0, 0.05),
end: Offset.zero,
).animate(
CurvedAnimation(
parent: animation,
curve: Curves.easeOutCubic,
),
);
return FadeTransition(
opacity: fadeAnimation,
child: SlideTransition(
position: slideAnimation,
child: child,
),
);
},
transitionDuration: const Duration(milliseconds: 300),
reverseTransitionDuration: const Duration(milliseconds: 200),
);
}
void showTopAlert(
BuildContext context,
String message, {
bool isError = false,
IconData? icon,
}) {
final overlayState = Overlay.of(context);
late OverlayEntry overlayEntry;
overlayEntry = OverlayEntry(
builder: (context) => _TopAlertWidget(
message: message,
isError: isError,
icon: icon,
onDismiss: () {
overlayEntry.remove();
},
),
);
overlayState.insert(overlayEntry);
}
class _TopAlertWidget extends StatefulWidget {
final String message;
final bool isError;
final IconData? icon;
final VoidCallback onDismiss;
const _TopAlertWidget({
required this.message,
required this.isError,
this.icon,
required this.onDismiss,
});
@override
State<_TopAlertWidget> createState() => _TopAlertWidgetState();
}
class _TopAlertWidgetState extends State<_TopAlertWidget> with SingleTickerProviderStateMixin {
late AnimationController _controller;
late Animation<double> _slideAnimation;
late Animation<double> _opacityAnimation;
Timer? _dismissTimer;
@override
void initState() {
super.initState();
_controller = AnimationController(
vsync: this,
duration: const Duration(milliseconds: 400),
);
_slideAnimation = CurvedAnimation(
parent: _controller,
curve: Curves.easeOutBack,
);
_opacityAnimation = CurvedAnimation(
parent: _controller,
curve: Curves.easeIn,
);
_controller.forward();
_dismissTimer = Timer(const Duration(milliseconds: 2800), () {
if (mounted) {
_controller.reverse().then((_) {
widget.onDismiss();
});
}
});
}
@override
void dispose() {
_dismissTimer?.cancel();
_controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
final mediaQuery = MediaQuery.of(context);
final topPadding = mediaQuery.padding.top + 16.0;
IconData alertIcon = widget.icon ??
(widget.isError ? Icons.error_outline : Icons.check_circle_outline);
Color backgroundColor = widget.isError ? const Color(0xFFBA1A20) : const Color(0xFF2E7D32);
return AnimatedBuilder(
animation: _controller,
builder: (context, child) {
return Positioned(
top: topPadding * _slideAnimation.value - 80.0 * (1.0 - _slideAnimation.value),
left: 16.0,
right: 16.0,
child: Opacity(
opacity: _opacityAnimation.value,
child: Material(
color: Colors.transparent,
child: Align(
alignment: Alignment.topCenter,
child: ConstrainedBox(
constraints: const BoxConstraints(maxWidth: 450),
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 12.0),
decoration: BoxDecoration(
color: backgroundColor,
borderRadius: BorderRadius.circular(20),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.12),
blurRadius: 16,
offset: const Offset(0, 6),
),
],
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Icon(
alertIcon,
color: Colors.white,
size: 20,
),
const SizedBox(width: 12),
Expanded(
child: Text(
widget.message,
style: const TextStyle(
color: Colors.white,
fontSize: 14,
fontWeight: FontWeight.w600,
),
),
),
],
),
),
),
),
),
),
);
},
);
}
}
class SlideFadeEntrance extends StatefulWidget {
final Widget child;
final Duration delay;
final Duration duration;
const SlideFadeEntrance({
super.key,
required this.child,
this.delay = Duration.zero,
this.duration = const Duration(milliseconds: 500),
});
@override
State<SlideFadeEntrance> createState() => _SlideFadeEntranceState();
}
class _SlideFadeEntranceState extends State<SlideFadeEntrance> with SingleTickerProviderStateMixin {
late AnimationController _controller;
late Animation<double> _opacityAnimation;
late Animation<double> _slideAnimation;
Timer? _timer;
@override
void initState() {
super.initState();
_controller = AnimationController(
vsync: this,
duration: widget.duration,
);
_opacityAnimation = Tween<double>(begin: 0.0, end: 1.0).animate(
CurvedAnimation(parent: _controller, curve: Curves.easeIn),
);
_slideAnimation = Tween<double>(begin: 12.0, end: 0.0).animate(
CurvedAnimation(parent: _controller, curve: Curves.easeOutCubic),
);
if (widget.delay == Duration.zero) {
_controller.forward();
} else {
_timer = Timer(widget.delay, () {
if (mounted) {
_controller.forward();
}
});
}
}
@override
void dispose() {
_timer?.cancel();
_controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return AnimatedBuilder(
animation: _controller,
builder: (context, child) {
return Opacity(
opacity: _opacityAnimation.value,
child: Transform.translate(
offset: Offset(0.0, _slideAnimation.value),
child: child,
),
);
},
child: widget.child,
);
}
}
class InteractiveScale extends StatefulWidget {
final Widget child;
final VoidCallback? onTap;
final double hoverScale;
final double tapScale;
const InteractiveScale({
super.key,
required this.child,
this.onTap,
this.hoverScale = 1.03,
this.tapScale = 0.96,
});
@override
State<InteractiveScale> createState() => _InteractiveScaleState();
}
class _InteractiveScaleState extends State<InteractiveScale> {
double _scale = 1.0;
@override
Widget build(BuildContext context) {
return MouseRegion(
onEnter: (_) => setState(() => _scale = widget.hoverScale),
onExit: (_) => setState(() => _scale = 1.0),
child: AnimatedScale(
scale: _scale,
duration: const Duration(milliseconds: 120),
child: GestureDetector(
behavior: HitTestBehavior.opaque,
onTapDown: (_) => setState(() => _scale = widget.tapScale),
onTapUp: (_) => setState(() => _scale = widget.hoverScale),
onTapCancel: () => setState(() => _scale = 1.0),
onTap: widget.onTap,
child: widget.child,
),
),
);
}
}