feat(ui): extend AdaptiveTextField and migrate travel form to use them
What changed, and why it matters
This commit is a routine user-interface refactor. It extends a shared text-field widget so it can replace a travel-form-specific text field, and updates the travel booking form to use the shared widget. There is no security-relevant change visible in the diff.
No security action required. Review as normal UI code.
Security signals we found
No strong security signals were identified.
Evidence from the diff
The change adds optional parameters (hintText, enabled, onTap, suffixText, errorText, inputFormatters) to AdaptiveTextField and wires them through to a Flutter TextField. The travel form is migrated from ShopInBitStep4TextField to AdaptiveTextField, preserving existing behavior including autocorrect=false, enableSuggestions=false, FilteringTextInputFormatter.digitsOnly for numeric fields, and readOnly date pickers. No input validation, sanitization, network, crypto, or authentication logic is modified.
Changed components
lib/widgets/textfields/adaptive_text_field.dartlib/pages/shopinbit/step_4_components/shopinbit_travel_form.dartInspect captured patch +59 / −17
diff --git a/lib/pages/shopinbit/step_4_components/shopinbit_travel_form.dart b/lib/pages/shopinbit/step_4_components/shopinbit_travel_form.dart
index 16db676..80382eb 100644
--- a/lib/pages/shopinbit/step_4_components/shopinbit_travel_form.dart
+++ b/lib/pages/shopinbit/step_4_components/shopinbit_travel_form.dart
@@ -7,6 +7,7 @@ import "../../../providers/db/drift_provider.dart";
import "../../../providers/global/shopin_bit_service_provider.dart";
import "../../../utilities/text_styles.dart";
import "../../../utilities/util.dart";
+import "../../../widgets/textfields/adaptive_text_field.dart";
import "shopinbit_country_picker.dart";
import "shopinbit_labeled_checkbox.dart";
import "shopinbit_privacy_checkbox.dart";
@@ -14,7 +15,6 @@ import "shopinbit_step4_dropdown.dart";
import "shopinbit_step4_header.dart";
import "shopinbit_step4_submit.dart";
import "shopinbit_step4_submit_button.dart";
-import "shopinbit_step4_text_field.dart";
import "shopinbit_traveler_counter.dart";
const String _exactDates = "Exact dates";
@@ -354,14 +354,16 @@ class _ShopInBitTravelFormState extends ConsumerState<ShopInBitTravelForm> {
onChanged: (value) => setState(() => _selectedArrangement = value),
),
SizedBox(height: isDesktop ? 24 : 16),
- ShopInBitStep4TextField(
+ AdaptiveTextField(
controller: _arrangementDetailsController,
focusNode: _arrangementDetailsFocusNode,
- hintText:
+ labelText:
"Describe your specific requirements "
"(luggage, cabin class, hotel stars, etc.)",
minLines: 3,
maxLines: 6,
+ autocorrect: false,
+ enableSuggestions: false,
errorText: arrangementDetailsError,
onChanged: (_) => setState(() {}),
),
@@ -376,19 +378,23 @@ class _ShopInBitTravelFormState extends ConsumerState<ShopInBitTravelForm> {
hintText: "Departure country",
),
SizedBox(height: isDesktop ? 24 : 16),
- ShopInBitStep4TextField(
+ AdaptiveTextField(
controller: _departureCityController,
focusNode: _departureCityFocusNode,
- hintText: "Departure city",
+ labelText: "Departure city",
+ autocorrect: false,
+ enableSuggestions: false,
errorText: departureCityError,
onChanged: (_) => setState(() {}),
),
SizedBox(height: isDesktop ? 24 : 16),
- ShopInBitStep4TextField(
+ AdaptiveTextField(
controller: _destinationsController,
focusNode: _destinationsFocusNode,
- hintText: "Destination city",
+ labelText: "Destination city",
enabled: !_needsRecommendations,
+ autocorrect: false,
+ enableSuggestions: false,
errorText: destinationsError,
onChanged: (_) => setState(() {}),
),
@@ -411,31 +417,35 @@ class _ShopInBitTravelFormState extends ConsumerState<ShopInBitTravelForm> {
SizedBox(height: isDesktop ? 24 : 16),
if (_selectedDateMode == _exactDates) ...[
- ShopInBitStep4TextField(
+ AdaptiveTextField(
controller: _departureDateController,
focusNode: _departureDateFocusNode,
- hintText: "DD/MM/YYYY",
labelText: "Departure date",
+ hintText: "DD/MM/YYYY",
readOnly: true,
onTap: () => _pickDate(
_departureDateController,
() => _departureDateTouched = true,
),
- suffixIcon: const Icon(Icons.calendar_today, size: 18),
+ suffixIcons: const [Icon(Icons.calendar_today, size: 18)],
+ autocorrect: false,
+ enableSuggestions: false,
errorText: departureDateError,
),
SizedBox(height: isDesktop ? 24 : 16),
- ShopInBitStep4TextField(
+ AdaptiveTextField(
controller: _returnDateController,
focusNode: _returnDateFocusNode,
- hintText: "DD/MM/YYYY",
labelText: "Return date",
+ hintText: "DD/MM/YYYY",
readOnly: true,
onTap: () => _pickDate(
_returnDateController,
() => _returnDateTouched = true,
),
- suffixIcon: const Icon(Icons.calendar_today, size: 18),
+ suffixIcons: const [Icon(Icons.calendar_today, size: 18)],
+ autocorrect: false,
+ enableSuggestions: false,
errorText: returnDateError,
),
SizedBox(height: isDesktop ? 24 : 16),
@@ -462,12 +472,14 @@ class _ShopInBitTravelFormState extends ConsumerState<ShopInBitTravelForm> {
onChanged: (value) => setState(() => _selectedMonthSeason = value),
),
SizedBox(height: isDesktop ? 24 : 16),
- ShopInBitStep4TextField(
+ AdaptiveTextField(
controller: _tripLengthController,
focusNode: _tripLengthFocusNode,
- hintText: "Number of nights",
+ labelText: "Number of nights",
keyboardType: TextInputType.number,
inputFormatters: [FilteringTextInputFormatter.digitsOnly],
+ autocorrect: false,
+ enableSuggestions: false,
errorText: tripLengthError,
onChanged: (_) => setState(() {}),
),
@@ -504,13 +516,15 @@ class _ShopInBitTravelFormState extends ConsumerState<ShopInBitTravelForm> {
SizedBox(height: isDesktop ? 24 : 16),
_TravelSectionLabel(text: "Budget", isDesktop: isDesktop),
SizedBox(height: isDesktop ? 12 : 8),
- ShopInBitStep4TextField(
+ AdaptiveTextField(
controller: _travelBudgetController,
focusNode: _travelBudgetFocusNode,
- hintText: "Minimum 1000 EUR",
+ labelText: "Minimum 1000 EUR",
keyboardType: TextInputType.number,
inputFormatters: [FilteringTextInputFormatter.digitsOnly],
suffixText: "EUR",
+ autocorrect: false,
+ enableSuggestions: false,
errorText: travelBudgetError,
onChanged: (_) => setState(() {}),
),
diff --git a/lib/widgets/textfields/adaptive_text_field.dart b/lib/widgets/textfields/adaptive_text_field.dart
index da30057..95d4543 100644
--- a/lib/widgets/textfields/adaptive_text_field.dart
+++ b/lib/widgets/textfields/adaptive_text_field.dart
@@ -13,23 +13,30 @@ class AdaptiveTextField extends StatefulWidget {
const AdaptiveTextField({
super.key,
this.labelText,
+ this.hintText,
this.controller,
this.focusNode,
this.autocorrect,
this.readOnly = false,
+ this.enabled = true,
this.enableSuggestions = true,
this.onChanged,
this.onChangedComprehensive,
this.onSubmitted,
+ this.onTap,
this.suffixIcons,
+ this.suffixText,
+ this.errorText,
this.contentPadding,
this.minLines,
this.maxLines,
+ this.inputFormatters,
this.showPasteClearButton = false,
this.keyboardType,
});
final String? labelText;
+ final String? hintText;
final TextEditingController? controller;
final FocusNode? focusNode;
@@ -39,11 +46,13 @@ class AdaptiveTextField extends StatefulWidget {
final int? maxLines;
final bool readOnly;
+ final bool enabled;
final bool enableSuggestions;
final void Function(String)? onChanged;
final void Function(String)? onChangedComprehensive;
final void Function(String)? onSubmitted;
+ final VoidCallback? onTap;
/// This will be ignored if [suffixIcons] is not null!
final bool showPasteClearButton;
@@ -51,6 +60,15 @@ class AdaptiveTextField extends StatefulWidget {
/// If this is not null, [showPasteClearButton] will be ignored.
final List<Widget>? suffixIcons;
+ /// Optional trailing text rendered in the decoration's suffixText slot.
+ /// Ignored when [suffixIcons] is non-empty or [showPasteClearButton] is
+ /// true, since those occupy the same visual space.
+ final String? suffixText;
+
+ final String? errorText;
+
+ final List<TextInputFormatter>? inputFormatters;
+
final TextInputType? keyboardType;
@override
@@ -111,17 +129,27 @@ class _AdaptiveTextFieldState extends State<AdaptiveTextField> {
controller: controller,
focusNode: _focusNode,
onChanged: widget.onChanged,
+ onTap: widget.onTap,
readOnly: widget.readOnly,
+ enabled: widget.enabled,
autocorrect: widget.autocorrect,
enableSuggestions: widget.enableSuggestions,
onSubmitted: widget.onSubmitted,
keyboardType: widget.keyboardType,
+ inputFormatters: widget.inputFormatters,
decoration:
standardInputDecoration(
widget.labelText,
_focusNode,
context,
).copyWith(
+ hintText: widget.hintText,
+ errorText: widget.errorText,
+ suffixText:
+ (widget.suffixIcons?.isNotEmpty != true &&
+ !widget.showPasteClearButton)
+ ? widget.suffixText
+ : null,
contentPadding:
widget.contentPadding ??
(Util.isDesktop
Why this scored 15/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.