Loading...
Loading...
Flutter 위젯 UI 작성 패턴 — StatelessWidget 선호, `const` 최적화, `ListenableBuilder`로 ViewModel 관찰, `ListView.builder`/`IndexedStack`, 공용 컴포넌트 재사용, 접근성(Semantics), 다이얼로그·스낵바 처리. "위젯 만들기", "UI 만들기", "StatelessWidget", "ListView", "ListenableBuilder", "Scaffold", "스낵바", "다이얼로그", "디자인 토큰", "공용 컴포넌트" 같은 표현에 트리거합니다.
npx skill4agent add junsuk5/survival-flutter-skills flutter-widget-uistateonActionStatelessWidgetChangeNotifierListenableBuilderStatefulWidgetTextEditingControllerScrollControllerTabControllerinitStatedisposeconstconstSizedBox(height: N)constconst SizedBox(height: 10),
const ChefProfile(),
const Icon(Icons.share, size: 20),constListenableBuilderbuilderviewModel.statereturn ListenableBuilder(
listenable: viewModel,
builder: (context, _) {
final state = viewModel.state;
if (state.isLoading) return const Center(child: CircularProgressIndicator());
return IngredientScreen(state: state, onAction: viewModel.onAction);
},
);ListenableBuilderColumnListView.builderListView.builder(
itemCount: state.ingredients.length,
itemBuilder: (context, index) {
return Column(
children: [
IngredientItem(ingredient: state.ingredients[index]),
const SizedBox(height: 10),
],
);
},
)itemBuilderKey(item.id.toString())IndexedStackIndexedStack(
index: state.selectedTabIndex,
children: [IngredientList(state: state), ProcedureList(state: state)],
)lib/core/presentation/components/BigButtonMediumButtonSmallButtonSearchInputFieldInputFieldFilterButtonFilterButtonsRatingButtonRecipeCardNewRecipeCardDishCardIngredientRecipeCardRecipeGridItemTwoTabChefProfileIngredientItemProcedureItemlib/ui/color_styles.dartlib/ui/text_styles.dartText('1 serve', style: TextStyles.smallerTextRegular.copyWith(color: ColorStyles.gray3)),BuildContext// ingredient_root.dart
IngredientScreen(
state: viewModel.state,
onAction: viewModel.onAction,
onTapMenu: (menu) {
switch (menu) {
case IngredientMenu.share:
showDialog(
context: context,
builder: (_) => ShareDialog(
link: 'app.Recipe.co/jollof_rice',
onTapCopyLink: (link) {
viewModel.onAction(IngredientAction.onTapShareMenu(link));
Navigator.pop(context);
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('Link Copied', textAlign: TextAlign.center)),
);
},
),
);
// ...
}
},
),viewModel.onAction(...)Navigator.pop(context)ScaffoldAppBarSafeAreaPadding(horizontal: 30)Columningredient_screen.dartSafeAreaAppBaractionsPopupMenuButtonPopupMenuItemonTaponTapMenuTextFieldTextFormFieldStatefulWidgetTextEditingControllerTextField(
onChanged: (value) => onAction(SearchAction.onQueryChange(value)),
)Semantics(label: '...')IconButton(tooltip: '...')heightPaddingmainAxisSizeExpandedIconButtonInkWellconstitemBuilderAnimatedBuilderTweenAnimationBuilderImplicitlyAnimatedWidgetcacheWidthcacheHeightStatelessWidgetstateonActionconstListView.builderIndexedStackcore/presentation/componentsColorStylesTextStylesBuildContextgetIt<...>()context.push(...)setStateColorStylesTextStylesColumnListView.builderconst