From 08a04c5d70605a40edd726613a1bfcaf949a68dc Mon Sep 17 00:00:00 2001 From: sneurlax Date: Fri, 1 May 2026 12:16:41 -0500 Subject: [PATCH 1/5] fix(shopinbit): load offer price in ticket list/detail, add message polling, fix closed ticket messages --- .../shopinbit/shopinbit_ticket_detail.dart | 21 +++++++++++++++++++ .../shopinbit/shopinbit_tickets_view.dart | 21 +++++++++++-------- 2 files changed, 33 insertions(+), 9 deletions(-) diff --git a/lib/pages/shopinbit/shopinbit_ticket_detail.dart b/lib/pages/shopinbit/shopinbit_ticket_detail.dart index b59239f01b..85ceb97cf0 100644 --- a/lib/pages/shopinbit/shopinbit_ticket_detail.dart +++ b/lib/pages/shopinbit/shopinbit_ticket_detail.dart @@ -80,6 +80,7 @@ class _ShopInBitTicketDetailState extends State { bool _sending = false; bool _loading = false; bool _retrying = false; + Timer? _pollTimer; @override void initState() { @@ -87,11 +88,18 @@ class _ShopInBitTicketDetailState extends State { _messageController = TextEditingController(); if (widget.model.apiTicketId != 0) { _loadFromApi(); + if (!_isCarResearch) { + _pollTimer = Timer.periodic( + const Duration(seconds: 30), + (_) => _loadFromApi(), + ); + } } } @override void dispose() { + _pollTimer?.cancel(); _messageController.dispose(); super.dispose(); } @@ -130,6 +138,19 @@ class _ShopInBitTicketDetailState extends State { statusResp.value!.state, ); } + + if (widget.model.status == ShopInBitOrderStatus.offerAvailable && + (widget.model.offerProductName == null || + widget.model.offerPrice == null)) { + final offerResp = await client.getTicketFull(id); + if (!offerResp.hasError && offerResp.value != null) { + final t = offerResp.value!; + widget.model.setOffer( + productName: t.productName, + price: t.customerPrice, + ); + } + } } unawaited( diff --git a/lib/pages/shopinbit/shopinbit_tickets_view.dart b/lib/pages/shopinbit/shopinbit_tickets_view.dart index 220900a8f4..ce62d3be35 100644 --- a/lib/pages/shopinbit/shopinbit_tickets_view.dart +++ b/lib/pages/shopinbit/shopinbit_tickets_view.dart @@ -121,15 +121,6 @@ class _ShopInBitTicketsViewState extends State { final localIdx = _tickets.indexWhere((t) => t.apiTicketId == ref.id); if (localIdx < 0) continue; - // Skip API calls for terminal tickets; they can still be - // refreshed on-demand when the user opens the detail view. - final localStatus = _tickets[localIdx].status; - if (localStatus == ShopInBitOrderStatus.closed || - localStatus == ShopInBitOrderStatus.cancelled || - localStatus == ShopInBitOrderStatus.refunded) { - continue; - } - // Car research tickets return 403 on /tickets/:id/* endpoints. if (_tickets[localIdx].category == ShopInBitCategory.car) continue; @@ -140,6 +131,18 @@ class _ShopInBitTicketsViewState extends State { statusResp.value!.state, ); + if (_tickets[localIdx].status == ShopInBitOrderStatus.offerAvailable && + (_tickets[localIdx].offerProductName == null || + _tickets[localIdx].offerPrice == null)) { + final offerResp = await service.client.getTicketFull(ref.id); + if (!offerResp.hasError && offerResp.value != null) { + _tickets[localIdx].setOffer( + productName: offerResp.value!.productName, + price: offerResp.value!.customerPrice, + ); + } + } + final msgsResp = await service.client.getMessages(ref.id); if (!msgsResp.hasError && msgsResp.value != null) { _tickets[localIdx].clearMessages(); From 0e7898a17bea7dc7b51784c3b675be8a62fc71fa Mon Sep 17 00:00:00 2001 From: sneurlax Date: Fri, 1 May 2026 13:49:50 -0500 Subject: [PATCH 2/5] fix(shopinbit): lock shipping country, fix billing overflow, remove T&C from payment, add done button --- .../shopinbit/shopinbit_payment_view.dart | 73 ++++--------------- .../shopinbit/shopinbit_shipping_view.dart | 20 +++-- 2 files changed, 23 insertions(+), 70 deletions(-) diff --git a/lib/pages/shopinbit/shopinbit_payment_view.dart b/lib/pages/shopinbit/shopinbit_payment_view.dart index 874767033d..2ef6505306 100644 --- a/lib/pages/shopinbit/shopinbit_payment_view.dart +++ b/lib/pages/shopinbit/shopinbit_payment_view.dart @@ -2,12 +2,10 @@ import 'dart:async'; import 'dart:io'; import 'package:decimal/decimal.dart'; -import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_svg/svg.dart'; -import 'package:url_launcher/url_launcher.dart'; import '../../app_config.dart'; import '../../models/isar/models/ethereum/eth_contract.dart'; @@ -48,7 +46,6 @@ class ShopInBitPaymentView extends ConsumerStatefulWidget { } class _ShopInBitPaymentViewState extends ConsumerState { - bool _termsAccepted = false; bool _loading = false; int _selectedMethod = 0; Timer? _pollTimer; @@ -76,8 +73,7 @@ class _ShopInBitPaymentViewState extends ConsumerState { 'payment_processing', }.contains(_status); - bool get _payNowEnabled => - _termsAccepted && !_isExpiredOrInvalid && !_isTerminal; + bool get _payNowEnabled => !_isExpiredOrInvalid && !_isTerminal; @override void initState() { @@ -160,11 +156,6 @@ class _ShopInBitPaymentViewState extends ConsumerState { } } - Future _openTerms() async { - const url = "https://api.shopinbit.com/static/policy/terms.html"; - await launchUrl(Uri.parse(url), mode: LaunchMode.externalApplication); - } - Future _checkForPayment() async { _pollTimer?.cancel(); setState(() => _loading = true); @@ -334,6 +325,14 @@ class _ShopInBitPaymentViewState extends ConsumerState { Navigator.of(context).pop(); } + void _navigateToTickets() { + if (Util.isDesktop) { + Navigator.of(context, rootNavigator: true).pop(); + } else { + Navigator.of(context).popUntil((route) => route.isFirst); + } + } + void _navigateToSendFrom({ required CryptoCurrency coin, required Amount? amount, @@ -709,59 +708,15 @@ class _ShopInBitPaymentViewState extends ConsumerState { ], ), ), + SizedBox(height: isDesktop ? 16 : 12), + PrimaryButton( + label: "View My Requests", + onPressed: _navigateToTickets, + ), ], SizedBox(height: isDesktop ? 24 : 16), // Coin list (replaces tab selector + QR + address + global button) if (!_isExpiredOrInvalid) ...coinRows, - SizedBox(height: isDesktop ? 16 : 12), - GestureDetector( - onTap: () { - setState(() { - _termsAccepted = !_termsAccepted; - }); - }, - child: Container( - color: Colors.transparent, - child: Row( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - SizedBox( - width: 20, - height: 20, - child: IgnorePointer( - child: Checkbox( - materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, - value: _termsAccepted, - onChanged: (_) {}, - ), - ), - ), - const SizedBox(width: 12), - Expanded( - child: RichText( - text: TextSpan( - style: isDesktop - ? STextStyles.desktopTextExtraExtraSmall(context) - : STextStyles.w500_14(context), - children: [ - const TextSpan(text: "I accept the "), - TextSpan( - text: "Terms & Conditions", - style: STextStyles.richLink( - context, - ).copyWith(fontSize: isDesktop ? null : 14), - recognizer: TapGestureRecognizer() - ..onTap = _openTerms, - ), - const TextSpan(text: "."), - ], - ), - ), - ), - ], - ), - ), - ), ], ); diff --git a/lib/pages/shopinbit/shopinbit_shipping_view.dart b/lib/pages/shopinbit/shopinbit_shipping_view.dart index 4ad2fe4ec4..be7ca26e7e 100644 --- a/lib/pages/shopinbit/shopinbit_shipping_view.dart +++ b/lib/pages/shopinbit/shopinbit_shipping_view.dart @@ -105,6 +105,10 @@ class _ShopInBitShippingViewState extends State { _billingCityFocusNode = FocusNode(); _billingPostalCodeFocusNode = FocusNode(); + _selectedCountryIso = widget.model.deliveryCountry.isNotEmpty + ? widget.model.deliveryCountry + : null; + for (final node in [ _nameFocusNode, _streetFocusNode, @@ -372,15 +376,9 @@ class _ShopInBitShippingViewState extends State { _countrySearchController.clear(); } }, - onChanged: _loadingCountries - ? null - : (value) { - setState(() { - _selectedCountryIso = value; - }); - }, + onChanged: null, hint: Text( - _loadingCountries ? "Loading countries..." : "Country", + "Country", style: isDesktop ? STextStyles.desktopTextExtraSmall(context).copyWith( color: Theme.of(context) @@ -677,7 +675,7 @@ class _ShopInBitShippingViewState extends State { ), ), ], - const Spacer(), + const SizedBox(height: 24), PrimaryButton( label: _submitting ? "Submitting..." : "Continue to payment", enabled: _canContinue, @@ -689,7 +687,7 @@ class _ShopInBitShippingViewState extends State { if (isDesktop) { return DesktopDialog( maxWidth: 580, - maxHeight: 600, + maxHeight: 700, child: Column( children: [ Row( @@ -711,7 +709,7 @@ class _ShopInBitShippingViewState extends State { horizontal: 32, vertical: 16, ), - child: content, + child: SingleChildScrollView(child: content), ), ), ], From 01194106fe12a811bceddd080cd97141ea5cdc20 Mon Sep 17 00:00:00 2001 From: sneurlax Date: Fri, 1 May 2026 15:09:15 -0500 Subject: [PATCH 3/5] fix(shopinbit): fix double display name prompt, skip service overview for returning users --- .../sub_widgets/desktop_shopinbit_view.dart | 92 ++++++++++++++----- 1 file changed, 68 insertions(+), 24 deletions(-) diff --git a/lib/pages_desktop_specific/more_view/sub_widgets/desktop_shopinbit_view.dart b/lib/pages_desktop_specific/more_view/sub_widgets/desktop_shopinbit_view.dart index b30ba6b8a1..5f25e98368 100644 --- a/lib/pages_desktop_specific/more_view/sub_widgets/desktop_shopinbit_view.dart +++ b/lib/pages_desktop_specific/more_view/sub_widgets/desktop_shopinbit_view.dart @@ -10,6 +10,7 @@ import '../../../db/isar/main_db.dart'; import '../../../models/shopinbit/shopinbit_order_model.dart'; import '../../../notifications/show_flush_bar.dart'; import '../../../pages/shopinbit/shopinbit_step_1.dart'; +import '../../../pages/shopinbit/shopinbit_step_2.dart'; import '../../../pages/shopinbit/shopinbit_tickets_view.dart'; import '../../../providers/desktop/current_desktop_menu_item.dart'; import '../../../services/shopinbit/shopinbit_service.dart'; @@ -89,6 +90,7 @@ class _DesktopServicesViewState extends ConsumerState { void _showShopDialog(BuildContext context) async { final service = ShopInBitService.instance; final model = ShopInBitOrderModel(); + bool isFirstRun = false; if (!service.loadSetupComplete()) { // First-time user: show setup. @@ -98,6 +100,7 @@ class _DesktopServicesViewState extends ConsumerState { builder: (_) => _ShopInBitDesktopSetupDialog(model: model), ); if (completed != true) return; // user cancelled + isFirstRun = true; } else { // Returning user: restore display name. final savedName = service.loadDisplayName(); @@ -106,30 +109,63 @@ class _DesktopServicesViewState extends ConsumerState { } } - // Show warning dialog. if (!mounted) return; - showDialog( - context: context, - barrierDismissible: false, - builder: (dialogContext) => DesktopDialog( - maxWidth: 550, - maxHeight: 300, - child: Padding( - padding: const EdgeInsets.symmetric(horizontal: 32, vertical: 20), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text("ShopinBit", style: STextStyles.desktopH2(dialogContext)), - const SizedBox(height: 16), - RichText( - text: TextSpan( - style: STextStyles.desktopTextSmall(dialogContext), + + if (isFirstRun) { + // First run: show service overview then go directly to Step2 + // (name was just entered in setup dialog, no need to show Step1 again). + showDialog( + context: context, + barrierDismissible: false, + builder: (dialogContext) => DesktopDialog( + maxWidth: 550, + maxHeight: 300, + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 32, vertical: 20), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text("ShopinBit", style: STextStyles.desktopH2(dialogContext)), + const SizedBox(height: 16), + RichText( + text: TextSpan( + style: STextStyles.desktopTextSmall(dialogContext), + children: const [ + TextSpan( + text: + "Please note the following before proceeding:" + "\n\n\u2022 Minimum order amount: 1,000 EUR" + "\n\u2022 Service fee: 10% of the order total", + ), + ], + ), + ), + const Spacer(), + Row( + mainAxisAlignment: MainAxisAlignment.center, children: [ - const TextSpan( - text: - "Please note the following before proceeding:" - "\n\n\u2022 Minimum order amount: 1,000 EUR" - "\n\u2022 Service fee: 10% of the order total", + SecondaryButton( + width: 200, + buttonHeight: ButtonHeight.l, + label: "Cancel", + onPressed: () { + Navigator.of(dialogContext, rootNavigator: true).pop(); + }, + ), + const SizedBox(width: 20), + PrimaryButton( + width: 200, + buttonHeight: ButtonHeight.l, + label: "Continue", + onPressed: () async { + Navigator.of(dialogContext, rootNavigator: true).pop(); + await showDialog( + context: context, + barrierDismissible: false, + builder: (_) => ShopInBitStep2(model: model), + ); + if (mounted) setState(() {}); + }, ), ], ), @@ -166,8 +202,16 @@ class _DesktopServicesViewState extends ConsumerState { ], ), ), - ), - ); + ); + } else { + // Returning user: go directly to Step1 (skip service overview dialog). + await showDialog( + context: context, + barrierDismissible: false, + builder: (_) => ShopInBitStep1(model: model), + ); + if (mounted) setState(() {}); + } } @override From 67569f6685d412239769594bd60f9b0bf035774a Mon Sep 17 00:00:00 2001 From: sneurlax Date: Fri, 1 May 2026 15:31:03 -0500 Subject: [PATCH 4/5] fix(shopinbit): fix guidelines skip, sticky button, travel form improvements --- lib/pages/shopinbit/shopinbit_step_2.dart | 17 +- lib/pages/shopinbit/shopinbit_step_4.dart | 231 +++++++++++++++++++--- 2 files changed, 210 insertions(+), 38 deletions(-) diff --git a/lib/pages/shopinbit/shopinbit_step_2.dart b/lib/pages/shopinbit/shopinbit_step_2.dart index 6fa7fe1ea9..01674db037 100644 --- a/lib/pages/shopinbit/shopinbit_step_2.dart +++ b/lib/pages/shopinbit/shopinbit_step_2.dart @@ -54,19 +54,24 @@ class _ShopInBitStep2State extends State { void _continue() { widget.model.category = _selected; + final skipGuidelines = ShopInBitService.instance.loadGuidelinesAccepted(); final skipGuidelines = ShopInBitService.instance.loadGuidelinesAccepted(); if (Util.isDesktop) { Navigator.of(context, rootNavigator: true).pop(); - showDialog( - context: context, - barrierDismissible: false, - builder: (_) => ShopInBitStep3(model: widget.model), - ); + if (skipGuidelines) { + widget.model.guidelinesAccepted = true; + Navigator.of( + context, + ).pushNamed(ShopInBitStep4.routeName, arguments: widget.model); + } else { + Navigator.of( + context, + ).pushNamed(ShopInBitStep3.routeName, arguments: widget.model); + } } else { if (skipGuidelines) { - // Returning user — skip guidelines. widget.model.guidelinesAccepted = true; Navigator.of( context, diff --git a/lib/pages/shopinbit/shopinbit_step_4.dart b/lib/pages/shopinbit/shopinbit_step_4.dart index ea3757835b..3ead68b6e8 100644 --- a/lib/pages/shopinbit/shopinbit_step_4.dart +++ b/lib/pages/shopinbit/shopinbit_step_4.dart @@ -78,6 +78,12 @@ class _ShopInBitStep4State extends State { // Travel-specific controllers late final TextEditingController _departureCountryController; late final FocusNode _departureCountryFocusNode; + String? _selectedDepartureCountryIso; + final TextEditingController _departureCountrySearchController = + TextEditingController(); + late final TextEditingController _arrangementDetailsController; + late final FocusNode _arrangementDetailsFocusNode; + bool _arrangementDetailsTouched = false; late final TextEditingController _departureCityController; late final FocusNode _departureCityFocusNode; late final TextEditingController _destinationsController; @@ -251,7 +257,8 @@ class _ShopInBitStep4State extends State { return !_submitting && _privacyAccepted && _selectedArrangement != null && - _departureCountryController.text.trim().isNotEmpty && + _arrangementDetailsController.text.trim().length >= 10 && + _selectedDepartureCountryIso != null && _departureCityController.text.trim().isNotEmpty && (_needsRecommendations || _destinationsController.text.trim().isNotEmpty) && @@ -338,6 +345,14 @@ class _ShopInBitStep4State extends State { } setState(() {}); }); + _arrangementDetailsController = TextEditingController(); + _arrangementDetailsFocusNode = FocusNode(); + _arrangementDetailsFocusNode.addListener(() { + if (!_arrangementDetailsFocusNode.hasFocus) { + _arrangementDetailsTouched = true; + } + setState(() {}); + }); _departureCityController = TextEditingController(); _departureCityFocusNode = FocusNode(); _departureCityFocusNode.addListener(() { @@ -412,6 +427,9 @@ class _ShopInBitStep4State extends State { _carBudgetFocusNode.dispose(); _departureCountryController.dispose(); _departureCountryFocusNode.dispose(); + _departureCountrySearchController.dispose(); + _arrangementDetailsController.dispose(); + _arrangementDetailsFocusNode.dispose(); _departureCityController.dispose(); _departureCityFocusNode.dispose(); _destinationsController.dispose(); @@ -483,8 +501,9 @@ class _ShopInBitStep4State extends State { } else if (widget.model.category == ShopInBitCategory.travel) { final parts = [ "Arrangement: $_selectedArrangement", + "Details: ${_arrangementDetailsController.text.trim()}", "Departure: ${_departureCityController.text.trim()}, " - "${_departureCountryController.text.trim()}", + "${_selectedDepartureCountryIso ?? ''}", ]; if (_needsRecommendations) { @@ -792,6 +811,122 @@ class _ShopInBitStep4State extends State { ); } + Widget _buildDepartureCountryPicker(bool isDesktop) { + return ClipRRect( + borderRadius: BorderRadius.circular(Constants.size.circularBorderRadius), + child: DropdownButtonHideUnderline( + child: DropdownButton2( + value: _selectedDepartureCountryIso, + items: _countries + .map( + (c) => DropdownMenuItem( + value: c['iso'] as String, + child: Text( + c['label'] as String, + style: isDesktop + ? STextStyles.desktopTextExtraSmall(context).copyWith( + color: Theme.of( + context, + ).extension()!.textFieldActiveText, + ) + : STextStyles.w500_14(context), + ), + ), + ) + .toList(), + onMenuStateChange: (isOpen) { + if (!isOpen) { + _departureCountrySearchController.clear(); + } + }, + onChanged: _loadingCountries + ? null + : (value) { + setState(() { + _selectedDepartureCountryIso = value; + _departureCountryTouched = true; + }); + }, + hint: Text( + _loadingCountries ? "Loading countries..." : "Departure country", + style: isDesktop + ? STextStyles.desktopTextExtraSmall(context).copyWith( + color: Theme.of( + context, + ).extension()!.textFieldDefaultSearchIconLeft, + ) + : STextStyles.fieldLabel(context), + ), + isExpanded: true, + buttonStyleData: ButtonStyleData( + decoration: BoxDecoration( + color: Theme.of( + context, + ).extension()!.textFieldDefaultBG, + borderRadius: BorderRadius.circular( + Constants.size.circularBorderRadius, + ), + ), + ), + iconStyleData: IconStyleData( + icon: Padding( + padding: const EdgeInsets.only(right: 10), + child: SvgPicture.asset( + Assets.svg.chevronDown, + width: 12, + height: 6, + color: Theme.of( + context, + ).extension()!.textFieldActiveSearchIconRight, + ), + ), + ), + dropdownStyleData: DropdownStyleData( + offset: const Offset(0, 0), + elevation: 0, + maxHeight: 300, + decoration: BoxDecoration( + color: Theme.of( + context, + ).extension()!.textFieldDefaultBG, + borderRadius: BorderRadius.circular( + Constants.size.circularBorderRadius, + ), + ), + ), + dropdownSearchData: DropdownSearchData( + searchController: _departureCountrySearchController, + searchInnerWidgetHeight: 48, + searchInnerWidget: TextFormField( + controller: _departureCountrySearchController, + decoration: InputDecoration( + isDense: true, + contentPadding: const EdgeInsets.symmetric( + horizontal: 16, + vertical: 14, + ), + hintText: "Search...", + hintStyle: STextStyles.fieldLabel(context), + border: InputBorder.none, + ), + ), + searchMatchFn: (item, searchValue) { + final label = _countries + .where((c) => c['iso'] == item.value) + .map((c) => c['label'] as String) + .firstOrNull; + return label?.toLowerCase().contains(searchValue.toLowerCase()) ?? + false; + }, + ), + menuItemStyleData: const MenuItemStyleData( + padding: EdgeInsets.symmetric(horizontal: 16, vertical: 8), + ), + ), + ), + ); + } + Widget _buildPrivacyCheckbox(bool isDesktop) { return GestureDetector( onTap: () { @@ -907,7 +1042,7 @@ class _ShopInBitStep4State extends State { ? STextStyles.desktopTextSmall(context) : STextStyles.itemSubtitle(context), ), - SizedBox(height: isDesktop ? 32 : 24), + SizedBox(height: isDesktop ? 16 : 12), // What to purchase free-text field TextField( @@ -1099,11 +1234,11 @@ class _ShopInBitStep4State extends State { ), ), ), - SizedBox(height: isDesktop ? 24 : 16), + SizedBox(height: isDesktop ? 12 : 12), // Country picker (shared) _buildCountryPicker(isDesktop), - SizedBox(height: isDesktop ? 16 : 12), + SizedBox(height: isDesktop ? 12 : 12), // Privacy checkbox (shared) _buildPrivacyCheckbox(isDesktop), @@ -1814,19 +1949,12 @@ class _ShopInBitStep4State extends State { onChanged: (val) => setState(() => _selectedArrangement = val), isDesktop: isDesktop, ), - - // === Where === - SizedBox(height: isDesktop ? 24 : 16), - Text( - "Where", - style: isDesktop - ? STextStyles.desktopTextSmall(context) - : STextStyles.w500_14(context), - ), - SizedBox(height: isDesktop ? 12 : 8), + SizedBox(height: isDesktop ? 16 : 12), TextField( - controller: _departureCountryController, - focusNode: _departureCountryFocusNode, + controller: _arrangementDetailsController, + focusNode: _arrangementDetailsFocusNode, + minLines: 3, + maxLines: 6, autocorrect: false, enableSuggestions: false, onChanged: (_) => setState(() {}), @@ -1840,8 +1968,8 @@ class _ShopInBitStep4State extends State { : STextStyles.field(context), decoration: standardInputDecoration( - "Departure country", - _departureCountryFocusNode, + "Describe your specific requirements (luggage, cabin class, hotel stars, etc.)", + _arrangementDetailsFocusNode, context, desktopMed: isDesktop, ).copyWith( @@ -1850,9 +1978,24 @@ class _ShopInBitStep4State extends State { horizontal: 16, vertical: 12, ), - errorText: departureCountryError, + errorText: + _arrangementDetailsTouched && + _arrangementDetailsController.text.trim().length < 10 + ? "Minimum 10 characters" + : null, ), ), + + // === Where === + SizedBox(height: isDesktop ? 24 : 16), + Text( + "Where", + style: isDesktop + ? STextStyles.desktopTextSmall(context) + : STextStyles.w500_14(context), + ), + SizedBox(height: isDesktop ? 12 : 8), + _buildDepartureCountryPicker(isDesktop), SizedBox(height: isDesktop ? 16 : 12), TextField( controller: _departureCityController, @@ -1971,10 +2114,23 @@ class _ShopInBitStep4State extends State { TextField( controller: _departureDateController, focusNode: _departureDateFocusNode, - autocorrect: false, - enableSuggestions: false, - keyboardType: TextInputType.datetime, - onChanged: (_) => setState(() {}), + readOnly: true, + onTap: () async { + final picked = await showDatePicker( + context: context, + initialDate: DateTime.now(), + firstDate: DateTime.now(), + lastDate: DateTime.now().add(const Duration(days: 3650)), + ); + if (picked != null) { + final formatted = + "${picked.day.toString().padLeft(2, '0')}/${picked.month.toString().padLeft(2, '0')}/${picked.year}"; + setState(() { + _departureDateController.text = formatted; + _departureDateTouched = true; + }); + } + }, style: isDesktop ? STextStyles.desktopTextExtraSmall(context).copyWith( color: Theme.of( @@ -1996,6 +2152,7 @@ class _ShopInBitStep4State extends State { vertical: 12, ), labelText: "Departure date", + suffixIcon: const Icon(Icons.calendar_today, size: 18), errorText: departureDateError, ), ), @@ -2003,10 +2160,23 @@ class _ShopInBitStep4State extends State { TextField( controller: _returnDateController, focusNode: _returnDateFocusNode, - autocorrect: false, - enableSuggestions: false, - keyboardType: TextInputType.datetime, - onChanged: (_) => setState(() {}), + readOnly: true, + onTap: () async { + final picked = await showDatePicker( + context: context, + initialDate: DateTime.now(), + firstDate: DateTime.now(), + lastDate: DateTime.now().add(const Duration(days: 3650)), + ); + if (picked != null) { + final formatted = + "${picked.day.toString().padLeft(2, '0')}/${picked.month.toString().padLeft(2, '0')}/${picked.year}"; + setState(() { + _returnDateController.text = formatted; + _returnDateTouched = true; + }); + } + }, style: isDesktop ? STextStyles.desktopTextExtraSmall(context).copyWith( color: Theme.of( @@ -2028,6 +2198,7 @@ class _ShopInBitStep4State extends State { vertical: 12, ), labelText: "Return date", + suffixIcon: const Icon(Icons.calendar_today, size: 18), errorText: returnDateError, ), ), @@ -2070,10 +2241,6 @@ class _ShopInBitStep4State extends State { "October", "November", "December", - "Spring (Mar-May)", - "Summer (Jun-Aug)", - "Fall (Sep-Nov)", - "Winter (Dec-Feb)", ], hint: "Month or season", onChanged: (val) => setState(() => _selectedMonthSeason = val), From ad92606d6b744f810abb01bad3cdb2998d1a94a3 Mon Sep 17 00:00:00 2001 From: sneurlax Date: Fri, 1 May 2026 15:48:44 -0500 Subject: [PATCH 5/5] fix(shopinbit): remove unused imports and dead _copyAddress method --- lib/pages/shopinbit/shopinbit_payment_view.dart | 11 ----------- lib/pages/shopinbit/shopinbit_shipping_view.dart | 1 - 2 files changed, 12 deletions(-) diff --git a/lib/pages/shopinbit/shopinbit_payment_view.dart b/lib/pages/shopinbit/shopinbit_payment_view.dart index 2ef6505306..0467d3fb7e 100644 --- a/lib/pages/shopinbit/shopinbit_payment_view.dart +++ b/lib/pages/shopinbit/shopinbit_payment_view.dart @@ -31,7 +31,6 @@ import '../../widgets/desktop/primary_button.dart'; import '../../widgets/desktop/secondary_button.dart'; import '../../widgets/rounded_white_container.dart'; import 'shopinbit_send_from_view.dart'; -import 'shopinbit_tickets_view.dart'; class ShopInBitPaymentView extends ConsumerStatefulWidget { const ShopInBitPaymentView({super.key, required this.model}); @@ -468,16 +467,6 @@ class _ShopInBitPaymentViewState extends ConsumerState { ); } - void _copyAddress(BuildContext context) { - Clipboard.setData(ClipboardData(text: _currentAddress)); - showFloatingFlushBar( - type: FlushBarType.info, - message: "Copied to clipboard", - iconAsset: Assets.svg.copy, - context: context, - ); - } - @override Widget build(BuildContext context) { final isDesktop = Util.isDesktop; diff --git a/lib/pages/shopinbit/shopinbit_shipping_view.dart b/lib/pages/shopinbit/shopinbit_shipping_view.dart index be7ca26e7e..013b276da2 100644 --- a/lib/pages/shopinbit/shopinbit_shipping_view.dart +++ b/lib/pages/shopinbit/shopinbit_shipping_view.dart @@ -5,7 +5,6 @@ import 'package:flutter/material.dart'; import 'package:flutter_svg/svg.dart'; import '../../models/shopinbit/shopinbit_order_model.dart'; -import '../../notifications/show_flush_bar.dart'; import '../../services/shopinbit/shopinbit_service.dart'; import '../../services/shopinbit/src/models/address.dart'; import '../../themes/stack_colors.dart';