import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'dart:ui'; import '../theme/app_colors.dart'; import 'themed_text.dart'; /// 글래스모피즘 효과가 적용된 통일된 앱바 class GlassmorphicAppBar extends StatelessWidget implements PreferredSizeWidget { final String title; final List? actions; final Widget? leading; final bool automaticallyImplyLeading; final double elevation; final Color? backgroundColor; final double blur; final double opacity; final PreferredSizeWidget? bottom; final bool centerTitle; final double? titleSpacing; final VoidCallback? onBackPressed; const GlassmorphicAppBar({ super.key, required this.title, this.actions, this.leading, this.automaticallyImplyLeading = true, this.elevation = 0, this.backgroundColor, this.blur = 20, this.opacity = 0.1, this.bottom, this.centerTitle = false, this.titleSpacing, this.onBackPressed, }); @override Size get preferredSize => Size.fromHeight( kToolbarHeight + (bottom?.preferredSize.height ?? 0.0) + 0.5); @override Widget build(BuildContext context) { final isDarkMode = Theme.of(context).brightness == Brightness.dark; final canPop = Navigator.of(context).canPop(); return ClipRRect( child: BackdropFilter( filter: ImageFilter.blur(sigmaX: blur, sigmaY: blur), child: Container( decoration: BoxDecoration( gradient: LinearGradient( begin: Alignment.topLeft, end: Alignment.bottomRight, colors: [ (backgroundColor ?? (isDarkMode ? AppColors.glassBackgroundDark : AppColors.glassBackground)).withValues(alpha: opacity), (backgroundColor ?? (isDarkMode ? AppColors.glassSurfaceDark : AppColors.glassSurface)).withValues(alpha: opacity * 0.8), ], ), border: Border( bottom: BorderSide( color: isDarkMode ? AppColors.primaryColor.withValues(alpha: 0.3) : AppColors.glassBorder.withValues(alpha: 0.5), width: 0.5, ), ), ), child: SafeArea( bottom: false, child: ClipRect( child: Column( mainAxisSize: MainAxisSize.min, children: [ Flexible( child: SizedBox( height: kToolbarHeight, child: NavigationToolbar( leading: leading ?? (automaticallyImplyLeading && (canPop || onBackPressed != null) ? _buildBackButton(context) : null), middle: _buildTitle(context), trailing: actions != null ? Row( mainAxisSize: MainAxisSize.min, children: actions!, ) : null, centerMiddle: centerTitle, middleSpacing: titleSpacing ?? NavigationToolbar.kMiddleSpacing, ), ), ), if (bottom != null) bottom!, ], ), ), ), ), ), ); } Widget _buildBackButton(BuildContext context) { return IconButton( icon: const Icon(Icons.arrow_back_ios_new_rounded), onPressed: onBackPressed ?? () { HapticFeedback.lightImpact(); Navigator.of(context).pop(); }, splashRadius: 24, tooltip: '뒤로가기', color: ThemedText.getContrastColor(context), ); } Widget _buildTitle(BuildContext context) { return Padding( padding: const EdgeInsets.symmetric(horizontal: 16), child: ThemedText.headline( text: title, style: const TextStyle( fontSize: 22, fontWeight: FontWeight.w600, letterSpacing: -0.2, ), ), ); } /// 투명 스타일 팩토리 static GlassmorphicAppBar transparent({ required String title, List? actions, Widget? leading, VoidCallback? onBackPressed, }) { return GlassmorphicAppBar( title: title, actions: actions, leading: leading, blur: 30, opacity: 0.05, onBackPressed: onBackPressed, ); } /// 반투명 스타일 팩토리 static GlassmorphicAppBar translucent({ required String title, List? actions, Widget? leading, VoidCallback? onBackPressed, }) { return GlassmorphicAppBar( title: title, actions: actions, leading: leading, blur: 20, opacity: 0.15, onBackPressed: onBackPressed, ); } } /// 확장된 글래스모피즘 앱바 (이미지나 추가 콘텐츠 포함) class GlassmorphicSliverAppBar extends StatelessWidget { final String title; final List? actions; final Widget? leading; final double expandedHeight; final bool floating; final bool pinned; final bool snap; final Widget? flexibleSpace; final double blur; final double opacity; final bool automaticallyImplyLeading; final VoidCallback? onBackPressed; final bool centerTitle; const GlassmorphicSliverAppBar({ super.key, required this.title, this.actions, this.leading, this.expandedHeight = kToolbarHeight, this.floating = false, this.pinned = true, this.snap = false, this.flexibleSpace, this.blur = 20, this.opacity = 0.1, this.automaticallyImplyLeading = true, this.onBackPressed, this.centerTitle = false, }); @override Widget build(BuildContext context) { final isDarkMode = Theme.of(context).brightness == Brightness.dark; final canPop = Navigator.of(context).canPop(); return SliverAppBar( expandedHeight: expandedHeight, floating: floating, pinned: pinned, snap: snap, backgroundColor: Colors.transparent, elevation: 0, automaticallyImplyLeading: false, leading: leading ?? (automaticallyImplyLeading && (canPop || onBackPressed != null) ? IconButton( icon: const Icon(Icons.arrow_back_ios_new_rounded), onPressed: onBackPressed ?? () { HapticFeedback.lightImpact(); Navigator.of(context).pop(); }, splashRadius: 24, tooltip: '뒤로가기', ) : null), actions: actions, centerTitle: centerTitle, flexibleSpace: LayoutBuilder( builder: (BuildContext context, BoxConstraints constraints) { final top = constraints.biggest.height; final isCollapsed = top <= kToolbarHeight + MediaQuery.of(context).padding.top; return FlexibleSpaceBar( title: isCollapsed ? ThemedText.headline( text: title, style: const TextStyle( fontSize: 18, fontWeight: FontWeight.w600, letterSpacing: -0.2, ), ) : null, centerTitle: centerTitle, titlePadding: const EdgeInsets.only(left: 16, bottom: 16, right: 16), background: Stack( fit: StackFit.expand, children: [ // 글래스모피즘 배경 ClipRRect( child: BackdropFilter( filter: ImageFilter.blur(sigmaX: blur, sigmaY: blur), child: Container( decoration: BoxDecoration( gradient: LinearGradient( begin: Alignment.topLeft, end: Alignment.bottomRight, colors: [ (isDarkMode ? AppColors.glassBackgroundDark : AppColors.glassBackground).withValues(alpha: opacity), (isDarkMode ? AppColors.glassSurfaceDark : AppColors.glassSurface).withValues(alpha: opacity * 0.8), ], ), border: Border( bottom: BorderSide( color: isDarkMode ? AppColors.primaryColor.withValues(alpha: 0.3) : AppColors.glassBorder.withValues(alpha: 0.5), width: 0.5, ), ), ), ), ), ), // 확장 상태에서만 보이는 타이틀 if (!isCollapsed) Positioned( left: 16, right: 16, bottom: 16, child: ThemedText.headline( text: title, style: const TextStyle( fontSize: 28, fontWeight: FontWeight.w700, letterSpacing: -0.5, ), ), ), // 커스텀 flexibleSpace가 있으면 추가 if (flexibleSpace != null) flexibleSpace!, ], ), ); }, ), ); } }