Files
iiEsaywebUIapp/lib/core/services/animation_service.dart

298 lines
9.0 KiB
Dart
Raw Normal View History

2025-08-10 01:20:45 +05:30
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
2025-08-10 01:20:45 +05:30
import '../../shared/theme/theme_extensions.dart';
part 'animation_service.g.dart';
2025-08-10 01:20:45 +05:30
/// Service for managing animations with performance optimization and accessibility
class AnimationService {
/// Get optimized animation duration based on context and settings
static Duration getOptimizedDuration(
BuildContext context,
Duration defaultDuration, {
bool respectReducedMotion = true,
}) {
if (respectReducedMotion && MediaQuery.of(context).disableAnimations) {
return Duration.zero;
}
// Optimize for 60fps - keep animations under 300ms for snappy feel
final optimizedDuration = Duration(
milliseconds: (defaultDuration.inMilliseconds * 0.8).round().clamp(
100,
300,
),
);
return optimizedDuration;
}
/// Get optimized curve for smooth 60fps animations
static Curve getOptimizedCurve({Curve defaultCurve = Curves.easeInOut}) {
// Use curves that are optimized for mobile performance
final curveType = defaultCurve.runtimeType.toString();
// Replace performance-heavy curves with lighter alternatives
if (curveType.contains('Bounce')) {
return Curves.easeInOutQuart; // Replace heavy bounce with smooth curve
} else if (curveType.contains('Elastic')) {
return Curves.easeInOutBack; // Lighter alternative to elastic
} else if (defaultCurve == Curves.easeInOut) {
return Curves.easeInOutCubic; // Better performance than default
}
return defaultCurve;
}
/// Create performant fade transition
static Widget createOptimizedFadeTransition({
required Widget child,
required Animation<double> animation,
Duration? duration,
}) {
return FadeTransition(opacity: animation, child: child);
}
/// Create performant slide transition
static Widget createOptimizedSlideTransition({
required Widget child,
required Animation<Offset> animation,
Duration? duration,
}) {
return SlideTransition(position: animation, child: child);
}
/// Create performant scale transition
static Widget createOptimizedScaleTransition({
required Widget child,
required Animation<double> animation,
Duration? duration,
}) {
return ScaleTransition(scale: animation, child: child);
}
/// Create optimized page transition
static PageRouteBuilder createOptimizedPageRoute({
required Widget page,
Duration? transitionDuration,
PageTransitionType type = PageTransitionType.slide,
}) {
return PageRouteBuilder(
pageBuilder: (context, animation, secondaryAnimation) => page,
transitionDuration:
transitionDuration ?? const Duration(milliseconds: 250),
reverseTransitionDuration:
transitionDuration ?? const Duration(milliseconds: 200),
transitionsBuilder: (context, animation, secondaryAnimation, child) {
final optimizedCurve = getOptimizedCurve();
final curvedAnimation = CurvedAnimation(
parent: animation,
curve: optimizedCurve,
);
switch (type) {
case PageTransitionType.fade:
return FadeTransition(opacity: curvedAnimation, child: child);
case PageTransitionType.slide:
return SlideTransition(
position: Tween<Offset>(
begin: const Offset(1.0, 0.0),
end: Offset.zero,
).animate(curvedAnimation),
child: child,
);
case PageTransitionType.scale:
return ScaleTransition(
scale: Tween<double>(
begin: 0.8,
end: 1.0,
).animate(curvedAnimation),
child: FadeTransition(opacity: curvedAnimation, child: child),
);
}
},
);
}
/// Create staggered animation for lists
static Widget createStaggeredListAnimation({
required Widget child,
required int index,
Duration? delay,
Duration? duration,
}) {
return TweenAnimationBuilder<double>(
tween: Tween(begin: 0, end: 1),
duration: duration ?? const Duration(milliseconds: 200),
curve: getOptimizedCurve(),
builder: (context, value, child) {
return Transform.translate(
offset: Offset(0, 20 * (1 - value)),
child: Opacity(opacity: value, child: child),
);
},
child: child,
);
}
/// Create performant shimmer animation
static Widget createOptimizedShimmer({
required Widget child,
Duration? duration,
Color? baseColor,
Color? highlightColor,
}) {
return TweenAnimationBuilder<double>(
tween: Tween(begin: 0, end: 1),
duration: duration ?? const Duration(milliseconds: 1500),
curve: Curves.linear,
builder: (context, value, child) {
return ShaderMask(
shaderCallback: (bounds) {
return LinearGradient(
begin: Alignment.centerLeft,
end: Alignment.centerRight,
colors: [
baseColor ?? context.conduitTheme.shimmerBase,
highlightColor ?? context.conduitTheme.shimmerHighlight,
baseColor ?? context.conduitTheme.shimmerBase,
],
stops: [0.0, value, 1.0],
).createShader(bounds);
},
child: child,
);
},
child: child,
);
}
/// Create optimized rotation animation
static Widget createOptimizedRotation({
required Widget child,
required Animation<double> animation,
double turns = 1.0,
}) {
return RotationTransition(
turns: Tween<double>(begin: 0, end: turns).animate(animation),
child: child,
);
}
/// Check if device can handle complex animations
static bool canHandleComplexAnimations(BuildContext context) {
// Simple heuristic based on screen density and platform
final devicePixelRatio = MediaQuery.of(context).devicePixelRatio;
final screenSize = MediaQuery.of(context).size;
final totalPixels = screenSize.width * screenSize.height * devicePixelRatio;
// If total pixels exceed 4M, assume it's a high-end device
return totalPixels > 4000000;
}
/// Create adaptive animation based on device capability
static Widget createAdaptiveAnimation({
required BuildContext context,
required Widget child,
required Widget Function(Widget) complexAnimation,
required Widget Function(Widget) simpleAnimation,
}) {
if (canHandleComplexAnimations(context) &&
!MediaQuery.of(context).disableAnimations) {
return complexAnimation(child);
} else {
return simpleAnimation(child);
}
}
}
/// Enum for page transition types
enum PageTransitionType { fade, slide, scale }
/// Provider for reduced motion preference
@riverpod
class ReducedMotion extends _$ReducedMotion {
@override
bool build() => false;
void set(bool value) => state = value;
}
2025-08-10 01:20:45 +05:30
/// Provider for animation performance settings
2025-09-21 22:31:44 +05:30
final animationPerformanceProvider =
NotifierProvider<AnimationPerformanceNotifier, AnimationPerformance>(
AnimationPerformanceNotifier.new,
);
class AnimationPerformanceNotifier extends Notifier<AnimationPerformance> {
@override
AnimationPerformance build() => AnimationPerformance.adaptive;
void set(AnimationPerformance performance) => state = performance;
}
2025-08-10 01:20:45 +05:30
/// Animation performance levels
enum AnimationPerformance {
high, // All animations enabled
adaptive, // Adaptive based on device
reduced, // Simplified animations
minimal, // Essential animations only
}
/// Provider for managing animation settings
final animationSettingsProvider =
2025-09-21 22:31:44 +05:30
NotifierProvider<AnimationSettingsNotifier, AnimationSettings>(
AnimationSettingsNotifier.new,
2025-08-10 01:20:45 +05:30
);
class AnimationSettings {
final bool reduceMotion;
final AnimationPerformance performance;
final double animationSpeed;
const AnimationSettings({
this.reduceMotion = false,
this.performance = AnimationPerformance.adaptive,
this.animationSpeed = 1.0,
});
AnimationSettings copyWith({
bool? reduceMotion,
AnimationPerformance? performance,
double? animationSpeed,
}) {
return AnimationSettings(
reduceMotion: reduceMotion ?? this.reduceMotion,
performance: performance ?? this.performance,
animationSpeed: animationSpeed ?? this.animationSpeed,
);
}
}
2025-09-21 22:31:44 +05:30
class AnimationSettingsNotifier extends Notifier<AnimationSettings> {
@override
AnimationSettings build() => const AnimationSettings();
2025-08-10 01:20:45 +05:30
void setReduceMotion(bool reduce) {
state = state.copyWith(reduceMotion: reduce);
}
void setPerformance(AnimationPerformance performance) {
state = state.copyWith(performance: performance);
}
void setAnimationSpeed(double speed) {
state = state.copyWith(animationSpeed: speed.clamp(0.5, 2.0));
}
Duration adjustDuration(Duration baseDuration) {
if (state.reduceMotion) return Duration.zero;
final adjustedMs = (baseDuration.inMilliseconds / state.animationSpeed)
.round();
return Duration(milliseconds: adjustedMs);
}
}