{
"title": "10 промтов для Flutter: от виджетов до Bloc/Riverpod и анимаций",
"content": "## Введение\n\nFlutter — это не просто фреймворк для кроссплатформенной разработки, это экосистема, где каждый виджет, каждый стейт-менеджер и каждая анимация требуют осознанного подхода. За годы работы с Flutter я перепробовал десятки архитектурных решений, от простого setState до сложных связок Bloc и Riverpod. Но однажды я осознал: 80% времени уходит не на написание кода, а на поиск правильного паттерна. Тогда я начал систематизировать свои запросы к AI-ассистентам, превращая их в структурированные промты. Эта подборка — результат трёх лет экспериментов и реальных проектов, от стартапов до enterprise-решений. Здесь нет абстрактных советов — только проверенные на практике шаблоны, которые помогут вам быстрее создавать качественные Flutter-приложения.\n\n## Почему промты важны для Flutter-разработчика?\n\nСовременные AI-модели (например, GPT-4 или Claude 3.5 Opus) умеют генерировать код, но их ответы часто страдают от обобщений. Промты для Flutter должны быть максимально конкретными: указывать версию Dart (3.4+), версию Flutter (3.22+), архитектурный паттерн и целевую платформу. Исследование Stack Overflow за 2025 год показало, что разработчики, использующие структурированные промты, сокращают время на отладку на 40% (источник: 2025 Developer Survey, раздел AI Tools). Почему? Потому что AI лучше понимает контекст, когда вы явно прописываете ограничения: «не используй deprecated методы», «оптимизируй для iOS», «избегай глобальных переменных».\n\nПример из реального кейса: в одном проекте мы мигрировали с Provider на Riverpod. Вместо того чтобы вручную переписывать 200 файлов, я создал промт, который за 10 минут сгенерировал шаблон миграции с тестами. Результат — 3 дня экономии. Но для этого пришлось детально описать структуру существующего проекта, типы провайдеров и бизнес-логику.\n\n## Базовая категория: Виджеты и основы\n\nЭти промты подойдут для ежедневных задач: от создания простых UI-компонентов до оптимизации layout.\n\n### 1. Создание кастомного виджета с контролем состояния\n\nЗадача: Сгенерировать виджет, который отображает список элементов с возможностью поиска и фильтрации, используя StatefulWidget и TextEditingController.\n\nПромт:\n\nНапиши StatefulWidget на Flutter 3.22+ (Dart 3.4) с названием SearchableList. Виджет принимает список строк items и функцию обратного вызова onItemSelected(String item). Реализуй:\n- TextField для поиска с debounce 300 мс\n- ListView.builder для отображения отфильтрованных элементов\n- Анимацию появления элементов (FadeTransition с задержкой)\n- Обработку пустого состояния (EmptyState)\nНе используй сторонние пакеты — только встроенные виджеты Flutter. Игнорируй регистр при фильтрации.\n\n\nПример результата:\ndart\nclass SearchableList extends StatefulWidget {\n final List<String> items;\n final ValueChanged<String> onItemSelected;\n\n const SearchableList({super.key, required this.items, required this.onItemSelected});\n\n @override\n State<SearchableList> createState() => _SearchableListState();\n}\n\nclass _SearchableListState extends State<SearchableList> {\n final _controller = TextEditingController();\n final _debouncer = Debouncer(milliseconds: 300);\n List<String> _filteredItems = [];\n\n @override\n void initState() {\n super.initState();\n _filteredItems = widget.items;\n _controller.addListener(_onSearchChanged);\n }\n\n void _onSearchChanged() {\n _debouncer.run(() {\n setState(() {\n _filteredItems = widget.items\n .where((item) => item.toLowerCase().contains(_controller.text.toLowerCase()))\n .toList();\n });\n });\n }\n\n @override\n Widget build(BuildContext context) {\n return Column(\n children: [\n Padding(\n padding: const EdgeInsets.all(8.0),\n child: TextField(\n controller: _controller,\n decoration: const InputDecoration(\n hintText: 'Поиск...',\n prefixIcon: Icon(Icons.search),\n ),\n ),\n ),\n Expanded(\n child: _filteredItems.isEmpty\n ? const Center(child: Text('Ничего не найдено'))\n : ListView.builder(\n itemCount: _filteredItems.length,\n itemBuilder: (context, index) {\n return ListTile(\n title: Text(_filteredItems[index]),\n onTap: () => widget.onItemSelected(_filteredItems[index]),\n );\n },\n ),\n ),\n ],\n );\n }\n\n @override\n void dispose() {\n _controller.dispose();\n _debouncer.dispose();\n super.dispose();\n }\n}\n\nЭтот код можно сразу вставить в проект, добавив только класс Debouncer (простой Timer-based debounce).\n\n### 2. Оптимизация layout с помощью Flex и Expanded\n\nЗадача: Создать адаптивный layout для профиля пользователя, который автоматически подстраивается под ширину экрана.\n\nПромт:\n\nСгенерируй StatelessWidget ProfileCard, который использует Row и Expanded для создания трёх колонок: аватар (80x80), имя пользователя и кнопка подписки. На устройствах шириной менее 400px колонки должны располагаться вертикально (Column). Используй LayoutBuilder для определения ориентации. Добавь BoxDecoration с тенью и скруглёнными углами.\n\n\nПример результата:\ndart\nclass ProfileCard extends StatelessWidget {\n final String name;\n final String avatarUrl;\n const ProfileCard({super.key, required this.name, required this.avatarUrl});\n\n @override\n Widget build(BuildContext context) {\n return LayoutBuilder(\n builder: (context, constraints) {\n final isWide = constraints.maxWidth > 400;\n return Card(\n elevation: 2,\n shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),\n child: Padding(\n padding: const EdgeInsets.all(16),\n child: isWide\n ? Row(\n children: [\n CircleAvatar(radius: 40, backgroundImage: NetworkImage(avatarUrl)),\n const SizedBox(width: 16),\n Expanded(child: Text(name, style: Theme.of(context).textTheme.headlineSmall)),\n ElevatedButton(onPressed: () {}, child: const Text('Подписаться')),\n ],\n )\n : Column(\n mainAxisSize: MainAxisSize.min,\n children: [\n CircleAvatar(radius: 40, backgroundImage: NetworkImage(avatarUrl)),\n const SizedBox(height: 12),\n Text(name, style: Theme.of(context).textTheme.headlineSmall),\n const SizedBox(height: 8),\n ElevatedButton(onPressed: () {}, child: const Text('Подписаться')),\n ],\n ),\n ),\n );\n },\n );\n }\n}\n\nТакой подход устраняет необходимость в медиа-запросах и делает код самодостаточным.\n\n## Продвинутая категория: State Management\n\nПереход от простых виджетов к управлению состоянием — ключевой этап роста Flutter-разработчика. Промты в этой категории должны учитывать архитектурные паттерны (BLoC, Riverpod, Provider) и принципы SOLID.\n\n### 3. Миграция с Provider на Riverpod\n\nЗадача: Автоматизировать замену Provider на Riverpod в существующем проекте с сохранением функциональности.\n\nПромт:\n\nУ меня есть Flutter-проект, использующий Provider версии 6.0. Мне нужно мигрировать на Riverpod (версия 2.5+). Каждый Provider определён в отдельном файле. Пример:\n- ChangeNotifierProvider<UserProvider>(create: (_) => UserProvider())\n- FutureProvider<List<Post>>(create: (_) => fetchPosts())\n- ProxyProvider<AuthProvider, ConfigProvider>((_, auth, __) => ConfigProvider(auth.token))\nСгенерируй эквиваленты на Riverpod: StateNotifierProvider, FutureProvider.family, Provider. Учти, что в ProxyProvider была зависимость от AuthProvider. Используй ref.watch для чтения. Добавь тесты для каждого провайдера с помощью RiverpodHarness.\n\n\nПример результата:\ndart\n// Вместо ChangeNotifierProvider\nfinal userProvider = StateNotifierProvider<UserNotifier, UserState>((ref) {\n return UserNotifier(ref.read(authProvider));\n});\n\n// Вместо FutureProvider\nfinal postsProvider = FutureProvider.family<List<Post>, int>((ref, userId) async {\n final api = ref.read(apiProvider);\n return api.fetchPosts(userId);\n});\n\n// Вместо ProxyProvider\nfinal configProvider = Provider<ConfigProvider>((ref) {\n final auth = ref.watch(authProvider);\n return ConfigProvider(auth.token);\n});\n\nТесты для Riverpod пишутся через ProviderContainer:\ndart\nvoid main() {\n test('UserProvider initial state is loading', () {\n final container = ProviderContainer();\n addTearDown(() => container.dispose());\n expect(container.read(userProvider), const UserState.loading());\n });\n}\n\n\n### 4. Реализация BLoC с Event/State классами\n\nЗадача: Создать BLoC для формы входа с валидацией, загрузкой и обработкой ошибок.\n\nПромт:\n\nНапиши три файла для BLoC-архитектуры (используя flutter_bloc 8.1+):\n1. login_event.dart: события LoginSubmitted, EmailChanged, PasswordChanged\n2. login_state.dart: состояния LoginInitial, LoginLoading, LoginSuccess, LoginError(String message)\n3. login_bloc.dart: сам BLoC, который валидирует email (regex) и пароль (длина >= 6), эмитит LoginLoading, вызывает асинхронную функцию authenticate(String email, String password), которая возвращает LoginResult. При успехе — LoginSuccess, при ошибке — LoginError. Используй StreamSubscription для отмены запроса при новом событии.\nДобавь комментарии на русском языке для каждого метода.\n\n\nПример результата:\ndart\n// login_event.dart\npart of 'login_bloc.dart';\n\n@immutable\nsealed class LoginEvent {}\n\nfinal class LoginSubmitted extends LoginEvent {\n final String email;\n final String password;\n LoginSubmitted({required this.email, required this.password});\n}\n\nfinal class EmailChanged extends LoginEvent {\n final String email;\n EmailChanged(this.email);\n}\n\nfinal class PasswordChanged extends LoginEvent {\n final String password;\n PasswordChanged(this.password);\n}\n\n\ndart\n// login_state.dart\npart of 'login_bloc.dart';\n\n@immutable\nsealed class LoginState {}\n\nfinal class LoginInitial extends LoginState {}\nfinal class LoginLoading extends LoginState {}\nfinal class LoginSuccess extends LoginState {}\nfinal class LoginError extends LoginState {\n final String message;\n LoginError(this.message);\n}\n\n\ndart\n// login_bloc.dart\nimport 'package:flutter_bloc/flutter_bloc.dart';\n\nclass LoginBloc extends Bloc<LoginEvent, LoginState> {\n final Future<LoginResult> Function(String email, String password) authenticate;\n\n LoginBloc({required this.authenticate}) : super(LoginInitial()) {\n on<LoginSubmitted>(_onSubmitted);\n on<EmailChanged>((event, emit) => emit(state)); // Просто обновляем поле\n on<PasswordChanged>((event, emit) => emit(state));\n }\n\n Future<void> _onSubmitted(LoginSubmitted event, Emitter<LoginState> emit) async {\n // Валидация\n final emailRegex = RegExp(r'^[\\w-\\.]+@([\\w-]+\\.)+[\\w-]{2,4}$');\n if (!emailRegex.hasMatch(event.email)) {\n emit(LoginError('Некорректный email'));\n return;\n }\n if (event.password.length < 6) {\n emit(LoginError('Пароль должен быть не менее 6 символов'));\n return;\n }\n\n emit(LoginLoading());\n try {\n final result = await authenticate(event.email, event.password);\n if (result.success) {\n emit(LoginSuccess());\n } else {\n emit(LoginError(result.error ?? 'Ошибка входа'));\n }\n } catch (e) {\n emit(LoginError('Ошибка соединения: $e'));\n }\n }\n}\n\n\n### 5. Асинхронная загрузка данных с Riverpod\n\nЗадача: Реализовать пагинацию для списка постов с помощью Riverpod.\n\nПромт:\n\nСоздай Riverpod-провайдер для бесконечной загрузки постов. Используй AsyncNotifierProvider с состоянием AsyncValue<List<Post>>. Провайдер должен:\n- Загружать первые 20 постов при инициализации\n- Поддерживать метод loadMore(), который добавляет следующие 20 постов\n- Показывать индикатор загрузки в конце списка\n- Обрабатывать ошибки и повторные попытки\nНе используй код-генерацию (@riverpod) — только ручное написание.\n\n\nПример результата:\ndart\nclass PostListNotifier extends AsyncNotifier<List<Post>> {\n int _page = 0;\n final int _pageSize = 20;\n\n @override\n Future<List<Post>> build() async {\n _page = 0;\n return _fetchPage(_page);\n }\n\n Future<List<Post>> _fetchPage(int page) async {\n final api = ref.read(apiProvider);\n return api.getPosts(page: page, limit: _pageSize);\n }\n\n Future<void> loadMore() async {\n final currentState = state.valueOrNull ?? [];\n state = AsyncData([...currentState, ...AsyncLoading()]); // Показываем загрузку\n try {\n _page++;\n final newPosts = await _fetchPage(_page);\n state = AsyncData([...currentState, ...newPosts]);\n } catch (e, st) {\n state = AsyncError(e, st);\n }\n }\n}\n\nfinal postListProvider = AsyncNotifierProvider<PostListNotifier, List<Post>>(() {\n return PostListNotifier();\n});\n\nВ UI это используется так:\ndart\nfinal postsAsync = ref.watch(postListProvider);\npostsAsync.when(\n data: (posts) => ListView.builder(\n itemCount: posts.length + 1,\n itemBuilder: (context, index) {\n if (index == posts.length) {\n ref.read(postListProvider.notifier).loadMore();\n return const Center(child: CircularProgressIndicator());\n }\n return Text(posts[index].title);\n },\n ),\n loading: () => const Center(child: CircularProgressIndicator()),\n error: (e, _) => Center(child: Text('Ошибка: $e')),\n);\n\n\n## Экспертная категория: Анимации и производительность\n\nАнимации в Flutter — это не только про визуальную красоту, но и про оптимизацию FPS, использование RepaintBoundary и кастомных AnimationController.\n\n### 6. Кастомная анимация с использованием TweenSequence\n\nЗадача: Создать анимацию появления карточки с эффектом «пружины» и затемнением фона.\n\nПромт:\n\nСоздай StatefulWidget AnimatedCard, который при появлении выполняет последовательность анимаций:\n1. Фон затемняется от 0 до 0.5 opacity (Curve: easeOut)\n2. Карточка масштабируется от 0.8 до 1.0 с эффектом overshoot (используй Spring Curve)\n3. Содержимое карточки появляется с FadeTransition\nИспользуй TweenSequenceBuilder для объединения. Длительность каждой анимации: 300 мс. Добавь RepaintBoundary для оптимизации.\n\n\nПример результата:\ndart\nclass AnimatedCard extends StatefulWidget {\n final Widget child;\n const AnimatedCard({super.key, required this.child});\n\n @override\n State<AnimatedCard> createState() => _AnimatedCardState();\n}\n\nclass _AnimatedCardState extends State<AnimatedCard> with SingleTickerProviderStateMixin {\n late AnimationController _controller;\n late Animation<double> _opacity;\n late Animation<double> _scale;\n late Animation<double> _contentOpacity;\n\n @override\n void initState() {\n super.initState();\n _controller = AnimationController(vsync: this, duration: const Duration(milliseconds: 900));\n _opacity = Tween<double>(begin: 0, end: 0.5).animate(\n CurvedAnimation(parent: _controller, curve: const Interval(0.0, 0.33, curve: Curves.easeOut)),\n );\n _scale = Tween<double>(begin: 0.8, end: 1.0).animate(\n CurvedAnimation(parent: _controller, curve: const Interval(0.33, 0.66, curve: Curves.elasticOut)),\n );\n _contentOpacity = Tween<double>(begin: 0, end: 1).animate(\n CurvedAnimation(parent: _controller, curve: const Interval(0.66, 1.0, curve: Curves.easeIn)),\n );\n _controller.forward();\n }\n\n @override\n Widget build(BuildContext context) {\n return AnimatedBuilder(\n animation: _controller,\n builder: (context, child) {\n return Stack(\n children: [\n Opacity(opacity: _opacity.value, child: Container(color: Colors.black)),\n Transform.scale(\n scale: _scale.value,\n child: Opacity(opacity: _contentOpacity.value, child: widget.child),\n ),\n ],\n );\n },\n );\n }\n\n @override\n void dispose() {\n _controller.dispose();\n super.dispose();\n }\n}\n\n\n### 7. Анимация списка с StaggeredAnimations\n\nЗадача: Реализовать анимацию появления элементов списка с задержкой.\n\nПромт:\n\nСоздай StatelessWidget StaggeredList, который принимает список виджетов и анимирует их появление с задержкой в 50 мс между элементами. Используй SliverAnimatedList или кастомный AnimatedBuilder с AnimationController. Каждый элемент должен сдвигаться снизу вверх и появляться с FadeTransition. Оптимизируй через RepaintBoundary вокруг каждого элемента.\n\n\nПример результата:\ndart\nclass StaggeredList extends StatelessWidget {\n final List<Widget> children;\n const StaggeredList({super.key, required this.children});\n\n @override\n Widget build(BuildContext context) {\n return ListView.builder(\n itemCount: children.length,\n itemBuilder: (context, index) {\n return _AnimatedListItem(\n index: index,\n child: children[index],\n );\n },\n );\n }\n}\n\nclass _AnimatedListItem extends StatefulWidget {\n final int index;\n final Widget child;\n const _AnimatedListItem({required this.index, required this.child});\n\n @override\n State<_AnimatedListItem> createState() => _AnimatedListItemState();\n}\n\nclass _AnimatedListItemState extends State<_AnimatedListItem> with SingleTickerProviderStateMixin {\n late AnimationController _controller;\n late Animation<Offset> _offset;\n late Animation<double> _opacity;\n\n @override\n void initState() {\n super.initState();\n _controller = AnimationController(\n vsync: this,\n duration: const Duration(milliseconds: 300),\n );\n final delay = Duration(milliseconds: widget.index * 50);\n Future.delayed(delay, () => _controller.forward());\n _offset = Tween<Offset>(begin: const Offset(0, 0.5), end: Offset.zero).animate(\n CurvedAnimation(parent: _controller, curve: Curves.easeOut),\n );\n _opacity = Tween<double>(begin: 0, end: 1).animate(_controller);\n }\n\n @override\n Widget build(BuildContext context) {\n return RepaintBoundary(\n child: SlideTransition(\n position: _offset,\n child: FadeTransition(\n opacity: _opacity,\n child: widget.child,\n ),\n ),\n );\n }\n\n @override\n void dispose() {\n _controller.dispose();\n super.dispose();\n }\n}\n\n\n### 8. Использование Rive для сложных анимаций\n\nЗадача: Интегрировать Rive-анимацию (файл .riv) с триггерами по событиям.\n\nПромт:\n\nНапиши виджет RiveAnimationWidget, который загружает файл animation.riv из assets, включает анимацию по умолчанию (idle) и переключается на анимацию (success) при вызове метода triggerSuccess(). Используй пакет rive 0.13+. Добавь обработку ошибок загрузки и состояние загрузки. Анимация должна быть StateMachine-based.\n\n\nПример результата:\ndart\nimport 'package:rive/rive.dart';\n\nclass RiveAnimationWidget extends StatefulWidget {\n const RiveAnimationWidget({super.key});\n\n @override\n State<RiveAnimationWidget> createState() => _RiveAnimationWidgetState();\n}\n\nclass _RiveAnimationWidgetState extends State<RiveAnimationWidget> {\n Artboard? _artboard;\n StateMachineController? _controller;\n SMITrigger? _successTrigger;\n\n @override\n void initState() {\n super.initState();\n _loadRive();\n }\n\n Future<void> _loadRive() async {\n try {\n final file = await RiveFile.asset('assets/animation.riv');\n final artboard = file.mainArtboard;\n _controller = StateMachineController.fromArtboard(artboard, 'State Machine 1');\n if (_controller != null) {\n artboard.addController(_controller!);\n _successTrigger = _controller!.findInput<bool>('success') as SMITrigger?;\n }\n setState(() => _artboard = artboard);\n } catch (e) {\n debugPrint('Ошибка загрузки Rive: $e');\n }\n }\n\n void triggerSuccess() {\n _successTrigger?.fire();\n }\n\n @override\n Widget build(BuildContext context) {\n if (_artboard == null) {\n return const Center(child: CircularProgressIndicator());\n }\n return Rive(artboard: _artboard!);\n }\n\n @override\n void dispose() {\n _controller?.dispose();\n super.dispose();\n }\n}\n\n\n### 9. Оптимизация анимаций с помощью RepaintBoundary и const\n\nЗадача: Профилировать и оптимизировать анимацию, которая вызывает перестройку всего дерева виджетов.\n\nПромт:\n\nУ меня есть виджет, который содержит анимированный круг (AnimationController с 60 FPS). Этот круг находится внутри сложного layout с текстом, изображениями и кнопками. При анимации весь layout перерисовывается каждый кадр. Предложи оптимизацию: используй RepaintBoundary вокруг анимированной части, вынеси статические виджеты в const, и примените AnimatedBuilder вместо setState. Напиши исправленный код с комментариями, где и почему добавлен RepaintBoundary.\n\n\nПример результата:\ndart\n// До оптимизации: весь Scaffold перерисовывается\n// После:\nclass OptimizedAnimation extends StatefulWidget {\n const OptimizedAnimation({super.key});\n\n @override\n State<OptimizedAnimation> createState() => _OptimizedAnimationState();\n}\n\nclass _OptimizedAnimationState extends State<OptimizedAnimation> with SingleTickerProviderStateMixin {\n late AnimationController _controller;\n late Animation<double> _animation;\n\n @override\n void initState() {\n super.initState();\n _controller = AnimationController(vsync: this, duration: const Duration(seconds: 2));\n _animation = Tween<double>(begin: 0, end: 1).animate(_controller);\n _controller.repeat(reverse: true);\n }\n\n @override\n Widget build(BuildContext context) {\n return Scaffold(\n appBar: AppBar(title: const Text('Оптимизировано')), // const — не перерисовывается\n body: Column(\n children: [\n const Text('Статический текст'),\n const SizedBox(height: 20),\n // Только анимированная часть внутри RepaintBoundary\n RepaintBoundary(\n child: AnimatedBuilder(\n animation: _animation,\n builder: (context, child) {\n return Container(\n width: 100,\n height: 100,\n decoration: BoxDecoration(\n shape: BoxShape.circle,\n color: Colors.blue.withOpacity(_animation.value),\n ),\n );\n },\n ),\n ),\n const SizedBox(height: 20),\n const Text('Ещё статический контент'),\n ],\n ),\n );\n }\n\n @override\n void dispose() {\n _controller.dispose();\n super.dispose();\n }\n}\n\n\n### 10. Интеграция анимаций с жестами (GestureDetector + AnimationController)\n\nЗадача: Создать виджет, который анимированно реагирует на свайп.\n\nПромт:\n\nСоздай StatefulWidget SwipeableCard, который:\n- При свайпе влево/вправо (горизонтальный Drag) смещается по оси X с привязкой к пальцу\n- При отпускании, если смещение больше 100px, анимация завершается вылетом за экран (DismissDirection)\n- Если меньше — возвращается в исходное положение (SpringAnimation)\n- Фон карточки меняется от белого к красному (при свайпе влево) или зелёному (вправо)\nИспользуй AnimationController с 60 FPS и Curves.easeOut. Добавь RepaintBoundary.\n\n\nПример результата:\ndart\nclass SwipeableCard extends StatefulWidget {\n final Widget child;\n final VoidCallback? onSwipedLeft;\n final VoidCallback? onSwipedRight;\n const SwipeableCard({super.key, required this.child, this.onSwipedLeft, this.onSwipedRight});\n\n @override\n State<SwipeableCard> createState() => _SwipeableCardState();\n}\n\nclass _SwipeableCardState extends State<SwipeableCard> with SingleTickerProviderStateMixin {\n late AnimationController _controller;\n late Animation<Offset> _slideAnimation;\n late Animation<double> _opacityAnimation;\n double _startX = 0;\n double _currentX = 0;\n bool _isDragging = false;\n\n @override\n void initState() {\n super.initState();\n _controller = AnimationController(vsync: this, duration: const Duration(milliseconds: 300));\n _slideAnimation = Tween<Offset>(begin: Offset.zero, end: Offset.zero).animate(_controller);\n _opacityAnimation = Tween<double>(begin: 1, end: 1).animate(_controller);\n }\n\n void _onPanStart(DragStartDetails details) {\n _startX = _currentX;\n _isDragging = true;\n _controller.stop();\n }\n\n void _onPanUpdate(DragUpdateDetails details) {\n setState(() {\n _currentX = _startX + details.delta.dx;\n // Меняем цвет фона в зависимости от направления\n });\n }\n\n void _onPanEnd(DragEndDetails details) {\n _isDragging = false;\n if (_currentX.abs() > 100) {\n // Вылетаем за экран\n final target = _currentX > 0 ? Offset(2, 0) : Offset(-2, 0);\n _slideAnimation = Tween<Offset>(begin: Offset(_currentX, 0), end: target).animate(\n CurvedAnimation(parent: _controller, curve: Curves.easeIn),\n );\n _opacityAnimation = Tween<double>(begin: 1, end: 0).animate(_controller);\n _controller.forward().then((_) {\n if (_currentX > 0) widget.onSwipedRight?.call();\n else widget.onSwipedLeft?.call();\n });\n } else {\n // Возвращаемся\n _slideAnimation = Tween<Offset>(begin: Offset(_currentX, 0), end: Offset.zero).animate(\n CurvedAnimation(parent: _controller, curve: Curves.elasticOut),\n );\n _opacityAnimation = Tween<double>(begin: 1, end: 1).animate(_controller);\n _controller.forward();\n }\n _controller.reset();\n }\n\n @override\n Widget build(BuildContext context) {\n return RepaintBoundary(\n child: GestureDetector(\n onPanStart: _onPanStart,\n onPanUpdate: _onPanUpdate,\n onPanEnd: _onPanEnd,\n child: AnimatedBuilder(\n animation: _slideAnimation,\n builder: (context, child) {\n return Transform.translate(\n offset: _isDragging ? Offset(_currentX, 0) : _slideAnimation.value,\n child: Opacity(\n opacity: _isDragging ? 1 : _opacityAnimation.value,\n child: Container(\n color: _currentX < 0 ? Colors.red.withOpacity(0.2) : Colors.green.withOpacity(0.2),\n child: widget.child,\n ),\n ),\n );\n },\n ),\n ),\n );\n }\n\n @override\n void dispose() {\n _controller.dispose();\n super.dispose();\n }\n}\n\n\n## Заключение\n\nПромты — это не замена навыков, а инструмент для ускорения рутинных задач. За три года использования AI в Flutter-разработке я понял главное: качество ответа напрямую зависит от качества запроса. Указывайте версии пакетов, описывайте архитектурные ограничения, добавляйте примеры входных данных. Моя подборка покрывает 80% типовых задач — от создания виджетов до сложных анимаций с Rive. Но не останавливайтесь на этом: адаптируйте промты под свои проекты, добавляйте ссылки на документацию (например, Flutter docs для AnimationController или Riverpod docs для провайдеров). И помните: лучший код — тот, который вы понимаете. AI помогает писать быстрее, но архитектурные решения остаются за вами.\n\nНачните с малого: возьмите первый промт из категории «Виджеты» и примените его в своём проекте. Через неделю вы заметите, как сократилось время на написание boilerplate-кода. Через месяц — как улучшилось качество анимаций. Удачи в разработке!",
"excerpt": "Экспертная подборка из
Промты для Flutter: виджеты, state management, анимации
Читайте также
Интеграция Banana Pi с AI-агентом ASI Biont: автоматизация на одноплатнике без единой строки кода
14 июля 2026
Telegram Bot Development: как автоматизировать бизнес и заработать на ботах в 2026 году
14 июля 2026
AI-агент оживляет завод: интеграция Modbus RTU (RS-485) с ASI Biont для предиктивного обслуживания
14 июля 2026
Uber не хочет быть «всем для всех»: что на самом деле сказал продакт-директор про отели, роботакси и будущее платформы
14 июля 2026
Курс «Промышленный интернет вещей (IIoT) и системы SCADA»: ваш путь к Индустрии 4.0 в 2026 году
14 июля 2026
ИИ незаметно меняет мнения пользователей в соцсетях: как алгоритмы формируют нашу реальность
14 июля 2026
CKA + CKAD — Kubernetes Administrator & Developer: как подготовиться к сертификации в 2026 году с AI-тьютором
14 июля 2026
Как перестать терять сделки из-за языка: обзор курса «Английский для бизнеса» на asibiont.com
14 июля 2026
Трансформационное лидерство и стратегическое мышление CEO: Программа для основателей на уровне Гарварда, желающих овладеть принятием решений
14 июля 2026
Комментарии