11import 'package:flutter/material.dart' ;
22import 'package:flutter_svg/svg.dart' ;
3+ import 'package:flutter_swipe_action_cell/core/cell.dart' ;
34import 'package:on_time_front/core/validation/backend_constraints.dart' ;
45import 'package:on_time_front/l10n/app_localizations.dart' ;
56import 'package:on_time_front/presentation/onboarding/preparation_name_select/input_models/preparation_name_input_model.dart' ;
@@ -18,7 +19,9 @@ class PreparationFormListField extends StatefulWidget {
1819 this .onNameFocusLost,
1920 this .onPreparationTimeChanged,
2021 this .onPreparationTimeTapped,
22+ this .onRemove,
2123 this .onNameSaved,
24+ this .canRemove = true ,
2225 this .isAdding = false ,
2326 this .showValidationErrors = false ,
2427 this .focusNode,
@@ -30,7 +33,9 @@ class PreparationFormListField extends StatefulWidget {
3033 final ValueChanged <String >? onNameFocusLost;
3134 final ValueChanged <Duration >? onPreparationTimeChanged;
3235 final VoidCallback ? onPreparationTimeTapped;
36+ final VoidCallback ? onRemove;
3337 final VoidCallback ? onNameSaved;
38+ final bool canRemove;
3439 final bool isAdding;
3540 final bool showValidationErrors;
3641 final FocusNode ? focusNode;
@@ -139,6 +144,125 @@ class _PreparationFormListFieldState extends State<PreparationFormListField> {
139144 };
140145 }
141146
147+ Widget _buildTile ({
148+ required BuildContext context,
149+ required String ? nameErrorText,
150+ required String ? timeErrorText,
151+ }) {
152+ final textTheme = Theme .of (context).textTheme;
153+ final colorScheme = Theme .of (context).colorScheme;
154+ return Tile (
155+ key: ValueKey <String >(widget.preparationStep.id),
156+ style: TileStyle (padding: EdgeInsets .fromLTRB (21 , 19 , 21 , 19 )),
157+ leading: widget.index == null
158+ ? dragIndicatorSvg
159+ : ReorderableDragStartListener (
160+ index: widget.index! ,
161+ child: dragIndicatorSvg,
162+ ),
163+ trailing: PreparationTimeInput (
164+ time: widget.preparationStep.preparationTime.value,
165+ hasError: timeErrorText != null ,
166+ onTap: widget.onPreparationTimeTapped,
167+ onPreparationTimeChanged: widget.onPreparationTimeChanged,
168+ ),
169+ child: Expanded (
170+ child: Padding (
171+ padding: const EdgeInsets .symmetric (horizontal: 18.0 ),
172+ child: Container (
173+ constraints: BoxConstraints (minHeight: 30 ),
174+ child: Center (
175+ child: TextFormField (
176+ scrollPadding: EdgeInsets .only (
177+ bottom: MediaQuery .of (context).viewInsets.bottom + 56 ,
178+ ),
179+ initialValue: widget.preparationStep.preparationName.value,
180+ onChanged: (value) {
181+ _nameValue = value;
182+ widget.onNameChanged? .call (value);
183+ },
184+ onFieldSubmitted: (value) {
185+ _nameValue = value;
186+ widget.onNameFocusLost? .call (value);
187+ widget.onNameSaved? .call ();
188+ },
189+ onTapOutside: (event) {
190+ FocusManager .instance.primaryFocus? .unfocus ();
191+ },
192+ decoration: InputDecoration (
193+ isDense: true ,
194+ border: nameErrorText == null
195+ ? InputBorder .none
196+ : UnderlineInputBorder (
197+ borderSide: BorderSide (
198+ color: colorScheme.error,
199+ width: 1.5 ,
200+ ),
201+ ),
202+ enabledBorder: nameErrorText == null
203+ ? InputBorder .none
204+ : UnderlineInputBorder (
205+ borderSide: BorderSide (
206+ color: colorScheme.error,
207+ width: 1.5 ,
208+ ),
209+ ),
210+ focusedBorder: nameErrorText == null
211+ ? InputBorder .none
212+ : UnderlineInputBorder (
213+ borderSide: BorderSide (
214+ color: colorScheme.error,
215+ width: 2 ,
216+ ),
217+ ),
218+ contentPadding: EdgeInsets .all (3.0 ),
219+ ),
220+ style: textTheme.bodyLarge,
221+ focusNode: _effectiveFocusNode,
222+ ),
223+ ),
224+ ),
225+ ),
226+ ),
227+ );
228+ }
229+
230+ Widget _buildSwipeableTile ({
231+ required BuildContext context,
232+ required String ? nameErrorText,
233+ required String ? timeErrorText,
234+ }) {
235+ final tile = _buildTile (
236+ context: context,
237+ nameErrorText: nameErrorText,
238+ timeErrorText: timeErrorText,
239+ );
240+ if (widget.onRemove == null ) {
241+ return tile;
242+ }
243+
244+ return SwipeActionCell (
245+ key: ValueKey <String >('swipe_${widget .preparationStep .id }' ),
246+ backgroundColor: Colors .transparent,
247+ trailingActions: [
248+ SwipeAction (
249+ onTap: (controller) {
250+ if (! widget.canRemove) {
251+ return ;
252+ }
253+ widget.onRemove? .call ();
254+ },
255+ color: Colors .transparent,
256+ content: _SwipeActionContent (
257+ icon: const Icon (Icons .delete, color: Colors .white, size: 24 ),
258+ color: Theme .of (context).colorScheme.error,
259+ ),
260+ ),
261+ ],
262+ child: tile,
263+ );
264+ }
265+
142266 @override
143267 Widget build (BuildContext context) {
144268 final textTheme = Theme .of (context).textTheme;
@@ -151,80 +275,10 @@ class _PreparationFormListFieldState extends State<PreparationFormListField> {
151275 child: Column (
152276 crossAxisAlignment: CrossAxisAlignment .stretch,
153277 children: [
154- Tile (
155- key: ValueKey <String >(widget.preparationStep.id),
156- style: TileStyle (padding: EdgeInsets .fromLTRB (21 , 19 , 21 , 19 )),
157- leading: widget.index == null
158- ? dragIndicatorSvg
159- : ReorderableDragStartListener (
160- index: widget.index! ,
161- child: dragIndicatorSvg,
162- ),
163- trailing: PreparationTimeInput (
164- time: widget.preparationStep.preparationTime.value,
165- hasError: timeErrorText != null ,
166- onTap: widget.onPreparationTimeTapped,
167- onPreparationTimeChanged: widget.onPreparationTimeChanged,
168- ),
169- child: Expanded (
170- child: Padding (
171- padding: const EdgeInsets .symmetric (horizontal: 18.0 ),
172- child: Container (
173- constraints: BoxConstraints (minHeight: 30 ),
174- child: Center (
175- child: TextFormField (
176- scrollPadding: EdgeInsets .only (
177- bottom: MediaQuery .of (context).viewInsets.bottom + 56 ,
178- ),
179- initialValue:
180- widget.preparationStep.preparationName.value,
181- onChanged: (value) {
182- _nameValue = value;
183- widget.onNameChanged? .call (value);
184- },
185- onFieldSubmitted: (value) {
186- _nameValue = value;
187- widget.onNameFocusLost? .call (value);
188- widget.onNameSaved? .call ();
189- },
190- onTapOutside: (event) {
191- FocusManager .instance.primaryFocus? .unfocus ();
192- },
193- decoration: InputDecoration (
194- isDense: true ,
195- border: nameErrorText == null
196- ? InputBorder .none
197- : UnderlineInputBorder (
198- borderSide: BorderSide (
199- color: colorScheme.error,
200- width: 1.5 ,
201- ),
202- ),
203- enabledBorder: nameErrorText == null
204- ? InputBorder .none
205- : UnderlineInputBorder (
206- borderSide: BorderSide (
207- color: colorScheme.error,
208- width: 1.5 ,
209- ),
210- ),
211- focusedBorder: nameErrorText == null
212- ? InputBorder .none
213- : UnderlineInputBorder (
214- borderSide: BorderSide (
215- color: colorScheme.error,
216- width: 2 ,
217- ),
218- ),
219- contentPadding: EdgeInsets .all (3.0 ),
220- ),
221- style: textTheme.bodyLarge,
222- focusNode: _effectiveFocusNode,
223- ),
224- ),
225- ),
226- ),
227- ),
278+ _buildSwipeableTile (
279+ context: context,
280+ nameErrorText: nameErrorText,
281+ timeErrorText: timeErrorText,
228282 ),
229283 if (errorTexts.isNotEmpty)
230284 Padding (
@@ -250,3 +304,22 @@ class _PreparationFormListFieldState extends State<PreparationFormListField> {
250304 );
251305 }
252306}
307+
308+ class _SwipeActionContent extends StatelessWidget {
309+ const _SwipeActionContent ({required this .icon, required this .color});
310+
311+ final Widget icon;
312+ final Color color;
313+
314+ @override
315+ Widget build (BuildContext context) {
316+ return Container (
317+ decoration: BoxDecoration (
318+ borderRadius: BorderRadius .circular (12 ),
319+ color: color,
320+ ),
321+ padding: const EdgeInsets .all (18.0 ),
322+ child: icon,
323+ );
324+ }
325+ }
0 commit comments