import 'package:flutter/material.dart'; import 'dart:math' as math; import '../utils/reduce_motion.dart'; /// 웨이브 애니메이션 배경 효과를 제공하는 위젯 /// /// [controller]와 [pulseController]를 통해 애니메이션을 제어합니다. class AnimatedWaveBackground extends StatelessWidget { final AnimationController controller; final AnimationController pulseController; const AnimatedWaveBackground({ Key? key, required this.controller, required this.pulseController, }) : super(key: key); @override Widget build(BuildContext context) { final reduce = ReduceMotion.isEnabled(context); final amp = reduce ? 0.3 : 1.0; // 기본 효과 강도 스케일 // 원 크기에 따라 속도/진폭 스케일을 동적으로 계산 // size가 클수록 느리고(차분), 작을수록 빠르고(활발) 크게 움직이게 함 MotionParams paramsFor(double size) { const ref = 160.0; // 기준 크기 // 진폭 스케일: 0.6 ~ 1.4 사이 (연속) final ampScale = _clamp(ref / size, 0.6, 1.4) * (reduce ? 0.6 : 1.0); // 속도 배수: 1~3의 정수로 제한하여 래핑 시 연속성 보장 final raw = 0.8 + (ref / size) * 0.6; // 약 0.8~1.4 범위 int speedMult = raw < 1.2 ? 1 : (raw < 1.6 ? 2 : 3); if (reduce && speedMult > 2) speedMult = 2; // 감속 모드 상한 return MotionParams(ampScale: ampScale, speedMult: speedMult); } return Stack( children: [ // 웨이브 애니메이션 배경 요소 - 사인/코사인 함수 대신 더 부드러운 곡선 사용 AnimatedBuilder( animation: controller, builder: (context, child) { // 0~1 사이의 값을 0~2π 사이의 값으로 변환하여 부드러운 주기 생성 final p = paramsFor(200); final angle = controller.value * 2 * math.pi * p.speedMult; // 사인 함수를 사용하여 부드러운 움직임 생성 (큰 원: 차분) final xOffset = 20 * amp * p.ampScale * math.sin(angle); final yOffset = 10 * amp * p.ampScale * math.cos(angle); return Positioned( right: -40 + xOffset, top: -60 + yOffset, child: Transform.rotate( // 회전도 선형적으로 변화하도록 수정 angle: 0.2 * amp * p.ampScale * math.sin(angle * 0.5), child: Container( width: 200, height: 200, decoration: BoxDecoration( color: Theme.of(context) .colorScheme .onSurface .withValues(alpha: 0.08), borderRadius: BorderRadius.circular(100), ), ), ), ); }, ), AnimatedBuilder( animation: controller, builder: (context, child) { // 첫 번째 원과 약간 다른 위상을 가지도록 설정 final p = paramsFor(220); final angle = (controller.value * 2 * math.pi * p.speedMult) + (math.pi / 3); final xOffset = 20 * amp * p.ampScale * math.cos(angle); final yOffset = 10 * amp * p.ampScale * math.sin(angle); return Positioned( left: -80 + xOffset, bottom: -70 + yOffset, child: Transform.rotate( // 반대 방향으로 회전하도록 설정 angle: -0.3 * amp * p.ampScale * math.sin(angle * 0.5), child: Container( width: 220, height: 220, decoration: BoxDecoration( color: Theme.of(context) .colorScheme .onSurface .withValues(alpha: 0.05), borderRadius: BorderRadius.circular(110), ), ), ), ); }, ), // 배경에 추가적인 작은 원 추가하여 깊이감 증가 AnimatedBuilder( animation: controller, builder: (context, child) { // 세 번째 원은 다른 위상으로 움직이도록 설정 final p = paramsFor(120); final angle = (controller.value * 2 * math.pi * p.speedMult) + (math.pi * 2 / 3); final xOffset = 15 * amp * p.ampScale * math.sin(angle * 0.9); final yOffset = 8 * amp * p.ampScale * math.cos(angle * 0.9); return Positioned( right: 40 + xOffset, bottom: -40 + yOffset, child: Transform.rotate( angle: 0.4 * amp * p.ampScale * math.cos(angle * 0.5), child: Container( width: 120, height: 120, decoration: BoxDecoration( color: Theme.of(context) .colorScheme .onSurface .withValues(alpha: 0.06), borderRadius: BorderRadius.circular(60), ), ), ), ); }, ), // 숨쉬는 효과가 있는 작은 원 AnimatedBuilder( animation: pulseController, builder: (context, child) { return Positioned( top: 10, left: 200, child: Container( width: 30, height: 30, decoration: BoxDecoration( color: Theme.of(context).colorScheme.onSurface.withValues( alpha: reduce ? 0.08 : 0.1 + 0.1 * pulseController.value), borderRadius: BorderRadius.circular(15), ), ), ); }, ), ], ); } } // 내부 유틸리티: 값 범위 제한 double _clamp(double v, double min, double max) => v < min ? min : (v > max ? max : v); class MotionParams { final double ampScale; final int speedMult; MotionParams({required this.ampScale, required this.speedMult}); }