Fluid Design Research.

This commit is contained in:
dbastrikin
2026-03-28 00:04:01 +02:00
parent 180c741424
commit a71dd40b6a

297
docs/fluid-design.md Normal file
View File

@@ -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-подход:** мягкий меш-градиент с 23 цветовыми пятнами.
```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<int> 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<double>(
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 |
Пункты 12 — изменения в одну строку с максимальным эффектом.
Пункты 35 — создание `GlassCard` и замена `Card` на него в 23 местах.
Пункты 68 — более глубокие изменения навигации и transition.