import 'package:flutter/material.dart'; /// 로딩 오버레이 위젯 /// 비동기 작업 중 화면을 덮는 로딩 인디케이터를 표시합니다. class LoadingOverlay extends StatelessWidget { final bool isLoading; final Widget child; final String? message; final Color? backgroundColor; final Color? indicatorColor; final double opacity; const LoadingOverlay({ super.key, required this.isLoading, required this.child, this.message, this.backgroundColor, this.indicatorColor, this.opacity = 0.7, }); @override Widget build(BuildContext context) { return Stack( children: [ child, if (isLoading) Container( color: (backgroundColor ?? Colors.black).withValues(alpha: opacity), child: Center( child: Container( padding: const EdgeInsets.all(24), decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(16), boxShadow: [ BoxShadow( color: Colors.black.withValues(alpha: 0.1), blurRadius: 10, offset: const Offset(0, 4), ), ], ), child: Column( mainAxisSize: MainAxisSize.min, children: [ CircularProgressIndicator( color: indicatorColor ?? Theme.of(context).primaryColor, ), if (message != null) ...[ const SizedBox(height: 16), Text( message!, style: const TextStyle( fontSize: 16, fontWeight: FontWeight.w500, ), ), ], ], ), ), ), ), ], ); } } /// 로딩 다이얼로그 /// 모달 형태의 로딩 인디케이터를 표시합니다. class LoadingDialog { static Future show({ required BuildContext context, String? message, Color? barrierColor, bool barrierDismissible = false, }) { return showDialog( context: context, barrierDismissible: barrierDismissible, barrierColor: barrierColor ?? Colors.black54, builder: (context) => PopScope( canPop: barrierDismissible, child: Center( child: Container( padding: const EdgeInsets.all(24), decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(16), ), child: Column( mainAxisSize: MainAxisSize.min, children: [ CircularProgressIndicator( color: Theme.of(context).primaryColor, ), if (message != null) ...[ const SizedBox(height: 16), Text( message, style: const TextStyle( fontSize: 16, fontWeight: FontWeight.w500, ), ), ], ], ), ), ), ), ); } static void hide(BuildContext context) { Navigator.of(context).pop(); } } /// 커스텀 로딩 인디케이터 /// 다양한 스타일의 로딩 애니메이션을 제공합니다. class CustomLoadingIndicator extends StatefulWidget { final double size; final Color? color; final LoadingStyle style; const CustomLoadingIndicator({ super.key, this.size = 50, this.color, this.style = LoadingStyle.circular, }); @override State createState() => _CustomLoadingIndicatorState(); } class _CustomLoadingIndicatorState extends State with SingleTickerProviderStateMixin { late AnimationController _controller; late Animation _animation; @override void initState() { super.initState(); _controller = AnimationController( duration: const Duration(seconds: 1), vsync: this, )..repeat(); _animation = CurvedAnimation( parent: _controller, curve: Curves.easeInOut, ); } @override void dispose() { _controller.dispose(); super.dispose(); } @override Widget build(BuildContext context) { final effectiveColor = widget.color ?? Theme.of(context).primaryColor; switch (widget.style) { case LoadingStyle.circular: return SizedBox( width: widget.size, height: widget.size, child: CircularProgressIndicator( color: effectiveColor, strokeWidth: 3, ), ); case LoadingStyle.dots: return SizedBox( width: widget.size, height: widget.size / 3, child: Row( mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: List.generate(3, (index) { return AnimatedBuilder( animation: _animation, builder: (context, child) { final delay = index * 0.2; final value = (_animation.value - delay).clamp(0.0, 1.0); return Container( width: widget.size / 5, height: widget.size / 5, decoration: BoxDecoration( color: effectiveColor.withValues(alpha: 0.3 + value * 0.7), shape: BoxShape.circle, ), ); }, ); }), ), ); case LoadingStyle.pulse: return AnimatedBuilder( animation: _animation, builder: (context, child) { return Container( width: widget.size, height: widget.size, decoration: BoxDecoration( shape: BoxShape.circle, color: effectiveColor.withValues(alpha: 0.3), ), child: Center( child: Container( width: widget.size * (0.3 + _animation.value * 0.5), height: widget.size * (0.3 + _animation.value * 0.5), decoration: BoxDecoration( shape: BoxShape.circle, color: effectiveColor.withValues(alpha: 1 - _animation.value), ), ), ), ); }, ); } } } enum LoadingStyle { circular, dots, pulse, }