import 'package:flutter/material.dart'; import 'package:flutter/rendering.dart'; import 'dart:math' as math; import '../theme/app_colors.dart'; import 'floating_navigation_bar.dart'; /// 글래스모피즘 디자인이 적용된 통일된 스캐폴드 class GlassmorphicScaffold extends StatefulWidget { final PreferredSizeWidget? appBar; final Widget body; final Widget? floatingActionButton; final FloatingActionButtonLocation? floatingActionButtonLocation; final List? backgroundGradient; final bool extendBodyBehindAppBar; final bool extendBody; final Widget? bottomNavigationBar; final bool useFloatingNavBar; final int? floatingNavBarIndex; final Function(int)? onFloatingNavBarTapped; final bool resizeToAvoidBottomInset; final Widget? drawer; final Widget? endDrawer; final Color? backgroundColor; final bool enableParticles; final bool enableWaveAnimation; const GlassmorphicScaffold({ super.key, this.appBar, required this.body, this.floatingActionButton, this.floatingActionButtonLocation, this.backgroundGradient, this.extendBodyBehindAppBar = true, this.extendBody = true, this.bottomNavigationBar, this.useFloatingNavBar = false, this.floatingNavBarIndex, this.onFloatingNavBarTapped, this.resizeToAvoidBottomInset = true, this.drawer, this.endDrawer, this.backgroundColor, this.enableParticles = false, this.enableWaveAnimation = false, }); @override State createState() => _GlassmorphicScaffoldState(); } class _GlassmorphicScaffoldState extends State with TickerProviderStateMixin { late AnimationController _particleController; late AnimationController _waveController; ScrollController? _scrollController; bool _isFloatingNavBarVisible = true; @override void initState() { super.initState(); _particleController = AnimationController( duration: const Duration(seconds: 20), vsync: this, )..repeat(); _waveController = AnimationController( duration: const Duration(seconds: 10), vsync: this, )..repeat(); if (widget.useFloatingNavBar) { _scrollController = ScrollController(); _setupScrollListener(); } } void _setupScrollListener() { _scrollController?.addListener(() { final currentScroll = _scrollController!.position.pixels; // 스크롤 방향에 따라 플로팅 네비게이션 바 표시/숨김 if (currentScroll > 50 && _scrollController!.position.userScrollDirection == ScrollDirection.reverse) { if (_isFloatingNavBarVisible) { setState(() => _isFloatingNavBarVisible = false); } } else if (_scrollController!.position.userScrollDirection == ScrollDirection.forward) { if (!_isFloatingNavBarVisible) { setState(() => _isFloatingNavBarVisible = true); } } }); } @override void dispose() { _particleController.dispose(); _waveController.dispose(); _scrollController?.dispose(); super.dispose(); } List _getBackgroundGradient() { if (widget.backgroundGradient != null) { return widget.backgroundGradient!; } // 디폴트 그라디언트 return AppColors.mainGradient; } @override Widget build(BuildContext context) { final backgroundGradient = _getBackgroundGradient(); return Stack( children: [ // 배경 그라디언트 _buildBackground(backgroundGradient), // 파티클 효과 (선택적) if (widget.enableParticles) _buildParticles(), // 웨이브 애니메이션 (선택적) if (widget.enableWaveAnimation) _buildWaveAnimation(), // 메인 스캐폴드 Scaffold( backgroundColor: widget.backgroundColor ?? Colors.transparent, appBar: widget.appBar, body: widget.body, floatingActionButton: widget.floatingActionButton, floatingActionButtonLocation: widget.floatingActionButtonLocation, bottomNavigationBar: widget.bottomNavigationBar, extendBodyBehindAppBar: widget.extendBodyBehindAppBar, extendBody: widget.extendBody, resizeToAvoidBottomInset: widget.resizeToAvoidBottomInset, drawer: widget.drawer, endDrawer: widget.endDrawer, ), // 플로팅 네비게이션 바 (선택적) if (widget.useFloatingNavBar && widget.floatingNavBarIndex != null) FloatingNavigationBar( selectedIndex: widget.floatingNavBarIndex!, isVisible: _isFloatingNavBarVisible, onItemTapped: widget.onFloatingNavBarTapped ?? (_) {}, ), ], ); } Widget _buildBackground(List gradientColors) { return Positioned.fill( child: Container( color: AppColors.backgroundColor, // 베이스 색상 추가 child: Container( decoration: BoxDecoration( gradient: LinearGradient( begin: Alignment.topLeft, end: Alignment.bottomRight, colors: gradientColors .map((color) => color.withValues(alpha: 0.3)) .toList(), ), ), ), ), ); } Widget _buildParticles() { return Positioned.fill( child: AnimatedBuilder( animation: _particleController, builder: (context, child) { return CustomPaint( painter: ParticlePainter( animation: _particleController, particleCount: 30, ), ); }, ), ); } Widget _buildWaveAnimation() { return Positioned( bottom: 0, left: 0, right: 0, height: 200, child: AnimatedBuilder( animation: _waveController, builder: (context, child) { return CustomPaint( painter: WavePainter( animation: _waveController, waveColor: AppColors.secondaryColor.withValues(alpha: 0.1), ), ); }, ), ); } } /// 파티클 페인터 class ParticlePainter extends CustomPainter { final Animation animation; final int particleCount; final List particles = []; ParticlePainter({ required this.animation, this.particleCount = 50, }) : super(repaint: animation) { _initParticles(); } void _initParticles() { final random = math.Random(); for (int i = 0; i < particleCount; i++) { particles.add(Particle( x: random.nextDouble(), y: random.nextDouble(), size: random.nextDouble() * 3 + 1, speed: random.nextDouble() * 0.5 + 0.1, opacity: random.nextDouble() * 0.5 + 0.1, )); } } @override void paint(Canvas canvas, Size size) { final paint = Paint()..style = PaintingStyle.fill; for (final particle in particles) { final progress = animation.value; final y = (particle.y + progress * particle.speed) % 1.0; paint.color = AppColors.pureWhite.withValues(alpha: particle.opacity); canvas.drawCircle( Offset(particle.x * size.width, y * size.height), particle.size, paint, ); } } @override bool shouldRepaint(covariant CustomPainter oldDelegate) => true; } /// 웨이브 페인터 class WavePainter extends CustomPainter { final Animation animation; final Color waveColor; WavePainter({ required this.animation, required this.waveColor, }) : super(repaint: animation); @override void paint(Canvas canvas, Size size) { final paint = Paint() ..color = waveColor ..style = PaintingStyle.fill; final path = Path(); final progress = animation.value; path.moveTo(0, size.height); for (double x = 0; x <= size.width; x++) { final y = math.sin((x / size.width * 2 * math.pi) + (progress * 2 * math.pi)) * 20 + size.height * 0.5; path.lineTo(x, y); } path.lineTo(size.width, size.height); path.close(); canvas.drawPath(path, paint); } @override bool shouldRepaint(covariant CustomPainter oldDelegate) => true; } /// 파티클 데이터 클래스 class Particle { final double x; final double y; final double size; final double speed; final double opacity; Particle({ required this.x, required this.y, required this.size, required this.speed, required this.opacity, }); }