diff --git a/docs/fluid-design.md b/docs/fluid-design.md new file mode 100644 index 0000000..aed9f35 --- /dev/null +++ b/docs/fluid-design.md @@ -0,0 +1,297 @@ +# 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.