Major UI/UX and architecture improvements
- Implemented new navigation system with NavigationProvider and route management - Added adaptive theme system with ThemeProvider for better theme handling - Introduced glassmorphism design elements (app bars, scaffolds, cards) - Added advanced animations (spring animations, page transitions, staggered lists) - Implemented performance optimizations (memory manager, lazy loading) - Refactored Analysis screen into modular components - Added floating navigation bar with haptic feedback - Improved subscription cards with swipe actions - Enhanced skeleton loading with better animations - Added cached network image support - Improved overall app architecture and code organization 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
311
lib/widgets/animated_page_transitions.dart
Normal file
311
lib/widgets/animated_page_transitions.dart
Normal file
@@ -0,0 +1,311 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'dart:math' as math;
|
||||
|
||||
/// 슬라이드 + 페이드 전환
|
||||
class SlidePageRoute<T> extends PageRouteBuilder<T> {
|
||||
final Widget page;
|
||||
final AxisDirection direction;
|
||||
|
||||
SlidePageRoute({
|
||||
required this.page,
|
||||
this.direction = AxisDirection.right,
|
||||
}) : super(
|
||||
pageBuilder: (context, animation, secondaryAnimation) => page,
|
||||
transitionDuration: const Duration(milliseconds: 300),
|
||||
reverseTransitionDuration: const Duration(milliseconds: 300),
|
||||
transitionsBuilder: (context, animation, secondaryAnimation, child) {
|
||||
Offset begin;
|
||||
switch (direction) {
|
||||
case AxisDirection.right:
|
||||
begin = const Offset(-1.0, 0.0);
|
||||
break;
|
||||
case AxisDirection.left:
|
||||
begin = const Offset(1.0, 0.0);
|
||||
break;
|
||||
case AxisDirection.up:
|
||||
begin = const Offset(0.0, 1.0);
|
||||
break;
|
||||
case AxisDirection.down:
|
||||
begin = const Offset(0.0, -1.0);
|
||||
break;
|
||||
}
|
||||
|
||||
const end = Offset.zero;
|
||||
const curve = Curves.easeOutCubic;
|
||||
|
||||
var tween = Tween(begin: begin, end: end).chain(
|
||||
CurveTween(curve: curve),
|
||||
);
|
||||
var offsetAnimation = animation.drive(tween);
|
||||
|
||||
var fadeTween = Tween(begin: 0.0, end: 1.0).chain(
|
||||
CurveTween(curve: curve),
|
||||
);
|
||||
var fadeAnimation = animation.drive(fadeTween);
|
||||
|
||||
return SlideTransition(
|
||||
position: offsetAnimation,
|
||||
child: FadeTransition(
|
||||
opacity: fadeAnimation,
|
||||
child: child,
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
/// 스케일 + 페이드 전환
|
||||
class ScalePageRoute<T> extends PageRouteBuilder<T> {
|
||||
final Widget page;
|
||||
final Alignment alignment;
|
||||
|
||||
ScalePageRoute({
|
||||
required this.page,
|
||||
this.alignment = Alignment.center,
|
||||
}) : super(
|
||||
pageBuilder: (context, animation, secondaryAnimation) => page,
|
||||
transitionDuration: const Duration(milliseconds: 400),
|
||||
reverseTransitionDuration: const Duration(milliseconds: 400),
|
||||
transitionsBuilder: (context, animation, secondaryAnimation, child) {
|
||||
const curve = Curves.elasticOut;
|
||||
|
||||
var scaleTween = Tween(begin: 0.0, end: 1.0).chain(
|
||||
CurveTween(curve: curve),
|
||||
);
|
||||
var scaleAnimation = animation.drive(scaleTween);
|
||||
|
||||
var fadeTween = Tween(begin: 0.0, end: 1.0).chain(
|
||||
CurveTween(curve: Curves.easeIn),
|
||||
);
|
||||
var fadeAnimation = animation.drive(fadeTween);
|
||||
|
||||
return ScaleTransition(
|
||||
scale: scaleAnimation,
|
||||
alignment: alignment,
|
||||
child: FadeTransition(
|
||||
opacity: fadeAnimation,
|
||||
child: child,
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
/// 회전 + 스케일 전환
|
||||
class RotatePageRoute<T> extends PageRouteBuilder<T> {
|
||||
final Widget page;
|
||||
|
||||
RotatePageRoute({required this.page})
|
||||
: super(
|
||||
pageBuilder: (context, animation, secondaryAnimation) => page,
|
||||
transitionDuration: const Duration(milliseconds: 500),
|
||||
reverseTransitionDuration: const Duration(milliseconds: 500),
|
||||
transitionsBuilder: (context, animation, secondaryAnimation, child) {
|
||||
const curve = Curves.easeInOut;
|
||||
|
||||
var rotateTween = Tween(begin: -0.5, end: 0.0).chain(
|
||||
CurveTween(curve: curve),
|
||||
);
|
||||
var rotateAnimation = animation.drive(rotateTween);
|
||||
|
||||
var scaleTween = Tween(begin: 0.0, end: 1.0).chain(
|
||||
CurveTween(curve: curve),
|
||||
);
|
||||
var scaleAnimation = animation.drive(scaleTween);
|
||||
|
||||
return Transform(
|
||||
alignment: Alignment.center,
|
||||
transform: Matrix4.identity()
|
||||
..setEntry(3, 2, 0.001)
|
||||
..rotateZ(rotateAnimation.value)
|
||||
..scale(scaleAnimation.value),
|
||||
child: child,
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
/// 3D 플립 전환
|
||||
class FlipPageRoute<T> extends PageRouteBuilder<T> {
|
||||
final Widget page;
|
||||
final bool horizontal;
|
||||
|
||||
FlipPageRoute({
|
||||
required this.page,
|
||||
this.horizontal = true,
|
||||
}) : super(
|
||||
pageBuilder: (context, animation, secondaryAnimation) => page,
|
||||
transitionDuration: const Duration(milliseconds: 800),
|
||||
reverseTransitionDuration: const Duration(milliseconds: 800),
|
||||
transitionsBuilder: (context, animation, secondaryAnimation, child) {
|
||||
final isAnimatingForward = animation.status == AnimationStatus.forward;
|
||||
|
||||
final flipAnimation = Tween(
|
||||
begin: 0.0,
|
||||
end: isAnimatingForward ? -math.pi : math.pi,
|
||||
).animate(CurvedAnimation(
|
||||
parent: animation,
|
||||
curve: Curves.easeInOut,
|
||||
));
|
||||
|
||||
return AnimatedBuilder(
|
||||
animation: flipAnimation,
|
||||
builder: (context, child) {
|
||||
final isShowingFront = flipAnimation.value.abs() < math.pi / 2;
|
||||
|
||||
return Transform(
|
||||
alignment: Alignment.center,
|
||||
transform: Matrix4.identity()
|
||||
..setEntry(3, 2, 0.001)
|
||||
..rotateY(horizontal ? flipAnimation.value : 0)
|
||||
..rotateX(horizontal ? 0 : flipAnimation.value),
|
||||
child: isShowingFront
|
||||
? Container()
|
||||
: Transform(
|
||||
alignment: Alignment.center,
|
||||
transform: Matrix4.identity()
|
||||
..rotateY(horizontal ? math.pi : 0)
|
||||
..rotateX(horizontal ? 0 : math.pi),
|
||||
child: child,
|
||||
),
|
||||
);
|
||||
},
|
||||
child: child,
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
/// 컨테이너 트랜스폼 (Material Design)
|
||||
class ContainerTransformPageRoute<T> extends PageRouteBuilder<T> {
|
||||
final Widget page;
|
||||
final Widget startWidget;
|
||||
final BorderRadius? borderRadius;
|
||||
|
||||
ContainerTransformPageRoute({
|
||||
required this.page,
|
||||
required this.startWidget,
|
||||
this.borderRadius,
|
||||
}) : super(
|
||||
pageBuilder: (context, animation, secondaryAnimation) => page,
|
||||
transitionDuration: const Duration(milliseconds: 500),
|
||||
reverseTransitionDuration: const Duration(milliseconds: 500),
|
||||
transitionsBuilder: (context, animation, secondaryAnimation, child) {
|
||||
return Stack(
|
||||
children: [
|
||||
// 배경 페이드
|
||||
FadeTransition(
|
||||
opacity: animation,
|
||||
child: Container(
|
||||
color: Colors.black.withValues(alpha: 0.3),
|
||||
),
|
||||
),
|
||||
// 컨테이너 확장 애니메이션
|
||||
AnimatedBuilder(
|
||||
animation: animation,
|
||||
builder: (context, _) {
|
||||
final progress = animation.value;
|
||||
final scale = 0.5 + (0.5 * progress);
|
||||
final radius = borderRadius?.topLeft.x ?? 0;
|
||||
final currentRadius = radius * (1 - progress);
|
||||
|
||||
return Transform.scale(
|
||||
scale: scale,
|
||||
child: ClipRRect(
|
||||
borderRadius: BorderRadius.circular(currentRadius),
|
||||
child: progress < 0.5 ? startWidget : child,
|
||||
),
|
||||
);
|
||||
},
|
||||
child: child,
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
/// 커스텀 Hero 애니메이션
|
||||
class CustomHeroPageRoute<T> extends PageRouteBuilder<T> {
|
||||
final Widget page;
|
||||
final String heroTag;
|
||||
|
||||
CustomHeroPageRoute({
|
||||
required this.page,
|
||||
required this.heroTag,
|
||||
}) : super(
|
||||
pageBuilder: (context, animation, secondaryAnimation) => page,
|
||||
transitionDuration: const Duration(milliseconds: 500),
|
||||
reverseTransitionDuration: const Duration(milliseconds: 500),
|
||||
transitionsBuilder: (context, animation, secondaryAnimation, child) {
|
||||
return FadeTransition(
|
||||
opacity: CurvedAnimation(
|
||||
parent: animation,
|
||||
curve: const Interval(0.5, 1.0),
|
||||
),
|
||||
child: child,
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
/// 공유 축 전환 (Material Design)
|
||||
class SharedAxisPageRoute<T> extends PageRouteBuilder<T> {
|
||||
final Widget page;
|
||||
final SharedAxisTransitionType transitionType;
|
||||
|
||||
SharedAxisPageRoute({
|
||||
required this.page,
|
||||
required this.transitionType,
|
||||
}) : super(
|
||||
pageBuilder: (context, animation, secondaryAnimation) => page,
|
||||
transitionDuration: const Duration(milliseconds: 300),
|
||||
reverseTransitionDuration: const Duration(milliseconds: 300),
|
||||
transitionsBuilder: (context, animation, secondaryAnimation, child) {
|
||||
late final Offset begin;
|
||||
late final Offset end;
|
||||
|
||||
switch (transitionType) {
|
||||
case SharedAxisTransitionType.horizontal:
|
||||
begin = const Offset(1.0, 0.0);
|
||||
end = Offset.zero;
|
||||
break;
|
||||
case SharedAxisTransitionType.vertical:
|
||||
begin = const Offset(0.0, 1.0);
|
||||
end = Offset.zero;
|
||||
break;
|
||||
case SharedAxisTransitionType.scaled:
|
||||
begin = Offset.zero;
|
||||
end = Offset.zero;
|
||||
break;
|
||||
}
|
||||
|
||||
final slideTween = Tween(begin: begin, end: end);
|
||||
final fadeTween = Tween(begin: 0.0, end: 1.0);
|
||||
final scaleTween = transitionType == SharedAxisTransitionType.scaled
|
||||
? Tween(begin: 0.8, end: 1.0)
|
||||
: Tween(begin: 1.0, end: 1.0);
|
||||
|
||||
final slideAnimation = animation.drive(slideTween);
|
||||
final fadeAnimation = animation.drive(fadeTween);
|
||||
final scaleAnimation = animation.drive(scaleTween);
|
||||
|
||||
return SlideTransition(
|
||||
position: slideAnimation,
|
||||
child: FadeTransition(
|
||||
opacity: fadeAnimation,
|
||||
child: ScaleTransition(
|
||||
scale: scaleAnimation,
|
||||
child: child,
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
enum SharedAxisTransitionType {
|
||||
horizontal,
|
||||
vertical,
|
||||
scaled,
|
||||
}
|
||||
Reference in New Issue
Block a user