# Apple Fluid Design — примеры изменений ## Контекст Текущий UI уже iOS-вдохновлённый (белые карточки на сером фоне, цвет `#007AFF`, flat Material 3). Задача — поднять его до уровня **Apple Fluid** (visionOS/iOS 16+ эстетика): стеклянные поверхности, мягкие градиентные фоны, пружинные анимации, размытые навбары. --- ## Ключевые файлы для изменений - `client/lib/core/theme/app_colors.dart` — цветовая система - `client/lib/core/theme/app_theme.dart` — Material theme - `client/lib/features/home/home_screen.dart` — главный экран - `client/lib/app.dart` — корень приложения --- ## 1. Градиентный фон вместо плоского `#F2F2F7` **Текущее состояние:** `scaffoldBackgroundColor: AppColors.background` — серый `#F2F2F7`. **Fluid-подход:** мягкий меш-градиент с 2–3 цветовыми пятнами. ```dart // Новый виджет-обёртка — заменяет Scaffold background class FluidBackground extends StatelessWidget { final Widget child; const FluidBackground({required this.child, super.key}); @override Widget build(BuildContext context) { return Container( decoration: const BoxDecoration( gradient: LinearGradient( begin: Alignment.topLeft, end: Alignment.bottomRight, colors: [ Color(0xFFEEF4FF), // холодный голубой Color(0xFFF5F0FF), // лавандовый Color(0xFFE8F8F2), // мятный ], stops: [0.0, 0.55, 1.0], ), ), child: child, ); } } ``` **Применение в `home_screen.dart`:** обернуть `Scaffold` в `FluidBackground`. --- ## 2. Стеклянные карточки (Glassmorphism) **Текущее состояние:** `Card` с `elevation: 0` и белым фоном. **Fluid-подход:** `BackdropFilter` + `ImageFilter.blur` + полупрозрачный контейнер. ```dart import 'dart:ui'; class GlassCard extends StatelessWidget { final Widget child; final EdgeInsets padding; final double borderRadius; const GlassCard({ required this.child, this.padding = const EdgeInsets.all(16), this.borderRadius = 20, super.key, }); @override Widget build(BuildContext context) { return ClipRRect( borderRadius: BorderRadius.circular(borderRadius), child: BackdropFilter( filter: ImageFilter.blur(sigmaX: 20, sigmaY: 20), child: Container( decoration: BoxDecoration( color: Colors.white.withValues(alpha: 0.72), borderRadius: BorderRadius.circular(borderRadius), border: Border.all( color: Colors.white.withValues(alpha: 0.6), width: 1.0, ), boxShadow: [ BoxShadow( color: const Color(0xFF007AFF).withValues(alpha: 0.06), blurRadius: 24, offset: const Offset(0, 8), ), ], ), padding: padding, child: child, ), ), ); } } ``` **Применение:** заменить `Card(...)` на `GlassCard(child: ...)` для `_CaloriesCard`, `_MacrosRow`. --- ## 3. Frosted-glass навигационная панель **Текущее состояние:** белый `BottomNavigationBar` с `elevation: 0`. **Fluid-подход:** размытая нижняя панель, как iOS Home indicator area. ```dart class FluidBottomNavBar extends StatelessWidget { final int currentIndex; final ValueChanged onTap; const FluidBottomNavBar({ required this.currentIndex, required this.onTap, super.key, }); @override Widget build(BuildContext context) { return ClipRect( child: BackdropFilter( filter: ImageFilter.blur(sigmaX: 30, sigmaY: 30), child: Container( decoration: BoxDecoration( color: Colors.white.withValues(alpha: 0.80), border: Border( top: BorderSide( color: Colors.white.withValues(alpha: 0.5), width: 0.5, ), ), ), child: BottomNavigationBar( currentIndex: currentIndex, onTap: onTap, backgroundColor: Colors.transparent, elevation: 0, // ... items ), ), ), ); } } ``` --- ## 4. Rounded шрифт — DM Sans / Plus Jakarta Sans **Текущее состояние:** `GoogleFonts.roboto()` — нейтральный, не «тёплый». **Fluid-подход:** `GoogleFonts.dmSans()` или `GoogleFonts.plusJakartaSans()` — округлые буквы, ближе к SF Pro Rounded. ```dart // app_theme.dart fontFamily: GoogleFonts.dmSans().fontFamily, ``` Изменение в одну строку — затрагивает весь UI сразу. --- ## 5. Цветные «blob»-тени на карточках вместо нейтральных **Текущее состояние:** тени отсутствуют (`elevation: 0`). **Fluid-подход:** мягкая цветная тень `#007AFF` с малой opacity. ```dart // Вместо CardTheme используем обёртку с boxShadow BoxDecoration fluidCardDecoration = BoxDecoration( color: Colors.white.withValues(alpha: 0.80), borderRadius: BorderRadius.circular(20), boxShadow: [ BoxShadow( color: const Color(0xFF007AFF).withValues(alpha: 0.08), blurRadius: 32, spreadRadius: 0, offset: const Offset(0, 12), ), BoxShadow( color: Colors.black.withValues(alpha: 0.04), blurRadius: 8, offset: const Offset(0, 2), ), ], ); ``` --- ## 6. Пружинные анимации (Spring Physics) **Текущее состояние:** стандартный `AnimatedContainer` с `Duration(milliseconds: 150)`. **Fluid-подход:** `SpringDescription` с bounce, как в UIKit. ```dart // Пример для DateSelector — при выборе даты TweenAnimationBuilder( tween: Tween(begin: 0.8, end: 1.0), duration: const Duration(milliseconds: 400), curve: Curves.easeOutBack, // ← даёт эффект bounce builder: (context, scaleValue, child) => Transform.scale(scale: scaleValue, child: child), child: selectedDatePill, ) ``` --- ## 7. Frosted Bottom Sheet **Текущее состояние:** белый bottom sheet с `borderRadius: 12`. **Fluid-подход:** стеклянный sheet с blur из контента под ним. ```dart // В showModalBottomSheet: showModalBottomSheet( context: context, backgroundColor: Colors.transparent, // ← ключевое builder: (sheetContext) => ClipRRect( borderRadius: const BorderRadius.vertical(top: Radius.circular(28)), child: BackdropFilter( filter: ImageFilter.blur(sigmaX: 40, sigmaY: 40), child: Container( decoration: BoxDecoration( color: Colors.white.withValues(alpha: 0.88), borderRadius: const BorderRadius.vertical(top: Radius.circular(28)), border: Border( top: BorderSide( color: Colors.white.withValues(alpha: 0.7), width: 1, ), ), ), child: FoodSearchSheet(...), ), ), ), ); ``` --- ## 8. Скругления: 12 → 20pt **Текущее состояние:** `BorderRadius.circular(12)` повсюду. **Fluid-подход:** `20pt` для карточек, `28pt` для bottom sheets, `14pt` для полей ввода. ```dart // app_theme.dart cardTheme: CardThemeData( shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(20), // было 12 ), ), bottomSheetTheme: const BottomSheetThemeData( shape: RoundedRectangleBorder( borderRadius: BorderRadius.vertical(top: Radius.circular(28)), // было 12 ), ), ``` --- ## Итог: приоритет изменений | Приоритет | Изменение | Файл | Эффект | |-----------|-----------|------|--------| | 1 | Шрифт DM Sans | `app_theme.dart` | Весь UI сразу | | 2 | Скругления 20/28pt | `app_theme.dart` | Весь UI сразу | | 3 | Градиентный фон | `home_screen.dart` + новый виджет | Главный экран | | 4 | `GlassCard` | новый файл `glass_card.dart` | Карточки калорий/макро | | 5 | Цветные тени | точечно в карточках | Глубина | | 6 | Frosted навбар | `app.dart` | Всё приложение | | 7 | Frosted bottom sheet | `food_search_sheet.dart` | Поиск еды | | 8 | Spring-анимации | `home_screen.dart` | DateSelector | Пункты 1–2 — изменения в одну строку с максимальным эффектом. Пункты 3–5 — создание `GlassCard` и замена `Card` на него в 2–3 местах. Пункты 6–8 — более глубокие изменения навигации и transition.