From 1cb05d5211e1e96d4b98ff55d484889ca7a0b5dc Mon Sep 17 00:00:00 2001 From: malik1004x Date: Fri, 17 Apr 2026 23:46:58 +0200 Subject: [PATCH] fix bridge ui (#3189) * fix bridge ui * padding on info bottom sheet * fix cutoff on infobottomsheet * fix wording on bridge details ui * no _amountController.clear() on bridge page navigation * autofocus keyboard on bridge page opened * remove KeyboardHideOverlay * fix text field resize * fix text field padding --- .../pages/bridge/bridge_amount_page.dart | 229 +++++++++--------- .../pages/bridge/bridge_confirm_sheet.dart | 38 +-- .../widgets/bridge/network_path_pill.dart | 44 ++-- .../base_bottom_sheet_widget.dart | 22 +- .../info_bottom_sheet_widget.dart | 1 + lib/view_model/bridge/bridge_view_model.dart | 3 +- lib/view_model/bridge_details_view_model.dart | 4 +- res/values/strings_en.arb | 3 + 8 files changed, 170 insertions(+), 174 deletions(-) diff --git a/lib/new-ui/pages/bridge/bridge_amount_page.dart b/lib/new-ui/pages/bridge/bridge_amount_page.dart index 42dc27c2f..4e953591d 100644 --- a/lib/new-ui/pages/bridge/bridge_amount_page.dart +++ b/lib/new-ui/pages/bridge/bridge_amount_page.dart @@ -4,7 +4,6 @@ import 'package:cake_wallet/routes.dart'; import 'package:cake_wallet/src/widgets/bottom_sheet/base_bottom_sheet_widget.dart'; import 'package:cake_wallet/src/widgets/bottom_sheet/info_bottom_sheet_widget.dart'; import 'package:cake_wallet/utils/request_review_handler.dart'; -import 'package:cake_wallet/new-ui/widgets/keyboard_hide_overlay.dart'; import 'package:cake_wallet/new-ui/widgets/modern_button.dart'; import 'package:cake_wallet/new-ui/widgets/receive_page/receive_top_bar.dart'; import 'package:cake_wallet/view_model/bridge_history_view_model.dart'; @@ -33,7 +32,10 @@ class BridgeAmountPage extends StatefulWidget { } class _BridgeAmountPageState extends State { - late final TextEditingController _amountController; + final TextEditingController _amountController = TextEditingController(); + final FocusNode _amountFocusNode = FocusNode(); + bool _amountFocused = false; + BridgeViewModel get bridgeViewModel => widget.bridgeViewModel; @override @@ -42,10 +44,11 @@ class _BridgeAmountPageState extends State { bridgeViewModel.applyInitialBridgeToken(widget.initialToken); bridgeViewModel.onBridgeSuccess = _showBridgeSuccessBottomSheet; - _amountController = TextEditingController(); + _amountFocusNode.addListener(() => setState(() => _amountFocused = _amountFocusNode.hasFocus)); WidgetsBinding.instance.addPostFrameCallback((_) { bridgeViewModel.ensureFiatPriceForSelectedToken(); + _amountFocusNode.requestFocus(); }); } @@ -100,22 +103,23 @@ class _BridgeAmountPageState extends State { Widget build(BuildContext context) { final theme = Theme.of(context); - return KeyboardHideOverlay( - unfocusOnTap: true, - child: Container( - decoration: BoxDecoration( - color: theme.colorScheme.surface, - borderRadius: const BorderRadius.vertical(top: Radius.circular(24)), - ), + return Container( + decoration: BoxDecoration( + color: theme.colorScheme.surface, + borderRadius: const BorderRadius.vertical(top: Radius.circular(24)), + ), + child: Padding( + padding: EdgeInsets.only(bottom: MediaQuery.of(context).viewInsets.bottom), child: Column( children: [ ModalTopBar( - title: 'Enter Amount', - leadingIcon: const Icon(Icons.arrow_back_ios_new, size: 18), + title: S.of(context).enter_amount, + leadingIcon: Icon(Icons.arrow_back_ios_new), onLeadingPressed: () => Navigator.of(context, rootNavigator: true).pop(), - trailingIcon: const Icon(Icons.calendar_month, size: 18), + trailingIcon: Icon(Icons.history), onTrailingPressed: () { - Navigator.pushNamed(context, Routes.bridgeHistoryPage, arguments: widget.bridgeHistoryViewModel); + Navigator.pushNamed(context, Routes.bridgeHistoryPage, + arguments: widget.bridgeHistoryViewModel); }, ), Expanded( @@ -130,66 +134,63 @@ class _BridgeAmountPageState extends State { crossAxisAlignment: CrossAxisAlignment.stretch, children: [ const Spacer(flex: 2), - LayoutBuilder( - builder: (context, constraints) { - return Center( - child: Row( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.baseline, - textBaseline: TextBaseline.alphabetic, - children: [ - ConstrainedBox( - constraints: BoxConstraints( - maxWidth: (constraints.maxWidth - 200) - .clamp(40.0, constraints.maxWidth), - minWidth: 40, - ), - child: TextFormField( - controller: _amountController, - maxLines: 1, - onChanged: bridgeViewModel.setAmount, - autovalidateMode: AutovalidateMode.always, - validator: bridgeViewModel.decimalAmountValidator, - keyboardType: TextInputType.numberWithOptions( - signed: false, - decimal: true, - ), - inputFormatters: [ - FilteringTextInputFormatter.allow( - RegExp(r'^\d*[.,]?\d*$'), - ), - ], - textAlign: TextAlign.center, - decoration: InputDecoration( - isDense: true, - hintText: '0.00', - hintStyle: theme.textTheme.displayMedium?.copyWith( - fontWeight: FontWeight.w400, - color: theme.colorScheme.onSurfaceVariant, - ), - ), - style: theme.textTheme.displayMedium?.copyWith( - fontWeight: FontWeight.w400, - fontSize: 45, - color: theme.colorScheme.onSurface, - ), - ), + Center( + child: Row( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.baseline, + textBaseline: TextBaseline.alphabetic, + children: [ + IntrinsicWidth( + child: TextFormField( + controller: _amountController, + focusNode: _amountFocusNode, + maxLines: 1, + onChanged: bridgeViewModel.setAmount, + autovalidateMode: AutovalidateMode.always, + validator: bridgeViewModel.decimalAmountValidator, + keyboardType: TextInputType.numberWithOptions( + signed: false, + decimal: true, ), - const SizedBox(width: 8), - Text( - bridgeViewModel.selectedToken?.title ?? '', - maxLines: 1, - overflow: TextOverflow.ellipsis, - style: theme.textTheme.displayMedium?.copyWith( + inputFormatters: [ + FilteringTextInputFormatter.allow( + RegExp(r'^\d*[.,]?\d*$'), + ), + ], + decoration: InputDecoration( + isDense: true, + isCollapsed: true, + contentPadding: EdgeInsets.zero, + fillColor: Colors.transparent, + hoverColor: Colors.transparent, + focusedBorder: InputBorder.none, + enabledBorder: InputBorder.none, + hintText: _amountFocused || _amountController.text.isNotEmpty ? null : "0.00", + hintStyle: theme.textTheme.displayMedium?.copyWith( fontWeight: FontWeight.w400, - fontSize: 45, color: theme.colorScheme.onSurfaceVariant, ), ), - ], + style: theme.textTheme.displayMedium?.copyWith( + fontWeight: FontWeight.w400, + fontSize: 45, + color: theme.colorScheme.onSurface, + ), + ), ), - ); - }, + const SizedBox(width: 8), + Text( + bridgeViewModel.selectedToken?.title ?? '', + maxLines: 1, + overflow: TextOverflow.ellipsis, + style: theme.textTheme.displayMedium?.copyWith( + fontWeight: FontWeight.w400, + fontSize: 45, + color: theme.colorScheme.onSurfaceVariant, + ), + ), + ], + ), ), const SizedBox(height: 12), Center( @@ -217,78 +218,71 @@ class _BridgeAmountPageState extends State { ], const Spacer(flex: 3), Row( + spacing: 8, crossAxisAlignment: CrossAxisAlignment.center, children: [ Expanded( - child: RichText( - maxLines: 2, - overflow: TextOverflow.ellipsis, - text: TextSpan( - children: [ - TextSpan( - text: 'Available ', - style: theme.textTheme.bodyLarge?.copyWith( - color: theme.colorScheme.onSurfaceVariant, - fontWeight: FontWeight.w400, - fontSize: 16, - letterSpacing: -0.08, - ), - ), - TextSpan( - text: '${bridgeViewModel.tokenBalanceFormatted} ' - '${bridgeViewModel.selectedToken?.title ?? ''}', - style: theme.textTheme.bodyLarge?.copyWith( - color: theme.colorScheme.onSurface, - fontWeight: FontWeight.w400, - fontSize: 16, - letterSpacing: -0.08, - ), - ), - ], + child: Row( + spacing: 8, + children: [ + Text( + S.of(context).available, + style: TextStyle( + fontSize: 16, + color: Theme.of(context).colorScheme.onSurfaceVariant), ), - ), - ), - const SizedBox(width: 10), - TextButton( - style: TextButton.styleFrom( - textStyle: theme.textTheme.bodyMedium?.copyWith( - fontWeight: FontWeight.w400, - fontSize: 14, - letterSpacing: -0.07, - ), - foregroundColor: theme.colorScheme.primary, - backgroundColor: theme.colorScheme.surfaceContainer, - padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(80), - ), - ), - onPressed: () { + Text( + "${bridgeViewModel.tokenBalanceFormatted} ${bridgeViewModel.selectedToken?.title ?? ""}", + style: TextStyle( + fontSize: 16, + color: Theme.of(context).colorScheme.onSurface), + ) + ], + )), + GestureDetector( + onTap: () { bridgeViewModel.setMaxAmount(); _amountController.text = bridgeViewModel.amount; _amountController.selection = TextSelection.collapsed( offset: _amountController.text.length, ); }, - child: Text(S.of(context).max), + child: Container( + height: 34, + decoration: BoxDecoration( + color: Theme.of(context).colorScheme.surfaceContainer, + borderRadius: BorderRadius.circular(999999)), + child: Center( + child: Padding( + padding: EdgeInsets.symmetric(horizontal: 16), + child: Text( + S.of(context).max, + style: TextStyle( + fontSize: 14, + color: Theme.of(context).colorScheme.primary), + ), + )), + ), ), - const SizedBox(width: 10), IgnorePointer( ignoring: !canNext, child: ModernButton( - size: 48, + size: 34, backgroundColor: theme.colorScheme.primary, iconColor: theme.colorScheme.onPrimary, - icon: Icon(Icons.arrow_forward, size: 25), + icon: Icon(Icons.arrow_forward, size: 20), onPressed: () { - _amountController.clear(); Navigator.pushNamed( - context, Routes.bridgeDestinationNetworkPage, arguments: bridgeViewModel); + context, Routes.bridgeDestinationNetworkPage, + arguments: bridgeViewModel); }, ), ), ], ), + SizedBox( + height: 24, + ) ], ); }, @@ -302,4 +296,3 @@ class _BridgeAmountPageState extends State { ); } } - diff --git a/lib/new-ui/pages/bridge/bridge_confirm_sheet.dart b/lib/new-ui/pages/bridge/bridge_confirm_sheet.dart index 211e25733..4941671a0 100644 --- a/lib/new-ui/pages/bridge/bridge_confirm_sheet.dart +++ b/lib/new-ui/pages/bridge/bridge_confirm_sheet.dart @@ -6,6 +6,7 @@ import 'package:cake_wallet/new-ui/widgets/receive_page/receive_top_bar.dart'; import 'package:cake_wallet/new-ui/widgets/send_page/send_confirm_bottom_widget.dart'; import 'package:cake_wallet/src/widgets/cake_image_widget.dart'; import 'package:cake_wallet/view_model/bridge/bridge_view_model.dart'; +import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:flutter_mobx/flutter_mobx.dart'; import 'package:mobx/mobx.dart'; @@ -98,7 +99,7 @@ class _BridgeConfirmSheetState extends State { return const Padding( padding: EdgeInsets.symmetric(vertical: 48), child: Center( - child: CircularProgressIndicator(), + child: CupertinoActivityIndicator(), ), ); } @@ -176,9 +177,14 @@ class _BridgeConfirmSheetState extends State { ), ], ), - NetworkPathPill( - sourceChainName: bridgeViewModel.wallet.type.name, - destChainName: bridgeViewModel.destinationChainInfo?.name ?? '', + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + NetworkPathPill( + sourceChainName: bridgeViewModel.wallet.type.name, + destChainName: bridgeViewModel.destinationChainInfo?.name ?? '', + ), + ], ), ConfirmDetailsCard(bridgeViewModel: bridgeViewModel), if (bridgeViewModel.executeError != null) ...[ @@ -199,22 +205,16 @@ class _BridgeConfirmSheetState extends State { child: Observer( builder: (_) { if (bridgeViewModel.isExecuting) { - return const LoadingBottomWidget( - text: 'Bridging...', - ); + return LoadingBottomWidget(text: "${S.of(context).bridging}...",); } - final canSwipe = bridgeViewModel.quote != null && !bridgeViewModel.isQuoteLoading; - return IgnorePointer( - ignoring: !canSwipe, - child: Opacity( - opacity: canSwipe ? 1 : 0.45, - child: ConfirmSwiper( - onConfirmed: () { - bridgeViewModel.executeBridge(); - }, - swiperText: 'Swipe to bridge', - ), - ), + if(bridgeViewModel.isQuoteLoading || bridgeViewModel.quote == null) { + return LoadingBottomWidget(text: "${S.of(context).loading}..."); + } + return ConfirmSwiper( + onConfirmed: () { + bridgeViewModel.executeBridge(); + }, + swiperText: S.of(context).swipe_to_bridge, ); }, ), diff --git a/lib/new-ui/widgets/bridge/network_path_pill.dart b/lib/new-ui/widgets/bridge/network_path_pill.dart index 762f5e2f4..313dad317 100644 --- a/lib/new-ui/widgets/bridge/network_path_pill.dart +++ b/lib/new-ui/widgets/bridge/network_path_pill.dart @@ -28,18 +28,16 @@ class NetworkPathPill extends StatelessWidget { color: scheme.onSurfaceVariant, ), const SizedBox(width: 8), - Flexible( - child: Text( - sourceChainName.capitalized(), - maxLines: 1, - overflow: TextOverflow.ellipsis, - style: Theme.of(context).textTheme.bodyLarge?.copyWith( - color: scheme.onSurface, - fontWeight: FontWeight.w400, - fontSize: 16, - letterSpacing: -0.08, - ), - ), + Text( + sourceChainName.capitalized(), + maxLines: 1, + overflow: TextOverflow.ellipsis, + style: Theme.of(context).textTheme.bodyLarge?.copyWith( + color: scheme.onSurface, + fontWeight: FontWeight.w400, + fontSize: 16, + letterSpacing: -0.08, + ), ), Padding( padding: const EdgeInsets.symmetric(horizontal: 8), @@ -56,18 +54,16 @@ class NetworkPathPill extends StatelessWidget { color: scheme.onSurfaceVariant, ), const SizedBox(width: 8), - Flexible( - child: Text( - destChainName, - maxLines: 1, - overflow: TextOverflow.ellipsis, - style: Theme.of(context).textTheme.bodyLarge?.copyWith( - color: scheme.onSurface, - fontWeight: FontWeight.w400, - fontSize: 16, - letterSpacing: -0.08, - ), - ), + Text( + destChainName, + maxLines: 1, + overflow: TextOverflow.ellipsis, + style: Theme.of(context).textTheme.bodyLarge?.copyWith( + color: scheme.onSurface, + fontWeight: FontWeight.w400, + fontSize: 16, + letterSpacing: -0.08, + ), ), ], ); diff --git a/lib/src/widgets/bottom_sheet/base_bottom_sheet_widget.dart b/lib/src/widgets/bottom_sheet/base_bottom_sheet_widget.dart index 374972826..4462a5622 100644 --- a/lib/src/widgets/bottom_sheet/base_bottom_sheet_widget.dart +++ b/lib/src/widgets/bottom_sheet/base_bottom_sheet_widget.dart @@ -55,13 +55,15 @@ abstract class BaseBottomSheet extends StatelessWidget { topLeft: Radius.circular(30.0), topRight: Radius.circular(30.0)), child: Container( color: Theme.of(context).colorScheme.surface, - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - _buildHeader(context), - contentWidget(context), - _buildFooter(context), - ], + child: SafeArea( + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + _buildHeader(context), + contentWidget(context), + _buildFooter(context), + ], + ), ), ), ), @@ -108,7 +110,7 @@ abstract class BaseBottomSheet extends StatelessWidget { case FooterType.slideActionButton: return Padding( - padding: const EdgeInsets.fromLTRB(40, 12, 40, 34), + padding: const EdgeInsets.fromLTRB(40, 12, 40, 24), child: StandardSlideButton( key: ValueKey('base_bottom_sheet_widget_standard_slide_button_key'), buttonText: slideActionButtonText ?? '', @@ -120,7 +122,7 @@ abstract class BaseBottomSheet extends StatelessWidget { case FooterType.singleActionButton: return Padding( - padding: const EdgeInsets.fromLTRB(16, 12, 16, 34), + padding: const EdgeInsets.fromLTRB(16, 12, 16, 24), child: LoadingPrimaryButton( key: singleActionButtonKey, text: singleActionButtonText ?? '', @@ -134,7 +136,7 @@ abstract class BaseBottomSheet extends StatelessWidget { case FooterType.doubleActionButton: return Padding( - padding: const EdgeInsets.fromLTRB(16, 0, 16, 34), + padding: const EdgeInsets.fromLTRB(16, 0, 16, 24), child: Row( children: [ Expanded( diff --git a/lib/src/widgets/bottom_sheet/info_bottom_sheet_widget.dart b/lib/src/widgets/bottom_sheet/info_bottom_sheet_widget.dart index 12627a3cf..08705e18b 100644 --- a/lib/src/widgets/bottom_sheet/info_bottom_sheet_widget.dart +++ b/lib/src/widgets/bottom_sheet/info_bottom_sheet_widget.dart @@ -100,6 +100,7 @@ class InfoBottomSheet extends BaseBottomSheet { ) else Container(), + SizedBox(height: 24,), if (content != null) Expanded( flex: 2, diff --git a/lib/view_model/bridge/bridge_view_model.dart b/lib/view_model/bridge/bridge_view_model.dart index 158c40cb1..2469fa1b2 100644 --- a/lib/view_model/bridge/bridge_view_model.dart +++ b/lib/view_model/bridge/bridge_view_model.dart @@ -17,6 +17,7 @@ import 'package:cake_wallet/store/app_store.dart'; import 'package:cake_wallet/store/bridge_transfers_store.dart'; import 'package:cake_wallet/store/dashboard/fiat_conversion_store.dart'; import 'package:cake_wallet/store/settings_store.dart'; +import 'package:cw_core/crypto_amount_format.dart'; import 'package:cw_core/crypto_currency.dart'; import 'package:cw_core/erc20_token.dart'; import 'package:cw_core/wallet_base.dart'; @@ -187,7 +188,7 @@ abstract class BridgeViewModelBase extends WalletChangeListenerViewModel with St String get quoteNativeFeeFormattedForDisplay { if (quoteNativeFee.isEmpty) return ''; - return '${quoteNativeFee} ${wallet.currency.title}'; + return '${quoteNativeFee.withMaxDecimals(8)} ${wallet.currency.title}'; } @computed diff --git a/lib/view_model/bridge_details_view_model.dart b/lib/view_model/bridge_details_view_model.dart index b401f754f..3a2b74527 100644 --- a/lib/view_model/bridge_details_view_model.dart +++ b/lib/view_model/bridge_details_view_model.dart @@ -97,9 +97,9 @@ abstract class BridgeDetailsViewModelBase with Store { ); final sourceName = - evm?.getChainNameByChainId(transfer.sourceChainId) ?? '${transfer.sourceChainId}'; + evm?.getChainInfoByChainId(transfer.sourceChainId)?.name ?? '${transfer.sourceChainId}'; final destName = - evm?.getChainNameByChainId(transfer.destinationChainId) ?? '${transfer.destinationChainId}'; + evm?.getChainInfoByChainId(transfer.destinationChainId)?.name ?? '${transfer.destinationChainId}'; items.add( StandartListItem( diff --git a/res/values/strings_en.arb b/res/values/strings_en.arb index 781e09745..eb7810b2c 100644 --- a/res/values/strings_en.arb +++ b/res/values/strings_en.arb @@ -87,6 +87,7 @@ "auto_generate_addresses": "Auto generate addresses", "auto_generate_subaddresses": "Auto generate subaddresses", "automatic": "Automatic", + "available": "Available", "available_balance": "Available Balance", "available_balance_description": "The “Available Balance” or “Confirmed Balance” are funds that can be spent immediately. If funds appear in the lower balance but not the top balance, then you must wait a few minutes for the incoming funds to get more network confirmations. After they get more confirmations, they will be spendable.", "avg_savings": "Avg. Savings", @@ -123,6 +124,7 @@ "block_remaining": "1 Block Remaining", "Blocks_remaining": "${status} Blocks Remaining", "bluetooth": "Bluetooth", + "bridging": "Bridging", "bright_theme": "Bright", "bscscan_history": "BSCScan History", "bump_fee": "Bump fee", @@ -1091,6 +1093,7 @@ "swap_providers": "Swap providers", "sweeping_wallet": "Sweeping wallet", "sweeping_wallet_alert": "This shouldn’t take long. DO NOT LEAVE THIS SCREEN OR THE SWEPT FUNDS MAY BE LOST.", + "swipe_to_bridge": "Swipe to bridge", "swipe_to_send": "Swipe to send", "switch_wallet": "Switch Wallet", "switchToETHWallet": "Please switch to an Ethereum wallet and try again",