بنقرة واحدة
screen
Генерация и рефакторинг Flutter экранов для Beeline v4 с Clean Architecture + BLoC + Design System
التثبيت باستخدام Codex أو Claude انسخ هذا Prompt والصقه في Codex أو Claude أو مساعد آخر ليراجع صفحة Skill ويثبّتها لك.
القائمة
Генерация и рефакторинг Flutter экранов для Beeline v4 с Clean Architecture + BLoC + Design System
التثبيت باستخدام Codex أو Claude انسخ هذا Prompt والصقه في Codex أو Claude أو مساعد آخر ليراجع صفحة Skill ويثبّتها لك.
استنادا إلى تصنيف SOC المهني
BLoC pattern implementation with events, states, and best practices. Use when implementing state management with BLoC, creating new BLoCs, or when user asks about "BLoC", "events", "states", or "flutter_bloc".
Domain/Data/Presentation layers with dependency rule enforcement. Use when structuring new features, organizing code, or when user asks about "clean architecture", "layers", or "separation of concerns".
GetIt and Injectable setup for dependency injection. Use when setting up DI, registering dependencies, or when user asks about "dependency injection", "GetIt", "Injectable", or "service locator".
Either/Result pattern and Failure classes for error handling. Use when implementing error handling, creating failure types, or when user asks about "error handling", "Either", "failures", or "exceptions".
Feature-first folder organization for Flutter projects. Use when creating new features, reorganizing code, or when user asks about "folder structure", "feature modules", or "project organization".
BLoC state transition testing with bloc_test package. Use when testing BLoCs, Cubits, or state management logic. Covers blocTest, state verification, and mock setup.
| name | screen |
| description | Генерация и рефакторинг Flutter экранов для Beeline v4 с Clean Architecture + BLoC + Design System |
✅ Использовать:
❌ НЕ использовать:
figma-integrationОпределить тип:
Действия:
# Проверить существующие файлы
ls packages/feature_*/lib/presentation/screens/
✅ Checklist:
Для нового feature module:
feature_name/lib/
├── domain/
│ ├── entities/
│ ├── repositories/
│ └── usecases/
├── data/
│ ├── datasources/
│ ├── models/
│ └── repositories/
├── presentation/
│ ├── bloc/
│ ├── screens/
│ └── widgets/
└── di/
Определить виджеты для выделения:
✅ Checklist:
Entity (domain/entities/):
class Payment extends Equatable {
final String id;
final double amount;
const Payment({required this.id, required this.amount});
@override
List<Object?> get props => [id, amount];
}
Repository interface (domain/repositories/):
abstract class PaymentRepository {
FutureResult<Payment> getPayment(String id);
FutureResult<void> createPayment(Payment payment);
}
UseCase (domain/usecases/):
class GetPaymentUseCase {
final PaymentRepository repository;
GetPaymentUseCase(this.repository);
FutureResult<Payment> call(String id) {
return repository.getPayment(id);
}
}
✅ Checklist:
FutureResult<T>Model (data/models/):
class PaymentModel {
final String id;
final double amount;
PaymentModel({required this.id, required this.amount});
factory PaymentModel.fromJson(Map<String, dynamic> json) => PaymentModel(
id: json['id'] as String,
amount: (json['amount'] as num).toDouble(),
);
Payment toEntity() => Payment(id: id, amount: amount);
}
DataSource (data/datasources/):
abstract class PaymentRemoteDataSource {
Future<PaymentModel> getPayment(String id);
}
class PaymentRemoteDataSourceImpl implements PaymentRemoteDataSource {
final HttpClient client;
@override
Future<PaymentModel> getPayment(String id) async {
final response = await client.get('/payments/$id');
return PaymentModel.fromJson(response.data);
}
}
Repository impl (data/repositories/):
class PaymentRepositoryImpl implements PaymentRepository {
final PaymentRemoteDataSource dataSource;
@override
FutureResult<Payment> getPayment(String id) async {
try {
final model = await dataSource.getPayment(id);
return Right(model.toEntity());
} catch (e) {
return Left(Failure.fromException(e));
}
}
}
✅ Checklist:
BLoC Events (presentation/bloc/):
sealed class PaymentEvent extends Equatable {
const PaymentEvent();
}
class LoadPaymentEvent extends PaymentEvent {
final String id;
const LoadPaymentEvent(this.id);
@override
List<Object?> get props => [id];
}
class SubmitPaymentEvent extends PaymentEvent {
final double amount;
const SubmitPaymentEvent(this.amount);
@override
List<Object?> get props => [amount];
}
BLoC States - определяются по потребностям экрана:
sealed class PaymentState extends Equatable {
const PaymentState();
}
// Состояния определяй исходя из UI:
// - Нужен loading? → добавь LoadingState
// - Нужен error? → добавь ErrorState
// - Какие данные показывать? → добавь соответствующие Loaded states
class PaymentLoadedState extends PaymentState {
final Payment payment;
const PaymentLoadedState(this.payment);
@override
List<Object?> get props => [payment];
}
class PaymentSubmittingState extends PaymentState {
final Payment payment; // сохраняем данные во время отправки
const PaymentSubmittingState(this.payment);
@override
List<Object?> get props => [payment];
}
class PaymentSuccessState extends PaymentState {
final String transactionId;
const PaymentSuccessState(this.transactionId);
@override
List<Object?> get props => [transactionId];
}
BLoC:
class PaymentBloc extends Bloc<PaymentEvent, PaymentState> {
final GetPaymentUseCase getPayment;
PaymentBloc(this.getPayment) : super(PaymentLoadedState(Payment.empty())) {
on<LoadPaymentEvent>(_onLoad);
on<SubmitPaymentEvent>(_onSubmit);
}
Future<void> _onLoad(LoadPaymentEvent event, Emitter<PaymentState> emit) async {
final result = await getPayment(event.id);
result.fold(
(failure) => emit(PaymentErrorState(failure.message)),
(payment) => emit(PaymentLoadedState(payment)),
);
}
}
Screen (presentation/screens/) - MAX 200 строк!
Выбор BlocBuilder vs BlocConsumer:
class PaymentScreen extends StatelessWidget {
const PaymentScreen({super.key});
@override
Widget build(BuildContext context) {
return BlocProvider(
create: (_) => GetIt.instance<PaymentBloc>()..add(const LoadPaymentEvent('1')),
child: Scaffold(
appBar: BeelineAppBar.primary(title: context.l10n.payment),
body: BlocConsumer<PaymentBloc, PaymentState>(
listener: (context, state) {
if (state is PaymentSuccessState) {
context.push('/payment/success/${state.transactionId}');
} else if (state is PaymentErrorState) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text(state.message)),
);
}
},
builder: (context, state) {
if (state is PaymentLoadedState) {
return PaymentContent(payment: state.payment);
} else if (state is PaymentSubmittingState) {
return PaymentContent(
payment: state.payment,
isSubmitting: true,
);
} else if (state is PaymentErrorState) {
return AppErrorStateVariants.networkError(
onRetry: () => context.read<PaymentBloc>().add(const LoadPaymentEvent('1')),
);
}
// Fallback - не должен срабатывать при правильной логике
return AppErrorStateVariants.loadingError(
onRetry: () => context.read<PaymentBloc>().add(const LoadPaymentEvent('1')),
);
},
),
),
);
}
}
✅ Checklist:
Применить ВСЕ правила:
| # | Правило | Что делать | Проверка |
|---|---|---|---|
| 1 | < 200 строк | Разбить на виджеты | wc -l file.dart |
| 2 | Inline vs функция | Inline если: <3 строк, 1 место. Функция если: много мест, >3-4 строк, нужно имя для читаемости | grep "Widget _build" |
| 3 | Нет event chains | Логика в UseCase/BLoC | Нет add() в handler |
| 4 | < 100 символов | Перенос строк | IDE warning |
| 5 | BLoC vs StatefulWidget | BLoC для API, Stateful для UI | — |
| 6 | ScreenUtil везде | .w .h .sp .r | grep "fontSize: [0-9]" |
| 7 | Абсолютные импорты | package:feature_* | Нет ../ |
| 8 | const для static | const Text(), const SizedBox() | — |
| 9 | Виджеты сразу | Определить структуру до кода | — |
| 10 | Failure.fromException | Единый класс ошибок | grep "ServerFailure" |
| 11 | Design System | AppColors.current, AppTextStyles | grep "Color(0x" |
| 12 | Удалить unused | Find Usages → Delete | grep "WidgetName" |
| 14 | Нет дубликатов | Extract shared widgets/utils | См. ниже |
| 13 | Локализация | context.l10n.* | grep "Text('.*')" |
Детали каждого правила: См. references/rules_quick_ref.md
cd packages/feature_name
dart analyze --fatal-infos
Быстрые проверки:
# Файлы > 200 строк
find lib/presentation -name "*.dart" -exec wc -l {} + | awk '$1 > 200'
# Hardcoded colors
grep -rE "Color\(0x[0-9A-F]{8}\)" lib/presentation/
# Hardcoded text
grep -rE "Text\s*\(\s*['\"]" lib/presentation/
# Old static colors
grep -rE "AppColors\.(primary|textPrimary)" lib/ | grep -v "AppColors.current"
# _build functions
grep -r "Widget _build" lib/presentation/
# Дублирующиеся виджеты
grep -rh "class.*extends.*Widget" lib/ | sed 's/class \([^ ]*\).*/\1/' | sort | uniq -d
# Дублирующиеся классы (entities, models, blocs)
grep -rh "^class " lib/ | sed 's/class \([^ ]*\).*/\1/' | sort | uniq -d
# Похожие методы - проверить вручную:
# - Одинаковые названия в разных файлах
# - Похожая логика (копипаст)
grep -rh "void \|Future<\|Widget " lib/ | sed 's/.*\(void\|Future\|Widget\) \([a-zA-Z_]*\).*/\2/' | sort | uniq -c | sort -rn | head -20
✅ Final Checklist:
dart analyze = 0 errors// ✅ С BuildContext (предпочтительно)
final colors = context.colors;
color: colors.primary
// ✅ Без BuildContext (BLoC, validators)
color: AppColors.current.primary
// ❌ FORBIDDEN
AppColors.primary // Статический доступ удален!
const TextStyle(color: colors.primary) // const + instance = ошибка!
Детали: См. references/design_system.md
// UI changes → BlocBuilder
BlocBuilder<Bloc, State>(builder: (context, state) => ...)
// Side effects (navigation, snackbar) → BlocListener
BlocListener<Bloc, State>(listener: (context, state) {
if (state is SuccessState) context.push('/success');
})
// Both → BlocConsumer
BlocConsumer<Bloc, State>(listener: ..., builder: ...)
Детали: См. references/bloc_patterns.md
context.push('/path/$id'); // Add to stack
context.go('/home'); // Replace stack
context.pop(); // Go back
context.push('/path', extra: data); // Pass data
class _FormScreenState extends State<FormScreen> {
final _formKey = GlobalKey<FormState>();
final _controller = TextEditingController();
@override
void dispose() {
_controller.dispose();
super.dispose();
}
void _submit() {
if (_formKey.currentState!.validate()) {
context.read<Bloc>().add(SubmitEvent(_controller.text));
}
}
}
// di/feature_injection.dart
gh.factory<PaymentRemoteDataSource>(() => PaymentRemoteDataSourceImpl(gh()));
gh.lazySingleton<PaymentRepository>(() => PaymentRepositoryImpl(gh()));
gh.lazySingleton<GetPaymentUseCase>(() => GetPaymentUseCase(gh()));
gh.factory<PaymentBloc>(() => PaymentBloc(gh()));
| Skill | Фокус | Результат |
|---|---|---|
| figma-integration | Pixel-perfect UI | Только presentation (виджеты) |
| screen | Clean Architecture | Domain + Data + Presentation |
Совместный workflow:
figma-integration → pixel-perfect виджеты с комментариямиscreen → рефакторинг в Clean Architecture (сохраняя комментарии)| Ошибка | Решение |
|---|---|
fontSize: 16 | fontSize: 16.sp |
Color(0xFFFFCD00) | colors.primary |
Text('Payments') | Text(context.l10n.payments) |
Widget _buildContent() | Inline или Widget class |
void _onTap() { bloc.add(...) } | Inline: onTap: () => bloc.add(...) |
AppColors.primary | AppColors.current.primary |
const TextStyle(color: colors.*) | Убрать const |
BorderRadius.circular() для bottom sheet | BorderRadius.only(top*) |
ServerFailure | Failure.fromException(e) |
return SizedBox.shrink() в builder | Fallback на error state |
if без else if в BlocBuilder | Использовать else if для states |
| Нет fallback в конце условий | Всегда else или финальный return с подходящим state |
references/rules_quick_ref.md - Детали всех 13 правил с bash-командамиreferences/architecture.md - Clean Architecture patternsreferences/design_system.md - Colors, TextStyles, компонентыreferences/bloc_patterns.md - Примеры BLoC из проектаreferences/localization.md - ARB файлы, placeholders, keysreferences/icons_guide.md - BeelineIcons, SVG colors🚀 Ready to generate screens for Beeline v4!